/[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 230 by dpavlin, Tue Oct 25 09:30:52 2005 UTC revision 312 by dpavlin, Sun Jan 29 15:54:10 2006 UTC
# Line 15  use File::Which; Line 15  use File::Which;
15  use Archive::Tar::Streamed;  use Archive::Tar::Streamed;
16  use Algorithm::Diff;  use Algorithm::Diff;
17  use Getopt::Std;  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";  my $bpc = BackupPC::Lib->new || die "can't create BackupPC::Lib";
73  my %Conf = $bpc->Conf();  my %Conf = $bpc->Conf();
# Line 22  my %Conf = $bpc->Conf(); Line 75  my %Conf = $bpc->Conf();
75  use BackupPC::SearchLib;  use BackupPC::SearchLib;
76  %BackupPC::SearchLib::Conf = %Conf;  %BackupPC::SearchLib::Conf = %Conf;
77    
 # cludge: minimum .tar.gz size  
 my $MIN_TAR_SIZE = 80;  
   
78  my $path = abs_path($0);  my $path = abs_path($0);
79  $path =~ s#/[^/]+$#/#;  $path =~ s#/[^/]+$#/#;
80  my $tarIncCreate = $path .= 'BackupPC_tarIncCreate';  my $tarIncCreate = $path .= 'BackupPC_tarIncCreate';
# Line 32  my $tarIncCreate = $path .= 'BackupPC_ta Line 82  my $tarIncCreate = $path .= 'BackupPC_ta
82  die "can't find $tarIncCreate: $!\n" unless (-x $tarIncCreate);  die "can't find $tarIncCreate: $!\n" unless (-x $tarIncCreate);
83    
84  my $bin;  my $bin;
85  foreach my $c (qw/gzip split/) {  foreach my $c (qw/gzip md5sum/) {
86          $bin->{$c} = which($c) || die "$0 needs $c, install it\n";          $bin->{$c} = which($c) || die "$0 needs $c, install it\n";
87  }  }
88    
 my %opt;  
 getopts("cd", \%opt );  
   
 my $debug = $opt{d};  
 my $check = $opt{c} && print STDERR "NOTICE: tar archive check forced\n";  
   
89  $|=1;  $|=1;
90    
91  my $start_t = time();  my $start_t = time();
# Line 72  sub curr_time { Line 116  sub curr_time {
116          return strftime($t_fmt,localtime());          return strftime($t_fmt,localtime());
117  }  }
118    
119  sub tar_join($) {  my $hsn_cache;
         my $filename = shift;  
120    
121          my $in = my $out = $filename;  sub get_backup_id($$$) {
122          $out .= '.tmp';          my ($host, $share, $num) = @_;
123    
124          # FIXME I should really order parts manually!          my $key = "$host $share $num";
125          system("cat $in/part* > $out && rm -Rf $in && mv $out $in") == 0 or die "can't join $in: $?";          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    sub backup_inc_deleted($) {
146            my $backup_id = shift;
147            my $sth_inc_deleted = $dbh->prepare(qq{
148                    update backups set
149                            inc_deleted = true
150                    where id = ?
151            });
152            $sth_inc_deleted->execute($backup_id);
153  }  }
154    
155  sub tar_check($$$$) {  sub tar_check($$$$) {
156          my ($host,$share,$num,$filename) = @_;          my ($host,$share,$num,$filename) = @_;
157    
158          if ($debug) {          my $t = time();
159                  print STDERR " {{ CHECK: ${host}:${share}#${num} and $filename";          print curr_time, " check $host:$share#$num -> $filename";
160          } else {  
161                  print " check";          # depending on expected returned value this is used like:
162            # my $uncompress_size = get_gzip_size('/full/path/to.gz');
163            # my ($compress_size, $uncompress_size) = get_gzip_size('/path.gz');
164            sub get_gzip_size($) {
165                    my $filename = shift;
166                    die "file $filename problem: $!" unless (-r $filename);
167                    open(my $gzip, $bin->{gzip}." -l $filename |") || die "can't gzip -l $filename: $!";
168                    my $line = <$gzip>;
169                    chomp($line);
170                    $line = <$gzip> if ($line =~ /^\s+compressed/);
171    
172                    my ($comp, $uncomp) = (0,0);
173    
174                    if ($line =~ m/^\s+(\d+)\s+(\d+)\s+\d+\.\d+/) {
175                            if (wantarray) {
176                                    return [ $1, $2 ];
177                            } else {
178                                    return $2;
179                            }
180                    } else {
181                            die "can't find size in line: $line";
182                    }
183          }          }
184    
185          if (-d $filename) {          sub check_part {
186                  print STDERR ", joining";                  my ($host, $share, $num, $part_nr, $tar_size, $size, $md5, $items) = @_;
187                  tar_join($filename);                  my $backup_id = get_backup_id($host, $share, $num);
188                    my $sth_md5 = $dbh->prepare(qq{
189                            select
190                                    id, tar_size, size, md5, items
191                            from backup_parts
192                            where backup_id = ? and part_nr = ?
193                    });
194    
195                    $sth_md5->execute($backup_id, $part_nr);
196    
197                    if (my $row = $sth_md5->fetchrow_hashref) {
198                            return if (
199                                    $row->{tar_size} >= $tar_size &&
200                                    $row->{size} == $size &&
201                                    $row->{md5} eq $md5 &&
202                                    $row->{items} == $items
203                            );
204                            print ", deleting invalid backup_parts $row->{id}";
205                            $dbh->do(qq{ delete from backup_parts where id = $row->{id} });
206                    }
207                    print ", inserting new";
208                    my $sth_insert = $dbh->prepare(qq{
209                            insert into backup_parts (
210                                    backup_id,
211                                    part_nr,
212                                    tar_size,
213                                    size,
214                                    md5,
215                                    items
216                            ) values (?,?,?,?,?,?)
217                    });
218    
219                    $sth_insert->execute($backup_id, $part_nr, $tar_size, $size, $md5, $items);
220                    $dbh->commit;
221          }          }
222    
223          print STDERR ", opening" if ($debug);          my @tar_parts;
         open(my $fh, "gzip -cd $filename |") or die "can't open $filename: $!";  
         binmode($fh);  
         my $tar = Archive::Tar::Streamed->new($fh);  
224    
225          print STDERR ", tar" if ($debug);          if (-d "$tar_dir/$filename") {
226                    print ", multi-part";
227                    opendir(my $dir, "$tar_dir/$filename") || die "can't readdir $tar_dir/$filename: $!";
228                    @tar_parts = map { my $p = $_; $p =~ s#^#${filename}/#; $p } grep { !/^\./ && !/md5/ && -f "$tar_dir/$filename/$_" } readdir($dir);
229                    closedir($dir);
230            } else {
231                    push @tar_parts, "${filename}.tar.gz";
232            }
233    
234            print " [parts: ",join(", ", @tar_parts),"]" if ($opt{d});
235    
236            my $same = 1;
237          my @tar_files;          my @tar_files;
238          while(my $entry = $tar->next) {  
239                  push @tar_files, $entry->name;          my $backup_part;
240    
241            print " reading" if ($opt{d});
242    
243            foreach my $tarfilename (@tar_parts) {
244    
245                    print "\n\t- $tarfilename";
246    
247                    my $path = "$tar_dir/$tarfilename";
248    
249                    my $size = (stat( $path ))[7] || die "can't stat $path: $!";
250    
251                    if ($size > $Conf{MaxArchiveSize}) {
252                            print ", part bigger than media $size > $Conf{MaxArchiveSize}\n";
253                            return 0;
254                    }
255    
256                    print ", $size bytes";
257    
258    
259                    open(my $fh, "gzip -cd $path |") or die "can't open $path: $!";
260                    binmode($fh);
261                    my $tar = Archive::Tar::Streamed->new($fh);
262    
263                    my $tar_size_inarc = 0;
264                    my $items = 0;
265    
266                    while(my $entry = $tar->next) {
267                            push @tar_files, $entry->name;
268                            $items++;
269                            $tar_size_inarc += $entry->size;
270    
271                            if ($tar_size_inarc > $Conf{MaxArchiveFileSize}) {
272                                    print ", part $tarfilename is too big $tar_size_inarc > $Conf{MaxArchiveFileSize}\n";
273                                    return 0;
274                            }
275    
276                    }
277    
278                    close($fh);
279    
280                    print ", $items items";
281    
282                    if ($tar_size_inarc == 0 && $items == 0) {
283                            print ", EMPTY tar\n";
284    
285                            my $backup_id = get_backup_id($host, $share, $num);
286                            backup_inc_deleted( $backup_id );
287    
288                            $dbh->commit;
289    
290                            return 1;
291                    }
292    
293                    my $tar_size = get_gzip_size( $path );
294    
295                    # real tar size is bigger because of padding    
296                    if ($tar_size_inarc > $tar_size) {
297                            print ", size of files in tar ($tar_size_inarc) bigger than whole tar ($tar_size)!\n";
298                            return 0;
299                    }
300    
301                    #
302                    # check if md5 exists, and if not, create one
303                    #
304    
305                    my $md5_path = $path;
306                    $md5_path =~ s/\.tar\.gz$/.md5/ || die "can't create md5 filename from $md5_path";
307                    if (! -e $md5_path || -z $md5_path) {
308                            print ", creating md5";
309                            system( $bin->{md5sum} . " $path > $md5_path") == 0 or die "can't create md5 $path: $!";
310                    } else {
311                            ## FIXME check if existing md5 is valid
312                    }
313    
314                    my $md5 = read_file( $md5_path ) || die "can't read md5sum file $md5_path: $!";
315                    $md5 =~ s#\s.*$##;
316    
317                    # extract part number from filename
318                    my $part_nr = 1;
319                    $part_nr = $1 if ($tarfilename =~ m#/(\d+)\.tar\.gz#);
320    
321                    #
322                    # finally, check if backup_parts table in database is valid
323                    #
324    
325                    check_part($host, $share, $num, $part_nr, $tar_size, $size, $md5, $items);
326          }          }
         @tar_files = sort @tar_files;  
         print STDERR " ",($#tar_files + 1), " files" if ($debug);  
327    
328          print STDERR ", database" if ($debug);          # short-cut and exit;
329            return $same unless($same);
330    
331            @tar_files = sort @tar_files;
332            print "\n\t",($#tar_files + 1), " tar files";
333    
334          my $sth = $dbh->prepare(qq{          my $sth = $dbh->prepare(qq{
335                  SELECT path,type                  SELECT path,type
# Line 129  sub tar_check($$$$) { Line 348  sub tar_check($$$$) {
348                  push @db_files, $path;                  push @db_files, $path;
349          }          }
350    
351          print STDERR " ",($#db_files + 1), " files, diff" if ($debug);          print " ",($#db_files + 1), " database files, diff";
352    
353          @db_files = sort @db_files;          @db_files = sort @db_files;
354    
         my $same = 1;  
355          if ($#tar_files != $#db_files) {          if ($#tar_files != $#db_files) {
356                  $same = 0;                  $same = 0;
357                  print STDERR " NUMBER" if ($debug);                  print " NUMBER";
358          } else {          } else {
359                  my $diff = Algorithm::Diff->new(\@tar_files, \@db_files);                  my $diff = Algorithm::Diff->new(\@tar_files, \@db_files);
360                  while ( $diff->Next() ) {                  while ( $diff->Next() ) {
# Line 147  sub tar_check($$$$) { Line 365  sub tar_check($$$$) {
365                  }                  }
366          }          }
367    
368          print " ",($same ? 'ok' : 'DIFFERENT');          print " ",($same ? 'ok' : 'DIFFERENT'),
369          print STDERR " }} " if ($debug);                  ", dur: ",fmt_time(time() - $t), "\n";
370    
371          return $same;          return $same;
372  }  }
# Line 163  select Line 381  select
381          hosts.name as host,          hosts.name as host,
382          shares.name as share,          shares.name as share,
383          backups.num as num,          backups.num as num,
384            backups.date,
385          inc_size,          inc_size,
386          parts          parts,
387            count(backup_parts.backup_id) as backup_parts
388  from backups  from backups
389          join shares on backups.hostid = shares.hostid          join shares on backups.hostid = shares.hostid
390                  and shares.id = backups.shareid                  and shares.id = backups.shareid
391          join hosts on shares.hostid = hosts.id          join hosts on shares.hostid = hosts.id
392  where not inc_deleted          full outer join backup_parts on backups.id = backup_parts.backup_id
393    where not inc_deleted and backups.size > 0
394    group by backups.id, hosts.name, shares.name, backups.num, backups.date, inc_size, parts, backup_parts.backup_id
395  order by backups.date  order by backups.date
396    
397  } );  } );
398    
 my $sth_inc_size = $dbh->prepare(qq{ update backups set inc_size = ?, parts = ? where id = ? });  
 my $sth_inc_deleted = $dbh->prepare(qq{ update backups set inc_deleted = ? where id = ? });  
   
   
399  $sth->execute();  $sth->execute();
400  my $num_backups = $sth->rows;  my $num_backups = $sth->rows;
401  my $curr_backup = 1;  my $curr_backup = 1;
402    
403  while (my $row = $sth->fetchrow_hashref) {  if ($opt{h}) {
404          my $tar_file = BackupPC::SearchLib::getGzipName($row->{'host'}, $row->{'share'}, $row->{'num'});          warn "making increments just for host $opt{h}\n";
405    }
         # this will return -1 if file doesn't exist  
         my $size = BackupPC::SearchLib::get_tgz_size_by_name($tar_file);  
   
         print curr_time, " $curr_backup/$num_backups ", $row->{'host'}, ":", $row->{'share'}, " #", $row->{'num'}, " -> $tar_file";  
         $curr_backup++;  
   
         my $t = time();  
   
         # re-create archive?  
         if ($row->{'inc_size'} == -1 || $size == -1 ||  
                 $row->{'inc_size'} != $size ||  
                 $check && ! tar_check($row->{'host'}, $row->{'share'}, $row->{'num'}, "$tar_dir/$tar_file")  
         ) {  
                 my $cmd = qq{rm -Rf $tar_dir/$tar_file && $tarIncCreate -h "$row->{'host'}" -s "$row->{'share'}" -n $row->{'num'} | $bin->{'gzip'} $Conf{GzipLevel} > ${tar_dir}/${tar_file}.tmp};  
                 print STDERR "## $cmd\n" if ($debug);  
   
                 system($cmd) == 0 or die "failed: $?";  
406    
407                  rename("${tar_dir}/${tar_file}.tmp", "$tar_dir/$tar_file") or die "can't rename $tar_dir/$tar_file: $!";  while (my $row = $sth->fetchrow_hashref) {
408    
409                  $size = (stat( "$tar_dir/$tar_file" ))[7];          if ($opt{h} && $row->{host} ne $opt{h}) {
410                    warn "skipped $row->{host}\n" if ($debug);
411                    next;
412          }          }
413    
414          if ($size > $MIN_TAR_SIZE) {          $curr_backup++;
   
                 my $max_size = $Conf{'MaxArchiveSize'} || die "problem with MaxArchiveSize parametar";  
                 $max_size *= 1024;      # convert to bytes  
   
                 my $max_file_size = $Conf{'MaxArchiveFileSize'} || die "problem with MaxArchiveFileSize parametar";  
                 $max_file_size *= 1024; # bytes  
415    
416                  if ($max_file_size > $max_size) {          my $tar_file = BackupPC::SearchLib::getGzipName($row->{'host'}, $row->{'share'}, $row->{'num'});
                         warn "MaxArchiveFileSize ($max_file_size) is bigger than MaxArchiveSize ($max_size)\n";  
                         $max_file_size = $max_size;  
                 }  
417    
418                  # maximum file size on ISO image is 4Gb          # this will return -1 if file doesn't exist
419                  # this will require Linux kernel 2.6.8 or newer          my $size = BackupPC::SearchLib::get_tgz_size_by_name($tar_file);
                 if ( $max_size > $max_file_size ) {  
                         $max_size = $max_file_size;  
                 }  
420    
421                  my $parts = int( ($size + $max_size - 1) / $max_size );          print "# size: $size backup.size: ", $row->{inc_size},"\n" if ($opt{d});
422    
423                  if (-d "$tar_dir/$tar_file" && $parts != $row->{'parts'}) {          if ( $row->{'inc_size'} != -1 && $size != -1 && $row->{'inc_size'} >= $size && $row->{parts} == $row->{backup_parts}) {
424                          print " join";                  if ($check) {
425                          tar_join("$tar_dir/$tar_file");                          tar_check($row->{'host'}, $row->{'share'}, $row->{'num'}, $tar_file) && next;
426                    } else {
427                            next;
428                  }                  }
429            }
430    
431                  if ($size > $max_size && ! -d "$tar_dir/$tar_file") {          print curr_time, " creating $curr_backup/$num_backups ", $row->{host}, ":", $row->{share}, " #", $row->{num},
432                          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("$bin->{'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: $!";  
                 }  
433    
434                  $sth_inc_size->execute($size, $parts, $row->{'backup_id'});          my $t = time();
                 $sth_inc_deleted->execute(0, $row->{'backup_id'});  
435    
436                  printf(" %1.2f MB", ($size / 1024 / 1024));          # re-create archive?
437            my $cmd = qq[ $tarIncCreate -h "$row->{host}" -s "$row->{share}" -n $row->{num} -f ];
438            print STDERR "## $cmd\n" if ($debug);
439    
440          } else {          if (system($cmd) != 0) {
441                  $sth_inc_deleted->execute(1, $row->{'backup_id'});                  print STDERR " FAILED, marking this backup deleted";
442                  unlink "$tar_dir/$tar_file" || die "can't delete $tar_dir/$tar_file: $!\n";                  backup_inc_deleted( $row->{backup_id} );
                 print " EMPTY";  
443          }          }
444    
445          print ", dur: ",fmt_time(time() - $t), "\n";          print ", dur: ",fmt_time(time() - $t), "\n";
446    
447          $dbh->commit;          $dbh->commit;

Legend:
Removed from v.230  
changed lines
  Added in v.312

  ViewVC Help
Powered by ViewVC 1.1.26