/[BackupPC]/trunk/bin/BackupPC_tarIncCreate
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_tarIncCreate

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

revision 100 by dpavlin, Tue Aug 30 20:29:26 2005 UTC revision 234 by dpavlin, Tue Nov 8 20:24:45 2005 UTC
# Line 1  Line 1 
1  #!/usr/bin/perl  #!/usr/bin/perl -w
2  #============================================================= -*-perl-*-  #============================================================= -*-perl-*-
3  #  #
4  # BackupPC_tarCreate: create a tar archive of an existing dump  # BackupPC_tarIncCreate: create a tar archive of an existing incremental dump
5  # for restore on a client.  #
6  #  #
7  # DESCRIPTION  # DESCRIPTION
8  #    #  
9  #   Usage: BackupPC_tarCreate [options] files/directories...  #   Usage: BackupPC_tarIncCreate [options]
10  #  #
11  #   Flags:  #   Flags:
12  #     Required options:  #     Required options:
# Line 31  Line 31 
31  #  #
32  # AUTHOR  # AUTHOR
33  #   Craig Barratt  <cbarratt@users.sourceforge.net>  #   Craig Barratt  <cbarratt@users.sourceforge.net>
34    #   Ivan Klaric <iklaric@gmail.com>
35    #   Dobrica Pavlinusic <dpavlin@rot13.org>
36  #  #
37  # COPYRIGHT  # COPYRIGHT
38  #   Copyright (C) 2001-2003  Craig Barratt  #   Copyright (C) 2001-2003  Craig Barratt
# Line 68  use BackupPC::Attrib qw(:all); Line 70  use BackupPC::Attrib qw(:all);
70  use BackupPC::FileZIO;  use BackupPC::FileZIO;
71  use BackupPC::View;  use BackupPC::View;
72  use BackupPC::SearchLib;  use BackupPC::SearchLib;
73  use Data::Dumper;  use Time::HiRes qw/time/;
74    use POSIX qw/strftime/;
75    use File::Which;
76    use File::Path;
77    use Data::Dumper;       ### FIXME
78    
79  die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );  die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
80  my $TopDir = $bpc->TopDir();  my $TopDir = $bpc->TopDir();
81  my $BinDir = $bpc->BinDir();  my $BinDir = $bpc->BinDir();
82  my %Conf   = $bpc->Conf();  my %Conf   = $bpc->Conf();
83  my @DBCache;  %BackupPC::SearchLib::Conf = %Conf;
 my $db_done = 0;  
