BackupPC_tarExtract (BackupPC-4.3.2) | : | BackupPC_tarExtract (BackupPC-4.4.0) | ||
---|---|---|---|---|
skipping to change at line 29 | skipping to change at line 29 | |||
# 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. | |||
# | # | |||
#======================================================================== | #======================================================================== | |||
use strict; | use strict; | |||
no utf8; | no utf8; | |||
use lib "__INSTALLDIR__/lib"; | use lib "__INSTALLDIR__/lib"; | |||
use Encode qw/from_to/; | use Encode qw/from_to/; | |||
use BackupPC::Lib; | use BackupPC::Lib; | |||
use BackupPC::XS qw( :all ); | use BackupPC::XS qw( :all ); | |||
use BackupPC::DirOps; | use BackupPC::DirOps; | |||
use Getopt::Std; | use Getopt::Std; | |||
use File::Path; | use File::Path; | |||
use Data::Dumper; | use Data::Dumper; | |||
use constant S_IFMT => 0170000; # type of file | use constant S_IFMT => 0170000; # type of file | |||
$SIG{BUS} = \&confess; | $SIG{BUS} = \&confess; | |||
$SIG{SEGV} = \&confess; | $SIG{SEGV} = \&confess; | |||
die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) ); | die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) ); | |||
my $TopDir = $bpc->TopDir(); | my $TopDir = $bpc->TopDir(); | |||
my $BinDir = $bpc->BinDir(); | my $BinDir = $bpc->BinDir(); | |||
my %Conf = $bpc->Conf(); | my %Conf = $bpc->Conf(); | |||
my %opts; | my %opts; | |||
skipping to change at line 76 | skipping to change at line 77 | |||
-f this is a full tar archive | -f this is a full tar archive | |||
-P inplace - don't put reverse deltas into 2nd most | -P inplace - don't put reverse deltas into 2nd most | |||
recent backup | recent backup | |||
-p don't show progress | -p don't show progress | |||
EOF | EOF | |||
exit(1); | exit(1); | |||
} | } | |||
select(STDOUT); $| = 1; | select(STDOUT); $| = 1; | |||
my $Full = $opts{f}; | my $Full = $opts{f}; | |||
my $FileCnt = 0; | my $FileCnt = 0; | |||
my $FileCntPeriod = 100; | my $FileCntPeriod = 100; | |||
print("$0: got Full = $Full\n"); | print("$0: got Full = $Full\n"); | |||
if ( $opts{h} !~ /^([\w\.\s-]+)$/ | if ( $opts{h} !~ /^([\w\.\s-]+)$/ | |||
|| $opts{h} =~ m{(^|/)\.\.(/|$)} ) { | || $opts{h} =~ m{(^|/)\.\.(/|$)} ) { | |||
print("$0: bad host name '$opts{h}'\n"); | print("$0: bad host name '$opts{h}'\n"); | |||
exit(1); | exit(1); | |||
} | } | |||
my $client = $opts{h}; | my $client = $opts{h}; | |||
if ( $opts{s} eq "" || $opts{s} =~ m{(^|/)\.\.(/|$)} ) { | if ( $opts{s} eq "" || $opts{s} =~ m{(^|/)\.\.(/|$)} ) { | |||
print("$0: bad share name '$opts{s}'\n"); | print("$0: bad share name '$opts{s}'\n"); | |||
exit(1); | exit(1); | |||
} | } | |||
my $ShareNameUM = $opts{s}; | my $ShareNameUM = $opts{s}; | |||
my $ShareName = $bpc->fileNameEltMangle($ShareNameUM); | my $ShareName = $bpc->fileNameEltMangle($ShareNameUM); | |||
my $Abort = 0; | my $Abort = 0; | |||
my $AbortReason; | my $AbortReason; | |||
# | # | |||
# Re-read config file, so we can include the PC-specific config | # Re-read config file, so we can include the PC-specific config | |||
# | # | |||
if ( defined(my $error = $bpc->ConfigRead($client)) ) { | if ( defined(my $error = $bpc->ConfigRead($client)) ) { | |||
print("BackupPC_tarExtract: Can't read PC's config file: $error\n"); | print("BackupPC_tarExtract: Can't read PC's config file: $error\n"); | |||
exit(1); | exit(1); | |||
} | } | |||
%Conf = $bpc->Conf(); | %Conf = $bpc->Conf(); | |||
BackupPC::XS::Lib::logLevelSet($Conf{XferLogLevel}); | BackupPC::XS::Lib::logLevelSet($Conf{XferLogLevel}); | |||
my @Backups = $bpc->BackupInfoRead($client); | my @Backups = $bpc->BackupInfoRead($client); | |||
my($lastBkupIdx, $lastBkupNum, $newBkupNum, $newBkupIdx); | my($lastBkupIdx, $lastBkupNum, $newBkupNum, $newBkupIdx); | |||
my($PaxHdrGlobal); | my($PaxHdrGlobal); | |||
$newBkupIdx = @Backups - 1; | $newBkupIdx = @Backups - 1; | |||
$newBkupNum = $Backups[$newBkupIdx]{num}; | $newBkupNum = $Backups[$newBkupIdx]{num}; | |||
my $Compress = $Backups[$newBkupIdx]{compress}; | my $Compress = $Backups[$newBkupIdx]{compress}; | |||
# | # | |||
# Cached attributes for the new backup and (if it exists) | # Cached attributes for the new backup and (if it exists) | |||
# the previous one | # the previous one | |||
# | # | |||
my($AttrNew, $AttrOld); | my($AttrNew, $AttrOld); | |||
my($DeltaNew, $DeltaOld); | my($DeltaNew, $DeltaOld); | |||
$AttrNew = BackupPC::XS::AttribCache::new($client, $newBkupNum, $ShareNameUM, | $AttrNew = BackupPC::XS::AttribCache::new($client, $newBkupNum, $ShareNameUM, $ | |||
$Backups[$newBkupIdx]{compress}); | Backups[$newBkupIdx]{compress}); | |||
$DeltaNew = BackupPC::XS::DeltaRefCnt::new("$TopDir/pc/$client/$newBkupNum"); | $DeltaNew = BackupPC::XS::DeltaRefCnt::new("$TopDir/pc/$client/$newBkupNum"); | |||
$AttrNew->setDeltaInfo($DeltaNew); | $AttrNew->setDeltaInfo($DeltaNew); | |||
my $Inode = 1; | my $Inode = 1; | |||
for ( my $i = 0 ; $i < @Backups ; $i++ ) { | for ( my $i = 0 ; $i < @Backups ; $i++ ) { | |||
$Inode = $Backups[$i]{inodeLast} + 1 if ( $Inode <= $Backups[$i]{inodeLast} ); | $Inode = $Backups[$i]{inodeLast} + 1 if ( $Inode <= $Backups[$i]{inodeLast} ); | |||
} | } | |||
my $Inode0 = $Inode; | my $Inode0 = $Inode; | |||
my $Inplace = $opts{P}; | my $Inplace = $opts{P}; | |||
if ( !$Inplace ) { | if ( !$Inplace ) { | |||
$lastBkupIdx = $newBkupIdx - 1; | $lastBkupIdx = $newBkupIdx - 1; | |||
if ( $lastBkupIdx < 0 ) { | if ( $lastBkupIdx < 0 ) { | |||
print("BackupPC_tarExtract: must specify -p on first backup\n"); | print("BackupPC_tarExtract: must specify -p on first backup\n"); | |||
exit(1); | exit(1); | |||
} | } | |||
$lastBkupNum = $Backups[$lastBkupIdx]{num}; | $lastBkupNum = $Backups[$lastBkupIdx]{num}; | |||
$AttrOld = BackupPC::XS::AttribCache::new($client, $lastBkupNum, $ShareName | $AttrOld = BackupPC::XS::AttribCache::new($client, $lastBkupNum, $ShareName | |||
UM, | UM, $Backups[$lastBkupIdx]{compress}); | |||
$Backups[$lastBkupIdx]{compress}) | ||||
; | ||||
$DeltaOld = BackupPC::XS::DeltaRefCnt::new("$TopDir/pc/$client/$lastBkupNum" ); | $DeltaOld = BackupPC::XS::DeltaRefCnt::new("$TopDir/pc/$client/$lastBkupNum" ); | |||
$AttrOld->setDeltaInfo($DeltaOld); | $AttrOld->setDeltaInfo($DeltaOld); | |||
} | } | |||
# | # | |||
# This constant and the line of code below that uses it is borrowed | # This constant and the line of code below that uses it is borrowed | |||
# from Archive::Tar. Thanks to Calle Dybedahl and Stephen Zander. | # from Archive::Tar. Thanks to Calle Dybedahl and Stephen Zander. | |||
# See www.cpan.org. | # See www.cpan.org. | |||
# | # | |||
# Archive::Tar is Copyright 1997 Calle Dybedahl. All rights reserved. | # Archive::Tar is Copyright 1997 Calle Dybedahl. All rights reserved. | |||
# Copyright 1998 Stephen Zander. All rights reserved. | # Copyright 1998 Stephen Zander. All rights reserved. | |||
# | # | |||
my $tar_unpack_header | my $tar_unpack_header = 'Z100 A8 A8 A8 a12 A12 A8 A1 Z100 A6 A2 Z32 Z32 A8 A8 A1 | |||
= 'Z100 A8 A8 A8 a12 A12 A8 A1 Z100 A6 A2 Z32 Z32 A8 A8 A155 x12'; | 55 x12'; | |||
my $tar_header_length = 512; | my $tar_header_length = 512; | |||
my $BufSize = 1048576; # 1MB or 2^20 | my $BufSize = 1048576; # 1MB or 2^20 | |||
my $MaxFiles = 20; | my $MaxFiles = 20; | |||
my $Errors = 0; | my $Errors = 0; | |||
my $FatalErrors = 0; | my $FatalErrors = 0; | |||
my $ExistFileCnt = 0; | my $ExistFileCnt = 0; | |||
my $ExistFileSize = 0; | my $ExistFileSize = 0; | |||
my $ExistFileCompSize = 0; | my $ExistFileCompSize = 0; | |||
my $NewFileCnt = 0; | my $NewFileCnt = 0; | |||
my $NewFileSize = 0; | my $NewFileSize = 0; | |||
my $NewFileCompSize = 0; | my $NewFileCompSize = 0; | |||
my $TarReadHdrCnt = 0; | my $TarReadHdrCnt = 0; | |||
print("$0 starting... (XferLogLevel = $Conf{XferLogLevel})\n"); | print("$0 starting... (XferLogLevel = $Conf{XferLogLevel})\n"); | |||
skipping to change at line 193 | skipping to change at line 191 | |||
exitMesg(); | exitMesg(); | |||
sub TarRead | sub TarRead | |||
{ | { | |||
my($fh, $totBytes) = @_; | my($fh, $totBytes) = @_; | |||
my($numBytes, $newBytes, $data); | my($numBytes, $newBytes, $data); | |||
print("tarRead $totBytes\n") if ( $Conf{XferLogLevel} >= 9 ); | print("tarRead $totBytes\n") if ( $Conf{XferLogLevel} >= 9 ); | |||
$data = "\0" x $totBytes; | $data = "\0" x $totBytes; | |||
$! = 0; | $! = 0; | |||
while ( $numBytes < $totBytes ) { | while ( $numBytes < $totBytes ) { | |||
return if ( $Abort ); | return if ( $Abort ); | |||
$newBytes = sysread($fh, | $newBytes = sysread($fh, substr($data, $numBytes, $totBytes - $numBytes) | |||
substr($data, $numBytes, $totBytes - $numBytes), | , $totBytes - $numBytes); | |||
$totBytes - $numBytes); | ||||
if ( $newBytes <= 0 ) { | if ( $newBytes <= 0 ) { | |||
return if ( $TarReadHdrCnt == 1 ); # empty tar file ok | return if ( $TarReadHdrCnt == 1 ); # empty tar file ok | |||
print("Unexpected end of tar archive (tot = $totBytes," | print( "Unexpected end of tar archive (tot = $totBytes," | |||
. " num = $numBytes, errno = $!, posn = " . sysseek($fh, 0, 1 | . " num = $numBytes, errno = $!, posn = " | |||
) . ")\n"); | . sysseek($fh, 0, 1) | |||
$Abort = 1; | . ")\n"); | |||
$Abort = 1; | ||||
$AbortReason = "Unexpected end of tar archive"; | $AbortReason = "Unexpected end of tar archive"; | |||
$Errors++; | $Errors++; | |||
$FatalErrors++; | $FatalErrors++; | |||
return; | return; | |||
} | } | |||
$numBytes += $newBytes; | $numBytes += $newBytes; | |||
} | } | |||
return $data; | return $data; | |||
} | } | |||
skipping to change at line 236 | skipping to change at line 234 | |||
if ( $size % $tar_header_length ) { | if ( $size % $tar_header_length ) { | |||
TarRead($fh, $tar_header_length - ($size % $tar_header_length)); | TarRead($fh, $tar_header_length - ($size % $tar_header_length)); | |||
} | } | |||
} | } | |||
sub TarReadFileInfo | sub TarReadFileInfo | |||
{ | { | |||
my($fh) = @_; | my($fh) = @_; | |||
my($head, $longName, $longLink); | my($head, $longName, $longLink); | |||
my($name, $mode, $uid, $gid, $size, $mtime, $chksum, $type, | my( | |||
$linkname, $magic, $version, $uname, $gname, $devmajor, | $name, $mode, $uid, $gid, $size, $mtime, $chksum, $type | |||
$devminor, $prefix); | , | |||
$linkname, $magic, $version, $uname, $gname, $devmajor, $devminor, $pref | ||||
ix | ||||
); | ||||
my $paxHdr = {}; | my $paxHdr = {}; | |||
while ( 1 ) { | while ( 1 ) { | |||
$head = TarReadHeader($fh); | $head = TarReadHeader($fh); | |||
return if ( $Abort || $head eq "" | return if ( $Abort || $head eq "" || $head eq "\0" x $tar_header_length | |||
|| $head eq "\0" x $tar_header_length ); | ); | |||
($name, # string | ( | |||
$mode, # octal number | $name, # string | |||
$uid, # octal number | $mode, # octal number | |||
$gid, # octal number | $uid, # octal number | |||
$size, # octal number | $gid, # octal number | |||
$mtime, # octal number | $size, # octal number | |||
$chksum, # octal number | $mtime, # octal number | |||
$type, # character | $chksum, # octal number | |||
$linkname, # string | $type, # character | |||
$magic, # string | $linkname, # string | |||
$version, # two bytes | $magic, # string | |||
$uname, # string | $version, # two bytes | |||
$gname, # string | $uname, # string | |||
$devmajor, # octal number | $gname, # string | |||
$devminor, # octal number | $devmajor, # octal number | |||
$prefix) = unpack($tar_unpack_header, $head); | $devminor, # octal number | |||
$prefix | ||||
$mode = oct $mode; | ) = unpack($tar_unpack_header, $head); | |||
$uid = oct $uid; | ||||
$gid = oct $gid; | $mode = oct $mode; | |||
if ( ord($size) == 128 ) { | $uid = oct $uid; | |||
# | $gid = oct $gid; | |||
# GNU tar extension: for >=8GB files the size is stored | if ( ord($size) == 128 ) { | |||
# in big endian binary. | # | |||
# | # GNU tar extension: for >=8GB files the size is stored | |||
$size = 65536 * 65536 * unpack("N", substr($size, 4, 4)) | # in big endian binary. | |||
+ unpack("N", substr($size, 8, 4)); | # | |||
} else { | $size = 65536 * 65536 * unpack("N", substr($size, 4, 4)) + unpack("N | |||
# | ", substr($size, 8, 4)); | |||
# We used to have a patch here for smbclient 2.2.x. For file | } else { | |||
# sizes between 2 and 4GB it sent the wrong size. But since | # | |||
# samba 3.0.0 has been released we no longer support this | # We used to have a patch here for smbclient 2.2.x. For file | |||
# patch since valid files could have sizes that start with | # sizes between 2 and 4GB it sent the wrong size. But since | |||
# 6 or 7 in octal (eg: 6-8GB files). | # samba 3.0.0 has been released we no longer support this | |||
# | # patch since valid files could have sizes that start with | |||
# $size =~ s/^6/2/; # fix bug in smbclient for >=2GB files | # 6 or 7 in octal (eg: 6-8GB files). | |||
# $size =~ s/^7/3/; # fix bug in smbclient for >=2GB files | # | |||
# | # $size =~ s/^6/2/; # fix bug in smbclient for >=2GB files | |||
# To avoid integer overflow in case we are in the 4GB - 8GB | # $size =~ s/^7/3/; # fix bug in smbclient for >=2GB files | |||
# range, we do the conversion in two parts. | # | |||
# | # To avoid integer overflow in case we are in the 4GB - 8GB | |||
# range, we do the conversion in two parts. | ||||
# | ||||
if ( $size =~ /([0-9]{9,})/ ) { | if ( $size =~ /([0-9]{9,})/ ) { | |||
my $len = length($1); | my $len = length($1); | |||
$size = oct(substr($1, 0, $len - 8)) * (1 << 24) | $size = oct(substr($1, 0, $len - 8)) * (1 << 24) + oct(substr($1 | |||
+ oct(substr($1, $len - 8)); | , $len - 8)); | |||
} else { | } else { | |||
$size = oct($size); | $size = oct($size); | |||
} | } | |||
} | } | |||
$mtime = oct $mtime; | $mtime = oct $mtime; | |||
$chksum = oct $chksum; | $chksum = oct $chksum; | |||
$devmajor = oct $devmajor; | $devmajor = oct $devmajor; | |||
$devminor = oct $devminor; | $devminor = oct $devminor; | |||
$name = "$prefix/$name" if $prefix; | $name = "$prefix/$name" if $prefix; | |||
$prefix = ""; | $prefix = ""; | |||
substr ($head, 148, 8) = " "; | substr($head, 148, 8) = " "; | |||
if (unpack ("%16C*", $head) != $chksum) { | ||||
print("$name: checksum error at " . sysseek($fh, 0, 1) , "\n"); | if ( unpack("%16C*", $head) != $chksum ) { | |||
$Errors++; | print("$name: checksum error at " . sysseek($fh, 0, 1), "\n"); | |||
$FatalErrors++; | $Errors++; | |||
$FatalErrors++; | ||||
} | } | |||
if ( $type eq "L" ) { | if ( $type eq "L" ) { | |||
$longName = TarRead($fh, $size) || return; | $longName = TarRead($fh, $size) || return; | |||
# remove trailing NULL | # remove trailing NULL | |||
$paxHdr = {}; | $paxHdr = {}; | |||
$paxHdr->{path} = substr($longName, 0, $size - 1); | $paxHdr->{path} = substr($longName, 0, $size - 1); | |||
TarFlush($fh, $size); | TarFlush($fh, $size); | |||
next; | next; | |||
} elsif ( $type eq "K" ) { | } elsif ( $type eq "K" ) { | |||
$longLink = TarRead($fh, $size) || return; | $longLink = TarRead($fh, $size) || return; | |||
# remove trailing NULL | # remove trailing NULL | |||
$paxHdr->{linkpath} = substr($longLink, 0, $size - 1); | $paxHdr->{linkpath} = substr($longLink, 0, $size - 1); | |||
TarFlush($fh, $size); | TarFlush($fh, $size); | |||
next; | next; | |||
} elsif ( $type eq "x" || $type eq "g" ) { | } elsif ( $type eq "x" || $type eq "g" ) { | |||
my $paxStr = TarRead($fh, $size) || return; | my $paxStr = TarRead($fh, $size) || return; | |||
$paxHdr = {}; | $paxHdr = {}; | |||
while ( $paxStr =~ /(\d+) (.*?)=(.*?)\n(.*)/s ) { | while ( $paxStr =~ /(\d+) / ) { | |||
$paxHdr->{$2} = $3; | my $setting = substr($paxStr, 0, $1); | |||
print("Got pax header $2 -> $3\n") if ( $Conf{XferLogLevel} >= 6 | $paxStr = substr($paxStr, $1); | |||
); | $setting =~ s/\n$//; | |||
$paxStr = $4; | if ( $setting =~ /\d+ (.*?)=(.*)/s ) { | |||
$paxHdr->{$1} = $2; | ||||
print("Got pax header $1 -> $2\n") if ( $Conf{XferLogLevel} | ||||
>= 6 ); | ||||
} | ||||
} | } | |||
TarFlush($fh, $size); | TarFlush($fh, $size); | |||
if ( $type eq "g" ) { | if ( $type eq "g" ) { | |||
$PaxHdrGlobal = $paxHdr; | $PaxHdrGlobal = $paxHdr; | |||
$paxHdr = {}; | $paxHdr = {}; | |||
} | } | |||
next; | next; | |||
} | } | |||
# | # | |||
# merge global headers if defined, then override extracted file meta dat a | # merge global headers if defined, then override extracted file meta dat a | |||
# | # | |||
$paxHdr = { %$PaxHdrGlobal, %$paxHdr } if ( $PaxHdrGlobal ); | $paxHdr = {%$PaxHdrGlobal, %$paxHdr} if ( $PaxHdrGlobal ); | |||
$name = $paxHdr->{path} if ( defined($paxHdr->{path}) ); | $name = $paxHdr->{path} if ( defined($paxHdr->{path}) ); | |||
$linkname = $paxHdr->{linkpath} if ( defined($paxHdr->{linkpath}) ); | $linkname = $paxHdr->{linkpath} if ( defined($paxHdr->{linkpath}) | |||
$size = $paxHdr->{size} if ( defined($paxHdr->{size}) ); | ); | |||
$mtime = $paxHdr->{mtime} if ( defined($paxHdr->{mtime}) ); | $size = $paxHdr->{size} if ( defined($paxHdr->{size}) ); | |||
$uid = $paxHdr->{uid} if ( defined($paxHdr->{uid}) ); | $mtime = $paxHdr->{mtime} if ( defined($paxHdr->{mtime}) ); | |||
$gid = $paxHdr->{gid} if ( defined($paxHdr->{gid}) ); | $uid = $paxHdr->{uid} if ( defined($paxHdr->{uid}) ); | |||
$uname = $paxHdr->{uname} if ( defined($paxHdr->{uname}) ); | $gid = $paxHdr->{gid} if ( defined($paxHdr->{gid}) ); | |||
$gname = $paxHdr->{gname} if ( defined($paxHdr->{gname}) ); | $uname = $paxHdr->{uname} if ( defined($paxHdr->{uname}) ); | |||
$gname = $paxHdr->{gname} if ( defined($paxHdr->{gname}) ); | ||||
printf("Got file '%s', mode 0%o, size %g, type %d\n", | printf("Got file '%s', mode 0%o, size %g, type %d\n", $name, $mode, $siz | |||
$name, $mode, $size, $type) if ( $Conf{XferLogLevel} >= 3 ); | e, $type) | |||
if ( $Conf{XferLogLevel} >= 3 ); | ||||
# | # | |||
# Map client charset encodings to utf8 | # Map client charset encodings to utf8 | |||
# | # | |||
# printf("File %s (hex: %s)\n", $name, unpack("H*", $name)); | # printf("File %s (hex: %s)\n", $name, unpack("H*", $name)); | |||
if ( $Conf{ClientCharset} ne "" ) { | if ( $Conf{ClientCharset} ne "" ) { | |||
from_to($name, $Conf{ClientCharset}, "utf8"); | from_to($name, $Conf{ClientCharset}, "utf8"); | |||
from_to($linkname, $Conf{ClientCharset}, "utf8"); | from_to($linkname, $Conf{ClientCharset}, "utf8"); | |||
} | } | |||
# printf("File now %s (hex: %s)\n", $name, unpack("H*", $name)); | # printf("File now %s (hex: %s)\n", $name, unpack("H*", $name)); | |||
my $xattr = {}; | ||||
foreach my $name ( keys(%$paxHdr) ) { | ||||
if ( $name =~ /^SCHILY\.xattr\.(.*)/s ) { | ||||
$xattr->{$1} = $paxHdr->{$name}; | ||||
} elsif ( $name = "SCHILY.acl.access" ) { | ||||
$xattr->{"user.gtar.%aacl"} = $paxHdr->{$name} if ( length($paxH | ||||
dr->{$name}) ); | ||||
delete($paxHdr->{$name}); | ||||
} elsif ( $name = "SCHILY.acl.default" ) { | ||||
$xattr->{"user.gtar.%dacl"} = $paxHdr->{$name} if ( length($paxH | ||||
dr->{$name}) ); | ||||
delete($paxHdr->{$name}); | ||||
} | ||||
} | ||||
$name =~ s{^\./+}{}; | $name =~ s{^\./+}{}; | |||
$name =~ s{/+\.?$}{}; | $name =~ s{/+\.?$}{}; | |||
$name =~ s{//+}{/}g; | $name =~ s{//+}{/}g; | |||
return { | return { | |||
name => $name, | name => $name, | |||
mangleName => $bpc->fileNameMangle($name), | mangleName => $bpc->fileNameMangle($name), | |||
mode => $mode, | mode => $mode, | |||
uid => $uid, | uid => $uid, | |||
gid => $gid, | gid => $gid, | |||
size => $size, | size => $size, | |||
mtime => $mtime, | mtime => $mtime, | |||
type => $type, | type => $type, | |||
linkname => $linkname, | linkname => $linkname, | |||
devmajor => $devmajor, | devmajor => $devmajor, | |||
devminor => $devminor, | devminor => $devminor, | |||
xattr => $xattr, | ||||
}; | }; | |||
} | } | |||
} | } | |||
sub fileReadAll | sub fileReadAll | |||
{ | { | |||
my($a, $f) = @_; | my($a, $f) = @_; | |||
return "" if ( $a->{size} == 0 ); | return "" if ( $a->{size} == 0 ); | |||
my $f = BackupPC::XS::FileZIO::open($a->{poolPath}, 0, $a->{compress}); | my $f = BackupPC::XS::FileZIO::open($a->{poolPath}, 0, $a->{compress}); | |||
skipping to change at line 413 | skipping to change at line 432 | |||
# | # | |||
sub moveFileToOld | sub moveFileToOld | |||
{ | { | |||
my($a, $f) = @_; | my($a, $f) = @_; | |||
if ( !$a || keys(%$a) == 0 ) { | if ( !$a || keys(%$a) == 0 ) { | |||
# | # | |||
# A new file will be created, so add delete attribute to old | # A new file will be created, so add delete attribute to old | |||
# | # | |||
if ( $AttrOld ) { | if ( $AttrOld ) { | |||
$AttrOld->set($f->{name}, { type => BPC_FTYPE_DELETED }); | $AttrOld->set($f->{name}, {type => BPC_FTYPE_DELETED}); | |||
print("moveFileToOld: added $f->{name} as BPC_FTYPE_DELETED in old\n ") | print("moveFileToOld: added $f->{name} as BPC_FTYPE_DELETED in old\n ") | |||
if ( $Conf{XferLogLevel} >= 5 ); | if ( $Conf{XferLogLevel} >= 5 ); | |||
} | } | |||
return; | return; | |||
} | } | |||
print("moveFileToOld: $a->{name}, $f->{name}, links = $a->{nlinks}, type = $ a->{type}\n") | print("moveFileToOld: $a->{name}, $f->{name}, links = $a->{nlinks}, type = $ a->{type}\n") | |||
if ( $Conf{XferLogLevel} >= 5 ); | if ( $Conf{XferLogLevel} >= 5 ); | |||
if ( $a->{type} != BPC_FTYPE_DIR ) { | if ( $a->{type} != BPC_FTYPE_DIR ) { | |||
if ( $a->{nlinks} > 0 ) { | if ( $a->{nlinks} > 0 ) { | |||
if ( $AttrOld ) { | if ( $AttrOld ) { | |||
if ( !$AttrOld->getInode($a->{inode}) ) { | if ( !$AttrOld->getInode($a->{inode}) ) { | |||
# | # | |||
# copy inode to old if it isn't already there | # copy inode to old if it isn't already there | |||
# | # | |||
$AttrOld->setInode($a->{inode}, $a); | $AttrOld->setInode($a->{inode}, $a); | |||
$DeltaOld->update($a->{compress}, $a->{digest}, 1); | $DeltaOld->update($a->{compress}, $a->{digest}, 1); | |||
} | } | |||
# | # | |||
# copy to old - no need for refeence count update since | # copy to old - no need for refeence count update since | |||
# inode is already there | # inode is already there | |||
# | # | |||
$AttrOld->set($f->{name}, $a, 1) if ( !$AttrOld->get($f->{name}) | $AttrOld->set($f->{name}, $a, 1) if ( !$AttrOld->get($f->{name}) | |||
); | ); | |||
} | } | |||
$a->{nlinks}--; | $a->{nlinks}--; | |||
if ( $a->{nlinks} <= 0 ) { | if ( $a->{nlinks} <= 0 ) { | |||
$AttrNew->deleteInode($a->{inode}); | $AttrNew->deleteInode($a->{inode}); | |||
$DeltaNew->update($a->{compress}, $a->{digest}, -1); | $DeltaNew->update($a->{compress}, $a->{digest}, -1); | |||
} else { | } else { | |||
$AttrNew->setInode($a->{inode}, $a); | $AttrNew->setInode($a->{inode}, $a); | |||
} | } | |||
} else { | } else { | |||
$DeltaNew->update($a->{compress}, $a->{digest}, -1); | $DeltaNew->update($a->{compress}, $a->{digest}, -1); | |||
if ( $AttrOld && !$AttrOld->get($f->{name}) && $AttrOld->set($f->{na me}, $a, 1) ) { | if ( $AttrOld && !$AttrOld->get($f->{name}) && $AttrOld->set($f->{na me}, $a, 1) ) { | |||
skipping to change at line 458 | skipping to change at line 477 | |||
} | } | |||
} | } | |||
$AttrNew->delete($f->{name}); | $AttrNew->delete($f->{name}); | |||
} else { | } else { | |||
if ( !$AttrOld || $AttrOld->get($f->{name}) ) { | if ( !$AttrOld || $AttrOld->get($f->{name}) ) { | |||
# | # | |||
# Delete the directory tree, including updating reference counts | # Delete the directory tree, including updating reference counts | |||
# | # | |||
my $pathNew = $AttrNew->getFullMangledPath($f->{name}); | my $pathNew = $AttrNew->getFullMangledPath($f->{name}); | |||
print("moveFileToOld(..., $f->{name}): deleting $pathNew\n") | print("moveFileToOld(..., $f->{name}): deleting $pathNew\n") | |||
if ( $Conf{XferLogLevel} >= 3 ); | if ( $Conf{XferLogLevel} >= 3 ); | |||
BackupPC::DirOps::RmTreeQuiet($bpc, $pathNew, $a->{compress}, $Delta New, $AttrNew); | BackupPC::DirOps::RmTreeQuiet($bpc, $pathNew, $a->{compress}, $Delta New, $AttrNew); | |||
} else { | } else { | |||
# | # | |||
# For a directory we need to move it to old, and copy | # For a directory we need to move it to old, and copy | |||
# any inodes that are referenced below this directory. | # any inodes that are referenced below this directory. | |||
# Also update the reference counts for the moved files. | # Also update the reference counts for the moved files. | |||
# | # | |||
my $pathNew = $AttrNew->getFullMangledPath($f->{name}); | my $pathNew = $AttrNew->getFullMangledPath($f->{name}); | |||
my $pathOld = $AttrOld->getFullMangledPath($f->{name}); | my $pathOld = $AttrOld->getFullMangledPath($f->{name}); | |||
print("moveFileToOld(..., $f->{name}): renaming $pathNew to $pathOld \n") | print("moveFileToOld(..., $f->{name}): renaming $pathNew to $pathOld \n") | |||
if ( $Conf{XferLogLevel} >= 3 ); | if ( $Conf{XferLogLevel} >= 3 ); | |||
pathCreate($pathOld); | pathCreate($pathOld); | |||
$AttrNew->flush(0, $f->{name}); | $AttrNew->flush(0, $f->{name}); | |||
if ( !rename($pathNew, $pathOld) ) { | if ( !rename($pathNew, $pathOld) ) { | |||
printf("moveFileToOld(..., %s: can't rename %s to %s ($!, %d, %d , %d)\n", | printf("moveFileToOld(..., %s: can't rename %s to %s ($!, %d, %d , %d)\n", | |||
$f->{name}, $pathNew, $pathOld, -e $pathNew, -e $path Old, -d $pathOld); | $f->{name}, $pathNew, $pathOld, -e $pathNew, -e $pathOld, -d $pathOld); | |||
$Errors++; | $Errors++; | |||
} else { | } else { | |||
BackupPC::XS::DirOps::refCountAll($pathOld, $a->{compress}, -1, $DeltaNew); | BackupPC::XS::DirOps::refCountAll($pathOld, $a->{compress}, -1, $DeltaNew); | |||
BackupPC::XS::DirOps::refCountAll($pathOld, $a->{compress}, 1, $DeltaOld); | BackupPC::XS::DirOps::refCountAll($pathOld, $a->{compress}, 1, $DeltaOld); | |||
copyInodes($f->{name}); | copyInodes($f->{name}); | |||
$AttrOld->set($f->{name}, $a, 1); | $AttrOld->set($f->{name}, $a, 1); | |||
} | } | |||
} | } | |||
$AttrNew->delete($f->{name}); | $AttrNew->delete($f->{name}); | |||
} | } | |||
} | } | |||
sub xattrEqual | ||||
{ | ||||
my($x1, $x2) = @_; | ||||
return 1 if ( !defined($x1) && !defined($x2) ); | ||||
return 0 if ( !defined($x1) || !defined($x2) || scalar(keys(%$x1)) != scalar | ||||
(keys(%$x2)) ); | ||||
foreach my $n ( keys(%$x1) ) { | ||||
return 0 if ( !defined($x2->{$n}) || $x1->{$n} ne $x2->{$n} ); | ||||
} | ||||
return 1; | ||||
} | ||||
sub TarReadFile | sub TarReadFile | |||
{ | { | |||
my($fh) = @_; | my($fh) = @_; | |||
my $f = TarReadFileInfo($fh) || return; | my $f = TarReadFileInfo($fh) || return; | |||
my($file, $exist, $digest); | my($file, $exist, $digest); | |||
my $a = $AttrNew->get($f->{name}); | my $a = $AttrNew->get($f->{name}); | |||
my $aOld = $AttrOld->get($f->{name}) if ( $AttrOld ); | my $aOld = $AttrOld->get($f->{name}) if ( $AttrOld ); | |||
my $same = 0; | my $same = 0; | |||
printProgress() if ( ($FileCnt % $FileCntPeriod) == 0 ); | printProgress() if ( ($FileCnt % $FileCntPeriod) == 0 ); | |||
$FileCnt++; | $FileCnt++; | |||
$a->{poolPath} = $bpc->MD52Path($a->{digest}, $a->{compress}) if ( length($a ->{digest}) ); | $a->{poolPath} = $bpc->MD52Path($a->{digest}, $a->{compress}) if ( length($a ->{digest}) ); | |||
dirCacheNewFile($f->{name}); | dirCacheNewFile($f->{name}); | |||
if ( $f->{type} == BPC_FTYPE_DIR ) { | if ( $f->{type} == BPC_FTYPE_DIR ) { | |||
# | # | |||
# Directory | # Directory | |||
# | # | |||
dirCacheNewDir($f->{name}); | dirCacheNewDir($f->{name}); | |||
my $pathNew = $AttrNew->getFullMangledPath($f->{name}); | my $pathNew = $AttrNew->getFullMangledPath($f->{name}); | |||
if ( -d $pathNew ) { | if ( -d $pathNew ) { | |||
logFileAction("same", $f) if ( $Conf{XferLogLevel} >= 1 ); | logFileAction("same", $f) if ( $Conf{XferLogLevel} >= 1 ); | |||
$same = 1; | $same = 1; | |||
} else { | } else { | |||
if ( -e $pathNew ) { | if ( -e $pathNew ) { | |||
print("TarReadFile: $pathNew ($f->{name}) isn't a directory... r enaming and recreating\n") | print("TarReadFile: $pathNew ($f->{name}) isn't a directory... r enaming and recreating\n") | |||
if ( defined($a) && $Conf{XferLogLe vel} >= 4 ); | if ( defined($a) && $Conf{XferLogLevel} >= 4 ); | |||
} else { | } else { | |||
print("TarReadFile: creating directory $pathNew ($f->{name})\n") | print("TarReadFile: creating directory $pathNew ($f->{name})\n") | |||
if ( defined($a) && $Conf{XferLogLe vel} >= 3 ); | if ( defined($a) && $Conf{XferLogLevel} >= 3 ); | |||
} | } | |||
moveFileToOld($a, $f); | moveFileToOld($a, $f); | |||
logFileAction("new", $f) if ( $Conf{XferLogLevel} >= 1 ); | logFileAction("new", $f) if ( $Conf{XferLogLevel} >= 1 ); | |||
# | # | |||
# make sure all the parent directories exist and have directory attr ibs | # make sure all the parent directories exist and have directory attr ibs | |||
# | # | |||
pathCreate($pathNew, 1); | pathCreate($pathNew, 1); | |||
my $name = $f->{name}; | my $name = $f->{name}; | |||
$name = "/$name" if ( $name !~ m{^/} ); | $name = "/$name" if ( $name !~ m{^/} ); | |||
while ( length($name) > 1 ) { | while ( length($name) > 1 ) { | |||
if ( $name =~ m{/} ) { | if ( $name =~ m{/} ) { | |||
$name =~ s{(.*)/.*}{$1}; | $name =~ s{(.*)/.*}{$1}; | |||
} else { | } else { | |||
$name = "/"; | $name = "/"; | |||
} | } | |||
my $a = $AttrNew->get($name); | my $a = $AttrNew->get($name); | |||
last if ( defined($a) && $a->{type} == BPC_FTYPE_DIR ); | last if ( defined($a) && $a->{type} == BPC_FTYPE_DIR ); | |||
print("TarReadFile: adding BPC_FTYPE_DIR attrib entry for $name\ n") | print("TarReadFile: adding BPC_FTYPE_DIR attrib entry for $name\ n") | |||
if ( $Conf{XferLogLevel} >= 3 ); | if ( $Conf{XferLogLevel} >= 3 ); | |||
dirCacheNewDir($name); | dirCacheNewDir($name); | |||
my $fNew = { | my $fNew = { | |||
name => $name, | name => $name, | |||
type => BPC_FTYPE_DIR, | type => BPC_FTYPE_DIR, | |||
mode => $f->{mode}, | mode => $f->{mode}, | |||
uid => $f->{uid}, | uid => $f->{uid}, | |||
gid => $f->{gid}, | gid => $f->{gid}, | |||
size => 0, | xattr => $f->{xattr}, | |||
mtime => $f->{mtime}, | size => 0, | |||
inode => $Inode++, | mtime => $f->{mtime}, | |||
nlinks => 0, | inode => $Inode++, | |||
compress => $Compress, | nlinks => 0, | |||
}; | compress => $Compress, | |||
}; | ||||
$AttrNew->set($name, $fNew); | $AttrNew->set($name, $fNew); | |||
moveFileToOld($a, $fNew); | moveFileToOld($a, $fNew); | |||
} | } | |||
} | } | |||
} elsif ( $f->{type} == BPC_FTYPE_FILE ) { | } elsif ( $f->{type} == BPC_FTYPE_FILE ) { | |||
# | # | |||
# Regular file | # Regular file | |||
# | # | |||
# | # | |||
# Write the file | # Write the file | |||
# | # | |||
my($nRead); | my($nRead); | |||
#print("Reading $f->{name}, $f->{size} bytes, type $f->{type}\n"); | #print("Reading $f->{name}, $f->{size} bytes, type $f->{type}\n"); | |||
my $poolWrite = BackupPC::XS::PoolWrite::new($Compress); | my $poolWrite = BackupPC::XS::PoolWrite::new($Compress); | |||
while ( $nRead < $f->{size} ) { | while ( $nRead < $f->{size} ) { | |||
my $thisRead = $f->{size} - $nRead < $BufSize | my $thisRead = $f->{size} - $nRead < $BufSize ? $f->{size} - $nRead | |||
? $f->{size} - $nRead : $BufSize; | : $BufSize; | |||
my $data = TarRead($fh, $thisRead); | my $data = TarRead($fh, $thisRead); | |||
if ( $data eq "" ) { | if ( $data eq "" ) { | |||
if ( !$Abort ) { | if ( !$Abort ) { | |||
print("Unexpected end of tar archive during read\n"); | print("Unexpected end of tar archive during read\n"); | |||
$AbortReason = "Unexpected end of tar archive"; | $AbortReason = "Unexpected end of tar archive"; | |||
$Errors++; | $Errors++; | |||
$FatalErrors++; | $FatalErrors++; | |||
} | } | |||
$Abort = 1; | $Abort = 1; | |||
print("Removing partial file $f->{name}\n") | print("Removing partial file $f->{name}\n") | |||
if ( $Conf{XferLogLevel} >= 1 ); | if ( $Conf{XferLogLevel} >= 1 ); | |||
$AttrNew->delete($f->{name}); | $AttrNew->delete($f->{name}); | |||
return; | return; | |||
} | } | |||
$poolWrite->write(\$data); | $poolWrite->write(\$data); | |||
$nRead += $thisRead; | $nRead += $thisRead; | |||
} | } | |||
($exist, $digest) = processClose($poolWrite, $f->{size}); | ($exist, $digest) = processClose($poolWrite, $f->{size}); | |||
if ( $a->{digest} eq $digest ) { | if ( $a->{digest} eq $digest ) { | |||
logFileAction("same", $f) if ( $Conf{XferLogLevel} >= 1 ); | logFileAction("same", $f) if ( $Conf{XferLogLevel} >= 1 ); | |||
$same = 1 if ( $a->{nlinks} == 0 ); | $same = 1 if ( $a->{nlinks} == 0 ); | |||
} | } | |||
if ( !$same ) { | if ( !$same ) { | |||
moveFileToOld($a, $f); | moveFileToOld($a, $f); | |||
logFileAction($exist ? "pool" : "new", $f) if ( $Conf{XferLogLevel} >= 1 ); | logFileAction($exist ? "pool" : "new", $f) if ( $Conf{XferLogLevel} >= 1 ); | |||
} | } | |||
TarFlush($fh, $f->{size}); | TarFlush($fh, $f->{size}); | |||
} elsif ( $f->{type} == BPC_FTYPE_HARDLINK ) { | } elsif ( $f->{type} == BPC_FTYPE_HARDLINK ) { | |||
# | # | |||
# Hardlink to another file. GNU tar is clever about files | # Hardlink to another file. GNU tar is clever about files | |||
# that are hardlinks to each other. The first link will be | # that are hardlinks to each other. The first link will be | |||
# sent as a regular file. The additional links will be sent | # sent as a regular file. The additional links will be sent | |||
# as this type. | # as this type. | |||
# | # | |||
# We promote the file to a hardlink by marking both as files | # We promote the file to a hardlink by marking both as files | |||
# that have hardlinks (nlinks >= 2). | # that have hardlinks (nlinks >= 2). | |||
# | # | |||
my($dir, $target); | my($dir, $target); | |||
# | # | |||
# link targets are relative to the top-level share | # link targets are relative to the top-level share | |||
# | # | |||
$target = $f->{linkname}; | $target = $f->{linkname}; | |||
$target =~ s{^\./+}{}; | $target =~ s{^\./+}{}; | |||
$target =~ s{/+\.?$}{}; | $target =~ s{/+\.?$}{}; | |||
$target =~ s{//+}{/}g; | $target =~ s{//+}{/}g; | |||
my $aTarget = $AttrNew->get($target); | my $aTarget = $AttrNew->get($target); | |||
$aTarget->{poolPath} = $bpc->MD52Path($aTarget->{digest}, $aTarget->{com | $aTarget->{poolPath} = $bpc->MD52Path($aTarget->{digest}, $aTarget->{com | |||
press}) if ( length($aTarget->{digest}) ); | press}) | |||
if ( length($aTarget->{digest}) ); | ||||
if ( $aTarget ) { | if ( $aTarget ) { | |||
if ( $aTarget->{nlinks} == 0 ) { | if ( $aTarget->{nlinks} == 0 ) { | |||
# | # | |||
# Promote the target to a hardlink. | # Promote the target to a hardlink. | |||
# | # | |||
moveFileToOld($aTarget, {name => $target}); | moveFileToOld($aTarget, {name => $target}); | |||
moveFileToOld($a, $f); | moveFileToOld($a, $f); | |||
$aTarget->{nlinks} = 2; | $aTarget->{nlinks} = 2; | |||
$AttrNew->set($target, $aTarget); | $AttrNew->set($target, $aTarget); | |||
$AttrNew->set($f->{name}, $aTarget); | $AttrNew->set($f->{name}, $aTarget); | |||
$DeltaNew->update($aTarget->{compress}, $aTarget->{digest}, 1) | $DeltaNew->update($aTarget->{compress}, $aTarget->{digest}, 1) | |||
if ( length($aTarget->{diges t}) ); | if ( length($aTarget->{digest}) ); | |||
logFileAction("link", $f) | logFileAction("link", $f) | |||
if ( $Conf{XferLogLevel} >= 1 ); | if ( $Conf{XferLogLevel} >= 1 ); | |||
$NewFileCnt++; | $NewFileCnt++; | |||
$NewFileSize += $f->{size}; | $NewFileSize += $f->{size}; | |||
$NewFileCompSize += -s $aTarget->{poolPath} | $NewFileCompSize += -s $aTarget->{poolPath} | |||
if ( -f $aTarget->{poolPath} ); | if ( -f $aTarget->{poolPath} ); | |||
} else { | } else { | |||
# | # | |||
# Copy the target attributes | # Copy the target attributes | |||
# | # | |||
$f->{type} = $aTarget->{type}; | $f->{type} = $aTarget->{type}; | |||
$f->{mode} = $aTarget->{mode}; | $f->{mode} = $aTarget->{mode}; | |||
$f->{uid} = $aTarget->{uid}; | $f->{uid} = $aTarget->{uid}; | |||
$f->{gid} = $aTarget->{gid}; | $f->{gid} = $aTarget->{gid}; | |||
$f->{size} = $aTarget->{size}; | $f->{size} = $aTarget->{size}; | |||
$f->{mtime} = $aTarget->{mtime}; | $f->{mtime} = $aTarget->{mtime}; | |||
$f->{inode} = $aTarget->{inode}; | $f->{inode} = $aTarget->{inode}; | |||
$f->{nlinks} = $aTarget->{nlinks}; | $f->{nlinks} = $aTarget->{nlinks}; | |||
$f->{digest} = $aTarget->{digest}; | $f->{digest} = $aTarget->{digest}; | |||
if ( defined($a) | $f->{xattr} = $aTarget->{xattr}; | |||
&& $a->{type} == $f->{type} | ||||
&& $a->{mode} == $f->{mode} | if ( defined($a) | |||
&& $a->{uid} == $f->{uid} | && $a->{type} == $f->{type} | |||
&& $a->{gid} == $f->{gid} | && $a->{mode} == $f->{mode} | |||
&& $a->{size} == $f->{size} | && $a->{uid} == $f->{uid} | |||
&& $a->{mtime} == $f->{mtime} | && $a->{gid} == $f->{gid} | |||
&& $a->{inode} == $f->{inode} | && $a->{size} == $f->{size} | |||
&& $a->{nlinks} == $f->{nlinks} | && $a->{mtime} == $f->{mtime} | |||
&& $a->{digest} eq $f->{digest} ) { | && $a->{inode} == $f->{inode} | |||
&& $a->{nlinks} == $f->{nlinks} | ||||
&& $a->{digest} eq $f->{digest} ) { | ||||
# | # | |||
# already linked | # already linked | |||
# | # | |||
$same = 1; | $same = 1; | |||
logFileAction("same", $f) | logFileAction("same", $f) | |||
if ( $Conf{XferLogLevel} >= 1 ); | if ( $Conf{XferLogLevel} >= 1 ); | |||
} else { | } else { | |||
# | # | |||
# make a new link | # make a new link | |||
# | # | |||
logFileAction("linkU", $f) | logFileAction("linkU", $f) | |||
if ( $Conf{XferLogLevel} >= 1 ); | if ( $Conf{XferLogLevel} >= 1 ); | |||
moveFileToOld($a, $f); | moveFileToOld($a, $f); | |||
# | # | |||
# Save the old inode, since the number of links will | # Save the old inode, since the number of links will | |||
# be increased | # be increased | |||
# | # | |||
if ( $AttrOld && !$AttrOld->getInode($aTarget->{inode}) ) { | if ( $AttrOld && !$AttrOld->getInode($aTarget->{inode}) ) { | |||
$AttrOld->setInode($aTarget->{inode}, $aTarget); | $AttrOld->setInode($aTarget->{inode}, $aTarget); | |||
$DeltaOld->update($aTarget->{compress}, $aTarget->{diges t}, 1); | $DeltaOld->update($aTarget->{compress}, $aTarget->{diges t}, 1); | |||
} | } | |||
$f->{nlinks}++; | $f->{nlinks}++; | |||
$AttrNew->set($f->{name}, $f); | $AttrNew->set($f->{name}, $f); | |||
} | } | |||
$ExistFileCnt++; | $ExistFileCnt++; | |||
$ExistFileSize += $f->{size}; | $ExistFileSize += $f->{size}; | |||
$ExistFileCompSize += -s $aTarget->{poolPath} | $ExistFileCompSize += -s $aTarget->{poolPath} | |||
if ( -f $aTarget->{poolPath} ); | if ( -f $aTarget->{poolPath} ); | |||
} | } | |||
} else { | } else { | |||
print("Can't find hardlink target $target for $f->{name}\n"); | print("Can't find hardlink target $target for $f->{name}\n"); | |||
$Errors++; | $Errors++; | |||
} | } | |||
return 1; | return 1; | |||
} elsif ( $f->{type} == BPC_FTYPE_SYMLINK ) { | } elsif ( $f->{type} == BPC_FTYPE_SYMLINK ) { | |||
# | # | |||
# Symbolic link: write the value of the link to a plain file, | # Symbolic link: write the value of the link to a plain file, | |||
# that we pool as usual (ie: we don't create a symlink). | # that we pool as usual (ie: we don't create a symlink). | |||
skipping to change at line 711 | skipping to change at line 745 | |||
# | # | |||
# Check if it is the same | # Check if it is the same | |||
# | # | |||
my $oldLink = fileReadAll($a, $f); | my $oldLink = fileReadAll($a, $f); | |||
if ( $oldLink eq $f->{linkname} ) { | if ( $oldLink eq $f->{linkname} ) { | |||
logFileAction("same", $f) if ( $Conf{XferLogLevel} >= 1 ); | logFileAction("same", $f) if ( $Conf{XferLogLevel} >= 1 ); | |||
$digest = $a->{digest}; | $digest = $a->{digest}; | |||
$ExistFileCnt++; | $ExistFileCnt++; | |||
$ExistFileSize += $f->{size}; | $ExistFileSize += $f->{size}; | |||
$ExistFileCompSize += -s $a->{poolPath} | $ExistFileCompSize += -s $a->{poolPath} | |||
if ( -f $a->{poolPath} ); | if ( -f $a->{poolPath} ); | |||
$same = 1; | $same = 1; | |||
} | } | |||
} | } | |||
if ( !$same ) { | if ( !$same ) { | |||
moveFileToOld($a, $f); | moveFileToOld($a, $f); | |||
my $poolWrite = BackupPC::XS::PoolWrite::new($Compress); | my $poolWrite = BackupPC::XS::PoolWrite::new($Compress); | |||
$poolWrite->write(\$f->{linkname}); | $poolWrite->write(\$f->{linkname}); | |||
($exist, $digest) = processClose($poolWrite, $f->{size}); | ($exist, $digest) = processClose($poolWrite, $f->{size}); | |||
logFileAction($exist ? "pool" : "new", $f) if ( $Conf{XferLogLevel} >= 1 ); | logFileAction($exist ? "pool" : "new", $f) if ( $Conf{XferLogLevel} >= 1 ); | |||
} | } | |||
} elsif ( $f->{type} == BPC_FTYPE_CHARDEV | } elsif ( $f->{type} == BPC_FTYPE_CHARDEV | |||
|| $f->{type} == BPC_FTYPE_BLOCKDEV | || $f->{type} == BPC_FTYPE_BLOCKDEV | |||
|| $f->{type} == BPC_FTYPE_FIFO ) { | || $f->{type} == BPC_FTYPE_FIFO ) { | |||
# | # | |||
# Special files: for char and block special we write the | # Special files: for char and block special we write the | |||
# major and minor numbers to a plain file, that we pool | # major and minor numbers to a plain file, that we pool | |||
# as usual. For a pipe file we create an empty file. | # as usual. For a pipe file we create an empty file. | |||
# The attributes remember the original file type. | # The attributes remember the original file type. | |||
# | # | |||
my $data; | my $data; | |||
if ( $f->{type} == BPC_FTYPE_FIFO ) { | if ( $f->{type} == BPC_FTYPE_FIFO ) { | |||
$data = ""; | $data = ""; | |||
} else { | } else { | |||
skipping to change at line 759 | skipping to change at line 793 | |||
if ( !$same ) { | if ( !$same ) { | |||
moveFileToOld($a, $f); | moveFileToOld($a, $f); | |||
my $poolWrite = BackupPC::XS::PoolWrite::new($Compress); | my $poolWrite = BackupPC::XS::PoolWrite::new($Compress); | |||
$poolWrite->write(\$data); | $poolWrite->write(\$data); | |||
$f->{size} = length($data); | $f->{size} = length($data); | |||
($exist, $digest) = processClose($poolWrite, $f->{size}, 0, 1); | ($exist, $digest) = processClose($poolWrite, $f->{size}, 0, 1); | |||
logFileAction($exist ? "pool" : "new", $f) if ( $Conf{XferLogLevel} >= 1 ); | logFileAction($exist ? "pool" : "new", $f) if ( $Conf{XferLogLevel} >= 1 ); | |||
} | } | |||
} else { | } else { | |||
print("Got unknown type $f->{type} for $f->{name}\n") | print("Got unknown type $f->{type} for $f->{name}\n") | |||
if ( $Conf{XferLogLevel} >= 1 ); | if ( $Conf{XferLogLevel} >= 1 ); | |||
$Errors++; | $Errors++; | |||
} | } | |||
$bpc->flushXSLibMesgs(); | $bpc->flushXSLibMesgs(); | |||
# | # | |||
# If the file was the same, we have to check the attributes to see if they | # If the file was the same, we have to check the attributes to see if they | |||
# are the same too. If the file is newly written, we just write the | # are the same too. If the file is newly written, we just write the | |||
# new attributes. | # new attributes. | |||
# | # | |||
my $attribSet = 1; | my $attribSet = 1; | |||
my $newCompress = $Compress; | my $newCompress = $Compress; | |||
$newCompress = $a->{compress} if ( $a && defined($a->{compress}) ); | $newCompress = $a->{compress} if ( $a && defined($a->{compress}) ); | |||
printf("File %s: old digest %s, new digest %s\n", $f->{name}, unpack("H*", $ a->{digest}), unpack("H*", $digest)) | printf("File %s: old digest %s, new digest %s\n", $f->{name}, unpack("H*", $ a->{digest}), unpack("H*", $digest)) | |||
if ( $a && $Conf{XferLogLevel} >= 5 ); | if ( $a && $Conf{XferLogLevel} >= 5 ); | |||
if ( $same && $a ) { | if ( $same && $a ) { | |||
if ( $a->{type} == $f->{type} | if ( $a->{type} == $f->{type} | |||
&& $a->{mode} == $f->{mode} | && $a->{mode} == $f->{mode} | |||
&& $a->{uid} == $f->{uid} | && $a->{uid} == $f->{uid} | |||
&& $a->{gid} == $f->{gid} | && $a->{gid} == $f->{gid} | |||
&& $a->{size} == $f->{size} | && $a->{size} == $f->{size} | |||
&& $a->{mtime} == $f->{mtime} | && $a->{mtime} == $f->{mtime} | |||
&& $a->{digest} eq $digest ) { | && $a->{digest} eq $digest | |||
&& xattrEqual($a->{xattr}, $f->{xattr}) ) { | ||||
# | # | |||
# same contents, same attributes, so no need to rewrite | # same contents, same attributes, so no need to rewrite | |||
# | # | |||
$attribSet = 0; | $attribSet = 0; | |||
} else { | } else { | |||
# | # | |||
# same contents, different attributes, so copy to old and | # same contents, different attributes, so copy to old and | |||
# we will write the new attributes below | # we will write the new attributes below | |||
# | # | |||
if ( $AttrOld && !$AttrOld->get($f->{name}) ) { | if ( $AttrOld && !$AttrOld->get($f->{name}) ) { | |||
skipping to change at line 806 | skipping to change at line 841 | |||
} | } | |||
} | } | |||
$f->{inode} = $a->{inode}; | $f->{inode} = $a->{inode}; | |||
$f->{nlinks} = $a->{nlinks}; | $f->{nlinks} = $a->{nlinks}; | |||
} | } | |||
} else { | } else { | |||
# | # | |||
# file is new or changed; update ref counts | # file is new or changed; update ref counts | |||
# | # | |||
$DeltaNew->update($newCompress, $digest, 1) | $DeltaNew->update($newCompress, $digest, 1) | |||
if ( $digest ne "" ); | if ( $digest ne "" ); | |||
} | } | |||
if ( $attribSet ) { | if ( $attribSet ) { | |||
my $newInode = $f->{inode}; | my $newInode = $f->{inode}; | |||
$newInode = $Inode++ if ( !defined($newInode) ); | $newInode = $Inode++ if ( !defined($newInode) ); | |||
my $nlinks = 0; | my $nlinks = 0; | |||
$nlinks = $f->{nlinks} if ( defined($f->{nlinks}) ); | $nlinks = $f->{nlinks} if ( defined($f->{nlinks}) ); | |||
$AttrNew->set($f->{name}, { | $AttrNew->set( | |||
type => $f->{type}, | $f->{name}, | |||
mode => $f->{mode}, | { | |||
uid => $f->{uid}, | type => $f->{type}, | |||
gid => $f->{gid}, | mode => $f->{mode}, | |||
size => $f->{size}, | uid => $f->{uid}, | |||
mtime => $f->{mtime}, | gid => $f->{gid}, | |||
inode => $newInode, | size => $f->{size}, | |||
nlinks => $nlinks, | mtime => $f->{mtime}, | |||
compress => $newCompress, | inode => $newInode, | |||
digest => $digest, | nlinks => $nlinks, | |||
}); | compress => $newCompress, | |||
digest => $digest, | ||||
xattr => $f->{xattr}, | ||||
} | ||||
); | ||||
} | } | |||
$bpc->flushXSLibMesgs(); | $bpc->flushXSLibMesgs(); | |||
return 1; | return 1; | |||
} | } | |||
sub processClose | sub processClose | |||
{ | { | |||
my($poolWrite, $origSize, $noStats, $noSizeStats) = @_; | my($poolWrite, $origSize, $noStats, $noSizeStats) = @_; | |||
my($exists, $digest, $outSize, $errs) = $poolWrite->close; | my($exists, $digest, $outSize, $errs) = $poolWrite->close; | |||
$Errors += $errs; | $Errors += $errs; | |||
if ( !$noStats ) { | if ( !$noStats ) { | |||
if ( $exists ) { | if ( $exists ) { | |||
$ExistFileCnt++; | $ExistFileCnt++; | |||
if ( !$noSizeStats ) { | if ( !$noSizeStats ) { | |||
$ExistFileSize += $origSize; | $ExistFileSize += $origSize; | |||
$ExistFileCompSize += $outSize; | $ExistFileCompSize += $outSize; | |||
} | } | |||
} else { | } else { | |||
$NewFileCnt++; | $NewFileCnt++; | |||
if ( !$noSizeStats ) { | if ( !$noSizeStats ) { | |||
$NewFileSize += $origSize; | $NewFileSize += $origSize; | |||
$NewFileCompSize += $outSize; | $NewFileCompSize += $outSize; | |||
} | } | |||
} | } | |||
} | } | |||
return ($exists && $origSize > 0, $digest); | return ($exists && $origSize > 0, $digest); | |||
} | } | |||
# | # | |||
# Generate a log file message for a completed file | # Generate a log file message for a completed file | |||
# | # | |||
sub logFileAction | sub logFileAction | |||
{ | { | |||
my($action, $f) = @_; | my($action, $f) = @_; | |||
my $owner = "$f->{uid}/$f->{gid}"; | my $owner = "$f->{uid}/$f->{gid}"; | |||
my $name = $f->{name}; | my $name = $f->{name}; | |||
$name = "." if ( $name eq "" ); | $name = "." if ( $name eq "" ); | |||
$name .= " -> " . $f->{linkname} if ( length($f->{linkname}) ); | $name .= " -> " . $f->{linkname} if ( length($f->{linkname}) ); | |||
my $type = (("", "p", "c", "", "d", "", "b", "", "", "", "l", "", "s")) | my $type = (("", "p", "c", "", "d", "", "b", "", "", "", "l", "", "s"))[($f- | |||
[($f->{mode} & S_IFMT) >> 12]; | >{mode} & S_IFMT) >> 12]; | |||
$type = "h" if ( $f->{type} == BPC_FTYPE_HARDLINK ); | $type = "h" if ( $f->{type} == BPC_FTYPE_HARDLINK ); | |||
$bpc->flushXSLibMesgs(); | $bpc->flushXSLibMesgs(); | |||
printf(" %-6s %1s%4o %9s %11.0f %s\n", | printf(" %-6s %1s%4o %9s %11.0f %s\n", $action, $type, $f->{mode} & 07777, | |||
$action, | $owner, $f->{size}, $name); | |||
$type, | ||||
$f->{mode} & 07777, | ||||
$owner, | ||||
$f->{size}, | ||||
$name); | ||||
} | } | |||
# | # | |||
# Create the parent directory of $fullPath (if necessary). | # Create the parent directory of $fullPath (if necessary). | |||
# If $noStrip != 0 then $fullPath is the directory to create, | # If $noStrip != 0 then $fullPath is the directory to create, | |||
# rather than the parent. | # rather than the parent. | |||
# | # | |||
sub pathCreate | sub pathCreate | |||
{ | { | |||
my($fullPath, $noStrip) = @_; | my($fullPath, $noStrip) = @_; | |||
# | # | |||
# Get parent directory of $fullPath | # Get parent directory of $fullPath | |||
# | # | |||
print("pathCreate: fullPath = $fullPath\n") if ( $Conf{XferLogLevel} >= 6 ) | print("pathCreate: fullPath = $fullPath\n") if ( $Conf{XferLogLevel} >= 6 ); | |||
; | ||||
$fullPath =~ s{/[^/]*$}{} if ( !$noStrip ); | $fullPath =~ s{/[^/]*$}{} if ( !$noStrip ); | |||
return 0 if ( -d $fullPath ); | return 0 if ( -d $fullPath ); | |||
unlink($fullPath) if ( -e $fullPath ); | unlink($fullPath) if ( -e $fullPath ); | |||
eval { mkpath($fullPath, 0, 0777) }; | eval { mkpath($fullPath, 0, 0777) }; | |||
if ( $@ ) { | if ( $@ ) { | |||
print("Can't create $fullPath ($!)\n"); | print("Can't create $fullPath ($!)\n"); | |||
$Errors++; | $Errors++; | |||
return -1; | return -1; | |||
} | } | |||
return 0; | return 0; | |||
} | } | |||
sub catch_signal | sub catch_signal | |||
skipping to change at line 916 | skipping to change at line 949 | |||
my $sigName = shift; | my $sigName = shift; | |||
# | # | |||
# The first time we receive a signal we try to gracefully | # The first time we receive a signal we try to gracefully | |||
# abort the backup. This allows us to keep a partial dump | # abort the backup. This allows us to keep a partial dump | |||
# with the in-progress file deleted and attribute caches | # with the in-progress file deleted and attribute caches | |||
# flushed to disk etc. | # flushed to disk etc. | |||
# | # | |||
if ( !$Abort ) { | if ( !$Abort ) { | |||
print("BackupPC_tarExtract: got signal $sigName\n"); | print("BackupPC_tarExtract: got signal $sigName\n"); | |||
$Abort++; | $Abort++; | |||
$AbortReason = "received signal $sigName"; | $AbortReason = "received signal $sigName"; | |||
$bpc->flushXSLibMesgs(); | $bpc->flushXSLibMesgs(); | |||
return; | return; | |||
} | } | |||
# | # | |||
# This is a second signal: time to clean up. | # This is a second signal: time to clean up. | |||
# | # | |||
print("BackupPC_tarExtract: quitting on second signal $sigName\n"); | print("BackupPC_tarExtract: quitting on second signal $sigName\n"); | |||
exitMesg(); | exitMesg(); | |||
} | } | |||
sub exitMesg | sub exitMesg | |||
skipping to change at line 943 | skipping to change at line 976 | |||
# | # | |||
if ( $AttrNew ) { | if ( $AttrNew ) { | |||
# | # | |||
# Make sure the top-level share has an attribute entry. | # Make sure the top-level share has an attribute entry. | |||
# Normally that is added when any directory appears in the archive. | # Normally that is added when any directory appears in the archive. | |||
# But if the archive only has files, we'll never add entries for | # But if the archive only has files, we'll never add entries for | |||
# the parent directories. | # the parent directories. | |||
# | # | |||
if ( !$AttrNew->get("/") ) { | if ( !$AttrNew->get("/") ) { | |||
print("adding top-level attrib for share $ShareNameUM\n") | print("adding top-level attrib for share $ShareNameUM\n") | |||
if ( $Conf{XferLogLevel} >= 4 ); | if ( $Conf{XferLogLevel} >= 4 ); | |||
my $fNew = { | my $fNew = { | |||
name => $ShareNameUM, | name => $ShareNameUM, | |||
type => BPC_FTYPE_DIR, | type => BPC_FTYPE_DIR, | |||
mode => 0775, | mode => 0775, | |||
uid => 0, | uid => 0, | |||
gid => 0, | gid => 0, | |||
size => 0, | size => 0, | |||
mtime => time(), | mtime => time(), | |||
inode => $Inode++, | inode => $Inode++, | |||
nlinks => 0, | nlinks => 0, | |||
compress => $Compress, | compress => $Compress, | |||
}; | }; | |||
$AttrNew->set("/", $fNew); | $AttrNew->set("/", $fNew); | |||
} | } | |||
$AttrNew->flush(1); | $AttrNew->flush(1); | |||
$bpc->flushXSLibMesgs(); | $bpc->flushXSLibMesgs(); | |||
} | } | |||
if ( $AttrOld ) { | if ( $AttrOld ) { | |||
$AttrOld->flush(1); | $AttrOld->flush(1); | |||
$bpc->flushXSLibMesgs(); | $bpc->flushXSLibMesgs(); | |||
} | } | |||
skipping to change at line 985 | skipping to change at line 1018 | |||
$DeltaNew->flush(); | $DeltaNew->flush(); | |||
$DeltaOld->flush() if ( $DeltaOld ); | $DeltaOld->flush() if ( $DeltaOld ); | |||
if ( $Abort ) { | if ( $Abort ) { | |||
print("BackupPC_tarExtact aborting ($AbortReason)\n"); | print("BackupPC_tarExtact aborting ($AbortReason)\n"); | |||
} | } | |||
# | # | |||
# Report results to BackupPC_dump | # Report results to BackupPC_dump | |||
# | # | |||
my $TotalFileCnt = $ExistFileCnt + $NewFileCnt; | my $TotalFileCnt = $ExistFileCnt + $NewFileCnt; | |||
my $TotalFileSize = $ExistFileSize + $NewFileSize; | my $TotalFileSize = $ExistFileSize + $NewFileSize; | |||
$bpc->flushXSLibMesgs(); | $bpc->flushXSLibMesgs(); | |||
$Errors += BackupPC::XS::Lib::logErrorCntGet(); | $Errors += BackupPC::XS::Lib::logErrorCntGet(); | |||
printProgress(); | printProgress(); | |||
print("Done: $Errors errors," | print( "Done: $Errors errors," | |||
. " $ExistFileCnt filesExist, $ExistFileSize sizeExist, $ExistFileCompSi | . " $ExistFileCnt filesExist, $ExistFileSize sizeExist, $ExistFileComp | |||
ze sizeExistComp," | Size sizeExistComp," | |||
. " $TotalFileCnt filesTotal, $TotalFileSize sizeTotal," | . " $TotalFileCnt filesTotal, $TotalFileSize sizeTotal," | |||
. " $NewFileCnt filesNew, $NewFileSize sizeNew, $NewFileCompSize sizeNew | . " $NewFileCnt filesNew, $NewFileSize sizeNew, $NewFileCompSize sizeN | |||
Comp, $Inode inodeLast\n"); | ewComp, $Inode inodeLast\n"); | |||
exit($FatalErrors ? 1 : 0); | exit($FatalErrors ? 1 : 0); | |||
} | } | |||
####################################################################### | ####################################################################### | |||
# For full backups we need to remember which files are in each | # For full backups we need to remember which files are in each | |||
# directory so that we can delete any files that didn't get sent | # directory so that we can delete any files that didn't get sent | |||
# in the archive. | # in the archive. | |||
####################################################################### | ####################################################################### | |||
my %DirCache; | my %DirCache; | |||
skipping to change at line 1024 | skipping to change at line 1057 | |||
my($dir) = @_; | my($dir) = @_; | |||
return if ( !$Full ); | return if ( !$Full ); | |||
$dir =~ s{/+$}{}; | $dir =~ s{/+$}{}; | |||
$dir =~ s{^/+}{}; | $dir =~ s{^/+}{}; | |||
$dir = "/$dir"; | $dir = "/$dir"; | |||
return if ( defined($DirCache{$dir}) ); | return if ( defined($DirCache{$dir}) ); | |||
print("dirCacheNewDir: populating dir = $dir\n") | print("dirCacheNewDir: populating dir = $dir\n") | |||
if ( $Conf{XferLogLevel} >= 4 ); | if ( $Conf{XferLogLevel} >= 4 ); | |||
my $all = $AttrNew->getAll($dir); | my $all = $AttrNew->getAll($dir); | |||
$bpc->flushXSLibMesgs(); | $bpc->flushXSLibMesgs(); | |||
foreach my $name ( keys(%$all) ) { | foreach my $name ( keys(%$all) ) { | |||
next if ( $name eq "." || $name eq ".." ); | next if ( $name eq "." || $name eq ".." ); | |||
print("dirCacheNewDir: populating dir = $dir with $name\n") | print("dirCacheNewDir: populating dir = $dir with $name\n") | |||
if ( $Conf{XferLogLevel} >= 4 ); | if ( $Conf{XferLogLevel} >= 4 ); | |||
$DirCache{$dir}{$name} = 1; | $DirCache{$dir}{$name} = 1; | |||
} | } | |||
dirCacheFlush($dir); | dirCacheFlush($dir); | |||
$bpc->flushXSLibMesgs(); | $bpc->flushXSLibMesgs(); | |||
} | } | |||
# | # | |||
# Called each time we encounter a new file | # Called each time we encounter a new file | |||
# | # | |||
# Does nothing if this isn't a Full backup. | # Does nothing if this isn't a Full backup. | |||
skipping to change at line 1061 | skipping to change at line 1094 | |||
$dir = $1; | $dir = $1; | |||
$file = $2; | $file = $2; | |||
} else { | } else { | |||
$dir = ""; | $dir = ""; | |||
$file = $path; | $file = $path; | |||
} | } | |||
$dir =~ s{/+$}{}; | $dir =~ s{/+$}{}; | |||
$dir =~ s{^/+}{}; | $dir =~ s{^/+}{}; | |||
$dir = "/$dir"; | $dir = "/$dir"; | |||
print("dirCacheNewFile: path = $path: dir = $dir, file = $file\n") | print("dirCacheNewFile: path = $path: dir = $dir, file = $file\n") | |||
if ( $Conf{XferLogLevel} >= 5 ); | if ( $Conf{XferLogLevel} >= 5 ); | |||
dirCacheNewDir($dir) if ( !defined($DirCache{$dir}) ); | dirCacheNewDir($dir) if ( !defined($DirCache{$dir}) ); | |||
delete($DirCache{$dir}{$file}); | delete($DirCache{$dir}{$file}); | |||
} | } | |||
# | # | |||
# Called to flush directories whose path is disjoint | # Called to flush directories whose path is disjoint | |||
# from the given directory. When a directory is flushed | # from the given directory. When a directory is flushed | |||
# we delete any files that were not encountered during the | # we delete any files that were not encountered during the | |||
# extract. This is how we update deleted files. | # extract. This is how we update deleted files. | |||
# | # | |||
skipping to change at line 1088 | skipping to change at line 1121 | |||
{ | { | |||
my($dir) = @_; | my($dir) = @_; | |||
return if ( !$Full ); | return if ( !$Full ); | |||
foreach my $d ( keys(%DirCache) ) { | foreach my $d ( keys(%DirCache) ) { | |||
next if ( defined($dir) && ($dir =~ m{^\Q$d/} || $d eq "/" || $dir eq $d ) ); | next if ( defined($dir) && ($dir =~ m{^\Q$d/} || $d eq "/" || $dir eq $d ) ); | |||
print("dirCacheFlush($dir): flushing $d\n") if ( $Conf{XferLogLevel} >= 5 ); | print("dirCacheFlush($dir): flushing $d\n") if ( $Conf{XferLogLevel} >= 5 ); | |||
foreach my $file ( keys(%{$DirCache{$d}}) ) { | foreach my $file ( keys(%{$DirCache{$d}}) ) { | |||
my $name = "$d/$file"; | my $name = "$d/$file"; | |||
my $a = $AttrNew->get($name); | my $a = $AttrNew->get($name); | |||
if ( !$a ) { | if ( !$a ) { | |||
print("dirCacheFlush($dir): skipping $d/$file since it has no at tributes\n") | print("dirCacheFlush($dir): skipping $d/$file since it has no at tributes\n") | |||
if ( $Conf{XferLogLevel} >= 5 ); | if ( $Conf{XferLogLevel} >= 5 ); | |||
next; | next; | |||
} | } | |||
if ( $a && $a->{inode} >= $Inode0 ) { | if ( $a && $a->{inode} >= $Inode0 ) { | |||
# | # | |||
# shouldn't happen - but if it's a new file then | # shouldn't happen - but if it's a new file then | |||
# don't delete it | # don't delete it | |||
# | # | |||
print("dirCacheFlush($dir): skipping $d/$file ($a->{inode} vs $I node0)\n") | print("dirCacheFlush($dir): skipping $d/$file ($a->{inode} vs $I node0)\n") | |||
if ( $Conf{XferLogLevel} >= 5 ); | if ( $Conf{XferLogLevel} >= 5 ); | |||
next; | next; | |||
} | } | |||
# | # | |||
# this file didn't appear in the new full tar archive, | # this file didn't appear in the new full tar archive, | |||
# so move it to old. | # so move it to old. | |||
# | # | |||
$name =~ s{//+}{/}g; | $name =~ s{//+}{/}g; | |||
$name =~ s{^\.?/+}{}; | $name =~ s{^\.?/+}{}; | |||
logFileAction("delete", { %$a, name => $name }) if ( $Conf{XferLogLe vel} >= 1 ); | logFileAction("delete", {%$a, name => $name}) if ( $Conf{XferLogLeve l} >= 1 ); | |||
if ( $a->{nlinks} > 0 ) { | if ( $a->{nlinks} > 0 ) { | |||
my $aOld = $AttrOld->getInode($a->{inode}) if ( $AttrOld ); | my $aOld = $AttrOld->getInode($a->{inode}) if ( $AttrOld ); | |||
if ( !$aOld && $AttrOld ) { | if ( !$aOld && $AttrOld ) { | |||
# | # | |||
# copy the inode to old | # copy the inode to old | |||
# | # | |||
print("dirCacheFlush(): unlink($name) -> setting old inode ( nlinks = $a->{nlinks})\n") | print("dirCacheFlush(): unlink($name) -> setting old inode ( nlinks = $a->{nlinks})\n") | |||
if ( $Conf{XferLogLevel} >= 3 ); | if ( $Conf{XferLogLevel} >= 3 ); | |||
$AttrOld->setInode($a->{inode}, $a); | $AttrOld->setInode($a->{inode}, $a); | |||
$DeltaOld->update($a->{compress}, $a->{digest}, 1); | $DeltaOld->update($a->{compress}, $a->{digest}, 1); | |||
} | } | |||
# | # | |||
# If this file is older than this backup, then move it | # If this file is older than this backup, then move it | |||
# to old (don't update the inode). | # to old (don't update the inode). | |||
# | # | |||
if ( $a && $a->{inode} < $Inode0 && $AttrOld && !$AttrOld->get($ name) ) { | if ( $a && $a->{inode} < $Inode0 && $AttrOld && !$AttrOld->get($ name) ) { | |||
print("dirCacheFlush(): unlink($name) -> setting old file (n links = $a->{nlinks})\n") | print("dirCacheFlush(): unlink($name) -> setting old file (n links = $a->{nlinks})\n") | |||
if ( $Conf{XferLogLevel} >= 3 ); | if ( $Conf{XferLogLevel} >= 3 ); | |||
$AttrOld->set($name, $a, 1); | $AttrOld->set($name, $a, 1); | |||
} | } | |||
# | # | |||
# now reduce the number of links and update the inode; | # now reduce the number of links and update the inode; | |||
# ref count is handled above. | # ref count is handled above. | |||
# | # | |||
$a->{nlinks}--; | $a->{nlinks}--; | |||
if ( $a->{nlinks} <= 0 ) { | if ( $a->{nlinks} <= 0 ) { | |||
$AttrNew->deleteInode($a->{inode}); | $AttrNew->deleteInode($a->{inode}); | |||
skipping to change at line 1159 | skipping to change at line 1192 | |||
delete($DirCache{$d}); | delete($DirCache{$d}); | |||
} | } | |||
} | } | |||
sub copyInodes | sub copyInodes | |||
{ | { | |||
my($dirName) = @_; | my($dirName) = @_; | |||
return if ( !defined($AttrOld) ); | return if ( !defined($AttrOld) ); | |||
my $dirPath = $AttrNew->getFullMangledPath($dirName); | my $dirPath = $AttrNew->getFullMangledPath($dirName); | |||
print("copyInodes: dirName = $dirName, dirPath = $dirPath\n") if ( $Conf{Xfe rLogLevel} >= 4 ); | print("copyInodes: dirName = $dirName, dirPath = $dirPath\n") if ( $Conf{Xfe rLogLevel} >= 4 ); | |||
my $attrAll = $AttrNew->getAll($dirName); | my $attrAll = $AttrNew->getAll($dirName); | |||
$bpc->flushXSLibMesgs(); | $bpc->flushXSLibMesgs(); | |||
# | # | |||
# Add non-attrib directories (ie: directories that were created | # Add non-attrib directories (ie: directories that were created | |||
# to store attributes in deeper directories), since these | # to store attributes in deeper directories), since these | |||
# directories may not appear in the attrib file at this level. | # directories may not appear in the attrib file at this level. | |||
# | # | |||
if ( defined(my $entries = BackupPC::DirOps::dirRead($bpc, $dirPath)) ) { | if ( defined(my $entries = BackupPC::DirOps::dirRead($bpc, $dirPath)) ) { | |||
foreach my $e ( @$entries ) { | foreach my $e ( @$entries ) { | |||
next if ( $e->{name} eq "." | next if ( $e->{name} eq "." | |||
|| $e->{name} eq ".." | || $e->{name} eq ".." | |||
|| $e->{name} eq "inode" | || $e->{name} eq "inode" | |||
|| !-d "$dirPath/$e->{name}" ); | || !-d "$dirPath/$e->{name}" ); | |||
my $fileUM = $bpc->fileNameUnmangle($e->{name}); | my $fileUM = $bpc->fileNameUnmangle($e->{name}); | |||
next if ( $attrAll && defined($attrAll->{$fileUM}) ); | next if ( $attrAll && defined($attrAll->{$fileUM}) ); | |||
$attrAll->{$fileUM} = { | $attrAll->{$fileUM} = { | |||
type => BPC_FTYPE_DIR, | type => BPC_FTYPE_DIR, | |||
noAttrib => 1, | noAttrib => 1, | |||
}; | }; | |||
} | } | |||
} | } | |||
foreach my $fileUM ( keys(%$attrAll) ) { | foreach my $fileUM ( keys(%$attrAll) ) { | |||
next if ( $fileUM eq "." || $fileUM eq ".." ); | next if ( $fileUM eq "." || $fileUM eq ".." ); | |||
my $a = $attrAll->{$fileUM}; | my $a = $attrAll->{$fileUM}; | |||
if ( $a->{type} == BPC_FTYPE_DIR ) { | if ( $a->{type} == BPC_FTYPE_DIR ) { | |||
# | # | |||
# recurse into this directory | # recurse into this directory | |||
# | # | |||
copyInodes("$dirName/$fileUM"); | copyInodes("$dirName/$fileUM"); | |||
next; | next; | |||
} | } | |||
print("copyInodes($dirName): $fileUM has inode=$a->{inode}, links = $a-> | print("copyInodes($dirName): $fileUM has inode=$a->{inode}, links = $a-> | |||
{nlinks}\n") if ( $Conf{XferLogLevel} >= 6 ); | {nlinks}\n") | |||
if ( $Conf{XferLogLevel} >= 6 ); | ||||
next if ( $a->{nlinks} == 0 ); | next if ( $a->{nlinks} == 0 ); | |||
# | # | |||
# Copy the inode if it doesn't exist in old and increment the | # Copy the inode if it doesn't exist in old and increment the | |||
# digest reference count. | # digest reference count. | |||
my $aInode = $AttrNew->getInode($a->{inode}); | my $aInode = $AttrNew->getInode($a->{inode}); | |||
if ( !defined($AttrOld->getInode($a->{inode})) ) { | if ( !defined($AttrOld->getInode($a->{inode})) ) { | |||
print("copyInodes($dirName): $fileUM moving inode $a->{inode} to old \n") if ( $Conf{XferLogLevel} >= 5 ); | print("copyInodes($dirName): $fileUM moving inode $a->{inode} to old \n") if ( $Conf{XferLogLevel} >= 5 ); | |||
$AttrOld->setInode($a->{inode}, $aInode); | $AttrOld->setInode($a->{inode}, $aInode); | |||
$DeltaOld->update($Compress, $aInode->{digest}, 1); | $DeltaOld->update($Compress, $aInode->{digest}, 1); | |||
} | } | |||
End of changes. 91 change blocks. | ||||
247 lines changed or deleted | 294 lines changed or added |