View.pm (BackupPC-4.3.2) | : | View.pm (BackupPC-4.4.0) | ||
---|---|---|---|---|
skipping to change at line 33 | skipping to change at line 33 | |||
# This program is distributed in the hope that it will be useful, | # This program is distributed in the hope that it will be useful, | |||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
# GNU General Public License for more details. | # GNU General Public License for more details. | |||
# | # | |||
# You should have received a copy of the GNU General Public License | # You should have received a copy of the GNU General Public License | |||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
# | # | |||
#======================================================================== | #======================================================================== | |||
# | # | |||
# Version 4.3.2, released 17 Feb 2020. | # Version 4.4.0, released 20 Jun 2020. | |||
# | # | |||
# See http://backuppc.sourceforge.net. | # See http://backuppc.sourceforge.net. | |||
# | # | |||
#======================================================================== | #======================================================================== | |||
package BackupPC::View; | package BackupPC::View; | |||
use strict; | use strict; | |||
use File::Path; | use File::Path; | |||
use BackupPC::Lib; | use BackupPC::Lib; | |||
use BackupPC::XS qw( :all ); | use BackupPC::XS qw( :all ); | |||
use BackupPC::DirOps qw( :BPC_DT_ALL ); | use BackupPC::DirOps qw( :BPC_DT_ALL ); | |||
use Data::Dumper; | use Data::Dumper; | |||
use Encode qw/from_to/; | use Encode qw/from_to/; | |||
sub new | sub new | |||
{ | { | |||
my($class, $bpc, $host, $backups, $options) = @_; | my($class, $bpc, $host, $backups, $options) = @_; | |||
my $m = bless { | my $m = bless { | |||
bpc => $bpc, # BackupPC::Lib object | bpc => $bpc, # BackupPC::Lib object | |||
host => $host, # host name | host => $host, # host name | |||
backups => $backups, # all backups for this host | backups => $backups, # all backups for this host | |||
num => -1, # backup number | num => -1, # backup number | |||
idx => -1, # index into backups for backup | idx => -1, # index into backups for backup | |||
# we are viewing | # we are viewing | |||
dirPath => undef, # path to current directory | dirPath => undef, # path to current directory | |||
dirAttr => undef, # attributes of current directory | dirAttr => undef, # attributes of current directory | |||
dirOpts => $options, # $options is a hash of file attributes we need: | dirOpts => $options, # $options is a hash of file attributes we need: | |||
# type, inode, or nlink. If set, these paramete rs | # type, inode, or nlink. If set, these paramete rs | |||
# are added to the returned hash. | # are added to the returned hash. | |||
# See BackupPC::DirOps::dirRead(). | # See BackupPC::DirOps::dirRead(). | |||
error => [], | error => [], | |||
}, $class; | }, $class; | |||
$m->{topDir} = $m->{bpc}->TopDir(); | $m->{topDir} = $m->{bpc}->TopDir(); | |||
return $m; | return $m; | |||
} | } | |||
# | # | |||
# Check if a directory exists in the given backup. | # Check if a directory exists in the given backup. | |||
# This is only for >= 4.x backups. | # This is only for >= 4.x backups. | |||
# | # | |||
sub dirNotDeleted | sub dirNotDeleted | |||
skipping to change at line 97 | skipping to change at line 97 | |||
my $topPath = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | my $topPath = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | |||
while ( !$last ) { | while ( !$last ) { | |||
my($file, $p); | my($file, $p); | |||
if ( $dir =~ m{(.*)/(.*)} ) { | if ( $dir =~ m{(.*)/(.*)} ) { | |||
# | # | |||
# Normal subdirectory or top-level directory | # Normal subdirectory or top-level directory | |||
# | # | |||
$dir = $1; | $dir = $1; | |||
$file = $2; | $file = $2; | |||
$p = $topPath . $m->{bpc}->fileNameEltMangle($share); | $p = $topPath . $m->{bpc}->fileNameEltMangle($share); | |||
$p .= $m->{bpc}->fileNameMangle($dir) if ( length($dir) ); | $p .= $m->{bpc}->fileNameMangle($dir) if ( length($dir) ); | |||
} else { | } else { | |||
# | # | |||
# Check that the share exists in this backup; | # Check that the share exists in this backup; | |||
# if not then any subdirectory must be empty. | # if not then any subdirectory must be empty. | |||
# | # | |||
$p = $topPath; | $p = $topPath; | |||
$file = $share; | $file = $share; | |||
$last = 1; | $last = 1; | |||
} | } | |||
next if ( !-d $p || !defined(BackupPC::DirOps::dirContainsAttrib($m->{bp c}, $dir)) ); | next if ( !-d $p || !defined(BackupPC::DirOps::dirContainsAttrib($m->{bp c}, $dir)) ); | |||
skipping to change at line 125 | skipping to change at line 125 | |||
return 1; | return 1; | |||
} | } | |||
return 1; | return 1; | |||
} | } | |||
sub hardLinkGet | sub hardLinkGet | |||
{ | { | |||
my($m, $a, $i) = @_; | my($m, $a, $i) = @_; | |||
if ( !$m->{attribCache}[$i] ) { | if ( !$m->{attribCache}[$i] ) { | |||
$m->{attribCache}[$i] = BackupPC::XS::AttribCache::new($m->{host}, | $m->{attribCache}[$i] = | |||
$m->{backups}[$i]{num}, "", | BackupPC::XS::AttribCache::new($m->{host}, $m->{backups}[$i]{num}, "", | |||
$m->{backups}[$i]{compress}); | $m->{backups}[$i]{compress}); | |||
} | } | |||
my $newAttrib = $m->{attribCache}[$i]->getInode($a->{inode}); | my $newAttrib = $m->{attribCache}[$i]->getInode($a->{inode}); | |||
return $a if ( !$newAttrib ); | return $a if ( !$newAttrib ); | |||
$newAttrib = { %$newAttrib }; | $newAttrib = {%$newAttrib}; | |||
$newAttrib->{name} = $a->{name}; | $newAttrib->{name} = $a->{name}; | |||
return $newAttrib; | return $newAttrib; | |||
} | } | |||
sub dirCache | sub dirCache | |||
{ | { | |||
my($m, $backupNum, $share, $dir) = @_; | my($m, $backupNum, $share, $dir) = @_; | |||
my($i, $level); | my($i, $level); | |||
#print STDERR "dirCache($backupNum, $share, $dir)\n"; | #print STDERR "dirCache($backupNum, $share, $dir)\n"; | |||
$dir = "/$dir" if ( $dir !~ m{^/} ); | $dir = "/$dir" if ( $dir !~ m{^/} ); | |||
$dir =~ s{/+$}{}; | $dir =~ s{/+$}{}; | |||
return if ( $m->{num} == $backupNum | return if ( $m->{num} == $backupNum | |||
&& $m->{share} eq $share | && $m->{share} eq $share | |||
&& defined($m->{dir}) | && defined($m->{dir}) | |||
&& $m->{dir} eq $dir ); | && $m->{dir} eq $dir ); | |||
$m->backupNumCache($backupNum) if ( $m->{num} != $backupNum ); | $m->backupNumCache($backupNum) if ( $m->{num} != $backupNum ); | |||
return if ( $m->{idx} < 0 ); | return if ( $m->{idx} < 0 ); | |||
$m->{files} = {}; | $m->{files} = {}; | |||
$level = $m->{backups}[$m->{idx}]{level} + 1; | $level = $m->{backups}[$m->{idx}]{level} + 1; | |||
# | # | |||
# Remember the requested share and dir | # Remember the requested share and dir | |||
# | # | |||
$m->{share} = $share; | $m->{share} = $share; | |||
$m->{dir} = $dir; | $m->{dir} = $dir; | |||
if ( $m->{backups}[$m->{idx}]{version} < 4 ) { | if ( $m->{backups}[$m->{idx}]{version} < 4 ) { | |||
# | # | |||
# Pre-4.x backup: merge backups, starting at the requested one, | # Pre-4.x backup: merge backups, starting at the requested one, | |||
# and working backwards merging each backup of lower level | # and working backwards merging each backup of lower level | |||
# until we finish with a level 0 backup (ie: a full). | # until we finish with a level 0 backup (ie: a full). | |||
# | # | |||
# In pre-4.x backups the full directory tree exists for all | # In pre-4.x backups the full directory tree exists for all | |||
# backups, even incrementals. | # backups, even incrementals. | |||
# | # | |||
$m->{mergeNums} = []; | $m->{mergeNums} = []; | |||
for ( $i = $m->{idx} ; $level > 0 && $i >= 0 ; $i-- ) { | for ( $i = $m->{idx} ; $level > 0 && $i >= 0 ; $i-- ) { | |||
#print(STDERR "Do $i ($m->{backups}[$i]{noFill},$m->{backups}[$i]{le vel})\n"); | #print(STDERR "Do $i ($m->{backups}[$i]{noFill},$m->{backups}[$i]{le vel})\n"); | |||
# | # | |||
# skip backups with the same or higher level | # skip backups with the same or higher level | |||
# | # | |||
next if ( $m->{backups}[$i]{level} >= $level ); | next if ( $m->{backups}[$i]{level} >= $level ); | |||
$level = $m->{backups}[$i]{level}; | $level = $m->{backups}[$i]{level}; | |||
$backupNum = $m->{backups}[$i]{num}; | $backupNum = $m->{backups}[$i]{num}; | |||
push(@{$m->{mergeNums}}, $backupNum); | push(@{$m->{mergeNums}}, $backupNum); | |||
my $mangle = $m->{backups}[$i]{mangle}; | ||||
my $compress = $m->{backups}[$i]{compress}; | my $mangle = $m->{backups}[$i]{mangle}; | |||
my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | my $compress = $m->{backups}[$i]{compress}; | |||
my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | ||||
my $legacyCharset = $m->{backups}[$i]{version} < 3.0; | my $legacyCharset = $m->{backups}[$i]{version} < 3.0; | |||
my $sharePathM; | my $sharePathM; | |||
if ( $mangle ) { | if ( $mangle ) { | |||
$sharePathM = $m->{bpc}->fileNameEltMangle($share) | $sharePathM = $m->{bpc}->fileNameEltMangle($share) . $m->{bpc}-> | |||
. $m->{bpc}->fileNameMangle($dir); | fileNameMangle($dir); | |||
} else { | } else { | |||
$sharePathM = $share . $dir; | $sharePathM = $share . $dir; | |||
} | } | |||
$path .= $sharePathM; | $path .= $sharePathM; | |||
#print(STDERR "Opening $path (share=$share, mangle=$mangle)\n"); | #print(STDERR "Opening $path (share=$share, mangle=$mangle)\n"); | |||
my $dirOpts = { %{$m->{dirOpts} || {} } }; | my $dirOpts = {%{$m->{dirOpts} || {}}}; | |||
my $attribOpts = { }; | my $attribOpts = {}; | |||
if ( $legacyCharset ) { | if ( $legacyCharset ) { | |||
$dirOpts->{charsetLegacy} | $dirOpts->{charsetLegacy} = $attribOpts->{charsetLegacy} = | |||
= $attribOpts->{charsetLegacy} | $m->{bpc}->{Conf}{ClientCharsetLegacy} || "iso-8859-1"; | |||
= $m->{bpc}->{Conf}{ClientCharsetLegacy} || "iso-8859-1" | ||||
; | ||||
} | } | |||
my $attr; | my $attr; | |||
if ( $mangle ) { | if ( $mangle ) { | |||
# TODO: removed charset attribOpts - need to do at this level? | # TODO: removed charset attribOpts - need to do at this level? | |||
$attr = BackupPC::XS::Attrib::new($compress); | $attr = BackupPC::XS::Attrib::new($compress); | |||
if ( !$attr->read($path) ) { | if ( !$attr->read($path) ) { | |||
push(@{$m->{error}}, "Can't read attribute file in $path\n") ; | push(@{$m->{error}}, "Can't read attribute file in $path\n") ; | |||
$attr = undef; | $attr = undef; | |||
} | } | |||
} | } | |||
if ( $attr && length($attr->digest()) ) { | if ( $attr && length($attr->digest()) ) { | |||
# | # | |||
# new style V4 attributes - everything is in the attrib file | # new style V4 attributes - everything is in the attrib file | |||
# | # | |||
my $attrAll = $attr->get(); | my $idx = 0; | |||
foreach my $fileUM ( keys(%$attrAll) ) { | my $a; | |||
while ( 1 ) { | ||||
($a, $idx) = $attr->iterate($idx); | ||||
last if ( !defined($a) ); | ||||
my $fileUM = $a->{name}; | ||||
# | # | |||
# skip directories in earlier backups (in V3 each backup | # skip directories in earlier backups (in V3 each backup | |||
# always has the complete directory tree). | # always has the complete directory tree). | |||
# | # | |||
my $a = $attrAll->{$fileUM}; | ||||
next if ( $i < $m->{idx} && $a->{type} == BPC_FTYPE_DIR ); | next if ( $i < $m->{idx} && $a->{type} == BPC_FTYPE_DIR ); | |||
#print(STDERR "Adding $fileUM with type $a->{type}\n"); | #print(STDERR "Adding $fileUM with type $a->{type}\n"); | |||
if ( !$m->{files}{$fileUM} ) { | if ( !$m->{files}{$fileUM} ) { | |||
$m->{files}{$fileUM} = $a; | $m->{files}{$fileUM} = $a; | |||
$attr->delete($fileUM); | $attr->delete($fileUM); | |||
($m->{files}{$fileUM}{relPath} = "$dir/$fileUM") =~ s{//+}{/}g; | ($m->{files}{$fileUM}{relPath} = "$dir/$fileUM") =~ s{// +}{/}g; | |||
if ( length($a->{digest}) ) { | if ( length($a->{digest}) ) { | |||
$m->{files}{$fileUM}{fullPath} = $m->{bpc}->MD52Path | $m->{files}{$fileUM}{fullPath} = $m->{bpc}->MD52Path | |||
($a->{digest}, | ($a->{digest}, $compress); | |||
$compress); | ||||
} else { | } else { | |||
$m->{files}{$fileUM}{fullPath} = "/dev/null"; | $m->{files}{$fileUM}{fullPath} = "/dev/null"; | |||
} | } | |||
$m->{files}{$fileUM}{backupNum} = $backupNum; | $m->{files}{$fileUM}{backupNum} = $backupNum; | |||
$m->{files}{$fileUM}{compress} = $compress; | $m->{files}{$fileUM}{compress} = $compress; | |||
} | } | |||
} | } | |||
} else { | } else { | |||
my $dirInfo = BackupPC::DirOps::dirRead($m->{bpc}, $path, $dirOp ts); | my $dirInfo = BackupPC::DirOps::dirRead($m->{bpc}, $path, $dirOp ts); | |||
if ( !defined($dirInfo) ) { | if ( !defined($dirInfo) ) { | |||
if ( $i == $m->{idx} ) { | if ( $i == $m->{idx} ) { | |||
# | # | |||
# Oops, directory doesn't exist. | # Oops, directory doesn't exist. | |||
# | # | |||
$m->{files} = undef; | $m->{files} = undef; | |||
return; | return; | |||
} | } | |||
next; | next; | |||
} | } | |||
foreach my $entry ( @$dirInfo ) { | foreach my $entry ( @$dirInfo ) { | |||
my $file = $1 if ( $entry->{name} =~ /(.*)/s ); | my $file = $1 if ( $entry->{name} =~ /(.*)/s ); | |||
my $fileUM = $file; | my $fileUM = $file; | |||
$fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle ); | $fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle ); | |||
# | # | |||
# skip special files | # skip special files | |||
# | # | |||
next if ( defined($m->{files}{$fileUM}) | next | |||
|| $file eq ".." | if ( defined($m->{files}{$fileUM}) | |||
|| $file eq "." | || $file eq ".." | |||
|| $file eq "backupInfo" | || $file eq "." | |||
|| $file eq "refCnt" | || $file eq "backupInfo" | |||
|| $mangle && $file =~ /^attrib/ ); | || $file eq "refCnt" | |||
|| $mangle && $file =~ /^attrib/ ); | ||||
if ( defined($attr) && defined(my $a = $attr->get($fileUM)) ) { | if ( defined($attr) && defined(my $a = $attr->get($fileUM)) ) { | |||
# | # | |||
# skip directories in earlier backups (in V3 each backup | # skip directories in earlier backups (in V3 each backup | |||
# always has the complete directory tree). | # always has the complete directory tree). | |||
# | # | |||
next if ( $i < $m->{idx} && $a->{type} == BPC_FTYPE_DIR ); | next if ( $i < $m->{idx} && $a->{type} == BPC_FTYPE_DIR ); | |||
#print(STDERR "Adding $fileUM with type $a->{type}\n"); | #print(STDERR "Adding $fileUM with type $a->{type}\n"); | |||
$m->{files}{$fileUM} ||= $a; | $m->{files}{$fileUM} ||= $a; | |||
$attr->delete($fileUM); | $attr->delete($fileUM); | |||
} else { | } else { | |||
#print(STDERR "No attrib; determining attribs of $fileUM \n"); | #print(STDERR "No attrib; determining attribs of $fileUM \n"); | |||
# | # | |||
# Very expensive in the non-attribute case when compress eion | # Very expensive in the non-attribute case when compress eion | |||
# is on. We have to stat the file and read compressed f iles | # is on. We have to stat the file and read compressed f iles | |||
# to determine their size. | # to determine their size. | |||
# | # | |||
my $realPath = "$path/$file"; | my $realPath = "$path/$file"; | |||
from_to($realPath, "utf8", $attribOpts->{charsetLegacy}) | from_to($realPath, "utf8", $attribOpts->{charsetLegacy}) | |||
if ( $attribOpts->{charsetLegacy} ne "" ); | if ( $attribOpts->{charsetLegacy} ne "" ); | |||
my @s = stat($realPath); | my @s = stat($realPath); | |||
next if ( $i < $m->{idx} && -d _ ); | next if ( $i < $m->{idx} && -d _ ); | |||
$m->{files}{$fileUM} = { | $m->{files}{$fileUM} = { | |||
type => -d _ ? BPC_FTYPE_DIR : BPC_FTYPE_FILE, | type => -d _ ? BPC_FTYPE_DIR : BPC_FTYPE_FILE, | |||
mode => $s[2], | mode => $s[2], | |||
uid => $s[4], | uid => $s[4], | |||
gid => $s[5], | gid => $s[5], | |||
size => -f _ ? $s[7] : 0, | size => -f _ ? $s[7] : 0, | |||
mtime => $s[9], | mtime => $s[9], | |||
skipping to change at line 313 | skipping to change at line 316 | |||
} else { | } else { | |||
my($data, $size); | my($data, $size); | |||
while ( $f->read(\$data, 65636 * 8) > 0 ) { | while ( $f->read(\$data, 65636 * 8) > 0 ) { | |||
$size += length($data); | $size += length($data); | |||
} | } | |||
$f->close; | $f->close; | |||
$m->{files}{$fileUM}{size} = $size; | $m->{files}{$fileUM}{size} = $size; | |||
} | } | |||
} | } | |||
} | } | |||
($m->{files}{$fileUM}{relPath} = "$dir/$fileUM") =~ s{//+ | ($m->{files}{$fileUM}{relPath} = "$dir/$fileUM") =~ | |||
}{/}g; | s{//+}{/}g; | |||
($m->{files}{$fileUM}{sharePathM} = "$sharePathM/$file") | ($m->{files}{$fileUM}{sharePathM} = "$sharePathM/$file") =~ | |||
=~ s{//+} | s{//+}{/}g; | |||
{/}g; | ($m->{files}{$fileUM}{fullPath} = "$path/$file") =~ | |||
($m->{files}{$fileUM}{fullPath} = "$path/$file") =~ s{//+} | s{//+}{/}g; | |||
{/}g; | ||||
from_to($m->{files}{$fileUM}{fullPath}, "utf8", $attribOpts- >{charsetLegacy}) | from_to($m->{files}{$fileUM}{fullPath}, "utf8", $attribOpts- >{charsetLegacy}) | |||
if ( $attribOpts->{charsetLegacy} ne "" | if ( $attribOpts->{charsetLegacy} ne "" ); | |||
); | $m->{files}{$fileUM}{backupNum} = $backupNum; | |||
$m->{files}{$fileUM}{backupNum} = $backupNum; | $m->{files}{$fileUM}{compress} = $compress; | |||
$m->{files}{$fileUM}{compress} = $compress; | $m->{files}{$fileUM}{nlink} = $entry->{nlink} | |||
$m->{files}{$fileUM}{nlink} = $entry->{nlink} | if ( $m->{dirOpts}{nlink} ); | |||
if ( $m->{dirOpts}{n | $m->{files}{$fileUM}{inode} = $entry->{inode} | |||
link} ); | if ( $m->{dirOpts}{inode} ); | |||
$m->{files}{$fileUM}{inode} = $entry->{inode} | ||||
if ( $m->{dirOpts}{i | ||||
node} ); | ||||
} | } | |||
} | } | |||
# | # | |||
# Also include deleted files | # Also include deleted files | |||
# | # | |||
if ( defined($attr) ) { | if ( defined($attr) ) { | |||
my $a = $attr->get; | my $a = $attr->get; | |||
foreach my $fileUM ( keys(%$a) ) { | foreach my $fileUM ( keys(%$a) ) { | |||
next if ( $a->{$fileUM}{type} != BPC_FTYPE_DELETED ); | next if ( $a->{$fileUM}{type} != BPC_FTYPE_DELETED ); | |||
my $file = $fileUM; | my $file = $fileUM; | |||
$file = $m->{bpc}->fileNameMangle($fileUM) if ( $mangle ); | $file = $m->{bpc}->fileNameMangle ($fileUM) if ( $mangle ); | |||
$m->{files}{$fileUM} = $a->{$fileUM}; | $m->{files}{$fileUM} = $a->{$fileUM}; | |||
$m->{files}{$fileUM}{relPath} = "$dir/$fileUM"; | $m->{files}{$fileUM}{relPath} = "$dir/$fileUM"; | |||
$m->{files}{$fileUM}{sharePathM} = "$sharePathM/$file"; | $m->{files}{$fileUM}{sharePathM} = "$sharePathM/$file"; | |||
$m->{files}{$fileUM}{fullPath} = "$path/$file"; | $m->{files}{$fileUM}{fullPath} = "$path/$file"; | |||
from_to($m->{files}{$fileUM}{fullPath}, "utf8", $attribOpts- >{charsetLegacy}) | from_to($m->{files}{$fileUM}{fullPath}, "utf8", $attribOpts- >{charsetLegacy}) | |||
if ( $attribOpts->{charsetLegacy} ne "" | if ( $attribOpts->{charsetLegacy} ne "" ); | |||
); | $m->{files}{$fileUM}{backupNum} = $backupNum; | |||
$m->{files}{$fileUM}{backupNum} = $backupNum; | $m->{files}{$fileUM}{compress} = $compress; | |||
$m->{files}{$fileUM}{compress} = $compress; | $m->{files}{$fileUM}{nlink} = 0; | |||
$m->{files}{$fileUM}{nlink} = 0; | $m->{files}{$fileUM}{inode} = 0; | |||
$m->{files}{$fileUM}{inode} = 0; | ||||
} | } | |||
} | } | |||
# | # | |||
# Prune deleted files | # Prune deleted files | |||
# | # | |||
foreach my $file ( keys(%{$m->{files}}) ) { | foreach my $file ( keys(%{$m->{files}}) ) { | |||
next if ( $m->{files}{$file}{type} != BPC_FTYPE_DELETED ); | next if ( $m->{files}{$file}{type} != BPC_FTYPE_DELETED ); | |||
delete($m->{files}{$file}); | delete($m->{files}{$file}); | |||
} | } | |||
} | } | |||
skipping to change at line 381 | skipping to change at line 383 | |||
$oldestFilled = $i; | $oldestFilled = $i; | |||
last; | last; | |||
} | } | |||
$m->{mergeNums} = []; | $m->{mergeNums} = []; | |||
my $hardlinks = {}; | my $hardlinks = {}; | |||
for ( $i = $oldestFilled ; $i >= $m->{idx} ; $i-- ) { | for ( $i = $oldestFilled ; $i >= $m->{idx} ; $i-- ) { | |||
#print(STDERR "Do $i ($m->{backups}[$i]{noFill},$m->{backups}[$i]{le vel})\n"); | #print(STDERR "Do $i ($m->{backups}[$i]{noFill},$m->{backups}[$i]{le vel})\n"); | |||
$backupNum = $m->{backups}[$i]{num}; | $backupNum = $m->{backups}[$i]{num}; | |||
push(@{$m->{mergeNums}}, $backupNum); | push(@{$m->{mergeNums}}, $backupNum); | |||
my $mangle = $m->{backups}[$i]{mangle}; | my $mangle = $m->{backups}[$i]{mangle}; | |||
my $compress = $m->{backups}[$i]{compress}; | my $compress = $m->{backups}[$i]{compress}; | |||
my $topPath = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | my $topPath = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | |||
my $sharePathM = $m->{bpc}->fileNameEltMangle($share) | my $sharePathM = $m->{bpc}->fileNameEltMangle($share) . $m->{bpc}->f | |||
. $m->{bpc}->fileNameMangle($dir); | ileNameMangle($dir); | |||
my $path = $topPath . $sharePathM; | my $path = $topPath . $sharePathM; | |||
#print(STDERR "Opening $path (share=$share, mangle=$mangle)\n"); | #print(STDERR "Opening $path (share=$share, mangle=$mangle)\n"); | |||
my $dirOpts = { %{$m->{dirOpts} || {} } }; | my $dirOpts = {%{$m->{dirOpts} || {}}}; | |||
my $attribOpts = { compress => $compress }; | my $attribOpts = {compress => $compress}; | |||
if ( !-d $path && $i == $oldestFilled ) { | if ( !-d $path && $i == $oldestFilled ) { | |||
# | # | |||
# if this is the last backup then the directory is empty | # if this is the last backup then the directory is empty | |||
# | # | |||
#print(STDERR "Path $path isn't a directory\n"); | #print(STDERR "Path $path isn't a directory\n"); | |||
next; | next; | |||
} | } | |||
my $attr = BackupPC::XS::Attrib::new($compress); | my $attr = BackupPC::XS::Attrib::new($compress); | |||
my $attrAll; | my $attrAll; | |||
if ( !$attr->read($path, "attrib") ) { | if ( !$attr->read($path, "attrib") ) { | |||
if ( -f "$path/attrib" ) { | if ( -f "$path/attrib" ) { | |||
push(@{$m->{error}}, "Can't read attribute file in $path\n") ; | push(@{$m->{error}}, "Can't read attribute file in $path\n") ; | |||
} | } | |||
} else { | } else { | |||
#print(STDERR "Got attr\n"); | #print(STDERR "Got attr\n"); | |||
$attrAll = $attr->get(); | my $idx = 0; | |||
foreach my $fileUM ( keys(%$attrAll) ) { | my $a; | |||
my $a = $attrAll->{$fileUM}; | while ( 1 ) { | |||
($a, $idx) = $attr->iterate($idx); | ||||
last if ( !defined($a) ); | ||||
my $fileUM = $a->{name}; | ||||
if ( $a->{type} == BPC_FTYPE_DELETED ) { | if ( $a->{type} == BPC_FTYPE_DELETED ) { | |||
#print(STDERR "deleting $fileUM\n"); | #print(STDERR "deleting $fileUM\n"); | |||
delete($m->{files}{$fileUM}); | delete($m->{files}{$fileUM}); | |||
delete($hardlinks->{$fileUM}); | delete($hardlinks->{$fileUM}); | |||
next; | next; | |||
} | } | |||
if ( $a->{nlinks} > 0 ) { | if ( $a->{nlinks} > 0 ) { | |||
$a = $m->hardLinkGet($a, $i); | $a = $m->hardLinkGet($a, $i); | |||
$hardlinks->{$fileUM} = 1; | $hardlinks->{$fileUM} = 1; | |||
} | } | |||
$m->{files}{$fileUM} = $a; | $m->{files}{$fileUM} = $a; | |||
($m->{files}{$fileUM}{relPath} = "$dir/$fileUM") =~ s{// | ($m->{files}{$fileUM}{relPath} = "$dir/$fileUM") =~ s{//+}{/ | |||
+}{/}g; | }g; | |||
if ( length($a->{digest}) ) { | if ( length($a->{digest}) ) { | |||
$m->{files}{$fileUM}{fullPath} = $m->{bpc}->MD52Path($a- | $m->{files}{$fileUM}{fullPath} = $m->{bpc}->MD52Path($a- | |||
>{digest}, | >{digest}, $compress); | |||
$co | ||||
mpress); | ||||
} else { | } else { | |||
$m->{files}{$fileUM}{fullPath} = "/dev/null"; | $m->{files}{$fileUM}{fullPath} = "/dev/null"; | |||
} | } | |||
$m->{files}{$fileUM}{backupNum} = $backupNum; | $m->{files}{$fileUM}{backupNum} = $backupNum; | |||
$m->{files}{$fileUM}{compress} = $compress; | $m->{files}{$fileUM}{compress} = $compress; | |||
$m->{files}{$fileUM}{inode} = $a->{inode}; | $m->{files}{$fileUM}{inode} = $a->{inode}; | |||
} | } | |||
} | } | |||
# | # | |||
# Update any inode information specific to this backup | # Update any inode information specific to this backup | |||
# for hardlinks in this directory | # for hardlinks in this directory | |||
# | # | |||
foreach my $f ( keys(%$hardlinks) ) { | foreach my $f ( keys(%$hardlinks) ) { | |||
next if ( !$m->{files}{$f} || $m->{files}{$f}{backupNum} == $bac kupNum ); | next if ( !$m->{files}{$f} || $m->{files}{$f}{backupNum} == $bac kupNum ); | |||
my $a = $m->hardLinkGet($m->{files}{$f}, $i); | my $a = $m->hardLinkGet($m->{files}{$f}, $i); | |||
next if ( $a == $m->{files}{$f} ); | next if ( $a == $m->{files}{$f} ); | |||
$m->{files}{$f} = { %{$m->{files}{$f}}, %$a }; | $m->{files}{$f} = {%{$m->{files}{$f}}, %$a}; | |||
if ( length($a->{digest}) ) { | if ( length($a->{digest}) ) { | |||
$m->{files}{$f}{fullPath} = $m->{bpc}->MD52Path($a->{digest} , $compress); | $m->{files}{$f}{fullPath} = $m->{bpc}->MD52Path($a->{digest} , $compress); | |||
} | } | |||
$m->{files}{$f}{backupNum} = $backupNum; | $m->{files}{$f}{backupNum} = $backupNum; | |||
$m->{files}{$f}{inode} = $a->{inode}; | $m->{files}{$f}{inode} = $a->{inode}; | |||
} | } | |||
} | } | |||
} | } | |||
#print STDERR "Returning:\n", Dumper($m->{files}) if ( length($dir) ); | #print STDERR "Returning:\n", Dumper($m->{files}) if ( length($dir) ); | |||
} | } | |||
# | # | |||
# Return list of shares for this backup | # Return list of shares for this backup | |||
# | # | |||
sub shareList | sub shareList | |||
{ | { | |||
my($m, $backupNum) = @_; | my($m, $backupNum) = @_; | |||
my @shareList; | my @shareList; | |||
$m->backupNumCache($backupNum) if ( $m->{num} != $backupNum ); | $m->backupNumCache($backupNum) if ( $m->{num} != $backupNum ); | |||
return if ( $m->{idx} < 0 ); | return if ( $m->{idx} < 0 ); | |||
if ( $m->{backups}[$m->{idx}]{version} < 4 ) { | if ( $m->{backups}[$m->{idx}]{version} < 4 ) { | |||
my $mangle = $m->{backups}[$m->{idx}]{mangle}; | my $mangle = $m->{backups}[$m->{idx}]{mangle}; | |||
my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | |||
return if ( !opendir(DIR, $path) ); | return if ( !opendir(DIR, $path) ); | |||
my @dir = readdir(DIR); | my @dir = readdir(DIR); | |||
closedir(DIR); | closedir(DIR); | |||
foreach my $file ( @dir ) { | foreach my $file ( @dir ) { | |||
$file = $1 if ( $file =~ /(.*)/s ); | $file = $1 if ( $file =~ /(.*)/s ); | |||
next if ( $file =~ /^attrib/ && $mangle | next | |||
|| $file eq "." | if ( $file =~ /^attrib/ && $mangle | |||
|| $file eq ".." | || $file eq "." | |||
|| $file eq "backupInfo" | || $file eq ".." | |||
|| $file eq "inode" | || $file eq "backupInfo" | |||
|| $file eq "refCnt" | || $file eq "inode" | |||
); | || $file eq "refCnt" ); | |||
my $fileUM = $file; | my $fileUM = $file; | |||
$fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle ); | $fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle ); | |||
push(@shareList, $fileUM); | push(@shareList, $fileUM); | |||
} | } | |||
$m->{dir} = undef; | $m->{dir} = undef; | |||
} else { | } else { | |||
# | # | |||
# For 4.x we use a view with share "" to see the shares | # For 4.x we use a view with share "" to see the shares | |||
# for this backup | # for this backup | |||
# | # | |||
skipping to change at line 571 | skipping to change at line 576 | |||
sub backupList | sub backupList | |||
{ | { | |||
my($m, $share, $dir) = @_; | my($m, $share, $dir) = @_; | |||
my($i, @backupList); | my($i, @backupList); | |||
my $exist; | my $exist; | |||
$dir = "/$dir" if ( $dir !~ m{^/} ); | $dir = "/$dir" if ( $dir !~ m{^/} ); | |||
$dir =~ s{/+$}{}; | $dir =~ s{/+$}{}; | |||
for ( $i = @{$m->{backups}} - 1 ; $i >= 0 ; $i-- ) { | for ( $i = @{$m->{backups}} - 1 ; $i >= 0 ; $i-- ) { | |||
my $backupNum = $m->{backups}[$i]{num}; | my $backupNum = $m->{backups}[$i]{num}; | |||
my $mangle = $m->{backups}[$i]{mangle}; | my $mangle = $m->{backups}[$i]{mangle}; | |||
my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | |||
my $sharePathM; | my $sharePathM; | |||
if ( $mangle ) { | if ( $mangle ) { | |||
$sharePathM = $m->{bpc}->fileNameEltMangle($share) | $sharePathM = $m->{bpc}->fileNameEltMangle($share) . $m->{bpc}->file | |||
. $m->{bpc}->fileNameMangle($dir); | NameMangle($dir); | |||
} else { | } else { | |||
$sharePathM = $share . $dir; | $sharePathM = $share . $dir; | |||
} | } | |||
$path .= $sharePathM; | $path .= $sharePathM; | |||
if ( $m->{backups}[$i]{version} < 4 ) { | if ( $m->{backups}[$i]{version} < 4 ) { | |||
# | # | |||
# For 3.x backups it is easy - the full directory tree | # For 3.x backups it is easy - the full directory tree | |||
# exists for every backup | # exists for every backup | |||
# | # | |||
next if ( !-d $path ); | next if ( !-d $path ); | |||
skipping to change at line 623 | skipping to change at line 627 | |||
return @backupList; | return @backupList; | |||
} | } | |||
# | # | |||
# Return the history of all backups for a particular directory | # Return the history of all backups for a particular directory | |||
# | # | |||
sub dirHistory | sub dirHistory | |||
{ | { | |||
my($m, $share, $dir) = @_; | my($m, $share, $dir) = @_; | |||
my($i, $level); | my($i, $level); | |||
my $files = {}; | my $files = {}; | |||
my $hardlinks = {}; | my $hardlinks = {}; | |||
$dir = "/$dir" if ( $dir !~ m{^/} ); | $dir = "/$dir" if ( $dir !~ m{^/} ); | |||
$dir =~ s{/+$}{}; | $dir =~ s{/+$}{}; | |||
# | # | |||
# Handle any 3.x backups first. We merge backups, starting at | # Handle any 3.x backups first. We merge backups, starting at | |||
# the first one, and working forward. | # the first one, and working forward. | |||
# | # | |||
for ( $i = 0 ; $i < @{$m->{backups}} ; $i++ ) { | for ( $i = 0 ; $i < @{$m->{backups}} ; $i++ ) { | |||
$level = $m->{backups}[$i]{level}; | $level = $m->{backups}[$i]{level}; | |||
my $backupNum = $m->{backups}[$i]{num}; | ||||
my $mangle = $m->{backups}[$i]{mangle}; | my $backupNum = $m->{backups}[$i]{num}; | |||
my $compress = $m->{backups}[$i]{compress}; | my $mangle = $m->{backups}[$i]{mangle}; | |||
my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | my $compress = $m->{backups}[$i]{compress}; | |||
my $legacyCharset = $m->{backups}[$i]{version} < 3.0; | my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | |||
my $legacyCharset = $m->{backups}[$i]{version} < 3.0; | ||||
my $sharePathM; | my $sharePathM; | |||
last if ( $m->{backups}[$i]{version} >= 4 ); | last if ( $m->{backups}[$i]{version} >= 4 ); | |||
if ( $mangle ) { | if ( $mangle ) { | |||
$sharePathM = $m->{bpc}->fileNameEltMangle($share) | $sharePathM = $m->{bpc}->fileNameEltMangle($share) . $m->{bpc}->file | |||
. $m->{bpc}->fileNameMangle($dir); | NameMangle($dir); | |||
} else { | } else { | |||
$sharePathM = $share . $dir; | $sharePathM = $share . $dir; | |||
} | } | |||
$path .= $sharePathM; | $path .= $sharePathM; | |||
#print(STDERR "Opening $path (share=$share)\n"); | #print(STDERR "Opening $path (share=$share)\n"); | |||
my $dirOpts = { %{$m->{dirOpts} || {} } }; | my $dirOpts = {%{$m->{dirOpts} || {}}}; | |||
my $attribOpts = { compress => $compress }; | my $attribOpts = {compress => $compress}; | |||
if ( $legacyCharset ) { | if ( $legacyCharset ) { | |||
$dirOpts->{charsetLegacy} | $dirOpts->{charsetLegacy} = $attribOpts->{charsetLegacy} = | |||
= $attribOpts->{charsetLegacy} | $m->{bpc}->{Conf}{ClientCharsetLegacy} || "iso-8859-1"; | |||
= $m->{bpc}->{Conf}{ClientCharsetLegacy} || "iso-8859-1"; | ||||
} | } | |||
my $dirInfo = BackupPC::DirOps::dirRead($m->{bpc}, $path, $dirOpts); | my $dirInfo = BackupPC::DirOps::dirRead($m->{bpc}, $path, $dirOpts); | |||
if ( !defined($dirInfo) ) { | if ( !defined($dirInfo) ) { | |||
# | # | |||
# Oops, directory doesn't exist. | # Oops, directory doesn't exist. | |||
# | # | |||
next; | next; | |||
} | } | |||
my $attr; | my $attr; | |||
if ( $mangle ) { | if ( $mangle ) { | |||
$attr = BackupPC::XS::Attrib::new($compress); | $attr = BackupPC::XS::Attrib::new($compress); | |||
if ( !$attr->read($path) ) { | if ( !$attr->read($path) ) { | |||
push(@{$m->{error}}, "Can't read attribute file in $path"); | push(@{$m->{error}}, "Can't read attribute file in $path"); | |||
$attr = undef; | $attr = undef; | |||
} | } | |||
} | } | |||
if ( $attr && length($attr->digest()) ) { | if ( $attr && length($attr->digest()) ) { | |||
# | # | |||
# new style V4 attributes - everything is in the attrib file | # new style V4 attributes - everything is in the attrib file | |||
# | # | |||
my $attrAll = $attr->get(); | my $idx = 0; | |||
foreach my $fileUM ( keys(%$attrAll) ) { | my $a; | |||
my $a = $attrAll->{$fileUM}; | while ( 1 ) { | |||
$files->{$fileUM}[$i] = $a; | ($a, $idx) = $attr->iterate($idx); | |||
last if ( !defined($a) ); | ||||
my $fileUM = $a->{name}; | ||||
$files->{$fileUM}[$i] = $a; | ||||
$attr->delete($fileUM); | $attr->delete($fileUM); | |||
($files->{$fileUM}[$i]{relPath} = "$dir/$fileUM") =~ s{//+}{ /}g; | ($files->{$fileUM}[$i]{relPath} = "$dir/$fileUM") =~ s{//+}{/}g; | |||
if ( length($a->{digest}) ) { | if ( length($a->{digest}) ) { | |||
$files->{$fileUM}[$i]{fullPath} = $m->{bpc}->MD52Path($a->{d | $files->{$fileUM}[$i]{fullPath} = $m->{bpc}->MD52Path($a->{d | |||
igest}, | igest}, $compress); | |||
$compre | ||||
ss); | ||||
} else { | } else { | |||
$files->{$fileUM}[$i]{fullPath} = "/dev/null"; | $files->{$fileUM}[$i]{fullPath} = "/dev/null"; | |||
} | } | |||
$files->{$fileUM}[$i]{backupNum} = $backupNum; | $files->{$fileUM}[$i]{backupNum} = $backupNum; | |||
$files->{$fileUM}[$i]{compress} = $compress; | $files->{$fileUM}[$i]{compress} = $compress; | |||
} | } | |||
} else { | } else { | |||
foreach my $entry ( @$dirInfo ) { | foreach my $entry ( @$dirInfo ) { | |||
my $file = $1 if ( $entry->{name} =~ /(.*)/s ); | my $file = $1 if ( $entry->{name} =~ /(.*)/s ); | |||
my $fileUM = $file; | my $fileUM = $file; | |||
$fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle ); | $fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle ); | |||
#print(STDERR "Doing $fileUM\n"); | #print(STDERR "Doing $fileUM\n"); | |||
# | # | |||
# skip special files | # skip special files | |||
# | # | |||
next if ( $file eq ".." | next | |||
|| $file eq "." | if ( $file eq ".." | |||
|| $mangle && $file =~ /^attrib/ | || $file eq "." | |||
|| defined($files->{$fileUM}[$i]) ); | || $mangle && $file =~ /^attrib/ | |||
|| defined($files->{$fileUM}[$i]) ); | ||||
my $realPath = "$path/$file"; | my $realPath = "$path/$file"; | |||
from_to($realPath, "utf8", $attribOpts->{charsetLegacy}) | from_to($realPath, "utf8", $attribOpts->{charsetLegacy}) | |||
if ( $attribOpts->{charsetLegacy} ne "" ); | if ( $attribOpts->{charsetLegacy} ne "" ); | |||
my @s = stat($realPath); | my @s = stat($realPath); | |||
if ( defined($attr) && defined(my $a = $attr->get($fileUM)) ) { | if ( defined($attr) && defined(my $a = $attr->get($fileUM)) ) { | |||
$files->{$fileUM}[$i] = $a; | $files->{$fileUM}[$i] = $a; | |||
$attr->delete($fileUM); | $attr->delete($fileUM); | |||
} else { | } else { | |||
# | # | |||
# Very expensive in the non-attribute case when compresseion | # Very expensive in the non-attribute case when compresseion | |||
# is on. We have to stat the file and read compressed files | # is on. We have to stat the file and read compressed files | |||
# to determine their size. | # to determine their size. | |||
# | # | |||
skipping to change at line 747 | skipping to change at line 753 | |||
} else { | } else { | |||
my($data, $size); | my($data, $size); | |||
while ( $f->read(\$data, 65636 * 8) > 0 ) { | while ( $f->read(\$data, 65636 * 8) > 0 ) { | |||
$size += length($data); | $size += length($data); | |||
} | } | |||
$f->close; | $f->close; | |||
$files->{$fileUM}[$i]{size} = $size; | $files->{$fileUM}[$i]{size} = $size; | |||
} | } | |||
} | } | |||
} | } | |||
($files->{$fileUM}[$i]{relPath} = "$dir/$fileUM") =~ s{//+}{/ | ($files->{$fileUM}[$i]{relPath} = "$dir/$fileUM") =~ s{/ | |||
}g; | /+}{/}g; | |||
($files->{$fileUM}[$i]{sharePathM} = "$sharePathM/$file") | ($files->{$fileUM}[$i]{sharePathM} = "$sharePathM/$file") =~ s{/ | |||
=~ s{//+}{/} | /+}{/}g; | |||
g; | ($files->{$fileUM}[$i]{fullPath} = "$path/$file") =~ s{/ | |||
($files->{$fileUM}[$i]{fullPath} = "$path/$file") =~ s{//+}{/} | /+}{/}g; | |||
g; | $files->{$fileUM}[$i]{backupNum} = $backupNum; | |||
$files->{$fileUM}[$i]{backupNum} = $backupNum; | $files->{$fileUM}[$i]{compress} = $compress; | |||
$files->{$fileUM}[$i]{compress} = $compress; | $files->{$fileUM}[$i]{nlink} = $entry->{nlink} | |||
$files->{$fileUM}[$i]{nlink} = $entry->{nlink} | if ( $m->{dirOpts}{nlink} ); | |||
if ( $m->{dirOpts}{nlink | $files->{$fileUM}[$i]{inode} = $entry->{inode} | |||
} ); | if ( $m->{dirOpts}{inode} ); | |||
$files->{$fileUM}[$i]{inode} = $entry->{inode} | } | |||
if ( $m->{dirOpts}{inode | } | |||
} ); | # | |||
} | # Flag deleted files | |||
} | # | |||
# | if ( defined($attr) ) { | |||
# Flag deleted files | my $a = $attr->get; | |||
# | foreach my $fileUM ( keys(%$a) ) { | |||
if ( defined($attr) ) { | next if ( $a->{$fileUM}{type} != BPC_FTYPE_DELETED ); | |||
my $a = $attr->get; | $files->{$fileUM}[$i]{type} = BPC_FTYPE_DELETED; | |||
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 | ||||
# | # tree. | |||
# Merge old backups. Don't merge directories from old | # | |||
# backups because every backup has an accurate directory | for ( my $k = $i - 1 ; $level > 0 && $k >= 0 ; $k-- ) { | |||
# tree. | next if ( $m->{backups}[$k]{level} >= $level ); | |||
# | $level = $m->{backups}[$k]{level}; | |||
for ( my $k = $i - 1 ; $level > 0 && $k >= 0 ; $k-- ) { | foreach my $fileUM ( keys(%$files) ) { | |||
next if ( $m->{backups}[$k]{level} >= $level ); | next | |||
$level = $m->{backups}[$k]{level}; | if ( !defined($files->{$fileUM}[$k]) | |||
foreach my $fileUM ( keys(%$files) ) { | || defined($files->{$fileUM}[$i]) | |||
next if ( !defined($files->{$fileUM}[$k]) | || $files->{$fileUM}[$k]{type} == BPC_FTYPE_DIR ); | |||
|| defined($files->{$fileUM}[$i]) | $files->{$fileUM}[$i] = $files->{$fileUM}[$k]; | |||
|| $files->{$fileUM}[$k]{type} == BPC_FTYPE_DIR ); | } | |||
$files->{$fileUM}[$i] = $files->{$fileUM}[$k]; | } | |||
} | ||||
} | ||||
} | } | |||
# | # | |||
# Remove deleted files | # Remove deleted files | |||
# | # | |||
for ( $i = 0 ; $i < @{$m->{backups}} ; $i++ ) { | for ( $i = 0 ; $i < @{$m->{backups}} ; $i++ ) { | |||
last if ( $m->{backups}[$i]{version} >= 4 ); | last if ( $m->{backups}[$i]{version} >= 4 ); | |||
foreach my $fileUM ( keys(%$files) ) { | foreach my $fileUM ( keys(%$files) ) { | |||
next if ( !defined($files->{$fileUM}[$i]) | next if ( !defined($files->{$fileUM}[$i]) || $files->{$fileUM}[$i]{t | |||
|| $files->{$fileUM}[$i]{type} != BPC_FTYPE_DELETED ); | ype} != BPC_FTYPE_DELETED ); | |||
$files->{$fileUM}[$i] = undef; | $files->{$fileUM}[$i] = undef; | |||
} | } | |||
} | } | |||
# | # | |||
# Now handle any >= 4.x backups. We merge backups, starting at | # Now handle any >= 4.x backups. We merge backups, starting at | |||
# the last and work backwards. | # the last and work backwards. | |||
# | # | |||
# In 4.x+ backups the full directory tree only exists in the | # In 4.x+ backups the full directory tree only exists in the | |||
# last backup. Prior backups will only have directories where | # last backup. Prior backups will only have directories where | |||
skipping to change at line 817 | skipping to change at line 822 | |||
for ( $i = @{$m->{backups}} - 1 ; $i >= 0 ; $i-- ) { | for ( $i = @{$m->{backups}} - 1 ; $i >= 0 ; $i-- ) { | |||
#print(STDERR "Do $i (num = $m->{backups}[$i]{num}, fill = $m->{backups} [$i]{noFill}, level = $m->{backups}[$i]{level})\n"); | #print(STDERR "Do $i (num = $m->{backups}[$i]{num}, fill = $m->{backups} [$i]{noFill}, level = $m->{backups}[$i]{level})\n"); | |||
last if ( $m->{backups}[$i]{version} < 4 ); | last if ( $m->{backups}[$i]{version} < 4 ); | |||
if ( $i < @{$m->{backups}} - 1 && $m->{backups}[$i]{noFill} ) { | if ( $i < @{$m->{backups}} - 1 && $m->{backups}[$i]{noFill} ) { | |||
# | # | |||
# Copy all the file information from $i + 1 to $i | # Copy all the file information from $i + 1 to $i | |||
# | # | |||
foreach my $fileUM ( keys(%$files) ) { | foreach my $fileUM ( keys(%$files) ) { | |||
$files->{$fileUM}[$i] = $files->{$fileUM}[$i+1]; | $files->{$fileUM}[$i] = $files->{$fileUM}[$i + 1]; | |||
} | } | |||
} | } | |||
my $backupNum = $m->{backups}[$i]{num}; | my $backupNum = $m->{backups}[$i]{num}; | |||
my $mangle = $m->{backups}[$i]{mangle}; | my $mangle = $m->{backups}[$i]{mangle}; | |||
my $compress = $m->{backups}[$i]{compress}; | my $compress = $m->{backups}[$i]{compress}; | |||
my $topPath = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | my $topPath = "$m->{topDir}/pc/$m->{host}/$backupNum/"; | |||
my $sharePathM = $m->{bpc}->fileNameEltMangle($share) | my $sharePathM = $m->{bpc}->fileNameEltMangle($share) . $m->{bpc}->fileN | |||
. $m->{bpc}->fileNameMangle($dir); | ameMangle($dir); | |||
my $path = $topPath . $sharePathM; | my $path = $topPath . $sharePathM; | |||
#print(STDERR "Opening $path (share=$share, mangle=$mangle)\n"); | #print(STDERR "Opening $path (share=$share, mangle=$mangle)\n"); | |||
my $dirOpts = { %{$m->{dirOpts} || {} } }; | my $dirOpts = {%{$m->{dirOpts} || {}}}; | |||
my $attribOpts = { compress => $compress }; | my $attribOpts = {compress => $compress}; | |||
if ( !-d $path ) { | if ( !-d $path ) { | |||
# | # | |||
# if this is a filled backup then the directory is empty | # if this is a filled backup then the directory is empty | |||
# | # | |||
next if ( !$m->{backups}[$i]{noFill} ); | next if ( !$m->{backups}[$i]{noFill} ); | |||
# | # | |||
# if we have some entries already we need to check that | # if we have some entries already we need to check that | |||
# this directory wasn't deleted. We need to look up | # this directory wasn't deleted. We need to look up | |||
# up each level until we find an attrib file that exists. | # up each level until we find an attrib file that exists. | |||
skipping to change at line 855 | skipping to change at line 859 | |||
$files->{$fileUM}[$i] = undef; | $files->{$fileUM}[$i] = undef; | |||
} | } | |||
} | } | |||
} | } | |||
my $attr = BackupPC::XS::Attrib::new($compress); | my $attr = BackupPC::XS::Attrib::new($compress); | |||
if ( BackupPC::DirOps::dirContainsAttrib($m->{bpc}, $path) ) { | if ( BackupPC::DirOps::dirContainsAttrib($m->{bpc}, $path) ) { | |||
if ( !$attr->read($path) ) { | if ( !$attr->read($path) ) { | |||
push(@{$m->{error}}, "Can't read attribute file in $path\n"); | push(@{$m->{error}}, "Can't read attribute file in $path\n"); | |||
} else { | } else { | |||
my $attrAll = $attr->get(); | my $idx = 0; | |||
foreach my $fileUM ( keys(%$attrAll) ) { | my $a; | |||
my $a = $attrAll->{$fileUM}; | while ( 1 ) { | |||
($a, $idx) = $attr->iterate($idx); | ||||
last if ( !defined($a) ); | ||||
my $fileUM = $a->{name}; | ||||
if ( $a->{type} == BPC_FTYPE_DELETED ) { | if ( $a->{type} == BPC_FTYPE_DELETED ) { | |||
delete($files->{$fileUM}[$i]); | delete($files->{$fileUM}[$i]); | |||
delete($hardlinks->{$fileUM}); | delete($hardlinks->{$fileUM}); | |||
next; | next; | |||
} | } | |||
if ( $a->{nlinks} > 0 ) { | if ( $a->{nlinks} > 0 ) { | |||
$a = $m->hardLinkGet($a, $i); | $a = $m->hardLinkGet($a, $i); | |||
$hardlinks->{$fileUM} = 1; | $hardlinks->{$fileUM} = 1; | |||
} | } | |||
$files->{$fileUM}[$i] = $a; | $files->{$fileUM}[$i] = $a; | |||
($files->{$fileUM}[$i]{relPath} = "$dir/$fileUM") =~ s{/ /+}{/}g; | ($files->{$fileUM}[$i]{relPath} = "$dir/$fileUM") =~ s{//+}{ /}g; | |||
if ( length($a->{digest}) ) { | if ( length($a->{digest}) ) { | |||
$files->{$fileUM}[$i]{fullPath} = $m->{bpc}->MD52Path($a ->{digest}, $compress); | $files->{$fileUM}[$i]{fullPath} = $m->{bpc}->MD52Path($a ->{digest}, $compress); | |||
} else { | } else { | |||
$files->{$fileUM}[$i]{fullPath} = "/dev/null"; | $files->{$fileUM}[$i]{fullPath} = "/dev/null"; | |||
} | } | |||
$files->{$fileUM}[$i]{backupNum} = $backupNum; | $files->{$fileUM}[$i]{backupNum} = $backupNum; | |||
$files->{$fileUM}[$i]{compress} = $compress; | $files->{$fileUM}[$i]{compress} = $compress; | |||
$files->{$fileUM}[$i]{inode} = $a->{inode}; | $files->{$fileUM}[$i]{inode} = $a->{inode}; | |||
} | } | |||
} | } | |||
} | } | |||
# | # | |||
# Update any inode information specific to this backup | # Update any inode information specific to this backup | |||
# for hardlinks in this directory | # for hardlinks in this directory | |||
# | # | |||
foreach my $f ( keys(%$hardlinks) ) { | foreach my $f ( keys(%$hardlinks) ) { | |||
next if ( !$files->{$f}[$i] || $files->{$f}[$i]{backupNum} == $backu pNum ); | next if ( !$files->{$f}[$i] || $files->{$f}[$i]{backupNum} == $backu pNum ); | |||
my $a = $m->hardLinkGet($files->{$f}[$i], $i); | my $a = $m->hardLinkGet($files->{$f}[$i], $i); | |||
next if ( $a == $files->{$f}[$i] ); | next if ( $a == $files->{$f}[$i] ); | |||
$files->{$f}[$i] = { %{$files->{$f}[$i]}, %$a }; | $files->{$f}[$i] = {%{$files->{$f}[$i]}, %$a}; | |||
if ( length($a->{digest}) ) { | if ( length($a->{digest}) ) { | |||
$files->{$f}[$i]{fullPath} = $m->{bpc}->MD52Path($a->{digest}, $ compress); | $files->{$f}[$i]{fullPath} = $m->{bpc}->MD52Path($a->{digest}, $ compress); | |||
} | } | |||
$files->{$f}[$i]{backupNum} = $backupNum; | $files->{$f}[$i]{backupNum} = $backupNum; | |||
$files->{$f}[$i]{inode} = $a->{inode}; | $files->{$f}[$i]{inode} = $a->{inode}; | |||
} | } | |||
} | } | |||
#print STDERR "Returning:\n", Dumper($files); | #print STDERR "Returning:\n", Dumper($files); | |||
return $files; | return $files; | |||
} | } | |||
# | # | |||
# Do a recursive find starting at the given path (either a file | # Do a recursive find starting at the given path (either a file | |||
# or directory). The callback function $callback is called on each | # or directory). The callback function $callback is called on each | |||
# file and directory. The function arguments are the attrs hashref, | # file and directory. The function arguments are the attrs hashref, | |||
# and additional callback arguments. The search is depth-first if | # and additional callback arguments. The search is depth-first if | |||
# depth is set. Returns -1 if $path does not exist. | # depth is set. Returns -1 if $path does not exist. | |||
# | # | |||
sub find | sub find | |||
{ | { | |||
my($m, $backupNum, $share, $path, $depth, $callback, @callbackArgs) = @_; | my($m, $backupNum, $share, $path, $depth, $callback, @callbackArgs) = @_; | |||
#print(STDERR "find: got $backupNum, $share, $path\n"); | #print(STDERR "find: got $backupNum, $share, $path\n"); | |||
# | # | |||
# First call the callback on the given $path | # First call the callback on the given $path | |||
# | # | |||
my $attr = $m->fileAttrib($backupNum, $share, $path); | my $attr = $m->fileAttrib($backupNum, $share, $path); | |||
return -1 if ( !defined($attr) ); | return -1 if ( !defined($attr) ); | |||
&$callback($attr, @callbackArgs); | &$callback($attr, @callbackArgs); | |||
return if ( $attr->{type} != BPC_FTYPE_DIR ); | return if ( $attr->{type} != BPC_FTYPE_DIR ); | |||
# | # | |||
# Now recurse into subdirectories | # Now recurse into subdirectories | |||
# | # | |||
$m->findRecurse($backupNum, $share, $path, $depth, | $m->findRecurse($backupNum, $share, $path, $depth, $callback, @callbackArgs) | |||
$callback, @callbackArgs); | ; | |||
} | } | |||
# | # | |||
# Same as find(), except the callback is not called on the current | # Same as find(), except the callback is not called on the current | |||
# $path, only on the contents of $path. So if $path is a file then | # $path, only on the contents of $path. So if $path is a file then | |||
# no callback or recursion occurs. | # no callback or recursion occurs. | |||
# | # | |||
sub findRecurse | sub findRecurse | |||
{ | { | |||
my($m, $backupNum, $share, $path, $depth, $callback, @callbackArgs) = @_; | my($m, $backupNum, $share, $path, $depth, $callback, @callbackArgs) = @_; | |||
my $attr = $m->dirAttrib($backupNum, $share, $path); | my $attr = $m->dirAttrib($backupNum, $share, $path); | |||
return if ( !defined($attr) ); | return if ( !defined($attr) ); | |||
foreach my $file ( sort(keys(%$attr)) ) { | foreach my $file ( sort(keys(%$attr)) ) { | |||
&$callback($attr->{$file}, @callbackArgs); | &$callback($attr->{$file}, @callbackArgs); | |||
next if ( $depth <= 0 || $attr->{$file}{type} != BPC_FTYPE_DIR ); | next if ( $depth <= 0 || $attr->{$file}{type} != BPC_FTYPE_DIR ); | |||
# | # | |||
# For depth-first, recurse as we hit each directory | # For depth-first, recurse as we hit each directory | |||
# | # | |||
$m->findRecurse($backupNum, $share, "$path/$file", $depth, | $m->findRecurse($backupNum, $share, "$path/$file", $depth, $callback, @c | |||
$callback, @callbackArgs); | allbackArgs); | |||
} | } | |||
if ( $depth == 0 ) { | if ( $depth == 0 ) { | |||
# | # | |||
# For non-depth, recurse directories after we finish current dir | # For non-depth, recurse directories after we finish current dir | |||
# | # | |||
foreach my $file ( sort(keys(%{$attr})) ) { | foreach my $file ( sort(keys(%{$attr})) ) { | |||
next if ( $attr->{$file}{type} != BPC_FTYPE_DIR ); | next if ( $attr->{$file}{type} != BPC_FTYPE_DIR ); | |||
$m->findRecurse($backupNum, $share, "$path/$file", $depth, | $m->findRecurse($backupNum, $share, "$path/$file", $depth, $callback | |||
$callback, @callbackArgs); | , @callbackArgs); | |||
} | } | |||
} | } | |||
} | } | |||
1; | 1; | |||
End of changes. 75 change blocks. | ||||
222 lines changed or deleted | 224 lines changed or added |