84  my %opts;  my %opts;
85    my $in_backup_increment;
86    
87    
88  if ( !getopts("th:n:p:r:s:b:w:", \%opts) || @ARGV < 1 ) {  if ( !getopts("th:n:p:r:s:b:w:vd", \%opts) ) {
89      print STDERR <<EOF;      print STDERR <<EOF;
90  usage: $0 [options] files/directories...  usage: $0 [options]
91    Required options:    Required options:
92       -h host         host from which the tar archive is created       -h host         host from which the tar archive is created
93       -n dumpNum      dump number from which the tar archive is created       -n dumpNum      dump number from which the tar archive is created
# Line 94  usage: $0 [options] files/directories... Line 101  usage: $0 [options] files/directories...
101       -p pathAdd      new path prefix       -p pathAdd      new path prefix
102       -b BLOCKS       BLOCKS x 512 bytes per record (default 20; same as tar)       -b BLOCKS       BLOCKS x 512 bytes per record (default 20; same as tar)
103       -w writeBufSz   write buffer size (default 1048576 = 1MB)       -w writeBufSz   write buffer size (default 1048576 = 1MB)
104         -v              verbose output
105         -d              debug output
106  EOF  EOF
107      exit(1);      exit(1);
108  }  }
# Line 110  if ( $opts{n} !~ /^(-?\d+)$/ ) { Line 119  if ( $opts{n} !~ /^(-?\d+)$/ ) {
119  }  }
120  my $Num = $opts{n};  my $Num = $opts{n};
121    
122    my $bin;
123    foreach my $c (qw/gzip md5sum tee/) {
124            $bin->{$c} = which($c) || die "$0 needs $c, install it\n";
125    }
126    
127  my @Backups = $bpc->BackupInfoRead($Host);  my @Backups = $bpc->BackupInfoRead($Host);
128  my $FileCnt = 0;  my $FileCnt = 0;
129  my $ByteCnt = 0;  my $ByteCnt = 0;
130  my $DirCnt = 0;  my $DirCnt = 0;
131  my $SpecialCnt = 0;  my $SpecialCnt = 0;
132  my $ErrorCnt = 0;  my $ErrorCnt = 0;
133    my $current_tar_size = 0;
134    
135  my $i;  my $i;
136  $Num = $Backups[@Backups + $Num]{num} if ( -@Backups <= $Num && $Num < 0 );  $Num = $Backups[@Backups + $Num]{num} if ( -@Backups <= $Num && $Num < 0 );
# Line 158  my(%HardLinkExtraFiles, @HardLinks); Line 173  my(%HardLinkExtraFiles, @HardLinks);
173  #  #
174  # Write out all the requested files/directories  # Write out all the requested files/directories
175  #  #
176  binmode(STDOUT);  
177  my $fh = *STDOUT;  my $max_file_size = $Conf{'MaxArchiveFileSize'} || die "problem with MaxArchiveFileSize parametar";
178  if ( $ShareName eq "*" ) {  $max_file_size *= 1024;
179      my $PathRemoveOrig = $PathRemove;  
180      my $PathAddOrig    = $PathAdd;  my $tar_dir = $Conf{InstallDir}.'/'.$Conf{GzipTempDir};
181      foreach $ShareName ( $view->shareList($Num) ) {  die "problem with $tar_dir, check GzipTempDir in configuration\n" unless (-d $tar_dir && -w $tar_dir);
182          #print(STDERR "Doing share ($ShareName)\n");  
183          $PathRemove = "/" if ( !defined($PathRemoveOrig) );  my $tar_file = BackupPC::SearchLib::getGzipName($Host, $ShareName, $Num) || die "can't getGzipName($Host, $ShareName, $Num)";
184          ($PathAdd = "/$ShareName/$PathAddOrig") =~ s{//+}{/}g;  
185          foreach my $dir ( @ARGV ) {  my $tar_path = $tar_dir . '/' . $tar_file . '.tmp';
186              archiveWrite($fh, $dir);  $tar_path =~ s#//#/#g;
187          }  
188          archiveWriteHardLinks($fh);  print STDERR "working dir: $tar_dir, max uncompressed size $max_file_size bytes, tar $tar_file\n" if ($opts{d});
189      }  
190    my $fh;
191    my $part = 0;
192    my $no_files = 0;
193    
194    sub new_tar_part {
195            if ($fh) {
196                    return if ($current_tar_size == 0);
197    
198                    print STDERR "# closing part $part\n" if ($opts{d});
199    
200                    # finish tar archive
201                    my $data = "\0" x ($tar_header_length * 2);
202                    TarWrite($fh, \$data);
203                    TarWrite($fh, undef);
204    
205                    close($fh) || die "can't close archive part $part: $!";
206            }
207    
208            $part++;
209    
210            # if this is first part, create directory
211    
212            if ($part == 1) {
213                    if (-d $tar_path) {
214                            print STDERR "# deleting existing $tar_path\n" if ($opts{d});
215                            rmtree($tar_path);
216                    }
217                    mkdir($tar_path) || die "can't create directory $tar_path: $!";
218            }
219    
220            my $file = $tar_path . '/' . $part;
221    
222            #
223            # create comprex pipe which will pass output through gzip
224            # for compression, create file on disk using tee
225            # and pipe same output to md5sum to create checksum
226            #
227    
228            my $cmd = '| ' . $bin->{'gzip'}   . ' ' . $Conf{GzipLevel} .      ' ' .
229                      '| ' . $bin->{'tee'}    . ' ' . $file . '.tar.gz' . ' ' .
230                      '| ' . $bin->{'md5sum'} . ' - > ' . $file . '.md5';
231    
232            print STDERR "## $cmd\n" if ($opts{d});
233    
234            open($fh, $cmd) or die "can't open $cmd: $!";
235            binmode($fh);
236            $current_tar_size = 0;
237    }
238    
239    new_tar_part();
240    
241    if (seedCache($Host, $ShareName, $Num)) {
242            archiveWrite($fh, '/');
243            archiveWriteHardLinks($fh);
244  } else {  } else {
245      foreach my $dir ( @ARGV ) {          print STDERR "NOTE: no files found for $Host:$ShareName, increment $Num\n" if ($opts{v});
246          archiveWrite($fh, $dir);          $no_files = 1;
     }  
     archiveWriteHardLinks($fh);  
247  }  }
248    
249  #  #
# Line 187  my $data = "\0" x ($tar_header_length * Line 254  my $data = "\0" x ($tar_header_length *
254  TarWrite($fh, \$data);  TarWrite($fh, \$data);
255  TarWrite($fh, undef);  TarWrite($fh, undef);
256    
257    if (! close($fh)) {
258            rmtree($tar_path);
259            die "can't close archive\n";
260    }
261    
262    # remove temporary files if there are no files
263    if ($no_files) {
264            rmtree($tar_path);
265    } elsif ($part == 1) {
266            warn "FIXME: if there is only one part move to parent directory and rename";
267    }
268    
269  #  #
270  # print out totals if requested  # print out totals if requested
271  #  #
# Line 199  if ( $ErrorCnt && !$FileCnt && !$DirCnt Line 278  if ( $ErrorCnt && !$FileCnt && !$DirCnt
278      # Got errors, with no files or directories; exit with non-zero      # Got errors, with no files or directories; exit with non-zero
279      # status      # status
280      #      #
281        cleanup();
282      exit(1);      exit(1);
283  }  }
284    
285  exit(0);  exit(0);
286    
287  ###########################################################################  ###########################################################################
# Line 266  sub TarWrite Line 347  sub TarWrite
347  {  {
348      my($fh, $dataRef) = @_;      my($fh, $dataRef) = @_;
349    
350    
351      if ( !defined($dataRef) ) {      if ( !defined($dataRef) ) {
352          #          #
353          # do flush by padding to a full $WriteBufSz          # do flush by padding to a full $WriteBufSz
# Line 273  sub TarWrite Line 355  sub TarWrite
355          my $data = "\0" x ($WriteBufSz - length($WriteBuf));          my $data = "\0" x ($WriteBufSz - length($WriteBuf));
356          $dataRef = \$data;          $dataRef = \$data;
357      }      }
358    
359        # poor man's tell :-)
360        $current_tar_size += length($$dataRef);
361    
362      if ( length($WriteBuf) + length($$dataRef) < $WriteBufSz ) {      if ( length($WriteBuf) + length($$dataRef) < $WriteBufSz ) {
363          #          #
364          # just buffer and return          # just buffer and return
# Line 390  sub TarWriteFileInfo Line 476  sub TarWriteFileInfo
476  }  }
477    
478  #  #
479  # returns 1 if a given directory has files somewhere under it  # seed cache of files in this increment
 # in a given dump of a given share  
480  #  #
481  sub checkSubDirs($$$$) {  sub seedCache($$$) {
482          my ($dir, $share, $host, $dumpNo) = @_;          my ($host, $share, $dumpNo) = @_;
483          my $ret;  
484          my $dsn = $Conf{SearchDSN};          my $dsn = $Conf{SearchDSN};
485          my $db_user = $Conf{SearchUser} || '';          my $db_user = $Conf{SearchUser} || '';
         my $search_sql;  
486                    
487          print(STDERR $dir);          print STDERR curr_time(), "getting files for $host:$share increment $dumpNo..." if ($opts{v});
488          # erase first dot          my $sql = q{
489          if (substr($dir, 0, 1) == '.')                  SELECT path,size
490          {                  FROM files
491                  $dir = substr($dir, 1, length($dir));                          JOIN shares on shares.id = shareid
492          }                          JOIN hosts on hosts.id = shares.hostid
493          # erase first slash                  WHERE hosts.name = ? and shares.name = ? and backupnum = ?
494          if (substr($dir, 0, 1) == '/')          };
495          {  
496                  $dir = substr($dir, 1, length($dir));                    my $dbh = DBI->connect($dsn, $db_user, "", { RaiseError => 1, AutoCommit => 1} );      
497          }          my $sth = $dbh->prepare($sql);  
498          # erase last slash          $sth->execute($host, $share, $dumpNo);
499          if (substr($dir, length($dir)-1, 1) == '/')          my $count = $sth->rows;
500          {          print STDERR " found $count items\n" if ($opts{v});
501                  $dir = substr($dir, 0, length($dir)-1);          while (my $row = $sth->fetchrow_arrayref) {
502    #print STDERR "+ ", $row->[0],"\n";
503                    $in_backup_increment->{ $row->[0] } = $row->[1];
504          }          }
505            
506            $sth->finish();
507            $dbh->disconnect();
508    
509          if (! $db_done)          return $count;
510          {  }
                 print STDERR "doing db...";  
                 my $search_sql = q{  
                                 SELECT hosts.name, shares.name, startfiles.name, COUNT(files.*) AS subfiles  
                                 FROM files startfiles  
                                          INNER JOIN shares ON (shares.id=startfiles.shareid)  
                                          INNER JOIN hosts ON  (hosts.id=shares.hostid)  
                                          INNER JOIN backups ON (  
                                                 backups.num=startfiles.backupnum AND  
                                                 backups.hostid=hosts.id AND backups.shareid=shares.id  
                                          )  
                                          LEFT JOIN files ON  (  
                                                 files.backupnum=startfiles.backupnum AND  
                                                 files.shareid=startfiles.shareid AND  
                                                 files.path LIKE startfiles.path || '/%' AND                
                                                 files.type<>startfiles.type AND                  
                                                 files.id <> startfiles.id  
                                         )                
                                 WHERE                            
                                         hosts.name=? AND                  
                                         shares.name=? AND            
                                         startfiles.type=? AND            
                                         startfiles.backupnum=?  
                                 GROUP BY hosts.name, shares.name, startfiles.name, startfiles.backupnum;  
                         };        
                 my $dbh = DBI->connect($dsn, $db_user, "", { RaiseError => 1, AutoCommit => 1} );        
                 my $sth = $dbh->prepare($search_sql);    
                 $sth->execute($host, $share, BPC_FTYPE_DIR, $dumpNo);  
                 print STDERR "done\n";  
                 while (my @r_data = $sth->fetchrow_array())  
                 {  
                         $DBCache[$r_data[0]][$r_data[1]][$r_data[2]] = 1;  
                 }  
                   
                 $sth->finish();  
511    
512                  $DBCache[$host][$share][$dir] = $ret;            #
513                  $dbh->disconnect();      # calculate overhad for one file in tar
514                  $db_done = 1;  #
515          }  sub tar_overhead($) {
516            my $name = shift || '';
517    
518            # header, padding of file and two null blocks at end
519            my $len = 4 * $tar_header_length;
520    
521          if ($DBCache[$host][$share][$dir] != undef && $DBCache[$host][$share][$dir] == 1)          # if filename is longer than 99 chars subtract blocks for
522          {          # long filename
523                  return 1;          if ( length($name) > 99 ) {
524                    $len += int( ( length($name) + $tar_header_length ) / $tar_header_length ) * $tar_header_length;
525          }          }
526          return 0;  
527            return $len;
528  }  }
529    
530  my $Attr;  my $Attr;
# Line 473  sub TarWriteFile Line 534  sub TarWriteFile
534  {  {
535      my($hdr, $fh, $tarPathOverride) = @_;      my($hdr, $fh, $tarPathOverride) = @_;
536    
   
537      my $tarPath = $hdr->{relPath};      my $tarPath = $hdr->{relPath};
538      $tarPath = $tarPathOverride if ( defined($tarPathOverride) );      $tarPath = $tarPathOverride if ( defined($tarPathOverride) );
539    
540      $tarPath =~ s{//+}{/}g;      $tarPath =~ s{//+}{/}g;
541    
542        #print STDERR "? $tarPath\n" if ($opts{d});
543        my $size = $in_backup_increment->{$tarPath};
544        return unless (defined($size));
545    
546        # is this file too large to fit into MaxArchiveFileSize?
547    
548        if ( ($current_tar_size + tar_overhead($tarPath) + $size) > $max_file_size ) {
549            print STDERR "# tar file $current_tar_size + $tar_header_length + $size > $max_file_size, splitting\n" if ($opts{d});
550            new_tar_part();
551        }
552    
553        print STDERR "A $tarPath [$size] tell: $current_tar_size\n" if ($opts{d});
554    
555      if ( defined($PathRemove)      if ( defined($PathRemove)
556              && substr($tarPath, 0, length($PathRemove)) eq $PathRemove ) {              && substr($tarPath, 0, length($PathRemove)) eq $PathRemove ) {
557          substr($tarPath, 0, length($PathRemove)) = $PathAdd;          substr($tarPath, 0, length($PathRemove)) = $PathAdd;
# Line 490  sub TarWriteFile Line 564  sub TarWriteFile
564          #          #
565          # Directory: just write the header          # Directory: just write the header
566          #          #
           
                   
567          $hdr->{name} .= "/" if ( $hdr->{name} !~ m{/$} );          $hdr->{name} .= "/" if ( $hdr->{name} !~ m{/$} );
568          # check if it has files under it in the database                  TarWriteFileInfo($fh, $hdr);
569                  if ( checkSubDirs($hdr->{path}, $ShareName, $Host, $Num) != 0 )          $DirCnt++;
                 {  
                 TarWriteFileInfo($fh, $hdr);  
                         $DirCnt++;  
                 }  
570      } elsif ( $hdr->{type} == BPC_FTYPE_FILE ) {      } elsif ( $hdr->{type} == BPC_FTYPE_FILE ) {
571          #          #
572          # Regular file: write the header and file          # Regular file: write the header and file
# Line 509  sub TarWriteFile Line 577  sub TarWriteFile
577              $ErrorCnt++;              $ErrorCnt++;
578              return;              return;
579          }          }
580          TarWriteFileInfo($fh, $hdr);          # do we need to split file?
581          my($data, $size);          if ($hdr->{size} < $max_file_size) {
582          while ( $f->read(\$data, $BufSize) > 0 ) {                  TarWriteFileInfo($fh, $hdr);
583              TarWrite($fh, \$data);                  my($data, $size);
584              $size += length($data);                  while ( $f->read(\$data, $BufSize) > 0 ) {
585          }                      TarWrite($fh, \$data);
586          $f->close;                      $size += length($data);
587          TarWritePad($fh, $size);                  }
588                    $f->close;
589                    TarWritePad($fh, $size);
590                  $FileCnt++;                  $FileCnt++;
591                  $ByteCnt += $size;                  $ByteCnt += $size;
592            } else {
593                    my $full_size = $hdr->{size};
594                    my $orig_name = $hdr->{name};
595                    my $max_part_size = $max_file_size - tar_overhead($hdr->{name});
596    
597                    my $parts = int(($full_size + $max_part_size - 1) / $max_part_size);
598                    print STDERR "# splitting $orig_name [$full_size bytes] into $parts parts\n" if ($opts{d});
599                    foreach my $subpart ( 1 .. $parts ) {
600                            new_tar_part();
601                            if ($subpart < $parts) {
602                                    $hdr->{size} = $max_part_size;
603                            } else {
604                                    $hdr->{size} = $full_size % $max_part_size;
605                            }
606                            $hdr->{name} = $orig_name . '/' . $subpart;
607                            print STDERR "## creating part $subpart ",$hdr->{name}, " [", $hdr->{size}," bytes]\n";
608    
609                            TarWriteFileInfo($fh, $hdr);
610                            my($data, $size);
611    if (0) {
612                            for ( 1 .. int($hdr->{size} / $BufSize) ) {
613                                    my $r_size = $f->read(\$data, $BufSize);
614                                    die "expected $BufSize bytes read, got $r_size bytes!" if ($r_size != $BufSize);
615                                    TarWrite($fh, \$data);
616                                    $size += length($data);
617                            }
618    }
619                            my $size_left = $hdr->{size} % $BufSize;
620                            my $r_size = $f->read(\$data, $size_left);
621                            die "expected $size_left bytes last read, got $r_size bytes!" if ($r_size != $size_left);
622    
623                            TarWrite($fh, \$data);
624                            $size += length($data);
625                            TarWritePad($fh, $size);
626                    }
627                    $f->close;
628                    $FileCnt++;
629                    $ByteCnt += $full_size;
630                    new_tar_part();
631            }
632      } elsif ( $hdr->{type} == BPC_FTYPE_HARDLINK ) {      } elsif ( $hdr->{type} == BPC_FTYPE_HARDLINK ) {
633          #          #
634          # Hardlink file: either write a hardlink or the complete file          # Hardlink file: either write a hardlink or the complete file
635                  # depending upon whether the linked-to file will be written          # depending upon whether the linked-to file will be written
636                  # to the archive.          # to the archive.
637          #          #
638                  # Start by reading the contents of the link.          # Start by reading the contents of the link.
639                  #          #
640          my $f = BackupPC::FileZIO->open($hdr->{fullPath}, 0, $hdr->{compress});          my $f = BackupPC::FileZIO->open($hdr->{fullPath}, 0, $hdr->{compress});
641          if ( !defined($f) ) {          if ( !defined($f) ) {
642              print(STDERR "Unable to open file $hdr->{fullPath}\n");              print(STDERR "Unable to open file $hdr->{fullPath}\n");
# Line 537  sub TarWriteFile Line 647  sub TarWriteFile
647          while ( $f->read(\$data, $BufSize) > 0 ) {          while ( $f->read(\$data, $BufSize) > 0 ) {
648              $hdr->{linkname} .= $data;              $hdr->{linkname} .= $data;
649          }          }
650                  $f->close;          $f->close;
651                  #          my $done = 0;
652                  # Check @ARGV and the list of hardlinked files we have explicity          my $name = $hdr->{linkname};
653                  # dumped to see if we have dumped this file or not          $name =~ s{^\./}{/};
654                  #          if ( $HardLinkExtraFiles{$name} ) {
655                  my $done = 0;              #
656                  my $name = $hdr->{linkname};              # Target file will be or was written, so just remember
657                  $name =~ s{^\./}{/};              # the hardlink so we can dump it later.
658                  if ( $HardLinkExtraFiles{$name} ) {              #
659                      $done = 1;              push(@HardLinks, $hdr);
660                  } else {              $SpecialCnt++;
661                      foreach my $arg ( @ARGV ) {          } else {
662                          $arg =~ s{^\./+}{/};              #
663                          $arg =~ s{/+$}{};              # Have to dump the original file.  Just call the top-level
664                          $done = 1 if ( $name eq $arg || $name =~ /^\Q$arg\// );              # routine, so that we save the hassle of dealing with
665                      }              # mangling, merging and attributes.
666                  }              #
667                  if ( $done ) {              $HardLinkExtraFiles{$hdr->{linkname}} = 1;
668                      #              archiveWrite($fh, $hdr->{linkname}, $hdr->{name});
669                      # Target file will be or was written, so just remember          }
                     # the hardlink so we can dump it later.  
                     #  
                     push(@HardLinks, $hdr);  
                     $SpecialCnt++;  
                 } else {  
                     #  
                     # Have to dump the original file.  Just call the top-level  
                     # routine, so that we save the hassle of dealing with  
                     # mangling, merging and attributes.  
                     #  
                     $HardLinkExtraFiles{$hdr->{linkname}} = 1;  
                     archiveWrite($fh, $hdr->{linkname}, $hdr->{name});  
                 }  
670      } elsif ( $hdr->{type} == BPC_FTYPE_SYMLINK ) {      } elsif ( $hdr->{type} == BPC_FTYPE_SYMLINK ) {
671          #          #
672          # Symbolic link: read the symbolic link contents into the header          # Symbolic link: read the symbolic link contents into the header
# Line 621  sub TarWriteFile Line 718  sub TarWriteFile
718          $ErrorCnt++;          $ErrorCnt++;
719      }      }
720  }  }
721    
722    my $t_fmt = '%Y-%m-%d %H:%M:%S';
723    sub curr_time {
724            return strftime($t_fmt,localtime());
725    }

Legend:
Removed from v.100  
changed lines
  Added in v.234

  ViewVC Help
Powered by ViewVC 1.1.26