"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "lib/BackupPC/View.pm" between
BackupPC-4.3.2.tar.gz and BackupPC-4.4.0.tar.gz

About: BackupPC is a high-performance, enterprise-grade system for backing up Linux and WinXX PCs and laptops to a server’s disk (http/cgi user interface).

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

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)