2 #============================================================= -*-perl-*-
4 # BackupPC_link: link new backup into pool
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.
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.
21 # Craig Barratt <cbarratt@users.sourceforge.net>
24 # Copyright (C) 2001-2009 Craig Barratt
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.
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.
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
40 #========================================================================
42 # Version 3.2.0, released 31 Jul 2010.
44 # See http://backuppc.sourceforge.net.
46 #========================================================================
50 use lib "/usr/local/BackupPC/lib";
53 use BackupPC::PoolWrite;
54 use BackupPC::Storage;
60 ###########################################################################
62 ###########################################################################
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();
72 print("usage: $0 <host>\n");
75 if ( $ARGV[0] !~ /^([\w\.\s-]+)$/ ) {
76 print("$0: bad host name '$ARGV[0]'\n");
80 my $Dir = "$TopDir/pc/$host";
81 my($CurrDumpDir, $Compress);
84 # Re-read config file, so we can include the PC-specific config
86 $bpc->ConfigRead($host);
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);
96 my @Backups = $bpc->BackupInfoRead($host);
97 $nFilesNew = $sizeNew = $sizeNewComp = 0;
98 $nFilesExist = $sizeExist = $sizeExistComp = 0;
100 for ( $num = 0 ; $num < @Backups ; $num++ ) {
101 last if ( $Backups[$num]{nFilesNew} eq ""
102 || -f "$Dir/NewFileList.$Backups[$num]{num}" );
104 last if ( $num >= @Backups );
106 # Process list of new files left by BackupPC_dump
108 $CurrDumpDir = "$Dir/$Backups[$num]{num}";
109 $Compress = $Backups[$num]{compress};
110 if ( open(NEW, "<", "$Dir/NewFileList.$Backups[$num]{num}") ) {
111 my(@shareAttribArgs);
115 next if ( !/(\w+) (\d+) (.*)/ );
116 if ( $3 eq "attrib" ) {
118 # Defer linking top-level attrib file until the end
119 # since it can appear multiple times when multiple shares
122 @shareAttribArgs = ($1, $2, "$CurrDumpDir/$3");
124 LinkNewFile($1, $2, "$CurrDumpDir/$3");
127 LinkNewFile(@shareAttribArgs) if ( @shareAttribArgs );
130 unlink("$Dir/NewFileList.$Backups[$num]{num}")
131 if ( -f "$Dir/NewFileList.$Backups[$num]{num}" );
134 # See if we should fill in this dump. We only need to fill
135 # in incremental dumps. We can only fill in the incremental
136 # dump if there is an existing filled in dump with the same
137 # type of compression (on or off). Eg, we can't fill in
138 # a compressed incremental if the most recent filled in dump
143 if ( $Backups[$num]{type} ne "incr" ) {
145 } elsif ( $Conf{IncrFill} ) {
147 for ( $i = $num - 1 ; $i >= 0 ; $i-- ) {
148 last if ( !$Backups[$i]{noFill}
149 && ($Backups[$i]{compress} ? 1 : 0)
150 == ($Compress ? 1 : 0) );
152 my $prevDump = "$Dir/$Backups[$i]{num}";
153 if ( $i >= 0 && -d $prevDump ) {
154 find({wanted => \&FillIncr, no_chdir => 1}, $prevDump);
156 $fillFromNum = $Backups[$i]{num};
160 # Update the backup info file in $TopDir/pc/$host/backups
162 @Backups = $bpc->BackupInfoRead($host);
163 $Backups[$num]{nFilesExist} += $nFilesExist;
164 $Backups[$num]{sizeExist} += $sizeExist;
165 $Backups[$num]{sizeExistComp} += $sizeExistComp;
166 $Backups[$num]{nFilesNew} += $nFilesNew;
167 $Backups[$num]{sizeNew} += $sizeNew;
168 $Backups[$num]{sizeNewComp} += $sizeNewComp;
169 $Backups[$num]{noFill} = $noFill;
170 $Backups[$num]{fillFromNum} = $fillFromNum;
172 # Save just this backup's info in case the main backups file
175 BackupPC::Storage->backupInfoWrite($Dir,
179 # Save the main backups file
181 $bpc->BackupInfoWrite($host, @Backups);
184 ###########################################################################
186 ###########################################################################
189 # Fill in an incremental dump by making hardlinks to the previous
194 my($name) = $File::Find::name;
197 $name = $1 if ( $name =~ /(.*)/ );
198 return if ( $name !~ m{\Q$Dir\E/(\d+)/(.*)} );
199 $newName = "$CurrDumpDir/$2";
200 if ( -d $name && -d $newName ) {
202 # Merge the file attributes.
204 my $newAttr = BackupPC::Attrib->new({ compress => $Compress });
205 my $attr = BackupPC::Attrib->new({ compress => $Compress });
206 $newAttr->read($newName) if ( -f $newAttr->fileName($newName) );
207 $attr->read($name) if ( -f $attr->fileName($name) );
208 $newAttr->merge($attr);
210 # Now write it out, adding a link to the pool if necessary
212 my $data = $newAttr->writeData;
213 my $origSize = length($data);
214 my $fileName = $newAttr->fileName($newName);
215 my $poolWrite = BackupPC::PoolWrite->new($bpc, $fileName,
216 length($data), $Compress);
217 $poolWrite->write(\$data);
218 my($exists, $digest, $outSize, $errs) = $poolWrite->close;
220 print("log ", join("", @$errs));
224 $sizeExist += $origSize;
225 $sizeExistComp += $outSize;
226 } elsif ( $outSize > 0 ) {
228 $sizeNew += $origSize;
229 $sizeNewComp += -s $outSize;
230 LinkNewFile($digest, $origSize, $fileName);
232 } elsif ( -f $name && !-f $newName ) {
234 # Exists in the older filled backup, and not in the new, so link it
236 my($exists, $digest, $origSize, $outSize, $errs)
237 = BackupPC::PoolWrite::LinkOrCopy(
240 $newName, $Compress);
243 $sizeExist += $origSize;
244 $sizeExistComp += $outSize;
245 } elsif ( $outSize > 0 ) {
247 $sizeNew += $origSize;
248 $sizeNewComp += -s $outSize;
249 LinkNewFile($digest, $origSize, $newName);
255 # Add a link in the pool to a new file
259 my($d, $size, $fileName) = @_;
260 my $res = $bpc->MakeFileLink($fileName, $d, 1, $Compress);
264 $sizeExistComp += -s $fileName;
265 } elsif ( $res == 2 ) {
268 $sizeNewComp += -s $fileName;
269 } elsif ( $res != 0 && $res != -1 ) {
270 print("log BackupPC_link got error $res when calling"
271 . " MakeFileLink($fileName, $d, 1)\n");