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

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

  ViewVC Help
Powered by ViewVC 1.1.26