X-Git-Url: http://git.rot13.org/?p=BackupPC.git;a=blobdiff_plain;f=bin%2FBackupPC_dump;h=f59e38a124247aebfca48a75c540f0b28edc4da7;hp=e6d75f385c0a50f9563379a18b916193bd09fc59;hb=a7e968ce327855f2ba2624ca8517069a936c9b5b;hpb=7dee89bfce659051d486cc66515bb7f22bbc4f09 diff --git a/bin/BackupPC_dump b/bin/BackupPC_dump index e6d75f3..f59e38a 100755 --- a/bin/BackupPC_dump +++ b/bin/BackupPC_dump @@ -52,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 @@ -70,19 +70,21 @@ # #======================================================================== # -# Version 2.0.0beta1, released 30 Mar 2003. +# Version 2.1.0_CVS, released 3 Jul 2003. # # 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; @@ -124,20 +126,19 @@ if ( $opts{d} ) { # $hostIP = $client; if ( $bpc->CheckHostAlive($hostIP) < 0 ) { - print("Exiting because CheckHostAlive($hostIP) failed\n") + print(STDERR "Exiting because CheckHostAlive($hostIP) failed\n") if ( $opts{v} ); exit(1); } if ( $Conf{NmbLookupCmd} eq "" ) { - print("Exiting because \$Conf{NmbLookupCmd} is empty\n") + print(STDERR "Exiting because \$Conf{NmbLookupCmd} is empty\n") if ( $opts{v} ); exit(1); } ($client, $user) = $bpc->NetBiosInfoGet($hostIP); if ( $client !~ /^([\w\.\s-]+)$/ ) { - print("Exiting because NetBiosInfoGet($hostIP) returned '$client'," - . " an invalid host name\n") - if ( $opts{v} ); + print(STDERR "Exiting because NetBiosInfoGet($hostIP) returned" + . " '$client', an invalid host name\n") if ( $opts{v} ); exit(1) } $Hosts = $bpc->HostInfoRead($client); @@ -146,7 +147,8 @@ if ( $opts{d} ) { $Hosts = $bpc->HostInfoRead($client); } if ( !defined($Hosts->{$client}) ) { - print("Exiting because host $client does not exist in the hosts file\n"); + print(STDERR "Exiting because host $client does not exist in the" + . " hosts file\n") if ( $opts{v} ); exit(1) } @@ -197,6 +199,13 @@ if ( $opts{e} ) { 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 @@ -212,12 +221,11 @@ if ( !$opts{d} ) { # Ok, NS doesn't know about it. Maybe it is a NetBios name # instead. # - print("Name server doesn't know about $host; trying NetBios\n") + 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 { @@ -251,7 +259,7 @@ $bpc->ServerDisconnect(); if ( $opts{d} ) { if ( $StatusHost{activeJob} ) { # oops, something is already running for this host - print("Exiting because backup is already running for $client\n") + print(STDERR "Exiting because backup is already running for $client\n") if ( $opts{v} ); exit(0); } @@ -261,6 +269,7 @@ if ( $opts{d} ) { my($needLink, @Backups, $type, $lastBkupNum, $lastFullBkupNum); my $lastFull = 0; my $lastIncr = 0; +my $partialIdx = -1; if ( $Conf{FullPeriod} == -1 && !$opts{f} && !$opts{i} || $Conf{FullPeriod} == -2 ) { @@ -305,9 +314,11 @@ 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; } } @@ -383,7 +394,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" ) { @@ -446,12 +457,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 @@ -465,6 +486,7 @@ for my $shareName ( @$ShareNames ) { close(WH); last; } + binmode(TAR); if ( !$tarPid ) { # # This is the tar child. Close the write end of the pipe, @@ -491,6 +513,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); } # @@ -601,7 +624,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; } } @@ -623,6 +646,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$/ ) { @@ -660,7 +686,15 @@ for my $shareName ( @$ShareNames ) { last; } } -my $lastNum = -1; + +# +# If any share had zero files then consider the dump bad +# +if ( $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. @@ -674,22 +708,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(); # @@ -712,75 +730,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"); @@ -805,10 +767,14 @@ sub NothingToDo sub catch_signal { my $signame = shift; - my $fileExt = $Conf{CompressLevel} > 0 ? ".z" : ""; # - # Ignore signals in children + # Children quit quietly on ALRM + # + exit(1) if ( $Pid != $$ && $signame eq "ALRM" ); + + # + # Ignore other signals in children # return if ( $Pid != $$ ); @@ -827,19 +793,44 @@ sub catch_signal 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); } # @@ -856,7 +847,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++; @@ -884,17 +875,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})) @@ -909,17 +890,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; } @@ -927,6 +898,131 @@ 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) = @_; @@ -977,7 +1073,8 @@ sub UserCommandRun LOG => *LOG, XferLOG => $XferLOG, stat => \%stat, - xferOK => $stat{xferOK}, + xferOK => $stat{xferOK} || 0, + hostError => $stat{hostError}, type => $type, }; my $cmd = $bpc->cmdVarSubstitute($Conf{$type}, $vars);