* Major changes from Ryan Kucera to add style sheets to the CGI
[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-2003  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.1.0_CVS, released 3 Jul 2003.
39 #
40 # See http://backuppc.sourceforge.net.
41 #
42 #========================================================================
43
44 use strict;
45 no  utf8;
46 use lib "/usr/local/BackupPC/lib";
47 use BackupPC::Lib;
48 use BackupPC::FileZIO;
49
50 use File::Find;
51 use File::Path;
52 use Data::Dumper;
53
54 die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
55 my $TopDir = $bpc->TopDir();
56 my $BinDir = $bpc->BinDir();
57 my %Conf   = $bpc->Conf();
58
59 $bpc->ChildInit();
60
61 my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
62 if ( $err ) {
63     print("Can't connect to server ($err)\n");
64     exit(1);
65 }
66 my $reply = $bpc->ServerMesg("status hosts");
67 $reply = $1 if ( $reply =~ /(.*)/s );
68 my(%Status, %Info, %Jobs, @BgQueue, @UserQueue, @CmdQueue);
69 eval($reply);
70
71 ###########################################################################
72 # When BackupPC_nightly starts, BackupPC will not run any simultaneous
73 # BackupPC_dump commands.  We first do things that contend with
74 # BackupPC_dump, eg: aging per-PC log files etc.
75 ###########################################################################
76
77 #
78 # Do per-PC log file aging
79 #
80 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
81 if ( $mday == 1 ) {
82     foreach my $host ( keys(%Status) ) {
83         my $lastLog = $Conf{MaxOldPerPCLogFiles} - 1;
84         unlink("$TopDir/pc/$host/LOG.$lastLog")
85                 if ( -f "$TopDir/pc/$host/LOG.$lastLog" );
86         unlink("$TopDir/pc/$host/LOG.$lastLog.z")
87                 if ( -f "$TopDir/pc/$host/LOG.$lastLog.z" );
88         for ( my $i = $lastLog - 1 ; $i >= 0 ; $i-- ) {
89             my $j = $i + 1;
90             if ( -f "$TopDir/pc/$host/LOG.$i" ) {
91                 rename("$TopDir/pc/$host/LOG.$i", "$TopDir/pc/$host/LOG.$j");
92             } elsif ( -f "$TopDir/pc/$host/LOG.$i.z" ) {
93                 rename("$TopDir/pc/$host/LOG.$i.z",
94                        "$TopDir/pc/$host/LOG.$j.z");
95             }
96         }
97         #
98         # Compress the log file LOG -> LOG.0.z (if enabled).
99         # Otherwise, just rename LOG -> LOG.0.
100         #
101         BackupPC::FileZIO->compressCopy("$TopDir/pc/$host/LOG",
102                                         "$TopDir/pc/$host/LOG.0.z",
103                                         "$TopDir/pc/$host/LOG.0",
104                                         $Conf{CompressLevel}, 1);
105         open(LOG, ">", "$TopDir/pc/$host/LOG") && close(LOG);
106     }
107 }
108
109 ###########################################################################
110 # Get statistics on the pool, and remove files that have only one link.
111 ###########################################################################
112
113 my $fileCnt;       # total number of files
114 my $dirCnt;        # total number of directories
115 my $blkCnt;        # total block size of files
116 my $fileCntRm;     # total number of removed files
117 my $blkCntRm;      # total block size of removed files
118 my $blkCnt2;       # total block size of files with just 2 links
119                    # (ie: files that only occur once among all backups)
120 my $fileCntRep;    # total number of file names containing "_", ie: files
121                    # that have repeated md5 checksums
122 my $fileRepMax;    # worse case number of files that have repeated checksums
123                    # (ie: max(nnn+1) for all names xxxxxxxxxxxxxxxx_nnn)
124 my $fileLinkMax;   # maximum number of hardlinks on a pool file
125 my $fileCntRename; # number of renamed files (to keep file numbering
126                    # contiguous)
127 my %FixList;       # list of paths that need to be renamed to avoid
128                    # new holes
129 for my $pool ( qw(pool cpool) ) {
130     $fileCnt       = 0;
131     $dirCnt        = 0;
132     $blkCnt        = 0;
133     $fileCntRm     = 0;
134     $blkCntRm      = 0;
135     $blkCnt2       = 0;
136     $fileCntRep    = 0;
137     $fileRepMax    = 0;
138     $fileLinkMax   = 0;
139     $fileCntRename = 0;
140     %FixList       = ();
141     find({wanted => \&GetPoolStats, no_chdir => 1}, "$TopDir/$pool");
142     my $kb   = $blkCnt / 2;
143     my $kbRm = $blkCntRm / 2;
144     my $kb2  = $blkCnt2 / 2;
145
146     #
147     # Now make sure that files with repeated checksums are still
148     # sequentially numbered
149     #
150     foreach my $name ( sort(keys(%FixList)) ) {
151         my $rmCnt = $FixList{$name} + 1;
152         my $new = -1;
153         for ( my $old = -1 ; ; $old++ ) {
154             my $oldName = $name;
155             $oldName .= "_$old" if ( $old >= 0 );
156             if ( !-f $oldName ) {
157                 #
158                 # We know we are done when we have missed at least
159                 # the number of files that were removed from this
160                 # base name, plus a couple just to be sure
161                 #
162                 last if ( $rmCnt-- <= 0 );
163                 next;
164             }
165             my $newName = $name;
166             $newName .= "_$new" if ( $new >= 0 );
167             $new++;
168             next if ( $oldName eq $newName );
169             rename($oldName, $newName);
170             $fileCntRename++;
171         }
172     }
173     print("BackupPC_stats = $pool,$fileCnt,$dirCnt,$kb,$kb2,$kbRm,$fileCntRm,"
174                           . "$fileCntRep,$fileRepMax,$fileCntRename,"
175                           . "$fileLinkMax\n");
176 }
177
178 ###########################################################################
179 # Tell BackupPC that it is now ok to start running BackupPC_dump
180 # commands.  We are guaranteed that no BackupPC_link commands will
181 # run since only a single CmdQueue command runs at a time, and
182 # that means we are safe.
183 ###########################################################################
184 printf("BackupPC_nightly lock_off\n");
185
186 ###########################################################################
187 # Send email 
188 ###########################################################################
189 system("$BinDir/BackupPC_sendEmail");
190
191 sub GetPoolStats
192 {
193     my($name) = $File::Find::name;
194     my($baseName)  = "";
195     my(@s);
196  
197     return if ( !-d && !-f );
198     $dirCnt += -d;
199     $name = $1 if ( $name =~ /(.*)/ );
200     @s = stat($name);
201     if ( $name =~ /(.*)_(\d+)$/ ) {
202         $baseName = $1;
203         if ( $s[3] != 1 ) {
204             $fileRepMax = $2 + 1 if ( $fileRepMax <= $2 );
205             $fileCntRep++;
206         }
207     }
208     if ( -f && $s[3] == 1 ) {
209         $blkCntRm += $s[12];
210         $fileCntRm++;
211         unlink($name);
212         #
213         # We must keep repeated files numbered sequential (ie: files
214         # that have the same checksum are appended with _0, _1 etc).
215         # There are two cases: we remove the base file xxxx, but xxxx_0
216         # exists, or we remove any file of the form xxxx_nnn.  We remember
217         # the base name and fix it up later (not in the middle of find).
218         #
219         $baseName = $name if ( $baseName eq "" );
220         $FixList{$baseName}++;
221     } else {
222         $fileCnt += -f;
223         $blkCnt  += $s[12];
224         $blkCnt2 += $s[12] if ( -f && $s[3] == 2 );
225         $fileLinkMax = $s[3] if ( $fileLinkMax < $s[3] );
226     }
227 }