2 #============================================================= -*-perl-*-
4 # BackupPC_nightly: Nightly cleanup & statistics script.
8 # BackupPC_nightly performs several administrative tasks:
10 # - monthly aging of per-PC log files (only with -m option)
12 # - pruning files from pool no longer used (ie: those with only one
15 # - sending email to users and administrators (only with -m option)
17 # Usage: BackupPC_nightly [-m] poolRangeStart poolRangeEnd
21 # -m Do monthly aging of per-PC log files and sending of email.
22 # Otherise, BackupPC_nightly just does pool pruning.
23 # Since several BackupPC_nightly processes might run
24 # concurrently, just the first one is given the -m flag
27 # The poolRangeStart and poolRangeEnd arguments are integers from 0 to 255.
28 # These specify which parts of the pool to process. There are 256 2nd-level
29 # directories in the pool (0/0, 0/1, ..., f/e, f/f). BackupPC_nightly
30 # processes the given subset of this list (0 means 0/0, 255 means f/f).
31 # Therefore, arguments of 0 255 process the entire pool, 0 127 does
32 # the first half (ie: 0/0 through 7/f), 127 255 does the other half
33 # (eg: 8/0 through f/f) and 0 15 does just the first 1/16 of the pool
34 # (ie: 0/0 through 0/f).
37 # Craig Barratt <cbarratt@users.sourceforge.net>
40 # Copyright (C) 2001-2009 Craig Barratt
42 # This program is free software; you can redistribute it and/or modify
43 # it under the terms of the GNU General Public License as published by
44 # the Free Software Foundation; either version 2 of the License, or
45 # (at your option) any later version.
47 # This program is distributed in the hope that it will be useful,
48 # but WITHOUT ANY WARRANTY; without even the implied warranty of
49 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
50 # GNU General Public License for more details.
52 # You should have received a copy of the GNU General Public License
53 # along with this program; if not, write to the Free Software
54 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
56 #========================================================================
58 # Version 3.2.0, released 31 Jul 2010.
60 # See http://backuppc.sourceforge.net.
62 #========================================================================
66 use lib "/usr/local/BackupPC/lib";
67 use BackupPC::Lib qw( :BPC_DT_ALL );
68 use BackupPC::FileZIO;
74 die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
75 my $TopDir = $bpc->TopDir();
76 my $BinDir = $bpc->BinDir();
77 my %Conf = $bpc->Conf();
78 my(%Status, %Info, %Jobs, @BgQueue, @UserQueue, @CmdQueue);
81 # We delete unused pool files (link count 1) in sorted inode
82 # order by gathering batches. We delete the first half of
83 # each batch (ie: $PendingDeleteMax / 2 at a time).
86 my $PendingDeleteMax = 10240;
91 if ( !getopts("m", \%opts) || @ARGV != 2 ) {
92 print("usage: $0 [-m] poolRangeStart poolRangeEnd\n");
95 if ( $ARGV[0] !~ /^(\d+)$/ || $1 > 255 ) {
96 print("$0: bad poolRangeStart '$ARGV[0]'\n");
99 my $poolRangeStart = $1;
100 if ( $ARGV[1] !~ /^(\d+)$/ || $1 > 255 ) {
101 print("$0: bad poolRangeEnd '$ARGV[1]'\n");
104 my $poolRangeEnd = $1;
107 my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
109 print("Can't connect to server ($err)\n");
112 my $reply = $bpc->ServerMesg("status hosts");
113 $reply = $1 if ( $reply =~ /(.*)/s );
117 ###########################################################################
118 # Get statistics on the pool, and remove files that have only one link.
119 ###########################################################################
121 my $fileCnt; # total number of files
122 my $dirCnt; # total number of directories
123 my $blkCnt; # total block size of files
124 my $fileCntRm; # total number of removed files
125 my $blkCntRm; # total block size of removed files
126 my $blkCnt2; # total block size of files with just 2 links
127 # (ie: files that only occur once among all backups)
128 my $fileCntRep; # total number of file names containing "_", ie: files
129 # that have repeated md5 checksums
130 my $fileRepMax; # worse case number of files that have repeated checksums
131 # (ie: max(nnn+1) for all names xxxxxxxxxxxxxxxx_nnn)
132 my $fileLinkMax; # maximum number of hardlinks on a pool file
133 my $fileLinkTotal; # total number of hardlinks on entire pool
134 my $fileCntRename; # number of renamed files (to keep file numbering
136 my %FixList; # list of paths that need to be renamed to avoid
138 my @hexChars = qw(0 1 2 3 4 5 6 7 8 9 a b c d e f);
140 for my $pool ( qw(pool cpool) ) {
141 for ( my $i = $poolRangeStart ; $i <= $poolRangeEnd ; $i++ ) {
142 my $dir = "$hexChars[int($i / 16)]/$hexChars[$i % 16]";
143 # print("Doing $pool/$dir\n") if ( ($i % 16) == 0 );
155 $bpc->find({wanted => \&GetPoolStats}, "$TopDir/$pool/$dir")
156 if ( -d "$TopDir/$pool/$dir" );
157 my $kb = $blkCnt / 2;
158 my $kbRm = $blkCntRm / 2;
159 my $kb2 = $blkCnt2 / 2;
162 # Main BackupPC_nightly counts the top-level directory
164 $dirCnt++ if ( $opts{m} && -d "$TopDir/$pool" && $i == 0 );
167 # Also count the next level directories
169 $dirCnt++ if ( ($i % 16) == 0
170 && -d "$TopDir/$pool/$hexChars[int($i / 16)]" );
173 # We need to process all pending deletes before we do the
176 if ( @PendingDelete ) {
178 processPendingDeletes(1);
182 # Now make sure that files with repeated checksums are still
183 # sequentially numbered
185 foreach my $name ( sort(keys(%FixList)) ) {
186 my $rmCnt = $FixList{$name} + 1;
188 for ( my $old = -1 ; ; $old++ ) {
190 $oldName .= "_$old" if ( $old >= 0 );
191 if ( !-f $oldName ) {
193 # We know we are done when we have missed at least
194 # the number of files that were removed from this
195 # base name, plus a couple just to be sure
197 last if ( $rmCnt-- <= 0 );
201 $newName .= "_$new" if ( $new >= 0 );
203 next if ( $oldName eq $newName );
204 rename($oldName, $newName);
208 print("BackupPC_stats $i = $pool,$fileCnt,$dirCnt,$kb,$kb2,$kbRm,"
209 . "$fileCntRm,$fileCntRep,$fileRepMax,"
210 . "$fileCntRename,$fileLinkMax,$fileLinkTotal\n");
215 processPendingDeletes(1);
217 ###########################################################################
218 # Tell BackupPC that it is now ok to start running BackupPC_dump
219 # commands. We are guaranteed that no BackupPC_link commands will
220 # run since only a single CmdQueue command runs at a time, and
221 # that means we are safe. As of 3.x this is irrelevant since
222 # BackupPC_dump runs independent of BackupPC_dump.
223 ###########################################################################
224 printf("BackupPC_nightly lock_off\n");
226 ###########################################################################
227 # Send email and generation of backupInfo files for each backup
228 ###########################################################################
230 print("log BackupPC_nightly now running BackupPC_sendEmail\n");
231 system("$BinDir/BackupPC_sendEmail");
232 doBackupInfoUpdate();
236 # Update the backupInfo files based on the backups file.
237 # We do this just once a week (on Sun) since it is only
238 # needed for old backups with BackupPC <= 2.1.2.
240 sub doBackupInfoUpdate
242 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
243 return if ( $wday != 0 );
245 foreach my $host ( sort(keys(%{$bpc->HostInfoRead()})) ) {
246 my @Backups = $bpc->BackupInfoRead($host);
248 for ( my $i = 0 ; $i < @Backups ; $i++ ) {
250 # BackupPC::Storage->backupInfoWrite won't overwrite
253 BackupPC::Storage->backupInfoWrite("$TopDir/pc/$host",
262 my($file, $fullPath) = @_;
263 my($inode, $nlinks, $nblocks) = (lstat($file))[1, 3, 12];
271 if ( $nlinks == 1 ) {
272 $blkCntRm += $nblocks;
275 # Save the files for later batch deletion.
277 # This is so we can remove them in inode order, and additionally
278 # reduce any remaining chance of race condition of linking to
279 # pool files vs removing pool files. (Other aspects of the
280 # design should eliminate race conditions.)
282 push(@PendingDelete, {
287 if ( @PendingDelete > $PendingDeleteMax ) {
288 processPendingDeletes(0);
291 # We must keep repeated files numbered sequential (ie: files
292 # that have the same checksum are appended with _0, _1 etc).
293 # There are two cases: we remove the base file xxxx, but xxxx_0
294 # exists, or we remove any file of the form xxxx_nnn. We remember
295 # the base name and fix it up later (not in the middle of find).
297 $fullPath =~ s/_\d+$//;
298 $FixList{$fullPath}++;
300 if ( $file =~ /_(\d+)$/ ) {
301 $fileRepMax = $1 + 1 if ( $fileRepMax <= $1 );
306 $blkCnt2 += $nblocks if ( $nlinks == 2 );
307 $fileLinkMax = $nlinks if ( $fileLinkMax < $nlinks );
308 $fileLinkTotal += $nlinks - 1;
312 sub processPendingDeletes
318 @delete = splice(@PendingDelete, 0, $PendingDeleteMax / 2);
320 @delete = @PendingDelete;
323 for my $f ( sort({ $a->{inode} <=> $b->{inode} } @delete) ) {
324 my($nlinks) = (lstat($f->{path}))[3];
326 next if ( $nlinks != 1 );
327 # print("Deleting $f->{path} ($f->{inode})\n");