Fixed some typos.
[BackupPC.git] / bin / BackupPC_link
1 #!/bin/perl
2 #============================================================= -*-perl-*-
3 #
4 # BackupPC_link: link new backup into pool
5 #
6 # DESCRIPTION
7 #
8 #   BackupPC_link inspects every file in a new backup and
9 #   checks if an existing file from any previous backup is
10 #   identical.  If so, the file is removed and replaced by
11 #   a hardlink to the existing file.  If the file is new,
12 #   a hardlink to the file is made in the pool area, so that
13 #   this file is available for checking against future backups.
14 #
15 #   Then, for incremental backups, hardlinks are made in the
16 #   backup directories to all files that were not extracted during
17 #   the incremental backups.  The means the incremental dump looks
18 #   like a complete image of the PC.
19 #
20 # AUTHOR
21 #   Craig Barratt  <cbarratt@users.sourceforge.net>
22 #
23 # COPYRIGHT
24 #   Copyright (C) 2001-2003  Craig Barratt
25 #
26 #   This program is free software; you can redistribute it and/or modify
27 #   it under the terms of the GNU General Public License as published by
28 #   the Free Software Foundation; either version 2 of the License, or
29 #   (at your option) any later version.
30 #
31 #   This program is distributed in the hope that it will be useful,
32 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
33 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
34 #   GNU General Public License for more details.
35 #
36 #   You should have received a copy of the GNU General Public License
37 #   along with this program; if not, write to the Free Software
38 #   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
39 #
40 #========================================================================
41 #
42 # Version 3.0.0alpha, released 23 Jan 2006.
43 #
44 # See http://backuppc.sourceforge.net.
45 #
46 #========================================================================
47
48 use strict;
49 no  utf8;
50 use lib "/usr/local/BackupPC/lib";
51 use BackupPC::Lib;
52 use BackupPC::Attrib;
53 use BackupPC::PoolWrite;
54 use BackupPC::Storage;
55
56 use File::Find;
57 use File::Path;
58 use Digest::MD5;
59
60 ###########################################################################
61 # Initialize
62 ###########################################################################
63
64 die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
65 my $TopDir = $bpc->TopDir();
66 my $BinDir = $bpc->BinDir();
67 my %Conf   = $bpc->Conf();
68
69 $bpc->ChildInit();
70
71 if ( @ARGV != 1 ) {
72     print("usage: $0 <host>\n");
73     exit(1);
74 }
75 if ( $ARGV[0] !~ /^([\w\.\s-]+)$/ ) {
76     print("$0: bad host name '$ARGV[0]'\n");
77     exit(1);
78 }
79 my $host = $1;
80 my $Dir  = "$TopDir/pc/$host";
81 my($CurrDumpDir, $Compress);
82
83 #
84 # Re-read config file, so we can include the PC-specific config
85 #
86 $bpc->ConfigRead($host);  
87 %Conf = $bpc->Conf();
88
89 ###########################################################################
90 # Process any backups that haven't been linked
91 ###########################################################################
92 my $md5 = Digest::MD5->new;
93 my($nFilesNew, $sizeNew, $sizeNewComp);
94 my($nFilesExist, $sizeExist, $sizeExistComp);
95 while ( 1 ) {
96     my @Backups = $bpc->BackupInfoRead($host);
97     $nFilesNew = $sizeNew = $sizeNewComp = 0;
98     $nFilesExist = $sizeExist = $sizeExistComp = 0;
99     my($num);
100     for ( $num = 0 ; $num < @Backups ; $num++ ) {
101         last if ( $Backups[$num]{nFilesNew} eq ""
102                     || -f "$Dir/NewFileList.$Backups[$num]{num}" );
103     }
104     last if ( $num >= @Backups );
105     #
106     # Process list of new files left by BackupPC_dump
107     #
108     $CurrDumpDir = "$Dir/$Backups[$num]{num}";
109     $Compress = $Backups[$num]{compress};
110     if ( open(NEW, "<", "$Dir/NewFileList.$Backups[$num]{num}") ) {
111         binmode(NEW);
112         while ( <NEW> ) {
113             chomp;
114             next if ( !/(\w+) (\d+) (.*)/ );
115             LinkNewFile($1, $2, "$CurrDumpDir/$3");
116         }
117         close(NEW);
118     }
119     unlink("$Dir/NewFileList.$Backups[$num]{num}")
120                 if ( -f "$Dir/NewFileList.$Backups[$num]{num}" );
121
122     #
123     # See if we should fill in this dump.  We only need to fill
124     # in incremental dumps.  We can only fill in the incremental
125     # dump if there is an existing filled in dump with the same
126     # type of compression (on or off).  Eg, we can't fill in
127     # a compressed incremental if the most recent filled in dump
128     # is not compressed.
129     #
130     my $noFill = 1;
131     my $fillFromNum;
132     if ( $Backups[$num]{type} ne "incr" ) {
133         $noFill = 0
134     } elsif ( $Conf{IncrFill} ) {
135         my $i;
136         for ( $i = $num - 1 ; $i >= 0 ; $i-- ) {
137             last if ( !$Backups[$i]{noFill}
138                           && ($Backups[$i]{compress} ? 1 : 0)
139                                        == ($Compress ? 1 : 0) );
140         }
141         my $prevDump = "$Dir/$Backups[$i]{num}";
142         if ( $i >= 0 && -d $prevDump ) {
143             find({wanted => \&FillIncr, no_chdir => 1}, $prevDump);
144             $noFill = 0;
145             $fillFromNum = $Backups[$i]{num};
146         }
147     }
148     #
149     # Update the backup info file in $TopDir/pc/$host/backups
150     #
151     @Backups = $bpc->BackupInfoRead($host);
152     $Backups[$num]{nFilesExist}   += $nFilesExist;
153     $Backups[$num]{sizeExist}     += $sizeExist;
154     $Backups[$num]{sizeExistComp} += $sizeExistComp;
155     $Backups[$num]{nFilesNew}     += $nFilesNew;
156     $Backups[$num]{sizeNew}       += $sizeNew;
157     $Backups[$num]{sizeNewComp}   += $sizeNewComp;
158     $Backups[$num]{noFill}         = $noFill;
159     $Backups[$num]{fillFromNum}    = $fillFromNum;
160     #
161     # Save just this backup's info in case the main backups file
162     # gets corrupted
163     #
164     BackupPC::Storage->backupInfoWrite($Dir,
165                                        $Backups[$num]{num},
166                                        $Backups[$num], 1);
167     #
168     # Save the main backups file
169     #
170     $bpc->BackupInfoWrite($host, @Backups);
171 }
172
173 ###########################################################################
174 # Subroutines
175 ###########################################################################
176
177 #
178 # Fill in an incremental dump by making hardlinks to the previous
179 # dump.
180 #
181 sub FillIncr
182 {
183     my($name) = $File::Find::name;
184     my($newName);
185
186     $name = $1 if ( $name =~ /(.*)/ );
187     return if ( $name !~ m{\Q$Dir\E/(\d+)/(.*)} );
188     $newName = "$CurrDumpDir/$2";
189     if ( -d $name && -d $newName ) {
190         #
191         # Merge the file attributes.
192         #
193         my $newAttr = BackupPC::Attrib->new({ compress => $Compress });
194         my $attr = BackupPC::Attrib->new({ compress => $Compress });
195         $newAttr->read($newName) if ( -f $newAttr->fileName($newName) );
196         $attr->read($name)       if ( -f $attr->fileName($name) );
197         $newAttr->merge($attr);
198         #
199         # Now write it out, adding a link to the pool if necessary
200         #
201         my $data = $newAttr->writeData;
202         my $origSize = length($data);
203         my $fileName = $newAttr->fileName($newName);
204         my $poolWrite = BackupPC::PoolWrite->new($bpc, $fileName,
205                                          length($data), $Compress);
206         $poolWrite->write(\$data);
207         my($exists, $digest, $outSize, $errs) = $poolWrite->close;
208         if ( @$errs ) {
209             print("log ", join("", @$errs));
210         }
211         if ( $exists ) {
212             $nFilesExist++;
213             $sizeExist += $origSize;
214             $sizeExistComp += $outSize;
215         } elsif ( $outSize > 0 ) {
216             $nFilesNew++;
217             $sizeNew += $origSize;
218             $sizeNewComp += -s $outSize;
219             LinkNewFile($digest, $origSize, $fileName);
220         }
221     } elsif ( -f $name && !-f $newName ) {
222         #
223         # Exists in the older filled backup, and not in the new, so link it
224         #
225         my($exists, $digest, $origSize, $outSize, $errs)
226                     = BackupPC::PoolWrite::LinkOrCopy(
227                                       $bpc,
228                                       $name, $Compress,
229                                       $newName, $Compress);
230         if ( $exists ) {
231             $nFilesExist++;
232             $sizeExist += $origSize;
233             $sizeExistComp += $outSize;
234         } elsif ( $outSize > 0 ) {
235             $nFilesNew++;
236             $sizeNew += $origSize;
237             $sizeNewComp += -s $outSize;
238             LinkNewFile($digest, $origSize, $newName);
239         }
240     }
241 }
242
243 #
244 # Add a link in the pool to a new file
245 #
246 sub LinkNewFile
247 {
248     my($d, $size, $fileName) = @_;
249     my $res = $bpc->MakeFileLink($fileName, $d, 1, $Compress);
250     if ( $res == 1 ) {
251         $nFilesExist++;
252         $sizeExist += $size;
253         $sizeExistComp += -s $fileName;
254     } elsif ( $res == 2 ) {
255         $nFilesNew++;
256         $sizeNew += $size;
257         $sizeNewComp += -s $fileName;
258     } elsif ( $res != 0 && $res != -1 ) {
259         print("log BackupPC_link got error $res when calling"
260              . " MakeFileLink($fileName, $d, 1)\n");
261     }
262 }