/[BackupPC]/trunk/bin/BackupPC_incPartsUpdate
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Diff of /trunk/bin/BackupPC_incPartsUpdate

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 196 by dpavlin, Thu Oct 13 18:32:59 2005 UTC revision 295 by dpavlin, Thu Jan 26 10:28:43 2006 UTC
# Line 6  use lib "__INSTALLDIR__/lib"; Line 6  use lib "__INSTALLDIR__/lib";
6  use DBI;  use DBI;
7  use BackupPC::Lib;  use BackupPC::Lib;
8  use BackupPC::View;  use BackupPC::View;
9    use BackupPC::Attrib qw/:all/;
10  use Data::Dumper;  use Data::Dumper;
11  use Time::HiRes qw/time/;  use Time::HiRes qw/time/;
12  use POSIX qw/strftime/;  use POSIX qw/strftime/;
 use BackupPC::SearchLib;  
13  use Cwd qw/abs_path/;  use Cwd qw/abs_path/;
14    use File::Which;
15    use Archive::Tar::Streamed;
16    use Algorithm::Diff;
17    use Getopt::Std;
18    use File::Slurp;
19    use File::Pid;
20    
21    =head1 NAME
22    
23    BackupPC_incPartsUpdate
24    
25    =head1 DESCRIPTION
26    
27    Create C<.tar.gz> increments on disk calling C<BackupPC_tarIncCreate>.
28    
29    Following options are supported (but all are optional):
30    
31    =over 4
32    
33    =item -h hostname
34    
35    Update parts for just single C<hostname>
36    
37    =item -c
38    
39    Force check for tar archives which exist on disk
40    
41    =item -d
42    
43    Turn debugging output
44    
45    =back
46    
47    =cut
48    
49    my %opt;
50    getopts("cdh:", \%opt );
51    
52    my $debug = $opt{d};
53    my $check = $opt{c} && print STDERR "NOTICE: tar archive check forced\n";
54    
55    my $pid_path = abs_path($0);
56    $pid_path =~ s/\W+/_/g;
57    
58    my $pidfile = new File::Pid({
59            file => "/tmp/$pid_path",
60    });
61    
62    if (my $pid = $pidfile->running ) {
63            die "$0 already running: $pid\n";
64    } elsif ($pidfile->pid ne $$) {
65            $pidfile->remove;
66            $pidfile = new File::Pid;
67    }
68    
69    print STDERR "$0 using pid ",$pidfile->pid," file ",$pidfile->file,"\n";
70    $pidfile->write;
71    
72    my $bpc = BackupPC::Lib->new || die "can't create BackupPC::Lib";
73    my %Conf = $bpc->Conf();
74    
75    use BackupPC::SearchLib;
76    %BackupPC::SearchLib::Conf = %Conf;
77    
78  my $path = abs_path($0);  my $path = abs_path($0);
79  $path =~ s#/[^/]+$#/#;  $path =~ s#/[^/]+$#/#;
# Line 18  my $tarIncCreate = $path .= 'BackupPC_ta Line 81  my $tarIncCreate = $path .= 'BackupPC_ta
81    
82  die "can't find $tarIncCreate: $!\n" unless (-x $tarIncCreate);  die "can't find $tarIncCreate: $!\n" unless (-x $tarIncCreate);
83    
84  my $debug = 0;  my $bin;
85    foreach my $c (qw/gzip md5sum/) {
86            $bin->{$c} = which($c) || die "$0 needs $c, install it\n";
87    }
88    
89  $|=1;  $|=1;
90    
91  my $start_t = time();  my $start_t = time();
92    
93  my $t_fmt = '%Y-%m-%d %H:%M:%S';  my $t_fmt = '%Y-%m-%d %H:%M:%S';
94    
 my $hosts;  
 my $bpc = BackupPC::Lib->new || die;  
 my %Conf = $bpc->Conf();  
 my $TopDir = $bpc->TopDir();  
 my $beenThere = {};  
   
