* Several improvements to restore: cancel now reports the correct
[BackupPC.git] / bin / BackupPC_nightly
1 #!/bin/perl -T
2 #============================================================= -*-perl-*-
3 #
4 # BackupPC_nightly: Nightly cleanup & statistics script.
5 #
6 # DESCRIPTION
7 #   BackupPC_nightly performs several administrative tasks:
8 #
9 #     - monthly aging of per-PC log files
10 #
11 #     - pruning files from pool no longer used (ie: those with only one
12 #       hard link).
13 #
14 #     - sending email to users and administrators.
15 #
16 # AUTHOR
17 #   Craig Barratt  <cbarratt@users.sourceforge.net>
18 #
19 # COPYRIGHT
20 #   Copyright (C) 2001  Craig Barratt
21 #
22 #   This program is free software; you can redistribute it and/or modify
23 #   it under the terms of the GNU General Public License as published by
24 #   the Free Software Foundation; either version 2 of the License, or
25 #   (at your option) any later version.
26 #
27 #   This program is distributed in the hope that it will be useful,
28 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
29 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30 #   GNU General Public License for more details.
31 #
32 #   You should have received a copy of the GNU General Public License
33 #   along with this program; if not, write to the Free Software
34 #   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
35 #
36 #========================================================================
37 #
38 # Version 2.0.0beta3, released 1 Jun 2003.
39 #
40 # See http://backuppc.sourceforge.net.
41 #
42 #========================================================================
43
44 use strict;
45 use lib "/usr/local/BackupPC/lib";
46 use BackupPC::Lib;
47 use BackupPC::FileZIO;
48
49 use File::Find;
50 use File::Path;
51 use Data::Dumper;
52
53 die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
54 my $TopDir = $bpc->TopDir();
55 my $BinDir = $bpc->BinDir();
56 my %Conf   = $bpc->Conf();
57
58 $bpc->ChildInit();
59
60 my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
61 if ( $err ) {
62     print("Can't connect to server ($err)\n");
63     exit(1);
64 }
65 my $reply = $bpc->ServerMesg("status hosts");
66 $reply = $1 if ( $reply =~ /(.*)/s );
67 my(%Status, %Info, %Jobs, @BgQueue, @UserQueue, @CmdQueue);
68 eval($reply);
69
70 ###########################################################################
71 # When BackupPC_nightly starts, BackupPC will not run any simultaneous
72 # BackupPC_dump commands.  We first do things that contend with
73 # BackupPC_dump, eg: aging per-PC log files etc.
74 ###########################################################################
75
76 #
77 # Do per-PC log file aging
78 #
79 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
80 if ( $mday == 1 ) {
81     foreach my $host ( keys(%Status) ) {
82         my $lastLog = $Conf{MaxOldPerPCLogFiles} - 1;
83         unlink("$TopDir/pc/$host/LOG.$lastLog")
84                 if ( -f "$TopDir/pc/$host/LOG.$lastLog" );
85         unlink("$TopDir/pc/$host/LOG.$lastLog.z")
86                 if ( -f "$TopDir/pc/$host/LOG.$lastLog.z" );
87         for ( my $i = $lastLog - 1 ; $i >= 0 ; $i-- ) {
88             my $j = $i + 1;
89             if ( -f "$TopDir/pc/$host/LOG.$i" ) {
90                 rename("$TopDir/pc/$host/LOG.$i", "$TopDir/pc/$host/LOG.$j");
91             } elsif ( -f "$TopDir/pc/$host/LOG.$i.z" ) {
92                 rename("$TopDir/pc/$host/LOG.$i.z",
93                        "$TopDir/pc/$host/LOG.$j.z");
94             }
95         }
96         #
97         # Compress the log file LOG -> LOG.0.z (if enabled).
98         # Otherwise, just rename LOG -> LOG.0.
99         #
100         BackupPC::FileZIO->compressCopy("$TopDir/pc/$host/LOG",
101                                         "$TopDir/pc/$host/LOG.0.z",
102                                         "$TopDir/pc/$host/LOG.0",
103                                         $Conf{CompressLevel}, 1);
104         open(LOG, ">", "$TopDir/pc/$host/LOG") && close(LOG);
105     }
106 }
107
108 ###########################################################################
109 # Get statistics on the pool, and remove files that have only one link.
110 ###########################################################################
111
112 my $fileCnt;       # total number of files
113 my $dirCnt;        # total number of directories
114 my $blkCnt;        # total block size of files
115 my $fileCntRm;     # total number of removed files
116 my $blkCntRm;      # total block size of removed files
117 my $blkCnt2;       # total block size of files with just 2 links
118                    # (ie: files that only occur once among all backups)
119 my $fileCntRep;    # total number of file names containing "_", ie: files
120                    # that have repeated md5 checksums
121 my $fileRepMax;    # worse case number of files that have repeated checksums
122                    # (ie: max(nnn+1) for all names xxxxxxxxxxxxxxxx_nnn)
123 my $fileLinkMax;   # maximum number of hardlinks on a pool file
124 my $fileCntRename; # number of renamed files (to keep file numbering
125                    # contiguous)
126 my %FixList;       # list of paths that need to be renamed to avoid
127                    # new holes
128 for my $pool ( qw(pool cpool) ) {
129     $fileCnt       = 0;
130     $dirCnt        = 0;
131     $blkCnt        = 0;
132     $fileCntRm     = 0;
133     $blkCntRm      = 0;
134     $blkCnt2       = 0;
135     $fileCntRep    = 0;
136     $fileRepMax    = 0;
137     $fileLinkMax   = 0;
138     $fileCntRename = 0;
139     %FixList       = ();
140     find({wanted => \&GetPoolStats, no_chdir => 1}, "$TopDir/$pool");
141     my $kb   = $blkCnt / 2;
142     my $kbRm = $blkCntRm / 2;
143     my $kb2  = $blkCnt2 / 2;
144
145     #
146     # Now make sure that files with repeated checksums are still
147     # sequentially numbered
148     #
149     foreach my $name ( sort(keys(%FixList)) ) {
150         my $rmCnt = $FixList{$name} + 1;
151         my $new = -1;
152         for ( my $old = -1 ; ; $old++ ) {
153             my $oldName = $name;
154             $oldName .= "_$old" if ( $old >= 0 );
155             if ( !-f $oldName ) {
156                 #
157                 # We know we are done when we have missed at least
158                 # the number of files that were removed from this
159                 # base name, plus a couple just to be sure
160                 #
161                 last if ( $rmCnt-- <= 0 );
162                 next;
163             }
164             my $newName = $name;
165             $newName .= "_$new" if ( $new >= 0 );
166             $new++;
167             next if ( $oldName eq $newName );
168             rename($oldName, $newName);
169             $fileCntRename++;
170         }
171     }
172     print("BackupPC_stats = $pool,$fileCnt,$dirCnt,$kb,$kb2,$kbRm,$fileCntRm,"
173                           . "$fileCntRep,$fileRepMax,$fileCntRename,"
174                           . "$fileLinkMax\n");
175 }
176
177 ###########################################################################
178 # Tell BackupPC that it is now ok to start running BackupPC_dump
179 # commands.  We are guaranteed that no BackupPC_link commands will
180 # run since only a single CmdQueue command runs at a time, and
181 # that means we are safe.
182 ###########################################################################
183 printf("BackupPC_nightly lock_off\n");
184
185 ###########################################################################
186 # Send email 
187 ###########################################################################
188 system("$BinDir/BackupPC_sendEmail");
189
190 sub GetPoolStats
191 {
192     my($name) = $File::Find::name;
193     my($baseName)  = "";
194     my(@s);
195  
196     return if ( !-d && !-f );
197     $dirCnt += -d;
198     $name = $1 if ( $name =~ /(.*)/ );
199     @s = stat($name);
200     if ( $name =~ /(.*)_(\d+)$/ ) {
201         $baseName = $1;
202         if ( $s[3] != 1 ) {
203             $fileRepMax = $2 + 1 if ( $fileRepMax <= $2 );
204             $fileCntRep++;
205         }
206     }
207     if ( -f && $s[3] == 1 ) {
208         $blkCntRm += $s[12];
209         $fileCntRm++;
210         unlink($name);
211         #
212         # We must keep repeated files numbered sequential (ie: files
213         # that have the same checksum are appended with _0, _1 etc).
214         # There are two cases: we remove the base file xxxx, but xxxx_0
215         # exists, or we remove any file of the form xxxx_nnn.  We remember
216         # the base name and fix it up later (not in the middle of find).
217         #
218         $baseName = $name if ( $baseName eq "" );
219         $FixList{$baseName}++;
220     } else {
221         $fileCnt += -f;
222         $blkCnt  += $s[12];
223         $blkCnt2 += $s[12] if ( -f && $s[3] == 2 );
224         $fileLinkMax = $s[3] if ( $fileLinkMax < $s[3] );
225     }
226 }