/[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 213 by dpavlin, Sun Oct 16 12:33:02 2005 UTC revision 371 by iklaric, Mon May 7 08:48:47 2007 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;  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 20  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 $debug = 0;  
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 62  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    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($$$$) {
156            my ($host,$share,$num,$filename) = @_;
157    
158            my $t = time();
159            print curr_time, " check $host:$share#$num -> $filename";
160    
161            # 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            sub check_part {
186                    my ($host, $share, $num, $part_nr, $tar_size, $size, $md5, $items) = @_;
187                    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            my @tar_parts;
224    
225            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;
238    
239            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            }
327    
328            # 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{
335                    SELECT path,type
336                    FROM files
337                    JOIN shares on shares.id = shareid
338                    JOIN hosts on hosts.id = shares.hostid
339                    WHERE hosts.name = ? and shares.name = ? and backupnum = ?
340            });
341            $sth->execute($host, $share, $num);
342            my @db_files;
343            while( my $row = $sth->fetchrow_hashref ) {
344    
345                    my $path = $row->{'path'} || die "no path?";
346                    $path =~ s#^/#./#;
347                    $path .= '/' if ($row->{'type'} == BPC_FTYPE_DIR);
348                    push @db_files, $path;
349            }
350    
351            print " ",($#db_files + 1), " database files, diff";
352    
353            @db_files = sort @db_files;
354    
355            if ($#tar_files != $#db_files) {
356                    $same = 0;
357                    print " NUMBER";
358            } else {
359                    my $diff = Algorithm::Diff->new(\@tar_files, \@db_files);
360                    while ( $diff->Next() ) {
361                            next if $diff->Same();
362                            $same = 0;
363                            print "< $_\n" for $diff->Items(1);
364                            print "> $_\n" for $diff->Items(2);
365                    }
366            }
367    
368            print " ",($same ? 'ok' : 'DIFFERENT'),
369                    ", dur: ",fmt_time(time() - $t), "\n";
370    
371            return $same;
372    }
373    
374    
375  #----- main  #----- main
376    
377  my $sth = $dbh->prepare( qq{  my $sth = $dbh->prepare( qq{
# Line 71  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 = ? });  
   
 %BackupPC::SearchLib::Conf = %Conf;  
   
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) {  
                 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 > 45) {          $curr_backup++;
   
                 my $max_size = $Conf{'MaxArchiveSize'} || die "problem with MaxArchieSize parametar";  
                 $max_size *= 1024;      # convert to bytes  
415    
416                  my $parts = int( ($size + $max_size - 1) / $max_size );          my $tar_file = BackupPC::SearchLib::getGzipName($row->{'host'}, $row->{'share'}, $row->{'num'});
417    
418                  if (-d "$tar_dir/$tar_file" && $parts != $row->{'parts'}) {          # this will return -1 if file doesn't exist
419                          print " join";          my $size = BackupPC::SearchLib::get_tgz_size_by_name($tar_file);
420    
421                          my $in = my $out = "$tar_dir/$tar_file";          print "# host: ".${row->{host}}.", share: ".$row->{'share'}.", backup_num:".$row->{num}." size: $size backup.size: ", $row->{inc_size},"\n" if ($opt{d});
                         $out .= '.tmp';  
422    
423                          # FIXME I should really order parts manually!          if ( $row->{'inc_size'} != -1 && $size != -1 && $row->{'inc_size'} >= $size && $row->{parts} == $row->{backup_parts}) {
424                          system("cat $in/part* > $out && rm -Rf $in && mv $out $in") == 0 or die "can't join $in: $?";                  if ($check) {
425                            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.213  
changed lines
  Added in v.371

  ViewVC Help
Powered by ViewVC 1.1.26