/[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 228 by dpavlin, Mon Oct 24 16:41:13 2005 UTC revision 295 by dpavlin, Thu Jan 26 10:28:43 2006 UTC
# Line 10  use BackupPC::Attrib qw/:all/; Line 10  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;  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  # cludge: minimum .tar.gz size  my %opt;
50  my $MIN_TAR_SIZE = 80;  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 27  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();
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 73  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    
146  sub tar_check($$$$) {  sub tar_check($$$$) {
147          my ($host,$share,$num,$filename) = @_;          my ($host,$share,$num,$filename) = @_;
148    
149          if ($debug) {          my $t = time();
150                  print STDERR " {{ CHECK: ${host}:${share}#${num} and $filename";          print curr_time, " check $host:$share#$num -> $filename";
151          } else {  
152                  print " check";          # 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          if (-d $filename) {          sub check_part {
177                  print STDERR ", joining";                  my ($host, $share, $num, $part_nr, $tar_size, $size, $md5, $items) = @_;
178                  tar_join($filename);                  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 STDERR ", opening" if ($debug);          print " [parts: ",join(", ", @tar_parts),"]" if ($opt{d});
         open(my $fh, "gzip -cd $filename |") or die "can't open $filename: $!";  
         binmode($fh);  
         my $tar = Archive::Tar::Streamed->new($fh);  
226    
227          print STDERR ", tar" if ($debug);          my $same = 1;
228          my @tar_files;          my @tar_files;
229          while(my $entry = $tar->next) {  
230                  push @tar_files, $entry->name;          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          }          }
         @tar_files = sort @tar_files;  
         print STDERR " ",($#tar_files + 1), " files" if ($debug);  
324    
325          print STDERR ", database" if ($debug);          # 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{          my $sth = $dbh->prepare(qq{
332                  SELECT path,type                  SELECT path,type
# Line 130  sub tar_check($$$$) { Line 345  sub tar_check($$$$) {
345                  push @db_files, $path;                  push @db_files, $path;
346          }          }
347    
348          print STDERR " ",($#db_files + 1), " files, diff" if ($debug);          print " ",($#db_files + 1), " database files, diff";
349    
350          @db_files = sort @db_files;          @db_files = sort @db_files;
351    
         my $same = 1;  
352          if ($#tar_files != $#db_files) {          if ($#tar_files != $#db_files) {
353                  $same = 0;                  $same = 0;
354                  print STDERR " NUMBER" if ($debug);                  print " NUMBER";
355          } else {          } else {
356                  my $diff = Algorithm::Diff->new(\@tar_files, \@db_files);                  my $diff = Algorithm::Diff->new(\@tar_files, \@db_files);
357                  while ( $diff->Next() ) {                  while ( $diff->Next() ) {
# Line 148  sub tar_check($$$$) { Line 362  sub tar_check($$$$) {
362                  }                  }
363          }          }
364    
365          print " ",($same ? 'ok' : 'DIFFERENT');          print " ",($same ? 'ok' : 'DIFFERENT'),
366          print STDERR " }} " if ($debug);                  ", dur: ",fmt_time(time() - $t), "\n";
367    
368          return $same;          return $same;
369  }  }
# Line 164  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    
 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 = ? });  
   
 %BackupPC::SearchLib::Conf = %Conf;  
   
396  $sth->execute();  $sth->execute();
397  my $num_backups = $sth->rows;  my $num_backups = $sth->rows;
398  my $curr_backup = 1;  my $curr_backup = 1;
399    
400  while (my $row = $sth->fetchrow_hashref) {  if ($opt{h}) {
401          my $tar_file = BackupPC::SearchLib::getGzipName($row->{'host'}, $row->{'share'}, $row->{'num'});          warn "making increments just for host $opt{h}\n";
402    }
         # 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: $?";  
403    
404                  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) {
405    
406                  $size = (stat( "$tar_dir/$tar_file" ))[7];          if ($opt{h} && $row->{host} ne $opt{h}) {
407                    warn "skipped $row->{host}\n" if ($debug);
408                    next;
409          }          }
410    
411          if ($size > $MIN_TAR_SIZE) {          $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  
414    
415                  # maximum file size on ISO image is 4Gb          # this will return -1 if file doesn't exist
416                  # this will require Linux kernel 2.6.8 or newer          my $size = BackupPC::SearchLib::get_tgz_size_by_name($tar_file);
                 my $max_iso_file_size = 2^32 - 2048;  
                 if ( $max_size > $max_iso_file_size ) {  
                         $max_size = $max_iso_file_size;  
                 }  
417    
418                  my $parts = int( ($size + $max_size - 1) / $max_size );          print "# size: $size backup.size: ", $row->{inc_size},"\n" if ($opt{d});
419    
420                  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}) {
421                          print " join";                  if ($check) {
422                          tar_join("$tar_dir/$tar_file");                          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("$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: $!";  
                 }  
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.228  
changed lines
  Added in v.295

  ViewVC Help
Powered by ViewVC 1.1.26