"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "bin/BackupPC_backupDelete" 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).

BackupPC_backupDelete  (BackupPC-4.3.2):BackupPC_backupDelete  (BackupPC-4.4.0)
skipping to change at line 34 skipping to change at line 34
# 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 Getopt::Std; use Getopt::Std;
use File::Copy; use File::Copy;
use File::Path; use File::Path;
use Data::Dumper; use Data::Dumper;
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 );
my $Errors = 0; my $Errors = 0;
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 $Hosts = $bpc->HostInfoRead(); my $Hosts = $bpc->HostInfoRead();
my $FileCnt = 0; my $FileCnt = 0;
my $FileCntNext = 100; my $FileCntNext = 100;
my $DirCnt = 0; my $DirCnt = 0;
my $DirCntNext = 20; my $DirCntNext = 20;
my $doRefCountUpdate; my $doRefCountUpdate;
my %opts; my %opts;
if ( !getopts("lLpmrh:n:s:", \%opts) || !defined($opts{h}) || !defined($opts{n})
|| (!defined($opts{s}) && @ARGV >= 1) ) { if ( !getopts("flLpmrh:n:s:", \%opts)
|| !defined($opts{h})
|| !defined($opts{n})
|| (!defined($opts{s}) && @ARGV >= 1) ) {
print STDERR <<EOF; print STDERR <<EOF;
usage: BackupPC_backupDelete -h host -n num [-p] [-l] [-L] [-r] [-m] [-s shareNa me [dirs...]] usage: BackupPC_backupDelete -h host -n num [-f] [-l] [-L] [-m] [-p] [-r] [-s sh areName [dirs...]]
Options: Options:
-h host host name -h host host name
-n num backup number to delete -n num backup number to delete
-s shareName don't delete the backup; delete just this share -s shareName don't delete the backup; delete just this share
(or only dirs below this share if specified) (or only dirs below this share if specified)
-p don't print progress information -f force delete even if keep is set for this backup
-L log output to host's LOG file (instead of stdout) -L log output to host's LOG file (instead of stdout)
-l don't remove XferLOG files -l don't remove XferLOG files
-r do a ref count update (default: none)
-m run even if a backup on this host is running -m run even if a backup on this host is running
(specifically, don't take the server host mutex) (specifically, don't take the server host mutex)
-p don't print progress information
-r do a ref count update (default: none)
If a shareName is specified, just that share (or share/dirs) are deleted. If a shareName is specified, just that share (or share/dirs) are deleted.
The backup itself is not deleted, nor is the log file removed. The backup itself is not deleted, nor is the log file removed.
EOF EOF
exit(1); exit(1);
} }
if ( $opts{h} !~ /^([\w\.\s-]+)$/ if ( $opts{h} !~ /^([\w\.\s-]+)$/
|| $opts{h} =~ m{(^|/)\.\.(/|$)} || $opts{h} =~ m{(^|/)\.\.(/|$)}
|| !defined($Hosts->{$opts{h}}) ) { || !defined($Hosts->{$opts{h}}) ) {
print(STDERR "BackupPC_backupDelete: bad host name '$opts{h}'\n"); print(STDERR "BackupPC_backupDelete: bad host name '$opts{h}'\n");
exit(1); exit(1);
} }
my $Host = $opts{h}; my $Host = $opts{h};
if ( defined(my $error = $bpc->ConfigRead($Host)) ) { if ( defined(my $error = $bpc->ConfigRead($Host)) ) {
print(STDERR "BackupPC_backupDelete: Can't read $Host's config file: $error\ n"); print(STDERR "BackupPC_backupDelete: Can't read $Host's config file: $error\ n");
exit(1); exit(1);
} }
%Conf = $bpc->Conf(); %Conf = $bpc->Conf();
if ( !$opts{m} && !defined($bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPo if ( !$opts{m}
rt})) && !defined($bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort}))
&& (my $status = $bpc->ServerMesg("hostMutex $Host -1 BackupPC_bac && (my $status = $bpc->ServerMesg("hostMutex $Host -1 BackupPC_backupDelete"
kupDelete")) =~ /fail/ ) { )) =~ /fail/ ) {
print(STDERR "$0: $status (use -m option to force running)\n"); print(STDERR "$0: $status (use -m option to force running)\n");
exit(1); exit(1);
} }
BackupPC::XS::Lib::logLevelSet($Conf{XferLogLevel}); BackupPC::XS::Lib::logLevelSet($Conf{XferLogLevel});
my $LogLevel = $Conf{XferLogLevel}; my $LogLevel = $Conf{XferLogLevel};
# #
# Write new-style attrib files (<= 4.0.0beta3 uses old-style), which are 0-lengt h # Write new-style attrib files (<= 4.0.0beta3 uses old-style), which are 0-lengt h
# files with the digest encoded in the file name (eg: attrib_md5HexDigest). We # files with the digest encoded in the file name (eg: attrib_md5HexDigest). We
skipping to change at line 128 skipping to change at line 136
exit(1); exit(1);
} }
$bpc->ChildInit(); $bpc->ChildInit();
my($idx, $idxMerge); my($idx, $idxMerge);
my @Backups = $bpc->BackupInfoRead($Host); my @Backups = $bpc->BackupInfoRead($Host);
for ( my $i = 0 ; $i < @Backups ; $i++ ) { for ( my $i = 0 ; $i < @Backups ; $i++ ) {
$idx = $i if ( $opts{n} == $Backups[$i]{num} ); $idx = $i if ( $opts{n} == $Backups[$i]{num} );
$idxMerge = $i if ( $opts{n} > $Backups[$i]{num} $idxMerge = $i
&& (!defined($idxMerge) || $Backups[$i]{num} > $Backups[ if ( $opts{n} > $Backups[$i]{num}
$idxMerge]{num}) ); && (!defined($idxMerge) || $Backups[$i]{num} > $Backups[$idxMerge]{num})
);
} }
if ( !defined($idx) ) { if ( !defined($idx) ) {
print(STDERR "BackupPC_backupDelete: can't find backup number $opts{n} on ho st $Host\n"); print(STDERR "BackupPC_backupDelete: can't find backup number $opts{n} on ho st $Host\n");
exit(1); exit(1);
} }
if ( $Backups[$idx]{keep} && !$opts{f} ) {
print(STDERR
"BackupPC_backupDelete: host $Host backup #$Backups[$idx]{num} has kee
p set; not deleting (use -f to override)\n"
);
exit(1);
}
my($LogFd); my($LogFd);
if ( $opts{L} ) { if ( $opts{L} ) {
my($delPid, $delFd); my($delPid, $delFd);
# #
# Fork into a parent which reads stdout and puts the output into # Fork into a parent which reads stdout and puts the output into
# the client's LOG file, while the child runs BackupPC_backupDelete. # the client's LOG file, while the child runs BackupPC_backupDelete.
# #
if ( !defined($delPid = open($delFd, "-|")) ) { if ( !defined($delPid = open($delFd, "-|")) ) {
print(STDERR "BackupPC_backupDelete: can't fork to -L logging\n"); print(STDERR "BackupPC_backupDelete: can't fork to -L logging\n");
exit(1); exit(1);
} }
binmode($delFd); binmode($delFd);
if ( !$delPid ) { if ( !$delPid ) {
# #
# This is the child: clone STDERR to STDOUT then continue # This is the child: clone STDERR to STDOUT then continue
# #
setpgrp 0,0; setpgrp 0, 0;
close(STDERR); close(STDERR);
open(STDERR, ">&STDOUT"); open(STDERR, ">&STDOUT");
} else { } else {
# #
# This is the parent; open the $Host LOG file and write $delFd # This is the parent; open the $Host LOG file and write $delFd
# there # there
# #
my($LogPath, $pids); my($LogPath, $pids);
($LogFd, $LogPath) = $bpc->openPCLogFile($Host); ($LogFd, $LogPath) = $bpc->openPCLogFile($Host);
if ( !defined($LogFd) ) { if ( !defined($LogFd) ) {
print(STDERR "BackupPC_backupDelete: unable to open/create $LogPath; print(STDERR "BackupPC_backupDelete: unable to open/create $LogPath;
exiting\n"); exiting\n");
exit(1); exit(1);
} }
$pids->{$$} = 1; $pids->{$$} = 1;
$pids->{$delPid} = 1; $pids->{$delPid} = 1;
pidHandler(keys(%$pids)); pidHandler(keys(%$pids));
while ( <$delFd> ) { while ( <$delFd> ) {
if ( $_ =~ /^__bpc_progress_/ ) { if ( $_ =~ /^__bpc_progress_/ ) {
print($_); print($_);
} elsif ( $_ =~ /^__bpc_pidStart__ (\d+)/ ) { } elsif ( $_ =~ /^__bpc_pidStart__ (\d+)/ ) {
$pids->{$1} = 1; $pids->{$1} = 1;
pidHandler(keys(%$pids)); pidHandler(keys(%$pids));
} elsif ( $_ =~ /^__bpc_pidEnd__ (\d+)/ ) { } elsif ( $_ =~ /^__bpc_pidEnd__ (\d+)/ ) {
delete($pids->{$1}); delete($pids->{$1});
pidHandler(keys(%$pids)); pidHandler(keys(%$pids));
} else { } else {
logMsg($_); logMsg($_);
} }
} }
close($delFd); close($delFd);
exit(0); exit(0);
} }
} else { } else {
print("__bpc_pidStart__ $$\n") if ( !$opts{p} ); print("__bpc_pidStart__ $$\n") if ( !$opts{p} );
} }
my($MergeCompress, $MergeDir, $MergeFilled); my($MergeCompress, $MergeDir, $MergeFilled);
my($AttrDel, $DeltaDel, $AttrMerge, $DeltaMerge); my($AttrDel, $DeltaDel, $AttrMerge, $DeltaMerge);
my $HostDir = "$TopDir/pc/$Host"; my $HostDir = "$TopDir/pc/$Host";
my $DelCompress = $Backups[$idx]{compress}; my $DelCompress = $Backups[$idx]{compress};
my $DelTopDir = "$HostDir/$Backups[$idx]{num}"; my $DelTopDir = "$HostDir/$Backups[$idx]{num}";
my $DelPaths = @ARGV ? [@ARGV] : [undef]; my $DelPaths = @ARGV ? [@ARGV] : [undef];
my $ShareName = $opts{s}; my $ShareName = $opts{s};
my $ShareNameM = $bpc->fileNameEltMangle($ShareName); my $ShareNameM = $bpc->fileNameEltMangle($ShareName);
if ( $Backups[$idx]{version} < 4 ) { if ( $Backups[$idx]{version} < 4 ) {
if ( defined($ShareName) ) { if ( defined($ShareName) ) {
foreach my $delPath ( @$DelPaths ) { foreach my $delPath ( @$DelPaths ) {
logMsg("BackupPC_backupDelete: removing pre-v4 path #$Backups[$idx]{ logMsg("BackupPC_backupDelete: removing pre-v4 path #$Backups[$idx]{
num}/$ShareName/$delPath\n") if ( $LogLevel >= 1 ); num}/$ShareName/$delPath\n")
if ( $LogLevel >= 1 );
if ( !$opts{p} ) { if ( !$opts{p} ) {
print("__bpc_progress_state__ delete #$Backups[$idx]{num}/$Share Name/$delPath\n"); print("__bpc_progress_state__ delete #$Backups[$idx]{num}/$Share Name/$delPath\n");
} }
# #
# no reference counting for pre V4 - just remove the tree(s) # no reference counting for pre V4 - just remove the tree(s)
# #
my $delPathM = $bpc->fileNameMangle($delPath); my $delPathM = $bpc->fileNameMangle($delPath);
BackupPC::DirOps::RmTreeQuiet($bpc, "$DelTopDir/$ShareNameM/$delPath M", -2, undef, undef, \&progressUpdate); BackupPC::DirOps::RmTreeQuiet($bpc, "$DelTopDir/$ShareNameM/$delPath M", -2, undef, undef, \&progressUpdate);
} }
} else { } else {
skipping to change at line 228 skipping to change at line 244
# #
BackupPC::DirOps::RmTreeQuiet($bpc, $DelTopDir, -2, undef, undef, \&prog ressUpdate); BackupPC::DirOps::RmTreeQuiet($bpc, $DelTopDir, -2, undef, undef, \&prog ressUpdate);
} }
} else { } else {
if ( defined($ShareName) ) { if ( defined($ShareName) ) {
# #
# Since we are only deleting portions of the backup tree, we # Since we are only deleting portions of the backup tree, we
# need to update reference counts # need to update reference counts
# #
$DeltaDel = BackupPC::XS::DeltaRefCnt::new($DelTopDir); $DeltaDel = BackupPC::XS::DeltaRefCnt::new($DelTopDir);
$AttrDel = BackupPC::XS::AttribCache::new($Host, $Backups[$idx]{num}, $S hareName, $Backups[$idx]{compress}); $AttrDel = BackupPC::XS::AttribCache::new($Host, $Backups[$idx]{num}, $ ShareName, $Backups[$idx]{compress});
$AttrDel->setDeltaInfo($DeltaDel); $AttrDel->setDeltaInfo($DeltaDel);
# #
# Create a needFsck file, so if we are killed and can't recover, we can # Create a needFsck file, so if we are killed and can't recover, we can
# make sure an fsck is run next time. # make sure an fsck is run next time.
# #
mkdir("$DelTopDir/refCnt", 0770) if ( !-d "$DelTopDir/refCnt" ); mkdir("$DelTopDir/refCnt", 0770) if ( !-d "$DelTopDir/refCnt" );
my $needFsckFH; my $needFsckFH;
if ( !(open($needFsckFH, ">", "$DelTopDir/refCnt/needFsck.del") && close ($needFsckFH)) ) { if ( !(open($needFsckFH, ">", "$DelTopDir/refCnt/needFsck.del") && close ($needFsckFH)) ) {
logMsg("BackupPC_backupDelete: can't create $DelTopDir/refCnt/needFs ck.del ($?)\n"); logMsg("BackupPC_backupDelete: can't create $DelTopDir/refCnt/needFs ck.del ($?)\n");
} }
} else { } else {
$AttrDel = BackupPC::XS::AttribCache::new($Host, $Backups[$idx]{num}, "" , $Backups[$idx]{compress}); $AttrDel = BackupPC::XS::AttribCache::new($Host, $Backups[$idx]{num}, "" , $Backups[$idx]{compress});
} }
foreach my $delPath ( @$DelPaths ) { foreach my $delPath ( @$DelPaths ) {
my($delPathM, $fullDelPath); my($delPathM, $fullDelPath);
if ( defined($delPath) ) { if ( defined($delPath) ) {
$fullDelPath = "/$delPath" if ( $delPath !~ m{^/} ); $fullDelPath = $delPath;
$fullDelPath = "/$fullDelPath" if ( $fullDelPath !~ m{^/} );
$delPathM = $bpc->fileNameMangle($delPath); $delPathM = $bpc->fileNameMangle($delPath);
$fullDelPath = "/$ShareName$fullDelPath"; $fullDelPath = "/$ShareName$fullDelPath";
$delPathM = "/$ShareNameM$delPathM"; $delPathM = "/$ShareNameM$delPathM";
$fullDelPath =~ s{//+}{/}; $fullDelPath =~ s{//+}{/};
$delPathM =~ s{//+}{/}; $delPathM =~ s{//+}{/};
} }
logMsg("BackupPC_backupDelete: removing #$Backups[$idx]{num}$fullDelPath \n") if ( $LogLevel >= 1 ); logMsg("BackupPC_backupDelete: removing #$Backups[$idx]{num}$fullDelPath \n") if ( $LogLevel >= 1 );
if ( !defined($idxMerge) if ( !defined($idxMerge) || $Backups[$idxMerge]{version} < 4 || !$Backup
|| $Backups[$idxMerge]{version} < 4 s[$idxMerge]{noFill} ) {
|| !$Backups[$idxMerge]{noFill} ) {
# #
# Either this is oldest backup or prior is non-V4 or prior # Either this is oldest backup or prior is non-V4 or prior
# is filled. There is no need to merge into the prior backup, # is filled. There is no need to merge into the prior backup,
# so just delete the tree. # so just delete the tree.
# #
if ( !$opts{p} ) { if ( !$opts{p} ) {
print("__bpc_progress_state__ delete #$Backups[$idx]{num}$fullDe lPath\n"); print("__bpc_progress_state__ delete #$Backups[$idx]{num}$fullDe lPath\n");
} }
logMsg("BackupPC_backupDelete: No prior backup for merge\n") if ( $L ogLevel >= 1 ); logMsg("BackupPC_backupDelete: No prior backup for merge\n") if ( $L ogLevel >= 1 );
BackupPC::DirOps::RmTreeQuiet($bpc, "$DelTopDir$delPathM", $DelCompr BackupPC::DirOps::RmTreeQuiet($bpc, "$DelTopDir$delPathM", $DelCompr
ess, $DeltaDel, $AttrDel, \&progressUpdate); ess, $DeltaDel, $AttrDel,
\&progressUpdate);
if ( defined($delPath) ) { if ( defined($delPath) ) {
my $ret = $AttrDel->delete($delPath); my $ret = $AttrDel->delete($delPath);
logMsg("AttrDel($delPath) returns $ret\n") if ( $LogLevel >= 5 ) ; logMsg("AttrDel($delPath) returns $ret\n") if ( $LogLevel >= 5 ) ;
$AttrDel->flush(1); $AttrDel->flush(1);
$bpc->flushXSLibMesgs(); $bpc->flushXSLibMesgs();
} }
} else { } else {
if ( !$opts{p} ) { if ( !$opts{p} ) {
print("__bpc_progress_state__ merge #$Backups[$idx]{num}$fullDel print(
Path -> #$Backups[$idxMerge]{num}$fullDelPath\n"); "__bpc_progress_state__ merge #$Backups[$idx]{num}$fullDelPa
th -> #$Backups[$idxMerge]{num}$fullDelPath\n"
);
} }
logMsg("BackupPC_backupDelete: Merge into backup $Backups[$idxMerge] logMsg("BackupPC_backupDelete: Merge into backup $Backups[$idxMerge]
{num}$fullDelPath\n") if ( $LogLevel >= 1 ); {num}$fullDelPath\n")
if ( $LogLevel >= 1 );
$Backups[$idxMerge]{noFill} = $Backups[$idx]{noFill}; $Backups[$idxMerge]{noFill} = $Backups[$idx]{noFill};
if ( ($Backups[$idx]{compress} == 0 && $Backups[$idxMerge]{compress} if ( ($Backups[$idx]{compress} == 0 && $Backups[$idxMerge]{compres
!= 0) s} != 0)
|| ($Backups[$idx]{compress} != 0 && $Backups[$idxMerge]{com || ($Backups[$idx]{compress} != 0 && $Backups[$idxMerge]{compres
press} == 0) ) { s} == 0) ) {
printf(STDERR "BackupPC_backupDelete: backups #%d and #%d have d ifferent compression - cannot merge\n", printf(STDERR "BackupPC_backupDelete: backups #%d and #%d have d ifferent compression - cannot merge\n",
$Backups[$idx]{num}, $Backups[$idxMerge]{num}); $Backups[$idx]{num},
$Backups[$idxMerge]{num}
);
print("__bpc_pidEnd__ $$\n") if ( !$opts{p} ); print("__bpc_pidEnd__ $$\n") if ( !$opts{p} );
exit(1); exit(1);
} }
$MergeCompress = $Backups[$idxMerge]{compress}; $MergeCompress = $Backups[$idxMerge]{compress};
$MergeDir = "$HostDir/$Backups[$idxMerge]{num}"; $MergeDir = "$HostDir/$Backups[$idxMerge]{num}";
$MergeFilled = !$Backups[$idxMerge]{noFill}; $MergeFilled = !$Backups[$idxMerge]{noFill};
# #
# Create a needFsck file, so if we are killed and can't recover, we can # Create a needFsck file, so if we are killed and can't recover, we can
# make sure an fsck is run next time. # make sure an fsck is run next time.
# #
skipping to change at line 304 skipping to change at line 325
my $needFsckFH; my $needFsckFH;
if ( !(open($needFsckFH, ">", "$MergeDir/refCnt/needFsck.del") && cl ose($needFsckFH)) ) { if ( !(open($needFsckFH, ">", "$MergeDir/refCnt/needFsck.del") && cl ose($needFsckFH)) ) {
logMsg("BackupPC_backupDelete: can't create $MergeDir/refCnt/nee dFsck.del ($?)\n"); logMsg("BackupPC_backupDelete: can't create $MergeDir/refCnt/nee dFsck.del ($?)\n");
} }
if ( !chdir($DelTopDir) ) { if ( !chdir($DelTopDir) ) {
print(STDERR "BackupPC_backupDelete: cannot chdir to $DelTopDir\ n"); print(STDERR "BackupPC_backupDelete: cannot chdir to $DelTopDir\ n");
print("__bpc_pidEnd__ $$\n") if ( !$opts{p} ); print("__bpc_pidEnd__ $$\n") if ( !$opts{p} );
exit(1); exit(1);
} }
$AttrMerge = BackupPC::XS::AttribCache::new($Host, $Backups[$idxMer $AttrMerge = BackupPC::XS::AttribCache::new($Host, $Backups[$idxMerg
ge]{num}, e]{num},
$ShareName, $Backups[$i $ShareName, $Backups[$idxMerge]{compress});
dxMerge]{compress});
$DeltaMerge = BackupPC::XS::DeltaRefCnt::new($MergeDir); $DeltaMerge = BackupPC::XS::DeltaRefCnt::new($MergeDir);
$AttrMerge->setDeltaInfo($DeltaMerge); $AttrMerge->setDeltaInfo($DeltaMerge);
my $dir = defined($delPath) ? ".$delPathM" : "."; my $dir = defined($delPath) ? ".$delPathM" : ".";
mergeDir(".", $dir); mergeDir(".", $dir);
BackupPC::DirOps::find($bpc, {wanted => \&mergeDir}, $dir, 1); BackupPC::DirOps::find($bpc, {wanted => \&mergeDir}, $dir, 1);
$AttrDel->flush(1); $AttrDel->flush(1);
$bpc->flushXSLibMesgs(); $bpc->flushXSLibMesgs();
# #
# Now delete the Del backup tree # Now delete the Del backup tree
# #
logMsg("removing remaining directory tree $DelTopDir$delPathM\n") if ( $LogLevel >= 5 ); logMsg("removing remaining directory tree $DelTopDir$delPathM\n") if ( $LogLevel >= 5 );
BackupPC::DirOps::RmTreeQuiet($bpc, "$DelTopDir$delPathM", $DelCompr BackupPC::DirOps::RmTreeQuiet($bpc, "$DelTopDir$delPathM", $DelCompr
ess, $DeltaDel, $AttrDel, \&progressUpdate); ess, $DeltaDel, $AttrDel,
\&progressUpdate);
if ( defined($delPath) ) { if ( defined($delPath) ) {
if ( defined(my $attr = $AttrDel->get($delPath)) && !defined($At trMerge->get($delPath)) ) { if ( defined(my $attr = $AttrDel->get($delPath)) && !defined($At trMerge->get($delPath)) ) {
$AttrMerge->set($delPath, $attr); $AttrMerge->set($delPath, $attr);
} }
my $ret = $AttrDel->delete($delPath); my $ret = $AttrDel->delete($delPath);
logMsg("AttrDel($delPath) returns $ret\n") if ( $LogLevel >= 5 ) ; logMsg("AttrDel($delPath) returns $ret\n") if ( $LogLevel >= 5 ) ;
} }
$AttrDel->flush(1); $AttrDel->flush(1);
$DeltaDel->flush() if ( $DeltaDel ); $DeltaDel->flush() if ( $DeltaDel );
skipping to change at line 346 skipping to change at line 368
} }
# #
# Make sure we update the reference counts below since there are (likely) # Make sure we update the reference counts below since there are (likely)
# deltas in the merge backup and/or original backup (if $DelPaths) # deltas in the merge backup and/or original backup (if $DelPaths)
# #
$doRefCountUpdate = 1; $doRefCountUpdate = 1;
} }
if ( !defined($ShareName) ) { if ( !defined($ShareName) ) {
if ( !$opts{l} ) { if ( !$opts{l} ) {
unlink("$HostDir/SmbLOG.$Backups[$idx]{num}") unlink("$HostDir/SmbLOG.$Backups[$idx]{num}")
if ( -f "$HostDir/SmbLOG.$Backups[$idx]{num}" ); if ( -f "$HostDir/SmbLOG.$Backups[$idx]{num}" );
unlink("$HostDir/SmbLOG.$Backups[$idx]{num}.z") unlink("$HostDir/SmbLOG.$Backups[$idx]{num}.z")
if ( -f "$HostDir/SmbLOG.$Backups[$idx]{num}.z" ); if ( -f "$HostDir/SmbLOG.$Backups[$idx]{num}.z" );
unlink("$HostDir/XferLOG.$Backups[$idx]{num}") unlink("$HostDir/XferLOG.$Backups[$idx]{num}")
if ( -f "$HostDir/XferLOG.$Backups[$idx]{num}" ); if ( -f "$HostDir/XferLOG.$Backups[$idx]{num}" );
unlink("$HostDir/XferLOG.$Backups[$idx]{num}.z") unlink("$HostDir/XferLOG.$Backups[$idx]{num}.z")
if ( -f "$HostDir/XferLOG.$Backups[$idx]{num}.z" ); if ( -f "$HostDir/XferLOG.$Backups[$idx]{num}.z" );
} }
splice(@Backups, $idx, 1); splice(@Backups, $idx, 1);
$bpc->BackupInfoWrite($Host, @Backups); $bpc->BackupInfoWrite($Host, @Backups);
} }
unlink("$MergeDir/refCnt/needFsck.del") if ( $Errors == 0 && $Conf{RefCntFsck} unlink("$MergeDir/refCnt/needFsck.del")
== 0 && -f "$MergeDir/refCnt/needFsck.del" ); if ( $Errors == 0 && $Conf{RefCntFsck} == 0 && -f "$MergeDir/refCnt/needFsck.d
unlink("$DelTopDir/refCnt/needFsck.del") if ( $Errors == 0 && $Conf{RefCntFsck} el" );
== 0 && -f "$DelTopDir/refCnt/needFsck.del" ); unlink("$DelTopDir/refCnt/needFsck.del")
if ( $Errors == 0 && $Conf{RefCntFsck} == 0 && -f "$DelTopDir/refCnt/needFsck.
del" );
if ( $doRefCountUpdate || $opts{r} ) { if ( $doRefCountUpdate || $opts{r} ) {
my $optsp = " -p" if ( $opts{p} ); my $optsp = " -p" if ( $opts{p} );
$Errors++ if ( system("$BinDir/BackupPC_refCountUpdate -h $Host -o 0$optsp") != 0 ); $Errors++ if ( system("$BinDir/BackupPC_refCountUpdate -h $Host -o 0$optsp") != 0 );
} }
logMsg("BackupPC_backupDelete: got $Errors errors\n") if ( $Errors > 0 || $LogLe vel >= 2 ); logMsg("BackupPC_backupDelete: got $Errors errors\n") if ( $Errors > 0 || $LogLe vel >= 2 );
print("__bpc_pidEnd__ $$\n") if ( !$opts{p} ); print("__bpc_pidEnd__ $$\n") if ( !$opts{p} );
exit($Errors ? 1 : 0); exit($Errors ? 1 : 0);
sub copyInodes sub copyInodes
{ {
my($name, $path) = @_; my($name, $path) = @_;
logMsg("copyInodes: name = $name, path = $path\n") if ( $LogLevel >= 6 ); logMsg("copyInodes: name = $name, path = $path\n") if ( $LogLevel >= 6 );
return if ( $path !~ m{(.*)/(attrib[^/]*)$} ); return if ( $path !~ m{(.*)/(attrib[^/]*)$} );
$path = $1; $path = $1;
$name = $2; $name = $2;
my $attr = BackupPC::XS::Attrib::new($MergeCompress); my $attr = BackupPC::XS::Attrib::new($MergeCompress);
if ( -f "$path/$name" && !$attr->read($path, $name) ) { if ( -f "$path/$name" && !$attr->read($path, $name) ) {
logMsg("Can't read attribute file $path/$name\n"); logMsg("Can't read attribute file $path/$name\n");
$Errors++; $Errors++;
return; return;
} }
my $digest = $attr->digest(); my $digest = $attr->digest();
if ( length($digest) ) { if ( length($digest) ) {
$DeltaMerge->update($MergeCompress, $digest, 1); $DeltaMerge->update($MergeCompress, $digest, 1);
$DeltaDel->update($DelCompress, $digest, -1) if ( $DeltaDel ); $DeltaDel->update($DelCompress, $digest, -1) if ( $DeltaDel );
} }
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};
$FileCnt++; $FileCnt++;
$DeltaMerge->update($a->{compress}, $a->{digest}, 1) if ( length($a->{di gest}) ); $DeltaMerge->update($a->{compress}, $a->{digest}, 1) if ( length($a->{di gest}) );
$DeltaDel->update($a->{compress}, $a->{digest}, -1) if ( $DeltaDel && le $DeltaDel->update($a->{compress}, $a->{digest}, -1) if ( $DeltaDel && l
ngth($a->{digest}) ); ength($a->{digest}) );
next if ( $a->{nlinks} == 0 ); next if ( $a->{nlinks} == 0 );
# #
# copy/update inode in merge, and reduce link count in delete backup # copy/update inode in merge, and reduce link count in delete backup
# #
my $aInode = $AttrDel->getInode($a->{inode}); my $aInode = $AttrDel->getInode($a->{inode});
if ( !(my $inodeMerge = $AttrMerge->getInode($a->{inode})) ) { if ( !(my $inodeMerge = $AttrMerge->getInode($a->{inode})) ) {
# #
# Copy the inode if it doesn't exist in Merge. # Copy the inode if it doesn't exist in Merge.
# #
$AttrMerge->setInode($a->{inode}, $aInode); $AttrMerge->setInode($a->{inode}, $aInode);
$DeltaMerge->update($aInode->{compress}, $aInode->{digest}, 1) $DeltaMerge->update($aInode->{compress}, $aInode->{digest}, 1)
if ( length($aInode->{digest}) ) ; if ( length($aInode->{digest}) );
} }
if ( $DeltaDel && $aInode ) { if ( $DeltaDel && $aInode ) {
$aInode->{nlinks}--; $aInode->{nlinks}--;
if ( $aInode->{nlinks} <= 0 ) { if ( $aInode->{nlinks} <= 0 ) {
$DeltaDel->update($DelCompress, $aInode->{digest}, -1) if ( $Del taDel && length($aInode->{digest}) ); $DeltaDel->update($DelCompress, $aInode->{digest}, -1) if ( $Del taDel && length($aInode->{digest}) );
$AttrDel->deleteInode($a->{inode}); $AttrDel->deleteInode($a->{inode});
} else { } else {
$AttrDel->setInode($a->{inode}, $aInode); $AttrDel->setInode($a->{inode}, $aInode);
} }
} }
skipping to change at line 447 skipping to change at line 477
my $delDir = "$DelTopDir/$d"; my $delDir = "$DelTopDir/$d";
my $mergeDir = "$MergeDir/$d"; my $mergeDir = "$MergeDir/$d";
logMsg("mergeDir: delDir = $delDir, mergeDir = $mergeDir\n") if ( $LogLevel >= 6 ); logMsg("mergeDir: delDir = $delDir, mergeDir = $mergeDir\n") if ( $LogLevel >= 6 );
my $attr = BackupPC::XS::Attrib::new($MergeCompress); my $attr = BackupPC::XS::Attrib::new($MergeCompress);
$attr->read($mergeDir, $name) if ( -d $mergeDir ); $attr->read($mergeDir, $name) if ( -d $mergeDir );
my $attrAll = $attr->get(); my $attrAll = $attr->get();
my($attrDelAll); my($attrDelAll);
my $dirty = 0; my $dirty = 0;
my $attrDel = BackupPC::XS::Attrib::new($DelCompress); my $attrDel = BackupPC::XS::Attrib::new($DelCompress);
if ( -d $delDir ) { if ( -d $delDir ) {
$attrDel->read($delDir, $name); $attrDel->read($delDir, $name);
$attrDelAll = $attrDel->get(); $attrDelAll = $attrDel->get();
$DeltaDel->update($DelCompress, $attrDel->digest(), -1) if ( $DeltaDel & & length($attrDel->digest()) ); $DeltaDel->update($DelCompress, $attrDel->digest(), -1) if ( $DeltaDel & & length($attrDel->digest()) );
} }
$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, $delDir)) ) { if ( defined(my $entries = BackupPC::DirOps::dirRead($bpc, $delDir)) ) {
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" );
if ( $e->{name} =~ /^attrib/ ) { if ( $e->{name} =~ /^attrib/ ) {
if ( $e->{name} =~ /attrib_(.{16,})/ ) { if ( $e->{name} =~ /attrib_(.{16,})/ ) {
my $digest = pack("H*", $1); my $digest = pack("H*", $1);
$DeltaDel->update($DelCompress, $digest, -1) if ( $DeltaDel && $attrDel->digest() ne $digest ); $DeltaDel->update($DelCompress, $digest, -1) if ( $DeltaDel && $attrDel->digest() ne $digest );
} }
logMsg("Removing attrib file $delDir/$e->{name}\n") if ( $LogLev el >= 5 ); logMsg("Removing attrib file $delDir/$e->{name}\n") if ( $LogLev el >= 5 );
unlink("$delDir/$e->{name}"); unlink("$delDir/$e->{name}");
} elsif ( -d "$delDir/$e->{name}" ) { } elsif ( -d "$delDir/$e->{name}" ) {
my $fileUM = $bpc->fileNameUnmangle($e->{name}); my $fileUM = $bpc->fileNameUnmangle($e->{name});
next if ( $attrDelAll && defined($attrDelAll->{$fileUM}) ); next if ( $attrDelAll && defined($attrDelAll->{$fileUM}) );
$attrDelAll->{$fileUM} = { $attrDelAll->{$fileUM} = {
type => BPC_FTYPE_DIR, type => BPC_FTYPE_DIR,
noAttrib => 1, noAttrib => 1,
}; };
} }
} }
} }
if ( defined(my $entries = BackupPC::DirOps::dirRead($bpc, $mergeDir)) ) { if ( defined(my $entries = BackupPC::DirOps::dirRead($bpc, $mergeDir)) ) {
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 "$mergeDir/$e->{name}" ); || !-d "$mergeDir/$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,
}; };
} }
} }
#logMsg "MergeDir $mergeDir contents:\n", Dumper($attrAll); #logMsg "MergeDir $mergeDir contents:\n", Dumper($attrAll);
#logMsg "DelTopDir $delDir contents:\n", Dumper($attrDelAll); #logMsg "DelTopDir $delDir contents:\n", Dumper($attrDelAll);
foreach my $fileUM ( keys(%$attrDelAll) ) { foreach my $fileUM ( keys(%$attrDelAll) ) {
my $a = $attrDelAll->{$fileUM}; my $a = $attrDelAll->{$fileUM};
$FileCnt++; $FileCnt++;
if ( defined(my $aMerge = $attrAll->{$fileUM}) ) { if ( defined(my $aMerge = $attrAll->{$fileUM}) ) {
logMsg("Got del file $delDir/$fileUM, type $a->{type}; mergeDir has type $aMerge->{type}\n") logMsg("Got del file $delDir/$fileUM, type $a->{type}; mergeDir has type $aMerge->{type}\n")
if ( $LogLevel >= 7 ); if ( $LogLevel >= 7 );
# #
# The file exists in both the previous and # The file exists in both the previous and
# deleted backups. # deleted backups.
# #
# If they are both directories, then don't do # If they are both directories, then don't do
# anything for now # anything for now
# #
if ( $a->{type} == BPC_FTYPE_DIR && if ( $a->{type} == BPC_FTYPE_DIR && $aMerge->{type} == BPC_FTYPE_DIR
$aMerge->{type} == BPC_FTYPE_DIR ) { ) {
# #
# if the deleted directory has real attributes, and the # if the deleted directory has real attributes, and the
# merge doesn't, then copy the attributes to merge # merge doesn't, then copy the attributes to merge
# #
if ( $aMerge->{noAttrib} && !$a->{noAttrib} ) { if ( $aMerge->{noAttrib} && !$a->{noAttrib} ) {
logMsg("Copying del attributes to merge for $delDir/$fileUM\ n") if ( $LogLevel >= 7 ); logMsg("Copying del attributes to merge for $delDir/$fileUM\ n") if ( $LogLevel >= 7 );
$attr->set($fileUM, $a); $attr->set($fileUM, $a);
$dirty = 1; $dirty = 1;
} }
next; next;
} }
# #
# The deleted version will get deleted. # The deleted version will get deleted.
# #
logMsg("removing $delDir/$fileUM\n") if ( $LogLevel >= 7 ); logMsg("removing $delDir/$fileUM\n") if ( $LogLevel >= 7 );
if ( $DeltaDel ) { if ( $DeltaDel ) {
skipping to change at line 567 skipping to change at line 595
# the reference counts for the merge backup. # the reference counts for the merge backup.
# #
if ( !$a->{noAttrib} ) { if ( !$a->{noAttrib} ) {
$attr->set($fileUM, $a); $attr->set($fileUM, $a);
if ( length($a->{digest}) ) { if ( length($a->{digest}) ) {
$DeltaMerge->update($a->{compress}, $a->{digest}, 1); $DeltaMerge->update($a->{compress}, $a->{digest}, 1);
$DeltaDel->update($a->{compress}, $a->{digest}, -1) if ( $DeltaD el ); $DeltaDel->update($a->{compress}, $a->{digest}, -1) if ( $DeltaD el );
} }
if ( $LogLevel >= 7 ) { if ( $LogLevel >= 7 ) {
my $digestStr = unpack("H*", $a->{digest}); my $digestStr = unpack("H*", $a->{digest});
logMsg("set $fileUM attrib (type = $a->{type}, size = $a->{size} logMsg(
, nlinks = $a->{nlinks}, inode = $a->{inode}, digest = $digestStr)\n") "set $fileUM attrib (type = $a->{type}, size = $a->{size}, n
links = $a->{nlinks}, inode = $a->{inode}, digest = $digestStr)\n"
);
} }
if ( $a->{nlinks} > 0 ) { if ( $a->{nlinks} > 0 ) {
my $aInode = $AttrDel->getInode($a->{inode}); my $aInode = $AttrDel->getInode($a->{inode});
if ( $LogLevel >= 7 ) { if ( $LogLevel >= 7 ) {
my $digestStr = unpack("H*", $aInode->{digest}); my $digestStr = unpack("H*", $aInode->{digest});
logMsg("got del $fileUM inode $a->{inode} (type = $aInode->{ logMsg(
type}, size = $aInode->{size}, nlinks = $aInode->{nlinks}, digest = $digestStr)\ "got del $fileUM inode $a->{inode} (type = $aInode->{typ
n") e}, size = $aInode->{size}, nlinks = $aInode->{nlinks}, digest = $digestStr)\n"
);
} }
if ( !(my $inodeMerge = $AttrMerge->getInode($a->{inode})) ) { if ( !(my $inodeMerge = $AttrMerge->getInode($a->{inode})) ) {
# #
# Copy the inode if it doesn't exist in Merge. # Copy the inode if it doesn't exist in Merge.
# #
$AttrMerge->setInode($a->{inode}, $aInode); $AttrMerge->setInode($a->{inode}, $aInode);
$DeltaMerge->update($aInode->{compress}, $aInode->{digest}, 1) $DeltaMerge->update($aInode->{compress}, $aInode->{digest}, 1)
if ( length($aInode->{digest}) ); if ( length($aInode->{digest}) );
if ( $LogLevel >= 7 ) { if ( $LogLevel >= 7 ) {
my $digestStr = unpack("H*", $aInode->{digest}); my $digestStr = unpack("H*", $aInode->{digest});
logMsg("setting merge inode $a->{inode} (type = $aInode- logMsg(
>{type}, size = $aInode->{size}, nlinks = $aInode->{nlinks}, digest = $digestStr "setting merge inode $a->{inode} (type = $aInode->{t
)\n") ype}, size = $aInode->{size}, nlinks = $aInode->{nlinks}, digest = $digestStr)\n
"
);
} }
} }
if ( $DeltaDel && $aInode ) { if ( $DeltaDel && $aInode ) {
$aInode->{nlinks}--; $aInode->{nlinks}--;
if ( $aInode->{nlinks} <= 0 ) { if ( $aInode->{nlinks} <= 0 ) {
$DeltaDel->update($DelCompress, $aInode->{digest}, -1) i f ( $DeltaDel ); $DeltaDel->update($DelCompress, $aInode->{digest}, -1) i f ( $DeltaDel );
$AttrDel->deleteInode($a->{inode}); $AttrDel->deleteInode($a->{inode});
if ( $LogLevel >= 7 ) { if ( $LogLevel >= 7 ) {
my $digestStr = unpack("H*", $aInode->{digest}); my $digestStr = unpack("H*", $aInode->{digest});
logMsg("deleted del inode $a->{inode} (type = $aInod logMsg(
e->{type}, size = $aInode->{size}, nlinks = $aInode->{nlinks}, digest = $digestS "deleted del inode $a->{inode} (type = $aInode->
tr)\n") {type}, size = $aInode->{size}, nlinks = $aInode->{nlinks}, digest = $digestStr)
\n"
);
} }
} else { } else {
$AttrDel->setInode($a->{inode}, $aInode); $AttrDel->setInode($a->{inode}, $aInode);
if ( $LogLevel >= 7 ) { if ( $LogLevel >= 7 ) {
my $digestStr = unpack("H*", $aInode->{digest}); my $digestStr = unpack("H*", $aInode->{digest});
logMsg("updated del inode $a->{inode} (type = $aInod logMsg(
e->{type}, size = $aInode->{size}, nlinks = $aInode->{nlinks}, digest = $digestS "updated del inode $a->{inode} (type = $aInode->
tr)\n") {type}, size = $aInode->{size}, nlinks = $aInode->{nlinks}, digest = $digestStr)
\n"
);
} }
} }
} }
} }
$dirty = 1; $dirty = 1;
} }
next if ( $a->{type} != BPC_FTYPE_DIR ); next if ( $a->{type} != BPC_FTYPE_DIR );
# #
# Since it's a directory, if it exists, move the whole thing over. # Since it's a directory, if it exists, move the whole thing over.
# We need to traverse the moved directory to make sure any referenced # We need to traverse the moved directory to make sure any referenced
skipping to change at line 643 skipping to change at line 681
BackupPC::DirOps::find($bpc, {wanted => \&copyInodes}, "$mergeDir/$file" , 1); BackupPC::DirOps::find($bpc, {wanted => \&copyInodes}, "$mergeDir/$file" , 1);
} }
printProgress() if ( $FileCnt >= $FileCntNext ); printProgress() if ( $FileCnt >= $FileCntNext );
$bpc->flushXSLibMesgs(); $bpc->flushXSLibMesgs();
if ( $MergeFilled ) { if ( $MergeFilled ) {
# #
# if the merge directory is filled, actually delete any files that have # if the merge directory is filled, actually delete any files that have
# BPC_FTYPE_DELETED types # BPC_FTYPE_DELETED types
# #
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};
next if ( $a->{type} != BPC_FTYPE_DELETED ); next if ( $a->{type} != BPC_FTYPE_DELETED );
logMsg("removing attrib for deleted file $mergeDir/$fileUM\n") if ( $LogLevel >= 7 ); logMsg("removing attrib for deleted file $mergeDir/$fileUM\n") if ( $LogLevel >= 7 );
$attr->delete($fileUM); $attr->delete($fileUM);
$dirty = 1; $dirty = 1;
} }
} }
if ( $dirty ) { if ( $dirty ) {
my $oldDigest = $attr->digest(); my $oldDigest = $attr->digest();
if ( !$attr->write($mergeDir, $name, $oldDigest, $DeltaMerge) ) { if ( !$attr->write($mergeDir, $name, $oldDigest, $DeltaMerge) ) {
my $digestStr = unpack("H*", $oldDigest); my $digestStr = unpack("H*", $oldDigest);
logMsg("mergeDir: attr write to $mergeDir/$name failed (digest was $ digestStr)\n"); logMsg("mergeDir: attr write to $mergeDir/$name failed (digest was $ digestStr)\n");
} }
logMsg(sprintf("rewriting attrib file %s; digest is %s was %s\n", logMsg(sprintf(
"$mergeDir/$name", unpack("H*", $attr->digest()), unpack( "rewriting attrib file %s; digest is %s was %s\n",
"H*", $oldDigest))) "$mergeDir/$name",
if ( $LogLevel >= 5 ); unpack("H*", $attr->digest()),
unpack("H*", $oldDigest)
))
if ( $LogLevel >= 5 );
} }
$bpc->flushXSLibMesgs(); $bpc->flushXSLibMesgs();
} }
sub progressUpdate sub progressUpdate
{ {
my($newCnt) = @_; my($newCnt) = @_;
if ( $DeltaDel ) { if ( $DeltaDel ) {
$FileCnt += $newCnt; $FileCnt += $newCnt;
skipping to change at line 714 skipping to change at line 760
} else { } else {
print($str); print($str);
} }
} }
# #
# print the list of pids # print the list of pids
# #
sub pidHandler sub pidHandler
{ {
my @pids = sort {$a <=> $b} @_; my @pids = sort { $a <=> $b } @_;
printf("xferPids %s\n", join(",", @pids)); printf("xferPids %s\n", join(",", @pids));
} }
 End of changes. 57 change blocks. 
134 lines changed or deleted 179 lines changed or added

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