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