* Commit for 2.1.0.
[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 2.1.0, released 20 Jun 2004.
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
55 use File::Find;
56 use File::Path;
57 use Digest::MD5;
58
59 ###########################################################################
60 # Initialize
61 ###########################################################################
62
63 die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
64 my $TopDir = $bpc->TopDir();
65 my $BinDir = $bpc->BinDir();
66 my %Conf   = $bpc->Conf();
67
68 $bpc->ChildInit();
69
70 if ( @ARGV != 1 ) {
71     print("usage: $0 <host>\n");
72     exit(1);
73 }
74 if ( $ARGV[0] !~ /^([\w\.\s-]+)$/ ) {
75     print("$0: bad host name '$ARGV[0]'\n");
76     exit(1);
77 }
78 my $host = $1;
79 my $Dir  = "$TopDir/pc/$host";
80 my($CurrDumpDir, $Compress);
81
82 #
83 # Re-read config file, so we can include the PC-specific config
84 #
85 $bpc->ConfigRead($host);  
86 %Conf = $bpc->Conf();
87
88 ###########################################################################
89 # Process any backups that haven't been linked
90 ###########################################################################
91 my $md5 = Digest::MD5->new;
92 my($nFilesNew, $sizeNew, $sizeNewComp);
93 my($nFilesExist, $sizeExist, $sizeExistComp);
94 while ( 1 ) {
95     my @Backups = $bpc->BackupInfoRead($host);
96     $nFilesNew = $sizeNew = $sizeNewComp = 0;
97     $nFilesExist = $sizeExist = $sizeExistComp = 0;
98     my($num);
99     for ( $num = 0 ; $num < @Backups ; $num++ ) {
100         last if ( $Backups[$num]{nFilesNew} eq ""
101                     || -f "$Dir/NewFileList.$Backups[$num]{num}" );
102     }
103     last if ( $num >= @Backups );
104     #
105     # Process list of new files left by BackupPC_dump
106     #
107     $CurrDumpDir = "$Dir/$Backups[$num]{num}";
108     $Compress = $Backups[$num]{compress};
109     if ( open(NEW, "<", "$Dir/NewFileList.$Backups[$num]{num}") ) {
110         binmode(NEW);
111         while ( <NEW> ) {
112             chomp;
113             next if ( !/(\w+) (\d+) (.*)/ );
114             LinkNewFile($1, $2, "$CurrDumpDir/$3");
115         }
116         close(NEW);
117     }
118     unlink("$Dir/NewFileList.$Backups[$num]{num}")
119                 if ( -f "$Dir/NewFileList.$Backups[$num]{num}" );
120
121     #
122     # See if we should fill in this dump.  We only need to fill
123     # in incremental dumps.  We can only fill in the incremental
124     # dump if there is an existing filled in dump with the same
125     # type of compression (on or off).  Eg, we can't fill in
126     # a compressed incremental if the most recent filled in dump
127     # is not compressed.
128     #
129     my $noFill = 1;
130     my $fillFromNum;
131     if ( $Backups[$num]{type} ne "incr" ) {
132         $noFill = 0
133     } elsif ( $Conf{IncrFill} ) {
134         my $i;
135         for ( $i = $num - 1 ; $i >= 0 ; $i-- ) {
136             last if ( !$Backups[$i]{noFill}
137                           && ($Backups[$i]{compress} ? 1 : 0)
138                                        == ($Compress ? 1 : 0) );
139         }
140         my $prevDump = "$Dir/$Backups[$i]{num}";
141         if ( $i >= 0 && -d $prevDump ) {
142             find({wanted => \&FillIncr, no_chdir => 1}, $prevDump);
143             $noFill = 0;
144             $fillFromNum = $Backups[$i]{num};
145         }
146     }
147     #
148     # Update the backup info file in $TopDir/pc/$host/backups
149     #
150     @Backups = $bpc->BackupInfoRead($host);
151     $Backups[$num]{nFilesExist}   += $nFilesExist;
152     $Backups[$num]{sizeExist}     += $sizeExist;
153     $Backups[$num]{sizeExistComp} += $sizeExistComp;
154     $Backups[$num]{nFilesNew}     += $nFilesNew;
155     $Backups[$num]{sizeNew}       += $sizeNew;
156     $Backups[$num]{sizeNewComp}   += $sizeNewComp;
157     $Backups[$num]{noFill}         = $noFill;
158     $Backups[$num]{fillFromNum}    = $fillFromNum;
159     $bpc->BackupInfoWrite($host, @Backups);
160 }
161
162 ###########################################################################
163 # Subroutines
164 ###########################################################################
165
166 #
167 # Fill in an incremental dump by making hardlinks to the previous
168 # dump.
169 #
170 sub FillIncr
171 {
172     my($name) = $File::Find::name;
173     my($newName);
174
175     $name = $1 if ( $name =~ /(.*)/ );
176     return if ( $name !~ m{\Q$Dir\E/(\d+)/(.*)} );
177     $newName = "$CurrDumpDir/$2";
178     if ( -d $name && -d $newName ) {
179         #
180         # Merge the file attributes.
181         #
182         my $newAttr = BackupPC::Attrib->new({ compress => $Compress });
183         my $attr = BackupPC::Attrib->new({ compress => $Compress });
184         $newAttr->read($newName) if ( -f $newAttr->fileName($newName) );
185         $attr->read($name)       if ( -f $attr->fileName($name) );
186         $newAttr->merge($attr);
187         #
188         # Now write it out, adding a link to the pool if necessary
189         #
190         my $data = $newAttr->writeData;
191         my $origSize = length($data);
192         my $fileName = $newAttr->fileName($newName);
193         my $poolWrite = BackupPC::PoolWrite->new($bpc, $fileName,
194                                          length($data), $Compress);
195         $poolWrite->write(\$data);
196         my($exists, $digest, $outSize, $errs) = $poolWrite->close;
197         if ( @$errs ) {
198             print("log ", join("", @$errs));
199         }
200         if ( $exists ) {
201             $nFilesExist++;
202             $sizeExist += $origSize;
203             $sizeExistComp += $outSize;
204         } elsif ( $outSize > 0 ) {
205             $nFilesNew++;
206             $sizeNew += $origSize;
207             $sizeNewComp += -s $outSize;
208             LinkNewFile($digest, $origSize, $fileName);
209         }
210     } elsif ( -f $name && !-f $newName ) {
211         #
212         # Exists in the older filled backup, and not in the new, so link it
213         #
214         link($name, $newName);
215     }
216 }
217
218 #
219 # Add a link in the pool to a new file
220 #
221 sub LinkNewFile
222 {
223     my($d, $size, $fileName) = @_;
224     my $res = $bpc->MakeFileLink($fileName, $d, 1, $Compress);
225     if ( $res == 1 ) {
226         $nFilesExist++;
227         $sizeExist += $size;
228         $sizeExistComp += -s $fileName;
229     } elsif ( $res == 2 ) {
230         $nFilesNew++;
231         $sizeNew += $size;
232         $sizeNewComp += -s $fileName;
233     } elsif ( $res != 0 && $res != -1 ) {
234         print("log BackupPC_link got error $res when calling"
235              . " MakeFileLink($fileName, $d, 1)\n");
236     }
237 }