fix
[BackupPC.git] / bin / BackupPC_link
1 #!/usr/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-2009  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.2.0, released 31 Jul 2010.
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         my(@shareAttribArgs);
112         binmode(NEW);
113         while ( <NEW> ) {
114             chomp;
115             next if ( !/(\w+) (\d+) (.*)/ );
116             if ( $3 eq "attrib" ) {
117                 #
118                 # Defer linking top-level attrib file until the end
119                 # since it can appear multiple times when multiple shares
120                 # are dumped.
121                 #
122                 @shareAttribArgs = ($1, $2, "$CurrDumpDir/$3");
123             } else {
124                 LinkNewFile($1, $2, "$CurrDumpDir/$3");
125             }
126         }
127         LinkNewFile(@shareAttribArgs) if ( @shareAttribArgs );
128         close(NEW);
129     }
130     unlink("$Dir/NewFileList.$Backups[$num]{num}")
131                 if ( -f "$Dir/NewFileList.$Backups[$num]{num}" );
132
133     #
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
139     # is not compressed.
140     #
141     my $noFill = 1;
142     my $fillFromNum;
143     if ( $Backups[$num]{type} ne "incr" ) {
144         $noFill = 0
145     } elsif ( $Conf{IncrFill} ) {
146         my $i;
147         for ( $i = $num - 1 ; $i >= 0 ; $i-- ) {
148             last if ( !$Backups[$i]{noFill}
149                           && ($Backups[$i]{compress} ? 1 : 0)
150                                        == ($Compress ? 1 : 0) );
151         }
152         my $prevDump = "$Dir/$Backups[$i]{num}";
153         if ( $i >= 0 && -d $prevDump ) {
154             find({wanted => \&FillIncr, no_chdir => 1}, $prevDump);
155             $noFill = 0;
156             $fillFromNum = $Backups[$i]{num};
157         }
158     }
159     #
160     # Update the backup info file in $TopDir/pc/$host/backups
161     #
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;
171     #
172     # Save just this backup's info in case the main backups file
173     # gets corrupted
174     #
175     BackupPC::Storage->backupInfoWrite($Dir,
176                                        $Backups[$num]{num},
177                                        $Backups[$num], 1);
178     #
179     # Save the main backups file
180     #
181     $bpc->BackupInfoWrite($host, @Backups);
182 }
183
184 ###########################################################################
185 # Subroutines
186 ###########################################################################
187
188 #
189 # Fill in an incremental dump by making hardlinks to the previous
190 # dump.
191 #
192 sub FillIncr
193 {
194     my($name) = $File::Find::name;
195     my($newName);
196
197     $name = $1 if ( $name =~ /(.*)/ );
198     return if ( $name !~ m{\Q$Dir\E/(\d+)/(.*)} );
199     $newName = "$CurrDumpDir/$2";
200     if ( -d $name && -d $newName ) {
201         #
202         # Merge the file attributes.
203         #
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);
209         #
210         # Now write it out, adding a link to the pool if necessary
211         #
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;
219         if ( @$errs ) {
220             print("log ", join("", @$errs));
221         }
222         if ( $exists ) {
223             $nFilesExist++;
224             $sizeExist += $origSize;
225             $sizeExistComp += $outSize;
226         } elsif ( $outSize > 0 ) {
227             $nFilesNew++;
228             $sizeNew += $origSize;
229             $sizeNewComp += -s $outSize;
230             LinkNewFile($digest, $origSize, $fileName);
231         }
232     } elsif ( -f $name && !-f $newName ) {
233         #
234         # Exists in the older filled backup, and not in the new, so link it
235         #
236         my($exists, $digest, $origSize, $outSize, $errs)
237                     = BackupPC::PoolWrite::LinkOrCopy(
238                                       $bpc,
239                                       $name, $Compress,
240                                       $newName, $Compress);
241         if ( $exists ) {
242             $nFilesExist++;
243             $sizeExist += $origSize;
244             $sizeExistComp += $outSize;
245         } elsif ( $outSize > 0 ) {
246             $nFilesNew++;
247             $sizeNew += $origSize;
248             $sizeNewComp += -s $outSize;
249             LinkNewFile($digest, $origSize, $newName);
250         }
251     }
252 }
253
254 #
255 # Add a link in the pool to a new file
256 #
257 sub LinkNewFile
258 {
259     my($d, $size, $fileName) = @_;
260     my $res = $bpc->MakeFileLink($fileName, $d, 1, $Compress);
261     if ( $res == 1 ) {
262         $nFilesExist++;
263         $sizeExist += $size;
264         $sizeExistComp += -s $fileName;
265     } elsif ( $res == 2 ) {
266         $nFilesNew++;
267         $sizeNew += $size;
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");
272     }
273 }