X-Git-Url: http://git.rot13.org//?a=blobdiff_plain;f=bin%2FBackupPC_dump;h=e6d75f385c0a50f9563379a18b916193bd09fc59;hb=7dee89bfce659051d486cc66515bb7f22bbc4f09;hp=7fe11fed47803250424734d3496bf6aeb15c4982;hpb=e9453b7611be63303572ae443d5fb56b73364678;p=BackupPC.git diff --git a/bin/BackupPC_dump b/bin/BackupPC_dump index 7fe11fe..e6d75f3 100755 --- a/bin/BackupPC_dump +++ b/bin/BackupPC_dump @@ -1,11 +1,11 @@ #!/bin/perl -T #============================================================= -*-perl-*- # -# BackupPC_dump: Dump a single PC. +# BackupPC_dump: Dump a single client. # # DESCRIPTION # -# Usage: BackupPC_dump [-i] [-f] [-d] [-e] +# Usage: BackupPC_dump [-i] [-f] [-d] [-e] [-v] # # Flags: # @@ -14,33 +14,36 @@ # # -f Do a full dump, overriding any scheduling. # -# -d Host is a DHCP pool address, so initially we have no -# idea which machine this actually is. BackupPC_dump -# determines the actual PC host name by using the NetBios -# name. +# -d Host is a DHCP pool address, and the client argument +# just an IP address. We lookup the NetBios name from +# the IP address. # -# -e Just do an dump expiry check for the host. Don't do anything else. # This is used periodically by BackupPC to make sure that dhcp hosts -# have correctly expired old backups. Without this, dhcp hosts that -# are no longer on the network will not expire old backups. +# -e Just do an dump expiry check for the client. Don't do anything +# else. This is used periodically by BackupPC to make sure that +# dhcp hosts have correctly expired old backups. Without this, +# dhcp hosts that are no longer on the network will not expire +# old backups. # -# BackupPC_dump is run periodically by BackupPC to backup $host. -# The file $TopDir/pc/$host/backups is read to decide whether a +# -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 -# scheduled, or a ping to $host fails, then BackupPC_dump quits. +# scheduled, or a ping to $client fails, then BackupPC_dump quits. # # The backup is done using the selected XferMethod (smb, tar, rsync etc), -# extracting the dump into $TopDir/pc/$host/new. The xfer output is -# put into $TopDir/pc/$host/XferLOG. +# extracting the dump into $TopDir/pc/$client/new. The xfer output is +# put into $TopDir/pc/$client/XferLOG. # # If the dump succeeds (based on parsing the output of the XferMethod): -# - $TopDir/pc/$host/new is renamed to $TopDir/pc/$host/nnn, where +# - $TopDir/pc/$client/new is renamed to $TopDir/pc/$client/nnn, where # nnn is the next sequential dump number. -# - $TopDir/pc/$host/XferLOG is renamed to $TopDir/pc/$host/XferLOG.nnn. -# - $TopDir/pc/$host/backups is updated. +# - $TopDir/pc/$client/XferLOG is renamed to $TopDir/pc/$client/XferLOG.nnn. +# - $TopDir/pc/$client/backups is updated. # # If the dump fails: -# - $TopDir/pc/$host/new is moved to $TopDir/trash for later removal. -# - $TopDir/pc/$host/XferLOG is renamed to $TopDir/pc/$host/XferLOG.bad +# - $TopDir/pc/$client/new is moved to $TopDir/trash for later removal. +# - $TopDir/pc/$client/XferLOG is renamed to $TopDir/pc/$client/XferLOG.bad # for later viewing. # # BackupPC_dump communicates to BackupPC via printing to STDOUT. @@ -67,7 +70,7 @@ # #======================================================================== # -# Version 1.6.0_CVS, released 10 Dec 2002. +# Version 2.0.0beta1, released 30 Mar 2003. # # See http://backuppc.sourceforge.net. # @@ -91,46 +94,72 @@ 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 $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\.-]+)$/ ) { - print("$0: bad host name '$ARGV[0]'\n"); +if ( $ARGV[0] !~ /^([\w\.\s-]+)$/ ) { + print("$0: bad client name '$ARGV[0]'\n"); exit(1); } -my $hostIP = $1; -my($host, $user); +my $client = $1; # BackupPC's client name (might not be real host name) +my $hostIP; # this is the IP address +my $host; # this is the real host name + +my($clientURI, $user); + +$bpc->verbose(1) if ( $opts{v} ); if ( $opts{d} ) { # - # The host name $hostIP is simply a DHCP address. We need to check + # The client name $client is simply a DHCP address. We need to check # if there is any machine at this address, and if so, get the actual # host name via NetBios using nmblookup. # - exit(1) if ( $bpc->CheckHostAlive($hostIP) < 0 ); - ($host, $user) = $bpc->NetBiosInfoGet($hostIP); - exit(1) if ( $host !~ /^([\w\.-]+)$/ ); - my $hosts = $bpc->HostInfoRead($host); - exit(1) if ( !defined($hosts->{$host}) ); + $hostIP = $client; + if ( $bpc->CheckHostAlive($hostIP) < 0 ) { + print("Exiting because CheckHostAlive($hostIP) failed\n") + if ( $opts{v} ); + exit(1); + } + if ( $Conf{NmbLookupCmd} eq "" ) { + print("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} ); + exit(1) + } + $Hosts = $bpc->HostInfoRead($client); + $host = $client; } else { - $host = $hostIP; + $Hosts = $bpc->HostInfoRead($client); +} +if ( !defined($Hosts->{$client}) ) { + print("Exiting because host $client does not exist in the hosts file\n"); + exit(1) } -my $Dir = "$TopDir/pc/$host"; -my $xferPid = -1; +my $Dir = "$TopDir/pc/$client"; +my @xferPid = (); my $tarPid = -1; # # Re-read config file, so we can include the PC-specific config # -if ( defined(my $error = $bpc->ConfigRead($host)) ) { - print("Can't read PC's config file: $error\n"); +$clientURI = $bpc->uriEsc($client); +if ( defined(my $error = $bpc->ConfigRead($client)) ) { + print("dump failed: Can't read PC's config file: $error\n"); exit(1); } %Conf = $bpc->Conf(); @@ -141,32 +170,65 @@ if ( defined(my $error = $bpc->ConfigRead($host)) ) { $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 # 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); + open(LOCK, ">", "$Dir/LOCK") && close(LOCK); } -open(LOG, ">>$Dir/LOG"); +open(LOG, ">>", "$Dir/LOG"); select(LOG); $| = 1; select(STDOUT); -########################################################################### -# Figure out what to do and do it -########################################################################### - # # For the -e option we just expire backups and quit # if ( $opts{e} ) { - BackupExpire($host); + BackupExpire($client); exit(0); } +if ( !$opts{d} ) { + # + # In the non-DHCP case, make sure the host can be looked up + # via NS, or otherwise find the IP address via NetBios. + # + if ( $Conf{ClientNameAlias} ne "" ) { + $host = $Conf{ClientNameAlias}; + } else { + $host = $client; + } + if ( !defined(gethostbyname($host)) ) { + # + # 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") + 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"); + exit(1); + } + } else { + $hostIP = $host; + } +} + +########################################################################### +# Figure out what to do and do it +########################################################################### + # # See if we should skip this host during a certain range # of times. @@ -177,7 +239,7 @@ if ( $err ne "" ) { print(LOG $bpc->timeStamp, "Can't connect to server ($err)\n"); exit(1); } -my $reply = $bpc->ServerMesg("status host($host)"); +my $reply = $bpc->ServerMesg("status host($clientURI)"); $reply = $1 if ( $reply =~ /(.*)/s ); my(%StatusHost); eval($reply); @@ -189,9 +251,11 @@ $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") + if ( $opts{v} ); exit(0); } - print("DHCP $hostIP $host\n"); + print("DHCP $hostIP $clientURI\n"); } my($needLink, @Backups, $type, $lastBkupNum, $lastFullBkupNum); @@ -225,13 +289,13 @@ if ( !$opts{i} && !$opts{f} && $StatusHost{backoffTime} > time ) { # # Now see if there are any old backups we should delete # -BackupExpire($host); +BackupExpire($client); # # Read Backup information, and find times of the most recent full and # incremental backups # -@Backups = $bpc->BackupInfoRead($host); +@Backups = $bpc->BackupInfoRead($client); for ( my $i = 0 ; $i < @Backups ; $i++ ) { $needLink = 1 if ( $Backups[$i]{nFilesNew} eq "" || -f "$Dir/NewFileList.$Backups[$i]{num}" ); @@ -269,13 +333,13 @@ my $delay = $bpc->CheckHostAlive($hostIP); if ( $delay < 0 ) { print(LOG $bpc->timeStamp, "no ping response\n"); print("no ping response\n"); - print("link $host\n") if ( $needLink ); + print("link $clientURI\n") if ( $needLink ); exit(1); } elsif ( $delay > $Conf{PingMaxMsec} ) { printf(LOG "%sping too slow: %.4gmsec\n", $bpc->timeStamp, $delay); printf("ping too slow: %.4gmsec (threshold is %gmsec)\n", $delay, $Conf{PingMaxMsec}); - print("link $host\n") if ( $needLink ); + print("link $clientURI\n") if ( $needLink ); exit(1); } @@ -309,6 +373,7 @@ if ( !defined($XferLOG) ) { print("dump failed: unable to open/create $Dir/XferLOG$fileExt\n"); exit(1); } +$XferLOG->writeTeeStdout(1) if ( $opts{v} ); unlink("$Dir/NewFileList"); my $startTime = time(); @@ -331,6 +396,12 @@ if ( $Conf{XferMethod} eq "tar" ) { $ShareNames = [ $ShareNames ] unless ref($ShareNames) eq "ARRAY"; +# +# Run an optional pre-dump command +# +UserCommandRun("DumpPreUserCmd"); +$NeedPostCmd = 1; + # # Now backup each of the shares # @@ -355,9 +426,10 @@ for my $shareName ( @$ShareNames ) { # Use rsync as the transport program. # if ( !defined($xfer = BackupPC::Xfer::Rsync->new($bpc)) ) { - print(LOG $bpc->timeStamp, - "dump failed: File::RsyncP module is not installed\n"); - print("dump failed: Rsync module is not installed\n"); + my $errStr = BackupPC::Xfer::Rsync::errStr; + print(LOG $bpc->timeStamp, "dump failed: $errStr\n"); + print("dump failed: $errStr\n"); + UserCommandRun("DumpPostUserCmd") if ( $NeedPostCmd ); exit(1); } } else { @@ -366,6 +438,7 @@ for my $shareName ( @$ShareNames ) { # $xfer = BackupPC::Xfer::Smb->new($bpc); } + my $useTar = $xfer->useTar; if ( $useTar ) { @@ -404,8 +477,8 @@ for my $shareName ( @$ShareNames ) { open(STDERR, ">&STDOUT"); close(STDIN); open(STDIN, "<&RH"); - exec("$BinDir/BackupPC_tarExtract '$host' '$shareName'" - . " $Conf{CompressLevel}"); + exec("$BinDir/BackupPC_tarExtract", $client, $shareName, + $Conf{CompressLevel}); print(LOG $bpc->timeStamp, "can't exec $BinDir/BackupPC_tarExtract\n"); exit(0); @@ -415,8 +488,8 @@ for my $shareName ( @$ShareNames ) { # We need to create the NewFileList output file # local(*NEW_FILES); - open(NEW_FILES, ">$TopDir/pc/$host/NewFileList") - || die("can't open $TopDir/pc/$host/NewFileList"); + open(NEW_FILES, ">", "$TopDir/pc/$client/NewFileList") + || die("can't open $TopDir/pc/$client/NewFileList"); $newFilesFH = *NEW_FILES; } @@ -425,6 +498,7 @@ for my $shareName ( @$ShareNames ) { # $xfer->args({ host => $host, + client => $client, hostIP => $hostIP, shareName => $shareName, pipeRH => *RH, @@ -438,13 +512,14 @@ for my $shareName ( @$ShareNames ) { lastFullBkupNum => $lastFullBkupNum, backups => \@Backups, compress => $Conf{CompressLevel}, - XferMethod => => $Conf{XferMethod}, + XferMethod => $Conf{XferMethod}, + pidHandler => \&pidHandler, }); if ( !defined($logMsg = $xfer->start()) ) { print(LOG $bpc->timeStamp, "xfer start failed: ", $xfer->errStr, "\n"); print("dump failed: ", $xfer->errStr, "\n"); - print("link $host\n") if ( $needLink ); + print("link $clientURI\n") if ( $needLink ); # # kill off the tar process, first nicely then forcefully # @@ -453,10 +528,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 @@ -464,17 +546,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 @@ -502,12 +580,12 @@ for my $shareName ( @$ShareNames ) { $tarOut = $2; $XferLOG->write(\"tarExtract: $_\n"); if ( /^Done: (\d+) errors, (\d+) filesExist, (\d+) sizeExist, (\d+) sizeExistComp, (\d+) filesTotal, (\d+) sizeTotal/ ) { - $tarErrs = $1; - $nFilesExist = $2; - $sizeExist = $3; - $sizeExistComp = $4; - $nFilesTotal = $5; - $sizeTotal = $6; + $tarErrs += $1; + $nFilesExist += $2; + $sizeExist += $3; + $sizeExistComp += $4; + $nFilesTotal += $5; + $sizeTotal += $6; } } } @@ -532,8 +610,13 @@ for my $shareName ( @$ShareNames ) { # # otherwise the xfer module does everything for us # - ($tarErrs, $nFilesExist, $sizeExist, $sizeExistComp, - $nFilesTotal, $sizeTotal) = $xfer->run(); + my @results = $xfer->run(); + $tarErrs += $results[0]; + $nFilesExist += $results[1]; + $sizeExist += $results[2]; + $sizeExistComp += $results[3]; + $nFilesTotal += $results[4]; + $sizeTotal += $results[5]; } # @@ -558,10 +641,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 @@ -577,9 +660,6 @@ for my $shareName ( @$ShareNames ) { last; } } -$XferLOG->close(); -close($newFilesFH) if ( defined($newFilesFH) ); - my $lastNum = -1; # @@ -589,8 +669,13 @@ if ( $stat{xferOK} && (my $errMsg = CorrectHostCheck($hostIP, $host)) ) { $stat{hostError} = $errMsg; $stat{xferOK} = 0; } + +UserCommandRun("DumpPostUserCmd") if ( $NeedPostCmd ); +$XferLOG->close(); +close($newFilesFH) if ( defined($newFilesFH) ); + if ( $stat{xferOK} ) { - @Backups = $bpc->BackupInfoRead($host); + @Backups = $bpc->BackupInfoRead($client); for ( my $i = 0 ; $i < @Backups ; $i++ ) { $lastNum = $Backups[$i]{num} if ( $lastNum < $Backups[$i]{num} ); } @@ -636,14 +721,14 @@ if ( !$stat{xferOK} ) { 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 $host\n") if ( $needLink ); + print("link $clientURI\n") if ( $needLink ); exit(1); } # # Add the new backup information to the backup file # -@Backups = $bpc->BackupInfoRead($host); +@Backups = $bpc->BackupInfoRead($client); my $i = @Backups; $Backups[$i]{num} = $lastNum; $Backups[$i]{type} = $type; @@ -661,7 +746,7 @@ $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($host, @Backups); +$bpc->BackupInfoWrite($client, @Backups); unlink("$Dir/timeStamp.level0"); @@ -700,7 +785,7 @@ print(LOG $bpc->timeStamp, . " $stat{xferErrCnt} xferErrs ($stat{xferBadFileCnt} bad files," . " $stat{xferBadShareCnt} bad shares, $otherCount other)\n"); -BackupExpire($host); +BackupExpire($client); print("$type backup complete\n"); @@ -713,7 +798,7 @@ sub NothingToDo my($needLink) = @_; print("nothing to do\n"); - print("link $host\n") if ( $needLink ); + print("link $clientURI\n") if ( $needLink ); exit(0); } @@ -722,20 +807,25 @@ sub catch_signal my $signame = shift; my $fileExt = $Conf{CompressLevel} > 0 ? ".z" : ""; + # + # Ignore 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"); @@ -748,7 +838,7 @@ sub catch_signal } else { print("dump failed: received signal=$signame\n"); } - print("link $host\n") if ( $needLink ); + print("link $clientURI\n") if ( $needLink ); exit(1); } @@ -758,9 +848,9 @@ sub catch_signal # sub BackupExpire { - my($host) = @_; - my($Dir) = "$TopDir/pc/$host"; - my(@Backups) = $bpc->BackupInfoRead($host); + my($client) = @_; + my($Dir) = "$TopDir/pc/$client"; + my(@Backups) = $bpc->BackupInfoRead($client); my($cntFull, $cntIncr, $firstFull, $firstIncr, $oldestIncr, $oldestFull); while ( 1 ) { @@ -834,15 +924,73 @@ sub BackupExpire last; } } - $bpc->BackupInfoWrite($host, @Backups); + $bpc->BackupInfoWrite($client, @Backups); } 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 +# +sub UserCommandRun +{ + my($type) = @_; + + return if ( !defined($Conf{$type}) ); + my $vars = { + xfer => $xfer, + client => $client, + host => $host, + hostIP => $hostIP, + user => $Hosts->{$client}{user}, + moreUsers => $Hosts->{$client}{moreUsers}, + share => $ShareNames->[0], + shares => $ShareNames, + XferMethod => $Conf{XferMethod}, + sshPath => $Conf{SshPath}, + LOG => *LOG, + XferLOG => $XferLOG, + stat => \%stat, + xferOK => $stat{xferOK}, + type => $type, + }; + my $cmd = $bpc->cmdVarSubstitute($Conf{$type}, $vars); + $XferLOG->write(\"Executing $type: @$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 { + $XferLOG->write(\$_[0]); + }, + $vars, \%Conf); +}