X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=bin%2FBackupPC_dump;h=23720cac33c358a9af97fd27d700a7f8cc3e1fbf;hb=626d6926fee223936cb597d12467c450b7b4410d;hp=0b46c90186c0ff28368477a8d396390a6e5c7800;hpb=0697368bbcef14908cd4684cf07744dc840464de;p=BackupPC.git diff --git a/bin/BackupPC_dump b/bin/BackupPC_dump index 0b46c90..23720ca 100755 --- a/bin/BackupPC_dump +++ b/bin/BackupPC_dump @@ -1,11 +1,11 @@ -#!/bin/perl -T +#!/bin/perl #============================================================= -*-perl-*- # # BackupPC_dump: Dump a single client. # # DESCRIPTION # -# Usage: BackupPC_dump [-i] [-f] [-d] [-e] +# Usage: BackupPC_dump [-i] [-f] [-d] [-e] [-v] # # Flags: # @@ -24,6 +24,8 @@ # dhcp hosts that are no longer on the network will not expire # old backups. # +# -v verbose. for manual usage: prints failure reasons in more detail. +# # BackupPC_dump is run periodically by BackupPC to backup $client. # The file $TopDir/pc/$client/backups is read to decide whether a # full or incremental backup needs to be run. If no backup is @@ -50,7 +52,7 @@ # Craig Barratt # # COPYRIGHT -# Copyright (C) 2001 Craig Barratt +# Copyright (C) 2001-2003 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 @@ -68,19 +70,21 @@ # #======================================================================== # -# Version 2.0.0_CVS, released 18 Jan 2003. +# Version 2.1.0_CVS, released 8 Feb 2004. # # 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::Rsync; +use Socket; use File::Path; use Getopt::Std; @@ -93,16 +97,16 @@ my $TopDir = $bpc->TopDir(); my $BinDir = $bpc->BinDir(); my %Conf = $bpc->Conf(); my $NeedPostCmd; +my $Hosts; $bpc->ChildInit(); my %opts; -getopts("defi", \%opts); -if ( @ARGV != 1 ) { - print("usage: $0 [-d] [-e] [-f] [-i] \n"); +if ( !getopts("defiv", \%opts) || @ARGV != 1 ) { + print("usage: $0 [-d] [-e] [-f] [-i] [-v] \n"); exit(1); } -if ( $ARGV[0] !~ /^([\w\.-\s]+)$/ ) { +if ( $ARGV[0] !~ /^([\w\.\s-]+)$/ ) { print("$0: bad client name '$ARGV[0]'\n"); exit(1); } @@ -112,6 +116,8 @@ my $host; # this is the real host name my($clientURI, $user); +$bpc->verbose(1) if ( $opts{v} ); + if ( $opts{d} ) { # # The client name $client is simply a DHCP address. We need to check @@ -119,16 +125,35 @@ if ( $opts{d} ) { # host name via NetBios using nmblookup. # $hostIP = $client; - exit(1) if ( $bpc->CheckHostAlive($hostIP) < 0 ); + if ( $bpc->CheckHostAlive($hostIP) < 0 ) { + print(STDERR "Exiting because CheckHostAlive($hostIP) failed\n") + if ( $opts{v} ); + exit(1); + } + if ( $Conf{NmbLookupCmd} eq "" ) { + print(STDERR "Exiting because \$Conf{NmbLookupCmd} is empty\n") + if ( $opts{v} ); + exit(1); + } ($client, $user) = $bpc->NetBiosInfoGet($hostIP); - exit(1) if ( $host !~ /^([\w\.-]+)$/ ); - my $hosts = $bpc->HostInfoRead($client); - exit(1) if ( !defined($hosts->{$client}) ); + if ( $client !~ /^([\w\.\s-]+)$/ ) { + print(STDERR "Exiting because NetBiosInfoGet($hostIP) returned" + . " '$client', an invalid host name\n") if ( $opts{v} ); + exit(1) + } + $Hosts = $bpc->HostInfoRead($client); $host = $client; +} else { + $Hosts = $bpc->HostInfoRead($client); +} +if ( !defined($Hosts->{$client}) ) { + print(STDERR "Exiting because host $client does not exist in the" + . " hosts file\n") if ( $opts{v} ); + exit(1) } my $Dir = "$TopDir/pc/$client"; -my $xferPid = -1; +my @xferPid = (); my $tarPid = -1; # @@ -147,6 +172,11 @@ if ( defined(my $error = $bpc->ConfigRead($client)) ) { $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 = $$; # # Make sure we eventually timeout if there is no activity from @@ -161,6 +191,21 @@ if ( !-f "$Dir/LOCK" ) { open(LOG, ">>", "$Dir/LOG"); select(LOG); $| = 1; select(STDOUT); +# +# For the -e option we just expire backups and quit +# +if ( $opts{e} ) { + BackupExpire($client); + exit(0); +} + +# +# For archive hosts we don't bother any further +# +if ($Conf{XferMethod} eq "archive" ) { + exit(0); +} + if ( !$opts{d} ) { # # In the non-DHCP case, make sure the host can be looked up @@ -176,10 +221,11 @@ if ( !$opts{d} ) { # Ok, NS doesn't know about it. Maybe it is a NetBios name # instead. # + print(STDERR "Name server doesn't know about $host; trying NetBios\n") + if ( $opts{v} ); if ( !defined($hostIP = $bpc->NetBiosHostIPFind($host)) ) { - print(LOG $bpc->timeStamp, - "dump failed: Can't find host $host\n"); - print("dump failed: Can't find host $host\n"); + print(LOG $bpc->timeStamp, "Can't find host $host via netbios\n"); + print("host not found\n"); exit(1); } } else { @@ -191,14 +237,6 @@ if ( !$opts{d} ) { # Figure out what to do and do it ########################################################################### -# -# For the -e option we just expire backups and quit -# -if ( $opts{e} ) { - BackupExpire($client); - exit(0); -} - # # See if we should skip this host during a certain range # of times. @@ -221,6 +259,8 @@ $bpc->ServerDisconnect(); if ( $opts{d} ) { if ( $StatusHost{activeJob} ) { # oops, something is already running for this host + print(STDERR "Exiting because backup is already running for $client\n") + if ( $opts{v} ); exit(0); } print("DHCP $hostIP $clientURI\n"); @@ -229,6 +269,8 @@ if ( $opts{d} ) { my($needLink, @Backups, $type, $lastBkupNum, $lastFullBkupNum); my $lastFull = 0; my $lastIncr = 0; +my $partialIdx = -1; +my $partialNum; if ( $Conf{FullPeriod} == -1 && !$opts{f} && !$opts{i} || $Conf{FullPeriod} == -2 ) { @@ -239,11 +281,32 @@ if ( !$opts{i} && !$opts{f} && $Conf{BlackoutGoodCnt} >= 0 && $StatusHost{aliveCnt} >= $Conf{BlackoutGoodCnt} ) { my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); my($currHours) = $hour + $min / 60 + $sec / 3600; - if ( $Conf{BlackoutHourBegin} <= $currHours - && $currHours <= $Conf{BlackoutHourEnd} - && grep($_ == $wday, @{$Conf{BlackoutWeekDays}}) ) { + my $blackout; + # + # Allow blackout to span midnight (specified by BlackoutHourBegin + # being greater than BlackoutHourEnd) + # + if ( $Conf{BlackoutHourBegin} > $Conf{BlackoutHourEnd} ) { + $blackout = $Conf{BlackoutHourBegin} <= $currHours + || $currHours <= $Conf{BlackoutHourEnd}; + if ( $currHours <= $Conf{BlackoutHourEnd} ) { + # + # This is after midnight, so decrement the weekday for the + # weekday check (eg: Monday 11pm-1am means Monday 2300 to + # Tuesday 0100, not Monday 2300-2400 plus Monday 0000-0100). + # + $wday--; + $wday += 7 if ( $wday < 0 ); + } + } else { + $blackout = $Conf{BlackoutHourBegin} <= $currHours + && $currHours <= $Conf{BlackoutHourEnd}; + } + if ( $blackout && grep($_ == $wday, @{$Conf{BlackoutWeekDays}}) ) { # print(LOG $bpc->timeStamp, "skipping because of blackout" # . " (alive $StatusHost{aliveCnt} times)\n"); + print(STDERR "Skipping $client because of blackout\n") + if ( $opts{v} ); NothingToDo($needLink); } } @@ -273,9 +336,12 @@ for ( my $i = 0 ; $i < @Backups ; $i++ ) { $lastFull = $Backups[$i]{startTime}; $lastFullBkupNum = $Backups[$i]{num}; } - } else { + } elsif ( $Backups[$i]{type} eq "incr" ) { $lastIncr = $Backups[$i]{startTime} if ( $lastIncr < $Backups[$i]{startTime} ); + } elsif ( $Backups[$i]{type} eq "partial" ) { + $partialIdx = $i; + $partialNum = $Backups[$i]{num}; } } @@ -341,6 +407,7 @@ if ( !defined($XferLOG) ) { print("dump failed: unable to open/create $Dir/XferLOG$fileExt\n"); exit(1); } +$XferLOG->writeTeeStderr(1) if ( $opts{v} ); unlink("$Dir/NewFileList"); my $startTime = time(); @@ -350,7 +417,7 @@ my $sizeExist = 0; my $sizeExistComp = 0; my $nFilesTotal = 0; my $sizeTotal = 0; -my($logMsg, %stat, $xfer, $ShareNames); +my($logMsg, %stat, $xfer, $ShareNames, $noFilesErr); my $newFilesFH; if ( $Conf{XferMethod} eq "tar" ) { @@ -413,12 +480,22 @@ for my $shareName ( @$ShareNames ) { # This xfer method outputs a tar format file, so we start a # BackupPC_tarExtract to extract the data. # - # Create a pipe to connect the Xfer method to BackupPC_tarExtract + # Create a socketpair to connect the Xfer method to BackupPC_tarExtract # WH is the write handle for writing, provided to the transport - # program, and RH is the other end of the pipe for reading, + # program, and RH is the other end of the socket for reading, # provided to BackupPC_tarExtract. # - pipe(RH, WH); + 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); + } # # fork a child for BackupPC_tarExtract. TAR is a file handle @@ -432,6 +509,7 @@ for my $shareName ( @$ShareNames ) { close(WH); last; } + binmode(TAR); if ( !$tarPid ) { # # This is the tar child. Close the write end of the pipe, @@ -444,6 +522,7 @@ for my $shareName ( @$ShareNames ) { open(STDERR, ">&STDOUT"); close(STDIN); open(STDIN, "<&RH"); + alarm(0); exec("$BinDir/BackupPC_tarExtract", $client, $shareName, $Conf{CompressLevel}); print(LOG $bpc->timeStamp, @@ -458,6 +537,7 @@ for my $shareName ( @$ShareNames ) { open(NEW_FILES, ">", "$TopDir/pc/$client/NewFileList") || die("can't open $TopDir/pc/$client/NewFileList"); $newFilesFH = *NEW_FILES; + binmode(NEW_FILES); } # @@ -480,6 +560,8 @@ for my $shareName ( @$ShareNames ) { backups => \@Backups, compress => $Conf{CompressLevel}, XferMethod => $Conf{XferMethod}, + pidHandler => \&pidHandler, + partialNum => $partialNum, }); if ( !defined($logMsg = $xfer->start()) ) { @@ -494,11 +576,17 @@ for my $shareName ( @$ShareNames ) { sleep(1); kill(9, $tarPid); } + if ( @xferPid ) { + kill(2, @xferPid); + sleep(1); + kill(9, @xferPid); + } UserCommandRun("DumpPostUserCmd") if ( $NeedPostCmd ); exit(1); } - $xferPid = $xfer->xferPid; + @xferPid = $xfer->xferPid; + if ( $useTar ) { # # The parent must close both handles on the pipe since the children @@ -506,17 +594,13 @@ for my $shareName ( @$ShareNames ) { # close(RH); close(WH); - print(LOG $bpc->timeStamp, $logMsg, - " (xferPid=$xferPid, tarPid=$tarPid)\n"); - } elsif ( $xferPid > 0 ) { - print(LOG $bpc->timeStamp, $logMsg, " (xferPid=$xferPid)\n"); - } else { - print(LOG $bpc->timeStamp, $logMsg, "\n"); } - print("started $type dump, pid=$xferPid, tarPid=$tarPid," - . " share=$shareName\n"); + print(LOG $bpc->timeStamp, $logMsg, "\n"); + print("started $type dump, share=$shareName\n"); - if ( $useTar || $xferPid > 0 ) { + pidHandler(@xferPid); + + if ( $useTar ) { # # Parse the output of the transfer program and BackupPC_tarExtract # while they run. Since we might be reading from two or more children @@ -565,7 +649,7 @@ for my $shareName ( @$ShareNames ) { # the transfer. # if ( my $errMsg = CorrectHostCheck($hostIP, $host) ) { - $stat{hostError} = $errMsg; + $stat{hostError} = $errMsg if ( $stat{hostError} eq "" ); last SCAN; } } @@ -587,6 +671,9 @@ for my $shareName ( @$ShareNames ) { # Merge the xfer status (need to accumulate counts) # my $newStat = $xfer->getStats; + if ( $newStat->{fileCnt} == 0 ) { + $noFilesErr ||= "No files dumped for share $shareName"; + } foreach my $k ( (keys(%stat), keys(%$newStat)) ) { next if ( !defined($newStat->{$k}) ); if ( $k =~ /Cnt$/ ) { @@ -605,10 +692,10 @@ for my $shareName ( @$ShareNames ) { # # kill off the tranfer program, first nicely then forcefully # - if ( $xferPid > 0 ) { - kill(2, $xferPid); + if ( @xferPid ) { + kill(2, @xferPid); sleep(1); - kill(9, $xferPid); + kill(9, @xferPid); } # # kill off the tar process, first nicely then forcefully @@ -624,7 +711,15 @@ for my $shareName ( @$ShareNames ) { last; } } -my $lastNum = -1; + +# +# If this is a full, and any share had zero files then consider the dump bad +# +if ( $type eq "full" && $stat{hostError} eq "" + && length($noFilesErr) && $Conf{BackupZeroFilesIsFatal} ) { + $stat{hostError} = $noFilesErr; + $stat{xferOK} = 0; +} # # Do one last check to make sure it is still the machine we expect. @@ -638,22 +733,6 @@ UserCommandRun("DumpPostUserCmd") if ( $NeedPostCmd ); $XferLOG->close(); close($newFilesFH) if ( defined($newFilesFH) ); -if ( $stat{xferOK} ) { - @Backups = $bpc->BackupInfoRead($client); - for ( my $i = 0 ; $i < @Backups ; $i++ ) { - $lastNum = $Backups[$i]{num} if ( $lastNum < $Backups[$i]{num} ); - } - $lastNum++; - $bpc->RmTreeDefer("$TopDir/trash", "$Dir/$lastNum") - if ( -d "$Dir/$lastNum" ); - if ( !rename("$Dir/new", "$Dir/$lastNum") ) { - print(LOG $bpc->timeStamp, - "Rename $Dir/new -> $Dir/$lastNum failed\n"); - $stat{xferOK} = 0; - } - rename("$Dir/XferLOG$fileExt", "$Dir/XferLOG.$lastNum$fileExt"); - rename("$Dir/NewFileList", "$Dir/NewFileList.$lastNum"); -} my $endTime = time(); # @@ -676,75 +755,19 @@ if ( !$stat{xferOK} ) { $stat{hostError} = "lost network connection during backup"; } print(LOG $bpc->timeStamp, "Dump aborted ($stat{hostError})\n"); - unlink("$Dir/timeStamp.level0"); - unlink("$Dir/SmbLOG.bad"); - unlink("$Dir/SmbLOG.bad$fileExt"); - unlink("$Dir/XferLOG.bad"); - unlink("$Dir/XferLOG.bad$fileExt"); - unlink("$Dir/NewFileList"); - rename("$Dir/XferLOG$fileExt", "$Dir/XferLOG.bad$fileExt"); - $bpc->RmTreeDefer("$TopDir/trash", "$Dir/new") if ( -d "$Dir/new" ); - print("dump failed: $stat{hostError}\n"); - print("link $clientURI\n") if ( $needLink ); - exit(1); -} -# -# Add the new backup information to the backup file -# -@Backups = $bpc->BackupInfoRead($client); -my $i = @Backups; -$Backups[$i]{num} = $lastNum; -$Backups[$i]{type} = $type; -$Backups[$i]{startTime} = $startTime; -$Backups[$i]{endTime} = $endTime; -$Backups[$i]{size} = $sizeTotal; -$Backups[$i]{nFiles} = $nFilesTotal; -$Backups[$i]{xferErrs} = $stat{xferErrCnt} || 0; -$Backups[$i]{xferBadFile} = $stat{xferBadFileCnt} || 0; -$Backups[$i]{xferBadShare} = $stat{xferBadShareCnt} || 0; -$Backups[$i]{nFilesExist} = $nFilesExist; -$Backups[$i]{sizeExist} = $sizeExist; -$Backups[$i]{sizeExistComp} = $sizeExistComp; -$Backups[$i]{tarErrs} = $tarErrs; -$Backups[$i]{compress} = $Conf{CompressLevel}; -$Backups[$i]{noFill} = $type eq "full" ? 0 : 1; -$Backups[$i]{mangle} = 1; # name mangling always on for v1.04+ -$bpc->BackupInfoWrite($client, @Backups); - -unlink("$Dir/timeStamp.level0"); - -# -# Now remove the bad files, replacing them if possible with links to -# earlier backups. -# -foreach my $file ( $xfer->getBadFiles ) { - my $j; - unlink("$Dir/$lastNum/$file"); - for ( $j = $i - 1 ; $j >= 0 ; $j-- ) { - next if ( !-f "$Dir/$Backups[$j]{num}/$file" ); - if ( !link("$Dir/$Backups[$j]{num}/$file", "$Dir/$lastNum/$file") ) { - print(LOG $bpc->timeStamp, - "Unable to link $lastNum/$file to" - . " $Backups[$j]{num}/$file\n"); - } else { - print(LOG $bpc->timeStamp, - "Bad file $lastNum/$file replaced by link to" - . " $Backups[$j]{num}/$file\n"); - } - last; - } - if ( $j < 0 ) { - print(LOG $bpc->timeStamp, - "Removed bad file $lastNum/$file (no older" - . " copy to link to)\n"); - } + # + # This exits. + # + BackupFailCleanup(); } +my $newNum = BackupSave(); + my $otherCount = $stat{xferErrCnt} - $stat{xferBadFileCnt} - $stat{xferBadShareCnt}; print(LOG $bpc->timeStamp, - "$type backup $lastNum complete, $stat{fileCnt} files," + "$type backup $newNum complete, $stat{fileCnt} files," . " $stat{byteCnt} bytes," . " $stat{xferErrCnt} xferErrs ($stat{xferBadFileCnt} bad files," . " $stat{xferBadShareCnt} bad shares, $otherCount other)\n"); @@ -769,37 +792,70 @@ sub NothingToDo sub catch_signal { my $signame = shift; - my $fileExt = $Conf{CompressLevel} > 0 ? ".z" : ""; + + # + # Children quit quietly on ALRM + # + exit(1) if ( $Pid != $$ && $signame eq "ALRM" ); + + # + # Ignore other signals in children + # + return if ( $Pid != $$ ); print(LOG $bpc->timeStamp, "cleaning up after signal $signame\n"); + $SIG{$signame} = 'IGNORE'; UserCommandRun("DumpPostUserCmd") if ( $NeedPostCmd ); $XferLOG->write(\"exiting after signal $signame\n"); $XferLOG->close(); - if ( $xferPid > 0 ) { - if ( kill(2, $xferPid) <= 0 ) { - sleep(1); - kill(9, $xferPid); - } + if ( @xferPid ) { + kill(2, @xferPid); + sleep(1); + kill(9, @xferPid); } if ( $tarPid > 0 ) { - if ( kill(2, $tarPid) <= 0 ) { - sleep(1); - kill(9, $tarPid); - } + kill(2, $tarPid); + sleep(1); + kill(9, $tarPid); } - unlink("$Dir/timeStamp.level0"); - unlink("$Dir/NewFileList"); - unlink("$Dir/XferLOG.bad"); - unlink("$Dir/XferLOG.bad$fileExt"); - rename("$Dir/XferLOG$fileExt", "$Dir/XferLOG.bad$fileExt"); - $bpc->RmTreeDefer("$TopDir/trash", "$Dir/new") if ( -d "$Dir/new" ); if ( $signame eq "INT" ) { - print("dump failed: aborted by user (signal=$signame)\n"); + $stat{hostError} = "aborted by user (signal=$signame)"; } else { - print("dump failed: received signal=$signame\n"); + $stat{hostError} = "received signal=$signame"; + } + BackupFailCleanup(); +} + +sub BackupFailCleanup +{ + my $fileExt = $Conf{CompressLevel} > 0 ? ".z" : ""; + + if ( $type ne "full" + || ($nFilesTotal == 0 && $xfer->getStats->{fileCnt} == 0) ) { + # + # No point in saving this dump; get rid of eveything. + # + unlink("$Dir/timeStamp.level0"); + unlink("$Dir/SmbLOG.bad"); + unlink("$Dir/SmbLOG.bad$fileExt"); + unlink("$Dir/XferLOG.bad"); + unlink("$Dir/XferLOG.bad$fileExt"); + unlink("$Dir/NewFileList"); + rename("$Dir/XferLOG$fileExt", "$Dir/XferLOG.bad$fileExt"); + $bpc->RmTreeDefer("$TopDir/trash", "$Dir/new") if ( -d "$Dir/new" ); + print("dump failed: $stat{hostError}\n"); + print("link $clientURI\n") if ( $needLink ); + exit(1); } + # + # Ok, now we should save this as a partial dump + # + $type = "partial"; + my $newNum = BackupSave(); + print("dump failed: $stat{hostError}\n"); print("link $clientURI\n") if ( $needLink ); - exit(1); + print(LOG $bpc->timeStamp, "Saved partial dump $newNum\n"); + exit(2); } # @@ -816,7 +872,7 @@ sub BackupExpire while ( 1 ) { $cntFull = $cntIncr = 0; $oldestIncr = $oldestFull = 0; - for ( $i = 0 ; $i < @Backups ; $i++ ) { + for ( my $i = 0 ; $i < @Backups ; $i++ ) { if ( $Backups[$i]{type} eq "full" ) { $firstFull = $i if ( $cntFull == 0 ); $cntFull++; @@ -844,17 +900,7 @@ sub BackupExpire # print(LOG $bpc->timeStamp, "removing incr backup $Backups[$firstIncr]{num}\n"); - $bpc->RmTreeDefer("$TopDir/trash", - "$Dir/$Backups[$firstIncr]{num}"); - unlink("$Dir/SmbLOG.$Backups[$firstIncr]{num}") - if ( -f "$Dir/SmbLOG.$Backups[$firstIncr]{num}" ); - unlink("$Dir/SmbLOG.$Backups[$firstIncr]{num}.z") - if ( -f "$Dir/SmbLOG.$Backups[$firstIncr]{num}.z" ); - unlink("$Dir/XferLOG.$Backups[$firstIncr]{num}") - if ( -f "$Dir/XferLOG.$Backups[$firstIncr]{num}" ); - unlink("$Dir/XferLOG.$Backups[$firstIncr]{num}.z") - if ( -f "$Dir/XferLOG.$Backups[$firstIncr]{num}.z" ); - splice(@Backups, $firstIncr, 1); + BackupRemove($client, \@Backups, $firstIncr); } elsif ( ($cntFull > $Conf{FullKeepCnt} || ($cntFull > $Conf{FullKeepCntMin} && $oldestFull > $Conf{FullAgeMax})) @@ -869,17 +915,7 @@ sub BackupExpire # print(LOG $bpc->timeStamp, "removing full backup $Backups[$firstFull]{num}\n"); - $bpc->RmTreeDefer("$TopDir/trash", - "$Dir/$Backups[$firstFull]{num}"); - unlink("$Dir/SmbLOG.$Backups[$firstFull]{num}") - if ( -f "$Dir/SmbLOG.$Backups[$firstFull]{num}" ); - unlink("$Dir/SmbLOG.$Backups[$firstFull]{num}.z") - if ( -f "$Dir/SmbLOG.$Backups[$firstFull]{num}.z" ); - unlink("$Dir/XferLOG.$Backups[$firstFull]{num}") - if ( -f "$Dir/XferLOG.$Backups[$firstFull]{num}" ); - unlink("$Dir/XferLOG.$Backups[$firstFull]{num}.z") - if ( -f "$Dir/XferLOG.$Backups[$firstFull]{num}.z" ); - splice(@Backups, $firstFull, 1); + BackupRemove($client, \@Backups, $firstFull); } else { last; } @@ -887,16 +923,159 @@ sub BackupExpire $bpc->BackupInfoWrite($client, @Backups); } +# +# Removes any partial backups +# +sub BackupPartialRemove +{ + my($client, $Backups) = @_; + + for ( my $i = @$Backups - 1 ; $i >= 0 ; $i-- ) { + next if ( $Backups->[$i]{type} ne "partial" ); + BackupRemove($client, $Backups, $i); + } +} + +sub BackupSave +{ + my @Backups = $bpc->BackupInfoRead($client); + my $num = -1; + + # + # Since we got a good backup we should remove any partial dumps + # (the new backup might also be a partial, but that's ok). + # + BackupPartialRemove($client, \@Backups); + + # + # Number the new backup + # + for ( my $i = 0 ; $i < @Backups ; $i++ ) { + $num = $Backups[$i]{num} if ( $num < $Backups[$i]{num} ); + } + $num++; + $bpc->RmTreeDefer("$TopDir/trash", "$Dir/$num") + if ( -d "$Dir/$num" ); + if ( !rename("$Dir/new", "$Dir/$num") ) { + print(LOG $bpc->timeStamp, + "Rename $Dir/new -> $Dir/$num failed\n"); + $stat{xferOK} = 0; + } + rename("$Dir/XferLOG$fileExt", "$Dir/XferLOG.$num$fileExt"); + rename("$Dir/NewFileList", "$Dir/NewFileList.$num"); + + # + # Add the new backup information to the backup file + # + my $i = @Backups; + $Backups[$i]{num} = $num; + $Backups[$i]{type} = $type; + $Backups[$i]{startTime} = $startTime; + $Backups[$i]{endTime} = $endTime; + $Backups[$i]{size} = $sizeTotal; + $Backups[$i]{nFiles} = $nFilesTotal; + $Backups[$i]{xferErrs} = $stat{xferErrCnt} || 0; + $Backups[$i]{xferBadFile} = $stat{xferBadFileCnt} || 0; + $Backups[$i]{xferBadShare} = $stat{xferBadShareCnt} || 0; + $Backups[$i]{nFilesExist} = $nFilesExist; + $Backups[$i]{sizeExist} = $sizeExist; + $Backups[$i]{sizeExistComp} = $sizeExistComp; + $Backups[$i]{tarErrs} = $tarErrs; + $Backups[$i]{compress} = $Conf{CompressLevel}; + $Backups[$i]{noFill} = $type eq "incr" ? 1 : 0; + $Backups[$i]{level} = $type eq "incr" ? 1 : 0; + $Backups[$i]{mangle} = 1; # name mangling always on for v1.04+ + $bpc->BackupInfoWrite($client, @Backups); + + unlink("$Dir/timeStamp.level0"); + + # + # Now remove the bad files, replacing them if possible with links to + # earlier backups. + # + foreach my $f ( $xfer->getBadFiles ) { + my $j; + my $shareM = $bpc->fileNameEltMangle($f->{share}); + my $fileM = $bpc->fileNameMangle($f->{file}); + unlink("$Dir/$num/$shareM/$fileM"); + for ( $j = $i - 1 ; $j >= 0 ; $j-- ) { + my $file; + if ( $Backups[$j]{mangle} ) { + $file = "$shareM/$fileM"; + } else { + $file = "$f->{share}/$f->{file}"; + } + next if ( !-f "$Dir/$Backups[$j]{num}/$file" ); + if ( !link("$Dir/$Backups[$j]{num}/$file", + "$Dir/$num/$shareM/$fileM") ) { + print(LOG $bpc->timeStamp, + "Unable to link $num/$shareM/$fileM to" + . " $Backups[$j]{num}/$file\n"); + } else { + print(LOG $bpc->timeStamp, + "Bad file $num/$shareM/$fileM replaced by link to" + . " $Backups[$j]{num}/$file\n"); + } + last; + } + if ( $j < 0 ) { + print(LOG $bpc->timeStamp, + "Removed bad file $num/$shareM/$fileM (no older" + . " copy to link to)\n"); + } + } + return $num; +} + +# +# Removes a specific backup +# +sub BackupRemove +{ + my($client, $Backups, $idx) = @_; + my($Dir) = "$TopDir/pc/$client"; + + $bpc->RmTreeDefer("$TopDir/trash", + "$Dir/$Backups->[$idx]{num}"); + unlink("$Dir/SmbLOG.$Backups->[$idx]{num}") + if ( -f "$Dir/SmbLOG.$Backups->[$idx]{num}" ); + unlink("$Dir/SmbLOG.$Backups->[$idx]{num}.z") + if ( -f "$Dir/SmbLOG.$Backups->[$idx]{num}.z" ); + unlink("$Dir/XferLOG.$Backups->[$idx]{num}") + if ( -f "$Dir/XferLOG.$Backups->[$idx]{num}" ); + unlink("$Dir/XferLOG.$Backups->[$idx]{num}.z") + if ( -f "$Dir/XferLOG.$Backups->[$idx]{num}.z" ); + splice(@{$Backups}, $idx, 1); +} + sub CorrectHostCheck { my($hostIP, $host) = @_; - return if ( $hostIP eq $host && !$Conf{FixedIPNetBiosNameCheck} ); + 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 ( $netBiosHost ne $host ); + if ( $netBiosHost ne $host ); return; } +# +# 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); + $XferLOG->write(\"Xfer PIDs are now $str\n") if ( defined($XferLOG) ); + print("xferPids $str\n"); +} + # # Run an optional pre- or post-dump command # @@ -906,18 +1085,22 @@ sub UserCommandRun return if ( !defined($Conf{$type}) ); my $vars = { - xfer => $xfer, - client => $client, - host => $host, - hostIP => $hostIP, - share => $ShareNames->[0], - shares => $ShareNames, + xfer => $xfer, + client => $client, + host => $host, + hostIP => $hostIP, + user => $Hosts->{$client}{user}, + moreUsers => $Hosts->{$client}{moreUsers}, + share => $ShareNames->[0], + shares => $ShareNames, XferMethod => $Conf{XferMethod}, - LOG => *LOG, - XferLOG => $XferLOG, - stat => \%stat, - xferOK => $stat{xferOK}, - type => $type, + sshPath => $Conf{SshPath}, + LOG => *LOG, + XferLOG => $XferLOG, + stat => \%stat, + xferOK => $stat{xferOK} || 0, + hostError => $stat{hostError}, + type => $type, }; my $cmd = $bpc->cmdVarSubstitute($Conf{$type}, $vars); $XferLOG->write(\"Executing $type: @$cmd\n");