X-Git-Url: http://git.rot13.org/?p=BackupPC.git;a=blobdiff_plain;f=lib%2FBackupPC%2FView.pm;h=b47080d2418581539acfabe71bafd5e9234e89ae;hp=f1a043aab63d4dc2cd0d54e7d669d2a80d855c3c;hb=HEAD;hpb=17dcbbebb871212f90b81bb97f8d1feb528bdc43 diff --git a/lib/BackupPC/View.pm b/lib/BackupPC/View.pm index f1a043a..b47080d 100644 --- a/lib/BackupPC/View.pm +++ b/lib/BackupPC/View.pm @@ -13,7 +13,7 @@ # Craig Barratt # # COPYRIGHT -# Copyright (C) 2002-2003 Craig Barratt +# Copyright (C) 2002-2009 Craig Barratt # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -31,7 +31,7 @@ # #======================================================================== # -# Version 2.1.0, released 20 Jun 2004. +# Version 3.2.0, released 31 Jul 2010. # # See http://backuppc.sourceforge.net. # @@ -46,24 +46,27 @@ use BackupPC::Lib; use BackupPC::Attrib qw(:all); use BackupPC::FileZIO; use Data::Dumper; +use Encode qw/from_to/; + +use Data::Dump qw(dump); sub new { - my($class, $bpc, $host, $backups) = @_; + my($class, $bpc, $host, $backups, $options) = @_; my $m = bless { - bpc => $bpc, # BackupPC::Lib object - host => $host, # host name - backups => $backups, # all backups for this host - num => -1, # backup number - idx => -1, # index into backups for backup - # we are viewing - dirPath => undef, # path to current directory - dirAttr => undef, # attributes of current directory + bpc => $bpc, # BackupPC::Lib object + host => $host, # host name + backups => $backups, # all backups for this host + num => -1, # backup number + idx => -1, # index into backups for backup + # we are viewing + dirPath => undef, # path to current directory + dirAttr => undef, # attributes of current directory + dirOpts => $options, # $options is a hash of file attributes we need: + # type, inode, or nlink. If set, these parameters + # are added to the returned hash. + # See BackupPC::Lib::dirRead(). }, $class; - for ( my $i = 0 ; $i < @{$m->{backups}} ; $i++ ) { - next if ( defined($m->{backups}[$i]{level}) ); - $m->{backups}[$i]{level} = $m->{backups}[$i]{type} eq "incr" ? 1 : 0; - } $m->{topDir} = $m->{bpc}->TopDir(); return $m; } @@ -92,24 +95,42 @@ sub dirCache $m->{share} = $share; $m->{dir} = $dir; + my $previous_increment_entry; + my $increment_backupNum = $backupNum; # used in loop + my @visit_backup_ids; + my $i; + for ( $i = $m->{idx} ; $i >= 0 ; $i-- ) { + + # we need two incremental backups to detect changes + next if $m->{backups}[$i]{level} >= $level && $#visit_backup_ids > 0; + + push @visit_backup_ids, $i; + + # for first full backup we need another full to detect changes + last if $m->{backups}[$i]{level} == 0 && $#visit_backup_ids > 0; + } + #warn "# visit_backup_ids = ",dump @visit_backup_ids; + # # merge backups, starting at the requested one, and working # backwards until we get to level 0. # $m->{mergeNums} = []; - for ( $i = $m->{idx} ; $level > 0 && $i >= 0 ; $i-- ) { +# for ( $i = $m->{idx} ; $level > 0 && $i >= 0 ; $i-- ) { + for my $i ( @visit_backup_ids ) { #print(STDERR "Do $i ($m->{backups}[$i]{noFill},$m->{backups}[$i]{level})\n"); # # skip backups with the same or higher level # - next if ( $m->{backups}[$i]{level} >= $level ); + #next if ( $m->{backups}[$i]{level} >= $level ); # ASA - visit_backup_ids $level = $m->{backups}[$i]{level}; $backupNum = $m->{backups}[$i]{num}; push(@{$m->{mergeNums}}, $backupNum); my $mangle = $m->{backups}[$i]{mangle}; my $compress = $m->{backups}[$i]{compress}; - my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; + my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; + my $legacyCharset = $m->{backups}[$i]{version} < 3.0; my $sharePathM; if ( $mangle ) { $sharePathM = $m->{bpc}->fileNameEltMangle($share) @@ -118,8 +139,20 @@ sub dirCache $sharePathM = $share . $dir; } $path .= $sharePathM; - #print(STDERR "Opening $path (share=$share)\n"); - if ( !opendir(DIR, $path) ) { + #print(STDERR "Opening $path (share=$share, mangle=$mangle)\n"); + + my $dirOpts = { %{$m->{dirOpts} || {} } }; + $dirOpts->{inode} = 1 if $m->{dirOpts}->{only_increment}; # ASA - detect unchanged +#warn "# dirOpts = ",dump $dirOpts; + my $attribOpts = { compress => $compress }; + if ( $legacyCharset ) { + $dirOpts->{charsetLegacy} + = $attribOpts->{charsetLegacy} + = $m->{bpc}->{Conf}{ClientCharsetLegacy} || "iso-8859-1"; + } + + my $dirInfo = $m->{bpc}->dirRead($path, $dirOpts); + if ( !defined($dirInfo) ) { if ( $i == $m->{idx} ) { # # Oops, directory doesn't exist. @@ -129,36 +162,55 @@ sub dirCache } next; } - my @dir = readdir(DIR); - closedir(DIR); my $attr; if ( $mangle ) { - $attr = BackupPC::Attrib->new({ compress => $compress }); - if ( -f $attr->fileName($path) && !$attr->read($path) ) { - $m->{error} = "Can't read attribute file in $path"; + $attr = BackupPC::Attrib->new($attribOpts); + if ( !$attr->read($path) ) { + $m->{error} = "Can't read attribute file in $path: " . $attr->errStr(); $attr = undef; } } - foreach my $file ( @dir ) { - $file = $1 if ( $file =~ /(.*)/ ); + foreach my $entry ( @$dirInfo ) { + my $file = $1 if ( $entry->{name} =~ /(.*)/s ); my $fileUM = $file; $fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle ); #print(STDERR "Doing $fileUM\n"); # # skip special files # - next if ( defined($m->{files}{$fileUM}) + next if ( 0 # defined($m->{files}{$fileUM}) # ASA - need fallthrough || $file eq ".." || $file eq "." + || $file eq "backupInfo" || $mangle && $file eq "attrib" ); - # - # skip directories in earlier backups (each backup always - # has the complete directory tree). - # - my @s = stat("$path/$file"); - next if ( $i < $m->{idx} && -d _ ); + + + # ASA - we only skip files when invoked with share name + if ( $m->{dirOpts}->{only_increment} && $share ) { + if ( defined $previous_increment_entry->{$fileUM} + && $previous_increment_entry->{$fileUM}->{inode} == $entry->{inode} ) { + #warn "# -- $backupNum $fileUM\n"; + $previous_increment_entry->{$fileUM}->{_hide}++; + next; + } elsif ( $backupNum < $increment_backupNum ) { + #warn "# << $backupNum $fileUM\n"; + next; + } else { + #warn "# ++ $backupNum $fileUM ",dump $entry; + $previous_increment_entry->{$fileUM} = $entry; + } + } else { + next if defined($m->{files}{$fileUM}); + } + if ( defined($attr) && defined(my $a = $attr->get($fileUM)) ) { $m->{files}{$fileUM} = $a; + $previous_increment_entry->{$fileUM}->{_dir}++ if $a->{type} == BPC_FTYPE_DIR; # ASA + # + # skip directories in earlier backups (each backup always + # has the complete directory tree). + # + next if ( $i < $m->{idx} && $a->{type} == BPC_FTYPE_DIR ); $attr->set($fileUM, undef); } else { # @@ -166,6 +218,13 @@ sub dirCache # is on. We have to stat the file and read compressed files # to determine their size. # + my $realPath = "$path/$file"; + + from_to($realPath, "utf8", $attribOpts->{charsetLegacy}) + if ( $attribOpts->{charsetLegacy} ne "" ); + + my @s = stat($realPath); + next if ( $i < $m->{idx} && -d _ ); $m->{files}{$fileUM} = { type => -d _ ? BPC_FTYPE_DIR : BPC_FTYPE_FILE, mode => $s[2], @@ -178,10 +237,10 @@ sub dirCache # # Compute the correct size by reading the whole file # - my $f = BackupPC::FileZIO->open("$path/$file", + my $f = BackupPC::FileZIO->open($realPath, 0, $compress); if ( !defined($f) ) { - $m->{error} = "Can't open $path/$file"; + $m->{error} = "Can't open $realPath"; } else { my($data, $size); while ( $f->read(\$data, 65636 * 8) > 0 ) { @@ -196,11 +255,16 @@ sub dirCache ($m->{files}{$fileUM}{sharePathM} = "$sharePathM/$file") =~ s{//+}{/}g; ($m->{files}{$fileUM}{fullPath} = "$path/$file") =~ s{//+}{/}g; + from_to($m->{files}{$fileUM}{fullPath}, "utf8", $attribOpts->{charsetLegacy}) + if ( $attribOpts->{charsetLegacy} ne "" ); $m->{files}{$fileUM}{backupNum} = $backupNum; $m->{files}{$fileUM}{compress} = $compress; - $m->{files}{$fileUM}{nlink} = $s[3]; - $m->{files}{$fileUM}{inode} = $s[1]; + $m->{files}{$fileUM}{nlink} = $entry->{nlink} + if ( $m->{dirOpts}{nlink} ); + $m->{files}{$fileUM}{inode} = $entry->{inode} + if ( $m->{dirOpts}{inode} ); } + # # Also include deleted files # @@ -214,6 +278,8 @@ sub dirCache $m->{files}{$fileUM}{relPath} = "$dir/$fileUM"; $m->{files}{$fileUM}{sharePathM} = "$sharePathM/$file"; $m->{files}{$fileUM}{fullPath} = "$path/$file"; + from_to($m->{files}{$fileUM}{fullPath}, "utf8", $attribOpts->{charsetLegacy}) + if ( $attribOpts->{charsetLegacy} ne "" ); $m->{files}{$fileUM}{backupNum} = $backupNum; $m->{files}{$fileUM}{compress} = $compress; $m->{files}{$fileUM}{nlink} = 0; @@ -221,6 +287,16 @@ sub dirCache } } } + + # ASA create increment + if ( $m->{dirOpts}->{only_increment} ) { + warn "# previous_increment_entry = ",dump $previous_increment_entry; + foreach my $file ( grep { exists $previous_increment_entry->{$_}->{_hide} && ! exists $previous_increment_entry->{$_}->{_dir} } keys %$previous_increment_entry ) { + delete $m->{files}{$file}; + #warn "OLD $file\n"; + } + } + # # Prune deleted files # @@ -248,10 +324,12 @@ sub shareList my @dir = readdir(DIR); closedir(DIR); foreach my $file ( @dir ) { - $file = $1 if ( $file =~ /(.*)/ ); + $file = $1 if ( $file =~ /(.*)/s ); next if ( $file eq "attrib" && $mangle || $file eq "." - || $file eq ".." ); + || $file eq ".." + || $file eq "backupInfo" + ); my $fileUM = $file; $fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle ); push(@shareList, $fileUM); @@ -286,7 +364,7 @@ sub fileAttrib my($m, $backupNum, $share, $path) = @_; #print(STDERR "fileAttrib($backupNum, $share, $path)\n"); - if ( $path =~ s{(.*)/+(.+)}{$1} ) { + if ( $path =~ s{(.*)/+(.+)}{$1}s ) { my $file = $2; $m->dirCache($backupNum, $share, $path); return $m->{files}{$file}; @@ -372,6 +450,7 @@ sub dirHistory my $mangle = $m->{backups}[$i]{mangle}; my $compress = $m->{backups}[$i]{compress}; my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; + my $legacyCharset = $m->{backups}[$i]{version} < 3.0; my $sharePathM; if ( $mangle ) { $sharePathM = $m->{bpc}->fileNameEltMangle($share) @@ -381,24 +460,32 @@ sub dirHistory } $path .= $sharePathM; #print(STDERR "Opening $path (share=$share)\n"); - if ( !opendir(DIR, $path) ) { + + my $dirOpts = { %{$m->{dirOpts} || {} } }; + my $attribOpts = { compress => $compress }; + if ( $legacyCharset ) { + $dirOpts->{charsetLegacy} + = $attribOpts->{charsetLegacy} + = $m->{bpc}->{Conf}{ClientCharsetLegacy} || "iso-8859-1"; + } + + my $dirInfo = $m->{bpc}->dirRead($path, $dirOpts); + if ( !defined($dirInfo) ) { # # Oops, directory doesn't exist. # next; } - my @dir = readdir(DIR); - closedir(DIR); my $attr; if ( $mangle ) { - $attr = BackupPC::Attrib->new({ compress => $compress }); - if ( -f $attr->fileName($path) && !$attr->read($path) ) { + $attr = BackupPC::Attrib->new($attribOpts); + if ( !$attr->read($path) ) { $m->{error} = "Can't read attribute file in $path"; $attr = undef; } } - foreach my $file ( @dir ) { - $file = $1 if ( $file =~ /(.*)/ ); + foreach my $entry ( @$dirInfo ) { + my $file = $1 if ( $entry->{name} =~ /(.*)/s ); my $fileUM = $file; $fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle ); #print(STDERR "Doing $fileUM\n"); @@ -409,7 +496,11 @@ sub dirHistory || $file eq "." || $mangle && $file eq "attrib" || defined($files->{$fileUM}[$i]) ); - my @s = stat("$path/$file"); + + my $realPath = "$path/$file"; + from_to($realPath, "utf8", $attribOpts->{charsetLegacy}) + if ( $attribOpts->{charsetLegacy} ne "" ); + my @s = stat($realPath); if ( defined($attr) && defined(my $a = $attr->get($fileUM)) ) { $files->{$fileUM}[$i] = $a; $attr->set($fileUM, undef); @@ -431,7 +522,7 @@ sub dirHistory # # Compute the correct size by reading the whole file # - my $f = BackupPC::FileZIO->open("$path/$file", + my $f = BackupPC::FileZIO->open("$realPath", 0, $compress); if ( !defined($f) ) { $m->{error} = "Can't open $path/$file"; @@ -451,10 +542,23 @@ sub dirHistory ($files->{$fileUM}[$i]{fullPath} = "$path/$file") =~ s{//+}{/}g; $files->{$fileUM}[$i]{backupNum} = $backupNum; $files->{$fileUM}[$i]{compress} = $compress; - $files->{$fileUM}[$i]{nlink} = $s[3]; - $files->{$fileUM}[$i]{inode} = $s[1]; + $files->{$fileUM}[$i]{nlink} = $entry->{nlink} + if ( $m->{dirOpts}{nlink} ); + $files->{$fileUM}[$i]{inode} = $entry->{inode} + if ( $m->{dirOpts}{inode} ); } + # + # Flag deleted files + # + if ( defined($attr) ) { + my $a = $attr->get; + foreach my $fileUM ( keys(%$a) ) { + next if ( $a->{$fileUM}{type} != BPC_FTYPE_DELETED ); + $files->{$fileUM}[$i]{type} = BPC_FTYPE_DELETED; + } + } + # # Merge old backups. Don't merge directories from old # backups because every backup has an accurate directory @@ -470,18 +574,19 @@ sub dirHistory $files->{$fileUM}[$i] = $files->{$fileUM}[$k]; } } + } - # - # Finally, remove deleted files - # - if ( defined($attr) ) { - my $a = $attr->get; - foreach my $fileUM ( keys(%$a) ) { - next if ( $a->{$fileUM}{type} != BPC_FTYPE_DELETED ); - $files->{$fileUM}[$i] = undef if ( defined($files->{$fileUM}) ); - } - } + # + # Remove deleted files + # + for ( $i = 0 ; $i < @{$m->{backups}} ; $i++ ) { + foreach my $fileUM ( keys(%$files) ) { + next if ( !defined($files->{$fileUM}[$i]) + || $files->{$fileUM}[$i]{type} != BPC_FTYPE_DELETED ); + $files->{$fileUM}[$i] = undef; + } } + #print STDERR "Returning:\n", Dumper($files); return $files; }