X-Git-Url: http://git.rot13.org/?p=BackupPC.git;a=blobdiff_plain;f=bin%2FBackupPC_restore;h=3bc3d6001dc1667f4fae82696a724e7dc819dc4c;hp=5104dd71060f9cf53e400159039ef478ba64f628;hb=06017d5181d9c62f8cbd1c6d58d831f54d7d4ad1;hpb=e9453b7611be63303572ae443d5fb56b73364678 diff --git a/bin/BackupPC_restore b/bin/BackupPC_restore index 5104dd7..3bc3d60 100755 --- a/bin/BackupPC_restore +++ b/bin/BackupPC_restore @@ -1,17 +1,17 @@ -#!/bin/perl -T +#!/usr/bin/perl #============================================================= -*-perl-*- # # BackupPC_restore: Restore files to a client. # # DESCRIPTION # -# Usage: BackupPC_restore +# Usage: BackupPC_restore # # AUTHOR # Craig Barratt # # COPYRIGHT -# Copyright (C) 2001 Craig Barratt +# Copyright (C) 2001-2009 Craig Barratt # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -29,18 +29,19 @@ # #======================================================================== # -# Version 1.6.0_CVS, released 10 Dec 2002. +# Version 3.2.0, released 31 Jul 2010. # # See http://backuppc.sourceforge.net. # #======================================================================== use strict; +no utf8; use lib "/usr/local/BackupPC/lib"; use BackupPC::Lib; use BackupPC::FileZIO; -use BackupPC::Xfer::Smb; -use BackupPC::Xfer::Tar; +use BackupPC::Xfer; +use Socket; use File::Path; use Getopt::Std; @@ -55,33 +56,30 @@ die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) ); my $TopDir = $bpc->TopDir(); my $BinDir = $bpc->BinDir(); my %Conf = $bpc->Conf(); +my $NeedPostCmd; -my($hostIP, $host, $reqFileName); +my($hostIP, $host, $client, $reqFileName, %stat); $bpc->ChildInit(); if ( @ARGV != 3 ) { - print("usage: $0 \n"); + print("usage: $0 \n"); exit(1); } $hostIP = $1 if ( $ARGV[0] =~ /(.+)/ ); -$host = $1 if ( $ARGV[1] =~ /(.+)/ ); +$client = $1 if ( $ARGV[1] =~ /(.+)/ ); if ( $ARGV[2] !~ /^([\w.]+)$/ ) { print("$0: bad reqFileName (arg #3): $ARGV[2]\n"); exit(1); } $reqFileName = $1; -my $Hosts = $bpc->HostInfoRead(); +my $startTime = time(); -# -# Re-read config file, so we can include the PC-specific config -# -$bpc->ConfigRead($host); -%Conf = $bpc->Conf(); +my $Hosts = $bpc->HostInfoRead($client); -my $Dir = "$TopDir/pc/$host"; -my $xferPid = -1; +my $Dir = "$TopDir/pc/$client"; +my @xferPid = (); my $tarPid = -1; # @@ -90,224 +88,347 @@ my $tarPid = -1; $SIG{INT} = \&catch_signal; $SIG{ALRM} = \&catch_signal; $SIG{TERM} = \&catch_signal; +$SIG{PIPE} = \&catch_signal; +$SIG{STOP} = \&catch_signal; +$SIG{TSTP} = \&catch_signal; +$SIG{TTIN} = \&catch_signal; +my $Pid = $$; + +mkpath($Dir, 0, 0777) if ( !-d $Dir ); +if ( !-f "$Dir/LOCK" ) { + open(LOCK, ">", "$Dir/LOCK") && close(LOCK); +} + +my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); +my $logPath = sprintf("$Dir/LOG.%02d%04d", $mon + 1, $year + 1900); + +if ( !-f $logPath ) { + # + # Compress and prune old log files + # + my $lastLog = $Conf{MaxOldPerPCLogFiles} - 1; + foreach my $file ( $bpc->sortedPCLogFiles($client) ) { + if ( $lastLog <= 0 ) { + unlink($file); + next; + } + $lastLog--; + next if ( $file =~ /\.z$/ || !$Conf{CompressLevel} ); + BackupPC::FileZIO->compressCopy($file, + "$file.z", + undef, + $Conf{CompressLevel}, 1); + } +} +open(LOG, ">>", $logPath); +select(LOG); $| = 1; select(STDOUT); # # Read the request file # if ( !(my $ret = do "$Dir/$reqFileName") ) { - die "couldn't parse $Dir/$reqFileName: $@" if $@; - die "couldn't do $Dir/$reqFileName: $!" unless defined $ret; - die "couldn't run $Dir/$reqFileName"; + my $err; + if ( $@ ) { + $err = "couldn't parse $Dir/$reqFileName: $@"; + } elsif ( !defined($ret) ) { + $err = "couldn't do $Dir/$reqFileName: $!"; + } else { + $err = "couldn't run $Dir/$reqFileName"; + } + $stat{hostError} = $err; + exit(RestoreCleanup($client)); +} + +# +# Re-read config file, so we can include the PC-specific config +# +if ( defined(my $error = $bpc->ConfigRead($client)) ) { + $stat{hostError} = "Can't read PC's config file: $error"; + exit(RestoreCleanup($client)); } +%Conf = $bpc->Conf(); # # Make sure we eventually timeout if there is no activity from # the data transport program. # -alarm($Conf{SmbClientTimeout}); +alarm($Conf{ClientTimeout}); -mkpath($Dir, 0, 0777) if ( !-d $Dir ); -if ( !-f "$Dir/LOCK" ) { - open(LOCK, ">$Dir/LOCK") && close(LOCK); +# +# See if the host name is aliased +# +if ( $Conf{ClientNameAlias} ne "" ) { + $host = $Conf{ClientNameAlias}; +} else { + $host = $client; +} + +# +# Find its IP address +# +if ( $hostIP !~ /\d+\.\d+\.\d+\.\d+/ ) { + if ( !defined(gethostbyname($host)) ) { + # + # Ok, NS doesn't know about it. Maybe it is a NetBios name + # instead. + # + if ( !defined($hostIP = $bpc->NetBiosHostIPFind($host)) ) { + $stat{hostError} = "Can't find host $host"; + exit(RestoreCleanup($client)); + } + } else { + $hostIP = $host; + } } -open(LOG, ">>$Dir/LOG"); -select(LOG); $| = 1; select(STDOUT); # # Check if $host is alive # my $delay = $bpc->CheckHostAlive($hostIP); if ( $delay < 0 ) { - print(LOG $bpc->timeStamp, "no ping response\n"); - print("no ping response\n"); - exit(1); + $stat{hostError} = "no ping response from $host ($hostIP)"; + exit(RestoreCleanup($client)); } elsif ( $delay > $Conf{PingMaxMsec} ) { - printf(LOG "%sping too slow: %.4gmsec\n", $bpc->timeStamp, $delay); - printf("ping too slow: %.4gmsec (threshold is %gmsec)\n", + $stat{hostError} = sprintf("ping too slow: %.4gmsec (max is %gmsec)\n", $delay, $Conf{PingMaxMsec}); - exit(1); + exit(RestoreCleanup($client)); } # # Make sure it is really the machine we expect # if ( (my $errMsg = CorrectHostCheck($hostIP, $host)) ) { - print(LOG $bpc->timeStamp, "restore failed: $errMsg\n"); - print("restore failed: $errMsg\n"); - exit(1); + $stat{hostError} = $errMsg; + exit(RestoreCleanup($client)); } # # Setup file extension for compression and open RestoreLOG output file # -$Conf{CompressLevel} = 0 if ( !BackupPC::FileZIO->compOk ); +if ( $Conf{CompressLevel} && !BackupPC::FileZIO->compOk ) { + $stat{hostError} = "Compress:Zlib not found"; + exit(RestoreCleanup($client)); +} my $fileExt = $Conf{CompressLevel} > 0 ? ".z" : ""; my $RestoreLOG = BackupPC::FileZIO->open("$Dir/RestoreLOG$fileExt", 1, $Conf{CompressLevel}); -my $startTime = time(); - my $tarCreateFileCnt = 0; my $tarCreateByteCnt = 0; +my $tarCreateDirCnt = 0; my $tarCreateErrCnt = 1; # assume not ok until we learn otherwise my $tarCreateErr; -my($logMsg, %stat, $xfer); - -# -# Now do the restore -# -local(*RH, *WH); +my($logMsg, $xfer); $stat{xferOK} = $stat{hostAbort} = undef; $stat{hostError} = $stat{lastOutputLine} = undef; +local(*RH, *WH); # -# Create a pipe to connect BackupPC_tarCreate to the transport program -# (smbclient, tar, etc). -# WH is the write handle for writing, provided to BackupPC_tarCreate -# and RH is the other end of the pipe for reading provided to the -# transport program. +# Run an optional pre-restore command # -pipe(RH, WH); +UserCommandRun("RestorePreUserCmd"); +if ( $? && $Conf{UserCmdCheckStatus} ) { + $stat{hostError} = "RestorePreUserCmd returned error status $?"; + exit(RestoreCleanup($client)); +} +$NeedPostCmd = 1; + +$xfer = BackupPC::Xfer::create($Conf{XferMethod}, $bpc); +if ( !defined($xfer) ) { + my $errStr = BackupPC::Xfer::errStr(); + UserCommandRun("RestorePostUserCmd") if ( $NeedPostCmd ); + $stat{hostError} = $errStr; + exit(RestoreCleanup($client)); +} + +my $useTar = $xfer->useTar; + +if ( $useTar ) { + # + # Create a socketpair to connect BackupPC_tarCreate to the transport + # program (smbclient, tar, etc). + # WH is the write handle for writing, provided to BackupPC_tarCreate + # and RH is the other end of the pipe for reading provided to the + # transport program. + # + if ( socketpair(RH, WH, AF_UNIX, SOCK_STREAM, PF_UNSPEC) ) { + shutdown(RH, 1); # no writing to this socket + shutdown(WH, 0); # no reading from this socket + setsockopt(RH, SOL_SOCKET, SO_RCVBUF, 8 * 65536); + setsockopt(WH, SOL_SOCKET, SO_SNDBUF, 8 * 65536); + } else { + # + # Default to pipe() if socketpair() doesn't work. + # + pipe(RH, WH); + } +} # # Run the transport program, which reads from RH and extracts the data. # +my @Backups = $bpc->BackupInfoRead($RestoreReq{hostSrc}); my $xferArgs = { - host => $host, - hostIP => $hostIP, - type => "restore", - shareName => $RestoreReq{shareDest}, - pipeRH => *RH, - pipeWH => *WH, - XferLOG => $RestoreLOG, + client => $client, + host => $host, + hostIP => $hostIP, + type => "restore", + shareName => $RestoreReq{shareDest}, + pipeRH => *RH, + pipeWH => *WH, + XferLOG => $RestoreLOG, + XferMethod => $Conf{XferMethod}, + logLevel => $Conf{XferLogLevel}, + bkupSrcHost => $RestoreReq{hostSrc}, + bkupSrcShare => $RestoreReq{shareSrc}, + bkupSrcNum => $RestoreReq{num}, + backups => \@Backups, + pathHdrSrc => $RestoreReq{pathHdrSrc}, + pathHdrDest => $RestoreReq{pathHdrDest}, + fileList => $RestoreReq{fileList}, + pidHandler => \&pidHandler, }; -if ( $Conf{XferMethod} eq "tar" ) { - # - # Use tar (eg: tar/ssh) as the transport program. + +$xfer->args($xferArgs); + +if ( !defined($logMsg = $xfer->start()) ) { + UserCommandRun("RestorePostUserCmd") if ( $NeedPostCmd ); + $stat{hostError} = "xfer start failed: ", $xfer->errStr; + exit(RestoreCleanup($client)); +} + +if ( $useTar ) { # - $xfer = BackupPC::Xfer::Tar->new($bpc, $xferArgs); -} else { + # Now do the restore by running BackupPC_tarCreate # - # Default is to use smbclient (smb) as the transport program. + # The parent must close the read handle since the transport program + # is using it. # - $xfer = BackupPC::Xfer::Smb->new($bpc, $xferArgs); -} -if ( !defined($logMsg = $xfer->start()) ) { - print(LOG $bpc->timeStamp, $xfer->errStr, "\n"); - print($xfer->errStr, "\n"); - exit(1); -} -# -# The parent must close the read handle since the transport program -# is using it. -# -close(RH); + close(RH); -# -# fork a child for BackupPC_tarCreate. TAR is a file handle -# on which we (the parent) read the stderr from BackupPC_tarCreate. -# -my @tarPathOpts; -if ( defined($RestoreReq{pathHdrDest}) - && $RestoreReq{pathHdrDest} ne $RestoreReq{pathHdrSrc} ) { - @tarPathOpts = ("-r", $RestoreReq{pathHdrSrc}, - "-p", $RestoreReq{pathHdrDest} - ); -} -my @tarArgs = ( - "-h", $RestoreReq{hostSrc}, - "-n", $RestoreReq{num}, - "-s", $RestoreReq{shareSrc}, - "-t", - @tarPathOpts, - @{$RestoreReq{fileList}}, -); -my $logMsg = "Running: $BinDir/BackupPC_tarCreate " - . join(" ", @tarArgs) . "\n"; -$RestoreLOG->write(\$logMsg); -if ( !defined($tarPid = open(TAR, "-|")) ) { - print(LOG $bpc->timeStamp, "can't fork to run tar\n"); - print("can't fork to run tar\n"); - close(WH); - # FIX: need to cleanup xfer - exit(0); -} -if ( !$tarPid ) { # - # This is the tarCreate child. Clone STDERR to STDOUT, - # STDOUT to WH, and then exec BackupPC_tarCreate. + # fork a child for BackupPC_tarCreate. TAR is a file handle + # on which we (the parent) read the stderr from BackupPC_tarCreate. # - setpgrp 0,0; - close(STDERR); - open(STDERR, ">&STDOUT"); - close(STDOUT); - open(STDOUT, ">&WH"); - exec("$BinDir/BackupPC_tarCreate", @tarArgs); - print(LOG $bpc->timeStamp, "can't exec $BinDir/BackupPC_tarCreate\n"); - # FIX: need to cleanup xfer - exit(0); -} -# -# The parent must close the write handle since BackupPC_tarCreate -# is using it. -# -close(WH); + my @tarPathOpts; + if ( defined($RestoreReq{pathHdrDest}) + && $RestoreReq{pathHdrDest} ne $RestoreReq{pathHdrSrc} ) { + @tarPathOpts = ("-r", $RestoreReq{pathHdrSrc}, + "-p", $RestoreReq{pathHdrDest} + ); + } + my @tarArgs = ( + "-h", $RestoreReq{hostSrc}, + "-n", $RestoreReq{num}, + "-s", $RestoreReq{shareSrc}, + "-t", + @tarPathOpts, + @{$RestoreReq{fileList}}, + ); + my $runMsg = "Running: " + . $bpc->execCmd2ShellCmd("$BinDir/BackupPC_tarCreate", @tarArgs) + . "\n"; + $RestoreLOG->write(\$runMsg); + if ( !defined($tarPid = open(TAR, "-|")) ) { + close(WH); + # FIX: need to cleanup xfer + UserCommandRun("RestorePostUserCmd") if ( $NeedPostCmd ); + $stat{hostError} = "Can't fork to run tar"; + exit(RestoreCleanup($client)); + } + binmode(TAR); + if ( !$tarPid ) { + # + # This is the tarCreate child. Clone STDERR to STDOUT, + # STDOUT to WH, and then exec BackupPC_tarCreate. + # + setpgrp 0,0; + close(STDERR); + open(STDERR, ">&STDOUT"); + close(STDOUT); + open(STDOUT, ">&WH"); + alarm(0); + exec("$BinDir/BackupPC_tarCreate", @tarArgs); + print(LOG $bpc->timeStamp, "can't exec $BinDir/BackupPC_tarCreate\n"); + # FIX: need to cleanup xfer + exit(0); + } + # + # The parent must close the write handle since BackupPC_tarCreate + # is using it. + # + close(WH); -$xferPid = $xfer->xferPid; -print(LOG $bpc->timeStamp, $logMsg, " (tarPid=$tarPid, xferPid=$xferPid)\n"); -print("started restore, tarPid=$tarPid, xferPid=$xferPid\n"); + @xferPid = $xfer->xferPid; -# -# Parse the output of the transfer program and BackupPC_tarCreate -# while they run. Since we are reading from two or more children -# we use a select. -# -my($FDread, $tarOut, $mesg); -vec($FDread, fileno(TAR), 1) = 1; -$xfer->setSelectMask(\$FDread); + print(LOG $bpc->timeStamp, $logMsg, "\n"); + print("started_restore\n"); -SCAN: while ( 1 ) { - my $ein = $FDread; - last if ( $FDread =~ /^\0*$/ ); - select(my $rout = $FDread, undef, $ein, undef); - if ( vec($rout, fileno(TAR), 1) ) { - if ( sysread(TAR, $mesg, 8192) <= 0 ) { - vec($FDread, fileno(TAR), 1) = 0; - if ( !close(TAR) ) { - $tarCreateErrCnt = 1; - $tarCreateErr = "BackupPC_tarCreate failed"; + pidHandler(@xferPid); + + # + # Parse the output of the transfer program and BackupPC_tarCreate + # while they run. Since we are reading from two or more children + # we use a select. + # + my($FDread, $tarOut, $mesg); + vec($FDread, fileno(TAR), 1) = 1; + $xfer->setSelectMask(\$FDread); + + SCAN: while ( 1 ) { + my $ein = $FDread; + last if ( $FDread =~ /^\0*$/ ); + alarm($Conf{ClientTimeout}); + select(my $rout = $FDread, undef, $ein, undef); + if ( vec($rout, fileno(TAR), 1) ) { + if ( sysread(TAR, $mesg, 8192) <= 0 ) { + vec($FDread, fileno(TAR), 1) = 0; + if ( !close(TAR) ) { + $tarCreateErrCnt = 1; + $tarCreateErr = "BackupPC_tarCreate failed"; + } + } else { + $tarOut .= $mesg; } - } else { - $tarOut .= $mesg; - } - } - while ( $tarOut =~ /(.*?)[\n\r]+(.*)/s ) { - $_ = $1; - $tarOut = $2; - $RestoreLOG->write(\"tarCreate: $_\n"); - if ( /^Done: (\d+) files, (\d+) bytes, (\d+) dirs, (\d+) specials, (\d+) errors/ ) { - $tarCreateFileCnt = $1; - $tarCreateByteCnt = $2; - $tarCreateErrCnt = $5; - } - } - last if ( !$xfer->readOutput(\$FDread, $rout) ); - while ( my $str = $xfer->logMsgGet ) { - print(LOG $bpc->timeStamp, "xfer: $str\n"); - } - if ( $xfer->getStats->{fileCnt} == 1 ) { - # - # Make sure it is still the machine we expect. We do this while - # the transfer is running to avoid a potential race condition if - # the ip address was reassigned by dhcp just before we started - # the transfer. - # - if ( my $errMsg = CorrectHostCheck($hostIP, $host) ) { - $stat{hostError} = $errMsg; - last SCAN; - } + } + while ( $tarOut =~ /(.*?)[\n\r]+(.*)/s ) { + $_ = $1; + $tarOut = $2; + $RestoreLOG->write(\"tarCreate: $_\n"); + if ( /^Done: (\d+) files, (\d+) bytes, (\d+) dirs, (\d+) specials, (\d+) errors/ ) { + $tarCreateFileCnt = $1; + $tarCreateByteCnt = $2; + $tarCreateDirCnt = $3; + $tarCreateErrCnt = $5; + } + } + last if ( !$xfer->readOutput(\$FDread, $rout) ); + while ( my $str = $xfer->logMsgGet ) { + print(LOG $bpc->timeStamp, "xfer: $str\n"); + } + if ( $xfer->getStats->{fileCnt} == 1 ) { + # + # Make sure it is still the machine we expect. We do this while + # the transfer is running to avoid a potential race condition if + # the ip address was reassigned by dhcp just before we started + # the transfer. + # + if ( my $errMsg = CorrectHostCheck($hostIP, $host) ) { + $stat{hostError} = $errMsg; + last SCAN; + } + } } +} else { + # + # otherwise the xfer module does everything for us + # + print(LOG $bpc->timeStamp, $logMsg . "\n"); + print("started_restore\n"); + ($tarCreateFileCnt, $tarCreateByteCnt, + $tarCreateErrCnt, $tarCreateErr) = $xfer->run(); } +alarm(0); # # Merge the xfer status (need to accumulate counts) @@ -326,106 +447,234 @@ foreach my $k ( (keys(%stat), keys(%$newStat)) ) { next; } } -$RestoreLOG->close(); -$stat{xferOK} = 0 if ( $stat{hostError} || $stat{hostAbort} || $tarCreateErr ); -if ( !$stat{xferOK} ) { +exit(RestoreCleanup($client)); + +########################################################################### +# Subroutines +########################################################################### + +sub CorrectHostCheck +{ + my($hostIP, $host) = @_; + return if ( $hostIP eq $host && !$Conf{FixedIPNetBiosNameCheck} + || $Conf{NmbLookupCmd} eq "" ); + my($netBiosHost, $netBiosUser) = $bpc->NetBiosInfoGet($hostIP); + return "host $host has mismatching netbios name $netBiosHost" + if ( lc($netBiosHost) ne lc(substr($host, 0, 15)) ); + return; +} + +sub catch_signal +{ + my $signame = shift; + # - # kill off the tranfer program, first nicely then forcefully + # Children quit quietly on ALRM # - kill(2, $xferPid); - sleep(1); - kill(9, $xferPid); + exit(1) if ( $Pid != $$ && $signame eq "ALRM" ); + # - # kill off the tar process, first nicely then forcefully + # Ignore signals in children # - kill(2, $tarPid); - sleep(1); - kill(9, $tarPid); -} - -my $lastNum = -1; -my @Restores; + return if ( $Pid != $$ ); -# -# Do one last check to make sure it is still the machine we expect. -# -if ( $stat{xferOK} && (my $errMsg = CorrectHostCheck($hostIP, $host)) ) { - $stat{hostError} = $errMsg; + # + # Note: needs to be tested for each kind of XferMethod + # + print(LOG $bpc->timeStamp, "cleaning up after signal $signame\n"); + $SIG{$signame} = 'IGNORE'; + $RestoreLOG->write(\"exiting after signal $signame\n"); $stat{xferOK} = 0; + if ( $signame eq "INT" ) { + $stat{hostError} = "aborted by user (signal=$signame)"; + } else { + $stat{hostError} = "aborted by signal=$signame"; + } + exit(RestoreCleanup($client)); } -@Restores = $bpc->RestoreInfoRead($host); -for ( my $i = 0 ; $i < @Restores ; $i++ ) { - $lastNum = $Restores[$i]{num} if ( $lastNum < $Restores[$i]{num} ); -} -$lastNum++; -rename("$Dir/RestoreLOG$fileExt", "$Dir/RestoreLOG.$lastNum$fileExt"); -rename("$Dir/$reqFileName", "$Dir/RestoreInfo.$lastNum"); -my $endTime = time(); # -# If the restore failed, clean up +# Cleanup and update the restore status # -if ( !$stat{xferOK} ) { +sub RestoreCleanup +{ + my($client) = @_; + + $stat{xferOK} = 0 if ( $stat{hostError} || $stat{hostAbort} + || $tarCreateErr ); + + if ( !$stat{xferOK} ) { + # + # kill off the tranfer program, first nicely then forcefully + # + if ( @xferPid ) { + kill($bpc->sigName2num("INT"), @xferPid); + sleep(1); + kill($bpc->sigName2num("KILL"), @xferPid); + } + # + # kill off the tar process, first nicely then forcefully + # + if ( $tarPid > 0 ) { + kill($bpc->sigName2num("INT"), $tarPid); + sleep(1); + kill($bpc->sigName2num("KILL"), $tarPid); + } + } + + my $lastNum = -1; + my @Restores; + # - # wait a short while and see if the system is still alive + # Do one last check to make sure it is still the machine we expect. # - $stat{hostError} ||= $tarCreateErr if ( $tarCreateErr ne "" ); - $stat{hostError} = $stat{lastOutputLine} if ( $stat{hostError} eq "" ); - if ( $stat{hostError} ) { - print(LOG $bpc->timeStamp, - "Got fatal error during xfer ($stat{hostError})\n"); + if ( $stat{xferOK} && (my $errMsg = CorrectHostCheck($hostIP, $host)) ) { + $stat{hostError} = $errMsg; + $stat{xferOK} = 0; + } + @Restores = $bpc->RestoreInfoRead($client); + for ( my $i = 0 ; $i < @Restores ; $i++ ) { + $lastNum = $Restores[$i]{num} if ( $lastNum < $Restores[$i]{num} ); } - sleep(2); - if ( $bpc->CheckHostAlive($hostIP) < 0 ) { - $stat{hostAbort} = 1; + $lastNum++; + + # + # Run an optional post-restore command + # + if ( $NeedPostCmd ) { + UserCommandRun("RestorePostUserCmd"); + if ( $? && $Conf{UserCmdCheckStatus} ) { + $stat{hostError} = "RestorePostUserCmd returned error status $?"; + $stat{xferOK} = 0; + } } - if ( $stat{hostAbort} ) { - $stat{hostError} = "lost network connection during restore"; + + rename("$Dir/RestoreLOG$fileExt", "$Dir/RestoreLOG.$lastNum$fileExt"); + rename("$Dir/$reqFileName", "$Dir/RestoreInfo.$lastNum"); + my $endTime = time(); + + # + # If the restore failed, clean up + # + if ( !$stat{xferOK} ) { + # + # wait a short while and see if the system is still alive + # + $stat{hostError} ||= $tarCreateErr if ( $tarCreateErr ne "" ); + $stat{hostError} = $stat{lastOutputLine} if ( $stat{hostError} eq "" ); + sleep(2); + if ( $bpc->CheckHostAlive($hostIP) < 0 ) { + $stat{hostAbort} = 1; + } + if ( $stat{hostAbort} && $stat{hostError} eq "" ) { + $stat{hostError} = "lost network connection during restore"; + } + $RestoreLOG->write(\"restore failed: $stat{hostError}\n") + if ( defined($RestoreLOG) ); } -} -# -# Add the new restore information to the restore file -# -@Restores = $bpc->RestoreInfoRead($host); -my $i = @Restores; -$Restores[$i]{num} = $lastNum; -$Restores[$i]{startTime} = $startTime; -$Restores[$i]{endTime} = $endTime; -$Restores[$i]{result} = $stat{xferOK} ? "ok" : "failed"; -$Restores[$i]{errorMsg} = $stat{hostError}; -$Restores[$i]{nFiles} = $tarCreateFileCnt; -$Restores[$i]{size} = $tarCreateByteCnt; -$Restores[$i]{tarCreateErrs} = $tarCreateErrCnt; -$Restores[$i]{xferErrs} = $stat{xferErrCnt} || 0; - -while ( @Restores > $Conf{RestoreInfoKeepCnt} ) { - my $num = $Restores[0]{num}; - unlink("$Dir/RestoreLOG.$num.z"); - unlink("$Dir/RestoreLOG.$num"); - unlink("$Dir/RestoreInfo.$num"); - shift(@Restores); -} -$bpc->RestoreInfoWrite($host, @Restores); + $RestoreLOG->close() if ( defined($RestoreLOG) ); -if ( !$stat{xferOK} ) { - print(LOG $bpc->timeStamp, "Restore aborted ($stat{hostError})\n"); - print("restore failed: $stat{hostError}\n"); -} else { - print("restore complete\n"); + # + # Add the new restore information to the restore file + # + @Restores = $bpc->RestoreInfoRead($client); + my $i = @Restores; + $Restores[$i]{num} = $lastNum; + $Restores[$i]{startTime} = $startTime; + $Restores[$i]{endTime} = $endTime; + $Restores[$i]{result} = $stat{xferOK} ? "ok" : "failed"; + $Restores[$i]{errorMsg} = $stat{hostError}; + $Restores[$i]{nFiles} = $tarCreateFileCnt; + $Restores[$i]{size} = $tarCreateByteCnt; + $Restores[$i]{tarCreateErrs} = $tarCreateErrCnt; + $Restores[$i]{xferErrs} = $stat{xferErrCnt} || 0; + + while ( @Restores > $Conf{RestoreInfoKeepCnt} ) { + my $num = $Restores[0]{num}; + unlink("$Dir/RestoreLOG.$num.z"); + unlink("$Dir/RestoreLOG.$num"); + unlink("$Dir/RestoreInfo.$num"); + shift(@Restores); + } + $bpc->RestoreInfoWrite($client, @Restores); + + if ( !$stat{xferOK} ) { + print(LOG $bpc->timeStamp, "restore failed ($stat{hostError})\n"); + print("restore failed: $stat{hostError}\n"); + return 1; + } else { + $stat{xferErrCnt} ||= 0; + print(LOG $bpc->timeStamp, "restore $lastNum complete" + . " ($tarCreateFileCnt files, $tarCreateByteCnt bytes," + . " $tarCreateDirCnt dirs, $stat{xferErrCnt} xferErrs)\n"); + print("restore complete\n"); + return; + } } -########################################################################### -# Subroutines -########################################################################### +# +# The Xfer method might tell us from time to time about processes +# it forks. We tell BackupPC about this (for status displays) and +# keep track of the pids in case we cancel the backup +# +sub pidHandler +{ + @xferPid = @_; + @xferPid = grep(/./, @xferPid); + return if ( !@xferPid && $tarPid < 0 ); + my @pids = @xferPid; + push(@pids, $tarPid) if ( $tarPid > 0 ); + my $str = join(",", @pids); + $RestoreLOG->write(\"Xfer PIDs are now $str\n") if ( defined($RestoreLOG) ); + print("xferPids $str\n"); +} -sub CorrectHostCheck +# +# Run an optional pre- or post-dump command +# +sub UserCommandRun { - my($hostIP, $host) = @_; - return if ( $hostIP eq $host && !$Conf{FixedIPNetBiosNameCheck} ); - my($netBiosHost, $netBiosUser) = $bpc->NetBiosInfoGet($hostIP); - return "host $host has mismatching netbios name $netBiosHost" - if ( $netBiosHost ne $host ); - return; + my($cmdType) = @_; + + return if ( !defined($Conf{$cmdType}) ); + my $vars = { + xfer => $xfer, + client => $client, + host => $host, + hostIP => $hostIP, + share => $RestoreReq{shareDest}, + XferMethod => $Conf{XferMethod}, + sshPath => $Conf{SshPath}, + LOG => *LOG, + user => $Hosts->{$client}{user}, + moreUsers => $Hosts->{$client}{moreUsers}, + XferLOG => $RestoreLOG, + stat => \%stat, + xferOK => $stat{xferOK} || 0, + hostError => $stat{hostError}, + type => "restore", + bkupSrcHost => $RestoreReq{hostSrc}, + bkupSrcShare => $RestoreReq{shareSrc}, + bkupSrcNum => $RestoreReq{num}, + backups => \@Backups, + pathHdrSrc => $RestoreReq{pathHdrSrc}, + pathHdrDest => $RestoreReq{pathHdrDest}, + fileList => $RestoreReq{fileList}, + cmdType => $cmdType, + }; + my $cmd = $bpc->cmdVarSubstitute($Conf{$cmdType}, $vars); + $RestoreLOG->write(\"Executing $cmdType: @$cmd\n"); + # + # Run the user's command, dumping the stdout/stderr into the + # Xfer log file. Also supply the optional $vars and %Conf in + # case the command is really perl code instead of a shell + # command. + # + $bpc->cmdSystemOrEval($cmd, + sub { + $RestoreLOG->write(\$_[0]); + }, + $vars, \%Conf); }