BackupPC_archive (BackupPC-4.3.2) | : | BackupPC_archive (BackupPC-4.4.0) | ||
---|---|---|---|---|
skipping to change at line 31 | skipping to change at line 31 | |||
# This program is distributed in the hope that it will be useful, | # This program is distributed in the hope that it will be useful, | |||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
# GNU General Public License for more details. | # GNU General Public License for more details. | |||
# | # | |||
# You should have received a copy of the GNU General Public License | # You should have received a copy of the GNU General Public License | |||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
# | # | |||
#======================================================================== | #======================================================================== | |||
# | # | |||
# Version 4.3.2, released 17 Feb 2020. | # Version 4.4.0, released 20 Jun 2020. | |||
# | # | |||
# See http://backuppc.sourceforge.net. | # See http://backuppc.sourceforge.net. | |||
# | # | |||
#======================================================================== | #======================================================================== | |||
use strict; | use strict; | |||
no utf8; | no utf8; | |||
use lib "__INSTALLDIR__/lib"; | use lib "__INSTALLDIR__/lib"; | |||
use BackupPC::Lib; | use BackupPC::Lib; | |||
use BackupPC::XS; | use BackupPC::XS; | |||
use BackupPC::Xfer::Archive; | use BackupPC::Xfer::Archive; | |||
use Getopt::Std; | use Getopt::Std; | |||
use vars qw( %ArchiveReq ); | use vars qw( %ArchiveReq ); | |||
########################################################################### | ########################################################################### | |||
# Initialize | # Initialize | |||
########################################################################### | ########################################################################### | |||
die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) ); | die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) ); | |||
my $TopDir = $bpc->TopDir(); | my $TopDir = $bpc->TopDir(); | |||
my $BinDir = $bpc->BinDir(); | my $BinDir = $bpc->BinDir(); | |||
my %Conf = $bpc->Conf(); | my %Conf = $bpc->Conf(); | |||
my $NeedPostCmd; | my $NeedPostCmd; | |||
my($user, $host, $client, $reqFileName, %stat); | my($user, $host, $client, $reqFileName, %stat); | |||
$bpc->ChildInit(); | $bpc->ChildInit(); | |||
my %opts; | my %opts; | |||
if ( !getopts("m", \%opts) || @ARGV != 3 ) { | if ( !getopts("m", \%opts) || @ARGV != 3 ) { | |||
print("usage: $0 [-m] <user> <archiveclient> <reqFileName>\n"); | print("usage: $0 [-m] <user> <archiveclient> <reqFileName>\n"); | |||
exit(1); | exit(1); | |||
} | } | |||
$user = $1 if ( $ARGV[0] =~ /(.+)/ ); | $user = $1 if ( $ARGV[0] =~ /(.+)/ ); | |||
$client = $1 if ( $ARGV[1] =~ /(.+)/ ); | $client = $1 if ( $ARGV[1] =~ /(.+)/ ); | |||
if ( $ARGV[2] !~ /^([\w\.\s-]+)$/ ) { | if ( $ARGV[2] !~ /^([\w\.\s-]+)$/ ) { | |||
print("$0: bad reqFileName (arg #3): $ARGV[2]\n"); | print("$0: bad reqFileName (arg #3): $ARGV[2]\n"); | |||
exit(1); | exit(1); | |||
} | } | |||
$reqFileName = $1; | $reqFileName = $1; | |||
if ( !$opts{m} && !defined($bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPo | if ( !$opts{m} | |||
rt})) | && !defined($bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort})) | |||
&& (my $status = $bpc->ServerMesg("hostMutex $client 1 BackupPC_ar | && (my $status = $bpc->ServerMesg("hostMutex $client 1 BackupPC_archive")) = | |||
chive")) =~ /fail/ ) { | ~ /fail/ ) { | |||
print(STDERR "$0: $status (use -m option to force running)\n"); | print(STDERR "$0: $status (use -m option to force running)\n"); | |||
exit(1); | exit(1); | |||
} | } | |||
my $startTime = time(); | my $startTime = time(); | |||
my $Dir = "$TopDir/pc/$client"; | my $Dir = "$TopDir/pc/$client"; | |||
my @xferPid = (); | my @xferPid = (); | |||
# | # | |||
skipping to change at line 114 | skipping to change at line 117 | |||
# | # | |||
# Read the request file | # Read the request file | |||
# | # | |||
if ( !(my $ret = do "$Dir/$reqFileName") ) { | if ( !(my $ret = do "$Dir/$reqFileName") ) { | |||
my $err; | my $err; | |||
if ( $@ ) { | if ( $@ ) { | |||
$err = "couldn't parse $Dir/$reqFileName: $@"; | $err = "couldn't parse $Dir/$reqFileName: $@"; | |||
} elsif ( !defined($ret) ) { | } elsif ( !defined($ret) ) { | |||
$err = "couldn't do $Dir/$reqFileName: $!"; | $err = "couldn't do $Dir/$reqFileName: $!"; | |||
} else { | } else { | |||
$err = "couldn't run $Dir/$reqFileName"; | $err = "couldn't run $Dir/$reqFileName"; | |||
} | } | |||
$stat{hostError} = $err; | $stat{hostError} = $err; | |||
exit(ArchiveCleanup($client)); | exit(ArchiveCleanup($client)); | |||
} | } | |||
# | # | |||
# Re-read config file, so we can include the PC-specific config | # Re-read config file, so we can include the PC-specific config | |||
# | # | |||
if ( defined(my $error = $bpc->ConfigRead($client)) ) { | if ( defined(my $error = $bpc->ConfigRead($client)) ) { | |||
$stat{hostError} = "Can't read PC's config file: $error"; | $stat{hostError} = "Can't read PC's config file: $error"; | |||
skipping to change at line 146 | skipping to change at line 149 | |||
# | # | |||
# See if the host name is aliased | # See if the host name is aliased | |||
# | # | |||
if ( $Conf{ClientNameAlias} ne "" ) { | if ( $Conf{ClientNameAlias} ne "" ) { | |||
$host = $Conf{ClientNameAlias}; | $host = $Conf{ClientNameAlias}; | |||
} else { | } else { | |||
$host = $client; | $host = $client; | |||
} | } | |||
my $fileExt = $Conf{CompressLevel} > 0 ? ".z" : ""; | my $fileExt = $Conf{CompressLevel} > 0 ? ".z" : ""; | |||
my $ArchiveLOG = BackupPC::XS::FileZIO::open("$Dir/ArchiveLOG$fileExt", 1, | my $ArchiveLOG = BackupPC::XS::FileZIO::open("$Dir/ArchiveLOG$fileExt", 1, $Conf | |||
$Conf{CompressLevel}); | {CompressLevel}); | |||
my($logMsg, $xfer); | my($logMsg, $xfer); | |||
$stat{xferOK} = 1; | $stat{xferOK} = 1; | |||
$stat{hostAbort} = undef; | $stat{hostAbort} = undef; | |||
$stat{hostError} = $stat{lastOutputLine} = undef; | $stat{hostError} = $stat{lastOutputLine} = undef; | |||
local(*RH, *WH); | local(*RH, *WH); | |||
# | # | |||
# Run an optional pre-archive command | # Run an optional pre-archive command | |||
# | # | |||
UserCommandRun("ArchivePreUserCmd"); | UserCommandRun("ArchivePreUserCmd"); | |||
if ( $? && $Conf{UserCmdCheckStatus} ) { | if ( $? && $Conf{UserCmdCheckStatus} ) { | |||
$stat{hostError} = "ArchivePreUserCmd returned error status $?"; | $stat{hostError} = "ArchivePreUserCmd returned error status $?"; | |||
skipping to change at line 173 | skipping to change at line 175 | |||
} | } | |||
$NeedPostCmd = 1; | $NeedPostCmd = 1; | |||
$xfer = BackupPC::Xfer::Archive->new($bpc); | $xfer = BackupPC::Xfer::Archive->new($bpc); | |||
# | # | |||
# Run the transport program | # Run the transport program | |||
# | # | |||
my $xferArgs = { | my $xferArgs = { | |||
client => $client, | client => $client, | |||
host => $host, | host => $host, | |||
user => $ArchiveReq{user}, | user => $ArchiveReq{user}, | |||
type => "archive", | type => "archive", | |||
XferLOG => $ArchiveLOG, | XferLOG => $ArchiveLOG, | |||
XferMethod => $Conf{XferMethod}, | XferMethod => $Conf{XferMethod}, | |||
pathHdrSrc => $ArchiveReq{pathHdrSrc}, | pathHdrSrc => $ArchiveReq{pathHdrSrc}, | |||
pathHdrDest => $ArchiveReq{pathHdrDest}, | pathHdrDest => $ArchiveReq{pathHdrDest}, | |||
HostList => \@{$ArchiveReq{HostList}}, | HostList => \@{$ArchiveReq{HostList}}, | |||
BackupList => \@{$ArchiveReq{BackupList}}, | BackupList => \@{$ArchiveReq{BackupList}}, | |||
archiveloc => $ArchiveReq{archiveloc}, | archiveloc => $ArchiveReq{archiveloc}, | |||
parfile => $ArchiveReq{parfile}, | parfile => $ArchiveReq{parfile}, | |||
compression => $ArchiveReq{compression}, | compression => $ArchiveReq{compression}, | |||
compext => $ArchiveReq{compext}, | compext => $ArchiveReq{compext}, | |||
splitsize => $ArchiveReq{splitsize}, | splitsize => $ArchiveReq{splitsize}, | |||
pidHandler => \&pidHandler, | pidHandler => \&pidHandler, | |||
}; | }; | |||
$xfer->args($xferArgs); | $xfer->args($xferArgs); | |||
if ( !defined($logMsg = $xfer->start()) ) { | if ( !defined($logMsg = $xfer->start()) ) { | |||
UserCommandRun("ArchivePostUserCmd") if ( $NeedPostCmd ); | UserCommandRun("ArchivePostUserCmd") if ( $NeedPostCmd ); | |||
$stat{hostError} = "xfer start failed: ", $xfer->errStr; | $stat{hostError} = "xfer start failed: ", $xfer->errStr; | |||
exit(ArchiveCleanup($client)); | exit(ArchiveCleanup($client)); | |||
} | } | |||
skipping to change at line 233 | skipping to change at line 235 | |||
return if ( $Pid != $$ ); | return if ( $Pid != $$ ); | |||
# | # | |||
# Note: needs to be tested for each kind of XferMethod | # Note: needs to be tested for each kind of XferMethod | |||
# | # | |||
print($LogFd $bpc->timeStamp, "cleaning up after signal $signame\n"); | print($LogFd $bpc->timeStamp, "cleaning up after signal $signame\n"); | |||
$SIG{$signame} = 'IGNORE'; | $SIG{$signame} = 'IGNORE'; | |||
$ArchiveLOG->write(\"exiting after signal $signame\n"); | $ArchiveLOG->write(\"exiting after signal $signame\n"); | |||
$stat{xferOK} = 0; | $stat{xferOK} = 0; | |||
if ( $signame eq "INT" ) { | if ( $signame eq "INT" ) { | |||
$stat{hostError} = "aborted by user (signal=$signame)"; | $stat{hostError} = "aborted by user (signal=$signame)"; | |||
} else { | } else { | |||
$stat{hostError} = "aborted by signal=$signame"; | $stat{hostError} = "aborted by signal=$signame"; | |||
} | } | |||
exit(ArchiveCleanup($client)); | exit(ArchiveCleanup($client)); | |||
} | } | |||
# | # | |||
# Cleanup and update the archive status | # Cleanup and update the archive status | |||
# | # | |||
sub ArchiveCleanup | sub ArchiveCleanup | |||
{ | { | |||
my($client) = @_; | my($client) = @_; | |||
$stat{xferOK} = 0 if ( $stat{hostError} || $stat{hostAbort} ); | $stat{xferOK} = 0 if ( $stat{hostError} || $stat{hostAbort} ); | |||
if ( !$stat{xferOK} ) { | if ( !$stat{xferOK} ) { | |||
# | # | |||
# Kill off the transfer program, first nicely then forcefully. | # Kill off the transfer program, first nicely then forcefully. | |||
# We use negative PIDs to make sure all processes in each | # We use negative PIDs to make sure all processes in each | |||
# group get the signal. | # group get the signal. | |||
# | # | |||
if ( @xferPid ) { | if ( @xferPid ) { | |||
foreach my $pid ( @xferPid ) { | foreach my $pid ( @xferPid ) { | |||
kill($bpc->sigName2num("INT"), -$pid); | kill($bpc->sigName2num("INT"), -$pid); | |||
} | } | |||
sleep(1); | sleep(1); | |||
foreach my $pid ( @xferPid ) { | foreach my $pid ( @xferPid ) { | |||
kill($bpc->sigName2num("KILL"), -$pid); | kill($bpc->sigName2num("KILL"), -$pid); | |||
} | } | |||
} | } | |||
} | } | |||
my $lastNum = -1; | my $lastNum = -1; | |||
my @Archives; | my @Archives; | |||
@Archives = $bpc->ArchiveInfoRead($client); | @Archives = $bpc->ArchiveInfoRead($client); | |||
for ( my $i = 0 ; $i < @Archives ; $i++ ) { | for ( my $i = 0 ; $i < @Archives ; $i++ ) { | |||
$lastNum = $Archives[$i]{num} if ( $lastNum < $Archives[$i]{num} ); | $lastNum = $Archives[$i]{num} if ( $lastNum < $Archives[$i]{num} ); | |||
} | } | |||
$lastNum++; | $lastNum++; | |||
# | # | |||
# Run an optional post-archive command | # Run an optional post-archive command | |||
# | # | |||
if ( $NeedPostCmd ) { | if ( $NeedPostCmd ) { | |||
UserCommandRun("ArchivePostUserCmd"); | UserCommandRun("ArchivePostUserCmd"); | |||
if ( $? && $Conf{UserCmdCheckStatus} ) { | if ( $? && $Conf{UserCmdCheckStatus} ) { | |||
$stat{hostError} = "RestorePreUserCmd returned error status $?"; | $stat{hostError} = "ArchivePostUserCmd returned error status $?"; | |||
$stat{xferOK} = 0; | $stat{xferOK} = 0; | |||
} | } | |||
} | } | |||
rename("$Dir/ArchiveLOG$fileExt", "$Dir/ArchiveLOG.$lastNum$fileExt"); | rename("$Dir/ArchiveLOG$fileExt", "$Dir/ArchiveLOG.$lastNum$fileExt"); | |||
rename("$Dir/$reqFileName", "$Dir/ArchiveInfo.$lastNum"); | rename("$Dir/$reqFileName", "$Dir/ArchiveInfo.$lastNum"); | |||
my $endTime = time(); | my $endTime = time(); | |||
# | # | |||
# If the archive failed, clean up | # If the archive failed, clean up | |||
# | # | |||
if ( !$stat{xferOK} ) { | if ( !$stat{xferOK} ) { | |||
$stat{hostError} = $stat{lastOutputLine} if ( $stat{hostError} eq "" ); | $stat{hostError} = $stat{lastOutputLine} if ( $stat{hostError} eq "" ); | |||
$stat{hostAbort} = 1; | $stat{hostAbort} = 1; | |||
$ArchiveLOG->write(\"Archive failed: $stat{hostError}") | $ArchiveLOG->write(\"Archive failed: $stat{hostError}") | |||
if ( defined($ArchiveLOG) ); | if ( defined($ArchiveLOG) ); | |||
} | } | |||
$ArchiveLOG->close() if ( defined($ArchiveLOG) ); | $ArchiveLOG->close() if ( defined($ArchiveLOG) ); | |||
# | # | |||
# Add the new archive information to the archive file | # Add the new archive information to the archive file | |||
# | # | |||
@Archives = $bpc->ArchiveInfoRead($client); | @Archives = $bpc->ArchiveInfoRead($client); | |||
my $i = @Archives; | my $i = @Archives; | |||
$Archives[$i]{num} = $lastNum; | $Archives[$i]{num} = $lastNum; | |||
$Archives[$i]{startTime} = $startTime; | $Archives[$i]{startTime} = $startTime; | |||
$Archives[$i]{endTime} = $endTime; | $Archives[$i]{endTime} = $endTime; | |||
$Archives[$i]{result} = $stat{xferOK} ? "ok" : "failed"; | $Archives[$i]{result} = $stat{xferOK} ? "ok" : "failed"; | |||
$Archives[$i]{errorMsg} = $stat{hostError}; | $Archives[$i]{errorMsg} = $stat{hostError}; | |||
while ( @Archives > $Conf{ArchiveInfoKeepCnt} ) { | while ( @Archives > $Conf{ArchiveInfoKeepCnt} ) { | |||
my $num = $Archives[0]{num}; | my $num = $Archives[0]{num}; | |||
unlink("$Dir/ArchiveLOG.$num.z"); | unlink("$Dir/ArchiveLOG.$num.z"); | |||
unlink("$Dir/ArchiveLOG.$num"); | unlink("$Dir/ArchiveLOG.$num"); | |||
unlink("$Dir/ArchiveInfo.$num"); | unlink("$Dir/ArchiveInfo.$num"); | |||
shift(@Archives); | shift(@Archives); | |||
} | } | |||
$bpc->ArchiveInfoWrite($client, @Archives); | $bpc->ArchiveInfoWrite($client, @Archives); | |||
if ( !$stat{xferOK} ) { | if ( !$stat{xferOK} ) { | |||
print($LogFd $bpc->timeStamp, "Archive failed ($stat{hostError})\n"); | print($LogFd $bpc->timeStamp, "Archive failed ($stat{hostError})\n"); | |||
print("archive failed: $stat{hostError}\n"); | print("archive failed: $stat{hostError}\n"); | |||
return 1; | return 1; | |||
} else { | } else { | |||
print($LogFd $bpc->timeStamp, "Archive Complete\n"); | print($LogFd $bpc->timeStamp, "Archive Complete\n"); | |||
print("archive complete\n"); | print("archive complete\n"); | |||
return; | return; | |||
} | } | |||
} | } | |||
# | # | |||
# The Xfer method might tell us from time to time about processes | # The Xfer method might tell us from time to time about processes | |||
# it forks. We tell BackupPC about this (for status displays) and | # it forks. We tell BackupPC about this (for status displays) and | |||
# keep track of the pids in case we cancel the backup | # keep track of the pids in case we cancel the backup | |||
# | # | |||
sub pidHandler | sub pidHandler | |||
{ | { | |||
@xferPid = @_; | @xferPid = @_; | |||
@xferPid = grep(/./, @xferPid); | @xferPid = grep(/./, @xferPid); | |||
return if ( !@xferPid ); | return if ( !@xferPid ); | |||
my @pids = @xferPid; | my @pids = @xferPid; | |||
my $str = join(",", @pids); | my $str = join(",", @pids); | |||
$ArchiveLOG->write(\"Xfer PIDs are now $str\n") if ( defined($ArchiveLOG) ); | $ArchiveLOG->write(\"Xfer PIDs are now $str\n") if ( defined($ArchiveLOG) ); | |||
print("xferPids $str\n"); | print("xferPids $str\n"); | |||
} | } | |||
# | # | |||
# Run an optional pre- or post-dump command | # Run an optional pre- or post-dump command | |||
# | # | |||
sub UserCommandRun | sub UserCommandRun | |||
{ | { | |||
my($cmdType) = @_; | my($cmdType) = @_; | |||
return if ( !defined($Conf{$cmdType}) ); | return if ( !defined($Conf{$cmdType}) ); | |||
my $vars = { | my $vars = { | |||
xfer => $xfer, | xfer => $xfer, | |||
client => $client, | client => $client, | |||
host => $host, | host => $host, | |||
user => $user, | user => $user, | |||
share => $ArchiveReq{shareDest}, | share => $ArchiveReq{shareDest}, | |||
XferMethod => $Conf{XferMethod}, | XferMethod => $Conf{XferMethod}, | |||
HostList => \@{$ArchiveReq{HostList}}, | HostList => \@{$ArchiveReq{HostList}}, | |||
BackupList => \@{$ArchiveReq{BackupList}}, | BackupList => \@{$ArchiveReq{BackupList}}, | |||
archiveloc => $ArchiveReq{archiveloc}, | archiveloc => $ArchiveReq{archiveloc}, | |||
parfile => $ArchiveReq{parfile}, | parfile => $ArchiveReq{parfile}, | |||
compression => $ArchiveReq{compression}, | compression => $ArchiveReq{compression}, | |||
compext => $ArchiveReq{compext}, | compext => $ArchiveReq{compext}, | |||
splitsize => $ArchiveReq{splitsize}, | splitsize => $ArchiveReq{splitsize}, | |||
sshPath => $Conf{SshPath}, | sshPath => $Conf{SshPath}, | |||
LOG => $LogFd, | LOG => $LogFd, | |||
XferLOG => $ArchiveLOG, | XferLOG => $ArchiveLOG, | |||
stat => \%stat, | stat => \%stat, | |||
xferOK => $stat{xferOK} || 0, | xferOK => $stat{xferOK} || 0, | |||
type => "archive", | type => "archive", | |||
cmdType => $cmdType, | cmdType => $cmdType, | |||
}; | }; | |||
my $cmd = $bpc->cmdVarSubstitute($Conf{$cmdType}, $vars); | my $cmd = $bpc->cmdVarSubstitute($Conf{$cmdType}, $vars); | |||
$ArchiveLOG->write(\"Executing $cmdType: @$cmd\n"); | $ArchiveLOG->write(\"Executing $cmdType: @$cmd\n"); | |||
# | # | |||
# Run the user's command, dumping the stdout/stderr into the | # Run the user's command, dumping the stdout/stderr into the | |||
# Xfer log file. Also supply the optional $vars and %Conf in | # Xfer log file. Also supply the optional $vars and %Conf in | |||
# case the command is really perl code instead of a shell | # case the command is really perl code instead of a shell | |||
# command. | # command. | |||
# | # | |||
$bpc->cmdSystemOrEval($cmd, | $bpc->cmdSystemOrEval( | |||
sub { | $cmd, | |||
$ArchiveLOG->write(\$_[0]); | sub { | |||
if ( $ArchiveLOG && length($_[0]) ) { | $ArchiveLOG->write(\$_[0]); | |||
$ArchiveLOG->write(\$_[0]); | if ( $ArchiveLOG && length($_[0]) ) { | |||
} elsif ( $LogFd && length($_[0]) ) { | $ArchiveLOG->write(\$_[0]); | |||
print($LogFd $bpc->timeStamp, "Output from $cmdType: ", $_[0 | } elsif ( $LogFd && length($_[0]) ) { | |||
]); | print($LogFd $bpc->timeStamp, "Output from $cmdType: ", $_[0]); | |||
} | } | |||
}, | }, | |||
$vars, \%Conf); | $vars, | |||
\%Conf | ||||
); | ||||
} | } | |||
End of changes. 25 change blocks. | ||||
102 lines changed or deleted | 106 lines changed or added |