95  my $dsn = $Conf{SearchDSN} || die "Need SearchDSN in config.pl\n";  my $dsn = $Conf{SearchDSN} || die "Need SearchDSN in config.pl\n";
96  my $user = $Conf{SearchUser} || '';  my $user = $Conf{SearchUser} || '';
97    
# Line 55  sub curr_time { Line 116  sub curr_time {
116          return strftime($t_fmt,localtime());          return strftime($t_fmt,localtime());
117  }  }
118    
119    my $hsn_cache;
120    
121    sub get_backup_id($$$) {
122            my ($host, $share, $num) = @_;
123    
124            my $key = "$host $share $num";
125            return $hsn_cache->{$key} if ($hsn_cache->{$key});
126    
127            my $sth = $dbh->prepare(qq{
128                    SELECT
129                            backups.id
130                    FROM backups
131                    INNER JOIN shares       ON backups.shareID=shares.ID
132                    INNER JOIN hosts        ON backups.hostID = hosts.ID
133                    WHERE hosts.name = ? and shares.name = ? and backups.num = ?
134            });
135            $sth->execute($host, $share, $num);
136            my ($id) = $sth->fetchrow_array;
137    
138            $hsn_cache->{"$host $share $num"} = $id;
139    
140            print STDERR "# $host $share $num == $id\n" if ($opt{d});
141    
142            return $id;
143    }
144    
145    
146    sub tar_check($$$$) {
147            my ($host,$share,$num,$filename) = @_;
148    
149            my $t = time();
150            print curr_time, " check $host:$share#$num -> $filename";
151    
152            # depending on expected returned value this is used like:
153            # my $uncompress_size = get_gzip_size('/full/path/to.gz');
154            # my ($compress_size, $uncompress_size) = get_gzip_size('/path.gz');
155            sub get_gzip_size($) {
156                    my $filename = shift;
157                    die "file $filename problem: $!" unless (-r $filename);
158                    open(my $gzip, $bin->{gzip}." -l $filename |") || die "can't gzip -l $filename: $!";
159                    my $line = <$gzip>;
160                    chomp($line);
161                    $line = <$gzip> if ($line =~ /^\s+compressed/);
162    
163                    my ($comp, $uncomp) = (0,0);
164    
165                    if ($line =~ m/^\s+(\d+)\s+(\d+)\s+\d+\.\d+/) {
166                            if (wantarray) {
167                                    return [ $1, $2 ];
168                            } else {
169                                    return $2;
170                            }
171                    } else {
172                            die "can't find size in line: $line";
173                    }
174            }
175    
176            sub check_part {
177                    my ($host, $share, $num, $part_nr, $tar_size, $size, $md5, $items) = @_;
178                    my $backup_id = get_backup_id($host, $share, $num);
179                    my $sth_md5 = $dbh->prepare(qq{
180                            select
181                                    id, tar_size, size, md5, items
182                            from backup_parts
183                            where backup_id = ? and part_nr = ?
184                    });
185    
186                    $sth_md5->execute($backup_id, $part_nr);
187    
188                    if (my $row = $sth_md5->fetchrow_hashref) {
189                            return if (
190                                    $row->{tar_size} >= $tar_size &&
191                                    $row->{size} == $size &&
192                                    $row->{md5} eq $md5 &&
193                                    $row->{items} == $items
194                            );
195                            print ", deleting invalid backup_parts $row->{id}";
196                            $dbh->do(qq{ delete from backup_parts where id = $row->{id} });
197                    }
198                    print ", inserting new";
199                    my $sth_insert = $dbh->prepare(qq{
200                            insert into backup_parts (
201                                    backup_id,
202                                    part_nr,
203                                    tar_size,
204                                    size,
205                                    md5,
206                                    items
207                            ) values (?,?,?,?,?,?)
208                    });
209    
210                    $sth_insert->execute($backup_id, $part_nr, $tar_size, $size, $md5, $items);
211                    $dbh->commit;
212            }
213    
214            my @tar_parts;
215    
216            if (-d "$tar_dir/$filename") {
217                    print ", multi-part";
218                    opendir(my $dir, "$tar_dir/$filename") || die "can't readdir $tar_dir/$filename: $!";
219                    @tar_parts = map { my $p = $_; $p =~ s#^#${filename}/#; $p } grep { !/^\./ && !/md5/ && -f "$tar_dir/$filename/$_" } readdir($dir);
220                    closedir($dir);
221            } else {
222                    push @tar_parts, "${filename}.tar.gz";
223            }
224    
225            print " [parts: ",join(", ", @tar_parts),"]" if ($opt{d});
226    
227            my $same = 1;
228            my @tar_files;
229    
230            my $backup_part;
231    
232            print " reading" if ($opt{d});
233    
234            foreach my $tarfilename (@tar_parts) {
235    
236                    print "\n\t- $tarfilename";
237    
238                    my $path = "$tar_dir/$tarfilename";
239    
240                    my $size = (stat( $path ))[7] || die "can't stat $path: $!";
241    
242                    if ($size > $Conf{MaxArchiveSize}) {
243                            print ", part bigger than media $size > $Conf{MaxArchiveSize}\n";
244                            return 0;
245                    }
246    
247                    print ", $size bytes";
248    
249    
250                    open(my $fh, "gzip -cd $path |") or die "can't open $path: $!";
251                    binmode($fh);
252                    my $tar = Archive::Tar::Streamed->new($fh);
253    
254                    my $tar_size_inarc = 0;
255                    my $items = 0;
256    
257                    while(my $entry = $tar->next) {
258                            push @tar_files, $entry->name;
259                            $items++;
260                            $tar_size_inarc += $entry->size;
261    
262                            if ($tar_size_inarc > $Conf{MaxArchiveFileSize}) {
263                                    print ", part $tarfilename is too big $tar_size_inarc > $Conf{MaxArchiveFileSize}\n";
264                                    return 0;
265                            }
266    
267                    }
268    
269                    close($fh);
270    
271                    print ", $items items";
272    
273                    if ($tar_size_inarc == 0 && $items == 0) {
274                            print ", EMPTY tar\n";
275    
276                            my $backup_id = get_backup_id($host, $share, $num);
277    
278                            my $sth_inc_deleted = $dbh->prepare(qq{
279                                    update backups set
280                                            inc_deleted = true
281                                    where id = ?
282                            });
283                            $sth_inc_deleted->execute($backup_id);
284    
285                            $dbh->commit;
286    
287                            return 1;
288                    }
289    
290                    my $tar_size = get_gzip_size( $path );
291    
292                    # real tar size is bigger because of padding    
293                    if ($tar_size_inarc > $tar_size) {
294                            print ", size of files in tar ($tar_size_inarc) bigger than whole tar ($tar_size)!\n";
295                            return 0;
296                    }
297    
298                    #
299                    # check if md5 exists, and if not, create one
300                    #
301    
302                    my $md5_path = $path;
303                    $md5_path =~ s/\.tar\.gz$/.md5/ || die "can't create md5 filename from $md5_path";
304                    if (! -e $md5_path || -z $md5_path) {
305                            print ", creating md5";
306                            system( $bin->{md5sum} . " $path > $md5_path") == 0 or die "can't create md5 $path: $!";
307                    } else {
308                            ## FIXME check if existing md5 is valid
309                    }
310    
311                    my $md5 = read_file( $md5_path ) || die "can't read md5sum file $md5_path: $!";
312                    $md5 =~ s#\s.*$##;
313    
314                    # extract part number from filename
315                    my $part_nr = 1;
316                    $part_nr = $1 if ($tarfilename =~ m#/(\d+)\.tar\.gz#);
317    
318                    #
319                    # finally, check if backup_parts table in database is valid
320                    #
321    
322                    check_part($host, $share, $num, $part_nr, $tar_size, $size, $md5, $items);
323            }
324    
325            # short-cut and exit;
326            return $same unless($same);
327    
328            @tar_files = sort @tar_files;
329            print "\n\t",($#tar_files + 1), " tar files";
330    
331            my $sth = $dbh->prepare(qq{
332                    SELECT path,type
333                    FROM files
334                    JOIN shares on shares.id = shareid
335                    JOIN hosts on hosts.id = shares.hostid
336                    WHERE hosts.name = ? and shares.name = ? and backupnum = ?
337            });
338            $sth->execute($host, $share, $num);
339            my @db_files;
340            while( my $row = $sth->fetchrow_hashref ) {
341    
342                    my $path = $row->{'path'} || die "no path?";
343                    $path =~ s#^/#./#;
344                    $path .= '/' if ($row->{'type'} == BPC_FTYPE_DIR);
345                    push @db_files, $path;
346            }
347    
348            print " ",($#db_files + 1), " database files, diff";
349    
350            @db_files = sort @db_files;
351    
352            if ($#tar_files != $#db_files) {
353                    $same = 0;
354                    print " NUMBER";
355            } else {
356                    my $diff = Algorithm::Diff->new(\@tar_files, \@db_files);
357                    while ( $diff->Next() ) {
358                            next if $diff->Same();
359                            $same = 0;
360                            print "< $_\n" for $diff->Items(1);
361                            print "> $_\n" for $diff->Items(2);
362                    }
363            }
364    
365            print " ",($same ? 'ok' : 'DIFFERENT'),
366                    ", dur: ",fmt_time(time() - $t), "\n";
367    
368            return $same;
369    }
370    
371    
372  #----- main  #----- main
373    
374  my $sth = $dbh->prepare( qq{  my $sth = $dbh->prepare( qq{
# Line 64  select Line 378  select
378          hosts.name as host,          hosts.name as host,
379          shares.name as share,          shares.name as share,
380          backups.num as num,          backups.num as num,
381            backups.date,
382          inc_size,          inc_size,
383          parts          parts,
384            count(backup_parts.backup_id) as backup_parts
385  from backups  from backups
386          join shares on backups.hostid = shares.hostid          join shares on backups.hostid = shares.hostid
387                  and shares.id = backups.shareid                  and shares.id = backups.shareid
388          join hosts on shares.hostid = hosts.id          join hosts on shares.hostid = hosts.id
389  where not inc_deleted          full outer join backup_parts on backups.id = backup_parts.backup_id
390    where not inc_deleted and backups.size > 0
391    group by backups.id, hosts.name, shares.name, backups.num, backups.date, inc_size, parts, backup_parts.backup_id
392  order by backups.date  order by backups.date
393    
394  } );  } );
395    
396  $sth->execute();  $sth->execute();
397    my $num_backups = $sth->rows;
398    my $curr_backup = 1;
399    
400  my $sth_inc_size = $dbh->prepare(qq{ update backups set inc_size = ?, parts = ? where id = ? });  if ($opt{h}) {
401  my $sth_inc_deleted = $dbh->prepare(qq{ update backups set inc_deleted = ? where id = ? });          warn "making increments just for host $opt{h}\n";
402    }
 %BackupPC::SearchLib::Conf = %Conf;  
403    
404  while (my $row = $sth->fetchrow_hashref) {  while (my $row = $sth->fetchrow_hashref) {
         my $tar_file = BackupPC::SearchLib::getGzipName($row->{'host'}, $row->{'share'}, $row->{'num'});  
   
         # this will return -1 if file doesn't exist  
         my $size = BackupPC::SearchLib::get_tgz_size_by_name($tar_file);  
405    
406          print curr_time, " ", $row->{'host'}, ":", $row->{'share'}, " #", $row->{'num'}, " -> $tar_file";          if ($opt{h} && $row->{host} ne $opt{h}) {
407                    warn "skipped $row->{host}\n" if ($debug);
408          my $t = time();                  next;
   
         # re-create archive?  
         if ($row->{'inc_size'} == -1 || $size == -1 || $row->{'inc_size'} != $size) {  
                 my $cmd = qq{rm -Rf $tar_dir/$tar_file && $tarIncCreate -h "$row->{'host'}" -s "$row->{'share'}" -n $row->{'num'} | gzip -9 > $tar_dir/$tar_file};  
                 print STDERR "## $cmd\n" if ($debug);  
   
                 system($cmd) == 0 or die "failed: $?";  
           
                 $size = (stat( "$tar_dir/$tar_file" ))[7];  
409          }          }
410    
411          if ($size > 45) {          $curr_backup++;
412    
413                  my $max_size = $Conf{'MaxArchiveSize'} || die "problem with MaxArchieSize parametar";          my $tar_file = BackupPC::SearchLib::getGzipName($row->{'host'}, $row->{'share'}, $row->{'num'});
                 $max_size *= 1024;      # convert to bytes  
   
                 my $parts = int( ($size + $max_size - 1) / $max_size );  
414    
415                  if (-d "$tar_dir/$tar_file" && $parts != $row->{'parts'}) {          # this will return -1 if file doesn't exist
416                          print " join";          my $size = BackupPC::SearchLib::get_tgz_size_by_name($tar_file);
417    
418                          my $in = my $out = "$tar_dir/$tar_file";          print "# size: $size backup.size: ", $row->{inc_size},"\n" if ($opt{d});
                         $out .= '.tmp';  
419    
420                          # FIXME I should really order parts manually!          if ( $row->{'inc_size'} != -1 && $size != -1 && $row->{'inc_size'} >= $size && $row->{parts} == $row->{backup_parts}) {
421                          system("cat $in/part* > $out && rm -Rf $in && mv $out $in") == 0 or die "can't join $in: $?";                  if ($check) {
422                            tar_check($row->{'host'}, $row->{'share'}, $row->{'num'}, $tar_file) && next;
423                    } else {
424                            next;
425                  }                  }
426            }
427    
428                  if ($size > $max_size && ! -d "$tar_dir/$tar_file") {          print curr_time, " creating $curr_backup/$num_backups ", $row->{host}, ":", $row->{share}, " #", $row->{num},
429                          print " split/$parts";                  " ", strftime('%Y-%m-%d', localtime($row->{date})), " -> $tar_file";
                         my $in = my $out = "$tar_dir/$tar_file";  
                         $out .= '.tmp';  
                         rename $in, $out || die "can't rename $in: $!";  
                         mkdir $in || die "can't mkdir $in: $!";  
   
                         my $suffix_len = length("$parts");  
                         system("split -d -b $max_size -a $suffix_len $out $in/part") == 0 or die "can't split $out: $?";  
                         unlink $out || die "can't unlink $out: $!";  
                 }  
430    
431                  $sth_inc_size->execute($size, $parts, $row->{'backup_id'});          my $t = time();
                 $sth_inc_deleted->execute(0, $row->{'backup_id'});  
432    
433                  printf(" %1.2f MB", ($size / 1024 / 1024));          # re-create archive?
434            my $cmd = qq{ $tarIncCreate -h "$row->{'host'}" -s "$row->{'share'}" -n $row->{'num'} -f };
435            print STDERR "## $cmd\n" if ($debug);
436    
437          } else {          if (system($cmd) != 0) {
438                  $sth_inc_deleted->execute(1, $row->{'backup_id'});                  print STDERR " FAILED";
                 unlink "$tar_dir/$tar_file" || die "can't delete $tar_dir/$tar_file: $!\n";  
                 print " EMPTY";  
439          }          }
440    
441          print ", dur: ",fmt_time(time() - $t), "\n";          print ", dur: ",fmt_time(time() - $t), "\n";
442    
443          $dbh->commit;          $dbh->commit;

Legend:
Removed from v.196  
changed lines
  Added in v.295

  ViewVC Help
Powered by ViewVC 1.1.26