From 5b79f9a3c01bca16dd4d211e76fc53daa549e421 Mon Sep 17 00:00:00 2001 From: cbarratt Date: Fri, 26 Dec 2008 13:12:23 +0000 Subject: [PATCH] * Added BackupPC::Xfer::Protocol as a common class for each Xfer method. This simplifies some of the xfer specific code. Implemented by Paul Mantz. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit * Added FTP xfer method, implemented by Paul Mantz. * Added BackupPC::Xfer module to provide a common interface to the different xfer methods. Implemented by Paul Mantz. * Moved setting of $bpc->{PoolDir} and $bpc->{CPoolDir} after the config file is read in BackupPC::Lib. Fix proposed by Tim Taylor and Joe Krahn. * Create $TopDir and related data directories in BackupPC_dump prior to hardlink test. Requested by Les Stott. * Modified lib/BackupPC/CGI/RSS.pm to replace \n with \r\n in the RSS http response headers. Patch submitted by Thomas Eckhardt. * Modified bin/BackupPC_archive to allow the archive request file name to contain spaces and dashes, requested by Tim Massey. * Fix to configure.pl for --no-fhs case to initialize ConfigDir from Dan Pritts. Also changed perl path to #!/usr/bin/env perl. * Modified bin/BackupPC_archiveHost to shell escape the output file name. That allows it to contain spaces and other special characters. Requested by Toni Van Remortel. * Added --config-override to configure.pl, allow config settings to be set on the command line. Proposed by Les Stott and Holger Parplies. * Minor updates to lib/BackupPC/Lang/fr.pm from Nicolas STRANSKY applied by GFK. * Minor updates to lib/BackupPC/Lang/de.pm from Klaus Weidenbach. * lib/BackupPC/Xfer/Smb.pm now increments xferErrCnt on NT_STATUS_ACCESS_DENIED and ERRnoaccess errors from smbclient. Reported by Jesús Martel. * Modified bin/BackupPC_sendEmail to not send any per-client email if $Conf{BackupsDisable} is set. --- ChangeLog | 43 ++ bin/BackupPC_archive | 2 +- bin/BackupPC_archiveHost | 19 +- bin/BackupPC_dump | 141 ++--- bin/BackupPC_restore | 42 +- bin/BackupPC_sendEmail | 6 +- conf/config.pl | 94 ++- configure.pl | 39 +- httpd/src/BackupPC.conf | 33 ++ lib/BackupPC/CGI/EditConfig.pm | 30 +- lib/BackupPC/CGI/RSS.pm | 2 +- lib/BackupPC/Config/Meta.pm | 57 +- lib/BackupPC/Lang/de.pm | 5 +- lib/BackupPC/Lang/en.pm | 1 + lib/BackupPC/Lang/es.pm | 1 + lib/BackupPC/Lang/fr.pm | 1 + lib/BackupPC/Lang/it.pm | 1 + lib/BackupPC/Lang/nl.pm | 1 + lib/BackupPC/Lang/pl.pm | 1 + lib/BackupPC/Lang/pt_br.pm | 3 +- lib/BackupPC/Lang/zh_CN.pm | 1 + lib/BackupPC/Lib.pm | 84 ++- lib/BackupPC/Xfer.pm | 152 +++++ lib/BackupPC/Xfer/Archive.pm | 67 +-- lib/BackupPC/Xfer/BackupPCd.pm | 58 +- lib/BackupPC/Xfer/Ftp.pm | 1019 ++++++++++++++++++++++++++++++++ lib/BackupPC/Xfer/Protocol.pm | 452 ++++++++++++++ lib/BackupPC/Xfer/Rsync.pm | 85 +-- lib/BackupPC/Xfer/Smb.pm | 88 +-- lib/BackupPC/Xfer/Tar.pm | 91 +-- lib/Net/FTP/AutoReconnect.pm | 509 ++++++++++++++++ lib/Net/FTP/RetrHandle.pm | 692 ++++++++++++++++++++++ makeDist | 23 +- makePatch | 4 +- 34 files changed, 3298 insertions(+), 549 deletions(-) create mode 100644 httpd/src/BackupPC.conf create mode 100644 lib/BackupPC/Xfer.pm create mode 100644 lib/BackupPC/Xfer/Ftp.pm create mode 100644 lib/BackupPC/Xfer/Protocol.pm create mode 100644 lib/Net/FTP/AutoReconnect.pm create mode 100644 lib/Net/FTP/RetrHandle.pm diff --git a/ChangeLog b/ChangeLog index 2840ee2..28ab594 100644 --- a/ChangeLog +++ b/ChangeLog @@ -21,6 +21,49 @@ # Version __VERSION__, __RELEASEDATE__ #------------------------------------------------------------------------ +* Added BackupPC::Xfer::Protocol as a common class for each Xfer + method. This simplifies some of the xfer specific code. + Implemented by Paul Mantz. + +* Added FTP xfer method, implemented by Paul Mantz. + +* Added BackupPC::Xfer module to provide a common interface to the + different xfer methods. Implemented by Paul Mantz. + +* Moved setting of $bpc->{PoolDir} and $bpc->{CPoolDir} after the + config file is read in BackupPC::Lib. Fix proposed by Tim Taylor + and Joe Krahn. + +* Create $TopDir and related data directories in BackupPC_dump + prior to hardlink test. Requested by Les Stott. + +* Modified lib/BackupPC/CGI/RSS.pm to replace \n with \r\n in the RSS + http response headers. Patch submitted by Thomas Eckhardt. + +* Modified bin/BackupPC_archive to allow the archive request file + name to contain spaces and dashes, requested by Tim Massey. + +* Fix to configure.pl for --no-fhs case to initialize ConfigDir + from Dan Pritts. Also changed perl path to #!/usr/bin/env perl. + +* Modified bin/BackupPC_archiveHost to shell escape the output file + name. That allows it to contain spaces and other special characters. + Requested by Toni Van Remortel. + +* Added --config-override to configure.pl, allow config settings to be + set on the command line. Proposed by Les Stott and Holger Parplies. + +* Minor updates to lib/BackupPC/Lang/fr.pm from Nicolas STRANSKY + applied by GFK. + +* Minor updates to lib/BackupPC/Lang/de.pm from Klaus Weidenbach. + +* lib/BackupPC/Xfer/Smb.pm now increments xferErrCnt on NT_STATUS_ACCESS_DENIED + and ERRnoaccess errors from smbclient. Reported by Jesús Martel. + +* Modified bin/BackupPC_sendEmail to not send any per-client email if + $Conf{BackupsDisable} is set. + #------------------------------------------------------------------------ # Version 3.1.0, 25 Nov 2007 #------------------------------------------------------------------------ diff --git a/bin/BackupPC_archive b/bin/BackupPC_archive index 7d38e81..87ed842 100644 --- a/bin/BackupPC_archive +++ b/bin/BackupPC_archive @@ -64,7 +64,7 @@ if ( @ARGV != 3 ) { } $user = $1 if ( $ARGV[0] =~ /(.+)/ ); $client = $1 if ( $ARGV[1] =~ /(.+)/ ); -if ( $ARGV[2] !~ /^([\w.]+)$/ ) { +if ( $ARGV[2] !~ /^([\w\.\s-]+)$/ ) { print("$0: bad reqFileName (arg #3): $ARGV[2]\n"); exit(1); } diff --git a/bin/BackupPC_archiveHost b/bin/BackupPC_archiveHost index f5535f1..2416df3 100755 --- a/bin/BackupPC_archiveHost +++ b/bin/BackupPC_archiveHost @@ -86,8 +86,9 @@ my $mesg = "Writing tar archive for host $host, backup #$bkupNum"; # # Build the command we will run # -$share = $bpc->shellEscape($share); -$host = $bpc->shellEscape($host); +$share = $bpc->shellEscape($share); +$host = $bpc->shellEscape($host); +my $outLocE = $bpc->shellEscape($outLoc); # # We prefer to use /bin/csh because the exit status of a pipeline @@ -112,8 +113,8 @@ if ( -b $outLoc || -c $outLoc || -f $outLoc ) { # # Output file is a device or a regular file, so don't use split # - $cmd .= ">> $outLoc"; - $mesg .= " to $outLoc"; + $cmd .= ">> $outLocE"; + $mesg .= " to $outLocE"; } else { mkpath($outLoc) if ( !-d $outLoc ); if ( !-d $outLoc ) { @@ -121,11 +122,11 @@ if ( -b $outLoc || -c $outLoc || -f $outLoc ) { exit(1); } if ( $splitSize > 0 && -x $splitPath ) { - $cmd .= "| $splitPath -b $splitSize - $outLoc/$host.$bkupNum.tar$fileExt."; - $mesg .= ", split to output files $outLoc/$host.$bkupNum.tar$fileExt.*"; + $cmd .= "| $splitPath -b $splitSize - $outLocE/$host.$bkupNum.tar$fileExt."; + $mesg .= ", split to output files $outLocE/$host.$bkupNum.tar$fileExt.*"; } else { - $cmd .= "> $outLoc/$host.$bkupNum.tar$fileExt"; - $mesg .= " to output file $outLoc/$host.$bkupNum.tar$fileExt"; + $cmd .= "> $outLocE/$host.$bkupNum.tar$fileExt"; + $mesg .= " to output file $outLocE/$host.$bkupNum.tar$fileExt"; } } print("$mesg\n"); @@ -147,7 +148,7 @@ if ( $ret ) { if ( -d $outLoc && -x $parPath ) { if ( $parfile != 0 ) { print("Running $parPath to create parity files\n"); - my $parCmd = "$parPath c -r$parfile $outLoc/$host.$bkupNum.tar$fileExt.par2 $outLoc/$host.$bkupNum.tar$fileExt*"; + my $parCmd = "$parPath c -r$parfile $outLocE/$host.$bkupNum.tar$fileExt.par2 $outLocE/$host.$bkupNum.tar$fileExt*"; $ret = system($parCmd); if ( $ret ) { print("Executing: $parCmd\n"); diff --git a/bin/BackupPC_dump b/bin/BackupPC_dump index b54c208..3e212d0 100755 --- a/bin/BackupPC_dump +++ b/bin/BackupPC_dump @@ -82,10 +82,7 @@ use lib "/usr/local/BackupPC/lib"; use BackupPC::Lib; use BackupPC::FileZIO; use BackupPC::Storage; -use BackupPC::Xfer::Smb; -use BackupPC::Xfer::Tar; -use BackupPC::Xfer::Rsync; -use BackupPC::Xfer::BackupPCd; +use BackupPC::Xfer; use Encode; use Socket; use File::Path; @@ -458,6 +455,28 @@ if ( @Backups == 0 NothingToDo($needLink); } +# +# Create top-level directories if they don't exist +# +foreach my $dir ( ( + "$Conf{TopDir}", + "$Conf{TopDir}/pool", + "$Conf{TopDir}/cpool", + "$Conf{TopDir}/pc", + "$Conf{TopDir}/trash", + ) ) { + next if ( -d $dir ); + mkpath($dir, 0, 0750); + if ( !-d $dir ) { + print("Failed to create $dir\n"); + printf(LOG "%sFailed to create directory %s\n", $bpc->timeStamp, $dir); + print("link $clientURI\n") if ( $needLink ); + exit(1); + } else { + printf(LOG "%sCreated directory %s\n", $bpc->timeStamp, $dir); + } +} + if ( !$bpc->HardlinkTest($Dir, "$TopDir/cpool") ) { print(LOG $bpc->timeStamp, "Can't create a test hardlink between a file" . " in $Dir and $TopDir/cpool. Either these are different" @@ -566,17 +585,7 @@ my $sizeTotal = 0; my($logMsg, %stat, $xfer, $ShareNames, $noFilesErr); my $newFilesFH; -if ( $Conf{XferMethod} eq "tar" ) { - $ShareNames = $Conf{TarShareName}; -} elsif ( $Conf{XferMethod} eq "rsync" || $Conf{XferMethod} eq "rsyncd" ) { - $ShareNames = $Conf{RsyncShareName}; -} elsif ( $Conf{XferMethod} eq "backuppcd" ) { - $ShareNames = $Conf{BackupPCdShareName}; -} else { - $ShareNames = $Conf{SmbShareName}; -} - -$ShareNames = [ $ShareNames ] unless ref($ShareNames) eq "ARRAY"; +$ShareNames = BackupPC::Xfer::getShareNames(\%Conf); # # Run an optional pre-dump command @@ -617,40 +626,14 @@ for my $shareName ( @$ShareNames ) { exit(1); } - if ( $Conf{XferMethod} eq "tar" ) { - # - # Use tar (eg: tar/ssh) as the transport program. - # - $xfer = BackupPC::Xfer::Tar->new($bpc); - } elsif ( $Conf{XferMethod} eq "rsync" || $Conf{XferMethod} eq "rsyncd" ) { - # - # Use rsync as the transport program. - # - if ( !defined($xfer = BackupPC::Xfer::Rsync->new($bpc)) ) { - my $errStr = BackupPC::Xfer::Rsync::errStr; - print(LOG $bpc->timeStamp, "dump failed: $errStr\n"); - print("dump failed: $errStr\n"); - UserCommandRun("DumpPostShareCmd", $shareName) if ( $NeedPostCmd ); - UserCommandRun("DumpPostUserCmd") if ( $NeedPostCmd ); - exit(1); - } - } elsif ( $Conf{XferMethod} eq "backuppcd" ) { - # - # Use backuppcd as the transport program. - # - if ( !defined($xfer = BackupPC::Xfer::BackupPCd->new($bpc)) ) { - my $errStr = BackupPC::Xfer::BackupPCd::errStr; - print(LOG $bpc->timeStamp, "dump failed: $errStr\n"); - print("dump failed: $errStr\n"); - UserCommandRun("DumpPostShareCmd", $shareName) if ( $NeedPostCmd ); - UserCommandRun("DumpPostUserCmd") if ( $NeedPostCmd ); - exit(1); - } - } else { - # - # Default is to use smbclient (smb) as the transport program. - # - $xfer = BackupPC::Xfer::Smb->new($bpc); + $xfer = BackupPC::Xfer::create($Conf{XferMethod}, $bpc); + if ( !defined($xfer) ) { + my $errStr = BackupPC::Xfer::errStr(); + print(LOG $bpc->timeStamp, "dump failed: $errStr\n"); + print("dump failed: $errStr\n"); + UserCommandRun("DumpPostShareCmd", $shareName) if ( $NeedPostCmd ); + UserCommandRun("DumpPostUserCmd") if ( $NeedPostCmd ); + exit(1); } my $useTar = $xfer->useTar; @@ -789,43 +772,41 @@ for my $shareName ( @$ShareNames ) { # we use a select. # my($FDread, $tarOut, $mesg); - vec($FDread, fileno(TAR), 1) = 1 if ( $useTar ); + vec($FDread, fileno(TAR), 1) = 1; $xfer->setSelectMask(\$FDread); SCAN: while ( 1 ) { my $ein = $FDread; last if ( $FDread =~ /^\0*$/ ); select(my $rout = $FDread, undef, $ein, undef); - if ( $useTar ) { - if ( vec($rout, fileno(TAR), 1) ) { - if ( sysread(TAR, $mesg, 8192) <= 0 ) { - vec($FDread, fileno(TAR), 1) = 0; - close(TAR); - } else { - $tarOut .= $mesg; - } - } - while ( $tarOut =~ /(.*?)[\n\r]+(.*)/s ) { - $_ = $1; - $tarOut = $2; - if ( /^ / ) { - $XferLOG->write(\"$_\n"); - } else { - $XferLOG->write(\"tarExtract: $_\n"); - } - if ( /^BackupPC_tarExtact aborting \((.*)\)/ ) { - $stat{hostError} = $1; - } - if ( /^Done: (\d+) errors, (\d+) filesExist, (\d+) sizeExist, (\d+) sizeExistComp, (\d+) filesTotal, (\d+) sizeTotal/ ) { - $tarErrs += $1; - $nFilesExist += $2; - $sizeExist += $3; - $sizeExistComp += $4; - $nFilesTotal += $5; - $sizeTotal += $6; - } - } - } + if ( vec($rout, fileno(TAR), 1) ) { + if ( sysread(TAR, $mesg, 8192) <= 0 ) { + vec($FDread, fileno(TAR), 1) = 0; + close(TAR); + } else { + $tarOut .= $mesg; + } + } + while ( $tarOut =~ /(.*?)[\n\r]+(.*)/s ) { + $_ = $1; + $tarOut = $2; + if ( /^ / ) { + $XferLOG->write(\"$_\n"); + } else { + $XferLOG->write(\"tarExtract: $_\n"); + } + if ( /^BackupPC_tarExtact aborting \((.*)\)/ ) { + $stat{hostError} = $1; + } + if ( /^Done: (\d+) errors, (\d+) filesExist, (\d+) sizeExist, (\d+) sizeExistComp, (\d+) filesTotal, (\d+) sizeTotal/ ) { + $tarErrs += $1; + $nFilesExist += $2; + $sizeExist += $3; + $sizeExistComp += $4; + $nFilesTotal += $5; + $sizeTotal += $6; + } + } last if ( !$xfer->readOutput(\$FDread, $rout) ); while ( my $str = $xfer->logMsgGet ) { print(LOG $bpc->timeStamp, "xfer: $str\n"); diff --git a/bin/BackupPC_restore b/bin/BackupPC_restore index f23afe8..4549ccf 100755 --- a/bin/BackupPC_restore +++ b/bin/BackupPC_restore @@ -40,10 +40,7 @@ no utf8; use lib "/usr/local/BackupPC/lib"; use BackupPC::Lib; use BackupPC::FileZIO; -use BackupPC::Xfer::Smb; -use BackupPC::Xfer::Tar; -use BackupPC::Xfer::Rsync; -use BackupPC::Xfer::BackupPCd; +use BackupPC::Xfer; use Socket; use File::Path; @@ -236,37 +233,14 @@ if ( $? && $Conf{UserCmdCheckStatus} ) { } $NeedPostCmd = 1; -if ( $Conf{XferMethod} eq "tar" ) { - # - # Use tar (eg: tar/ssh) as the transport program. - # - $xfer = BackupPC::Xfer::Tar->new($bpc); -} elsif ( $Conf{XferMethod} eq "rsync" || $Conf{XferMethod} eq "rsyncd" ) { - # - # Use rsync as the transport program. - # - if ( !defined($xfer = BackupPC::Xfer::Rsync->new($bpc)) ) { - my $errStr = BackupPC::Xfer::Rsync->errStr; - UserCommandRun("RestorePostUserCmd") if ( $NeedPostCmd ); - $stat{hostError} = $errStr; - exit(RestoreCleanup($client)); - } -} elsif ( $Conf{XferMethod} eq "backuppcd" ) { - # - # Use backuppcd as the transport program. - # - if ( !defined($xfer = BackupPC::Xfer::BackupPCd->new($bpc)) ) { - my $errStr = BackupPC::Xfer::BackupPCd->errStr; - UserCommandRun("RestorePostUserCmd") if ( $NeedPostCmd ); - $stat{hostError} = $errStr; - exit(RestoreCleanup($client)); - } -} else { - # - # Default is to use smbclient (smb) as the transport program. - # - $xfer = BackupPC::Xfer::Smb->new($bpc); +$xfer = BackupPC::Xfer::create($Conf{XferMethod}, $bpc); +if ( !defined($xfer) ) { + my $errStr = BackupPC::Xfer::errStr(); + UserCommandRun("RestorePostUserCmd") if ( $NeedPostCmd ); + $stat{hostError} = $errStr; + exit(RestoreCleanup($client)); } + my $useTar = $xfer->useTar; if ( $useTar ) { diff --git a/bin/BackupPC_sendEmail b/bin/BackupPC_sendEmail index 390093a..055884a 100755 --- a/bin/BackupPC_sendEmail +++ b/bin/BackupPC_sendEmail @@ -42,6 +42,7 @@ no utf8; use lib "/usr/local/BackupPC/lib"; use BackupPC::Lib; use BackupPC::FileZIO; +use Encode; use Data::Dumper; use Getopt::Std; @@ -363,6 +364,8 @@ sub user2name sub sendUserEmail { my($user, $host, $mesg, $subj, $vars) = @_; + return if ( $Conf{BackupsDisable} ); + $vars->{user} = $user; $vars->{host} = $host; $vars->{headers} = $Conf{EMailHeaders}; @@ -370,7 +373,7 @@ sub sendUserEmail $vars->{domain} = $Conf{EMailUserDestDomain}; $vars->{CgiURL} = $Conf{CgiURL}; $subj =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg; - $vars->{subj} = $subj; + $vars->{subj} = $subj; $mesg =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg; SendMail($mesg); $UserEmailInfo{$user}{lastTime} = time; @@ -388,6 +391,7 @@ sub SendMail if ( $opts{t} ) { binmode(STDOUT, ":utf8") if ( $utf8 ); + print("#" x 75, "\n"); print $mesg; return; diff --git a/conf/config.pl b/conf/config.pl index a807a79..abdcd32 100644 --- a/conf/config.pl +++ b/conf/config.pl @@ -888,6 +888,10 @@ $Conf{ClientCharset} = ''; # $Conf{ClientCharsetLegacy} = 'iso-8859-1'; +########################################################################### +# Samba Configuration +# (can be overwritten in the per-PC log file) +########################################################################### # # Name of the host share that is backed up when using SMB. This can be a # string or an array of strings if there are multiple shares per host. @@ -987,6 +991,10 @@ $Conf{SmbClientRestoreCmd} = '$smbClientPath \\\\$host\\$shareName' . ' $I_option -U $userName -E -N -d 1' . ' -c tarmode\\ full -Tx -'; +########################################################################### +# Tar Configuration +# (can be overwritten in the per-PC log file) +########################################################################### # # Which host directories to backup when using tar transport. This can be a # string or an array of strings if there are multiple directories to @@ -1130,6 +1138,10 @@ $Conf{TarClientRestoreCmd} = '$sshPath -q -x -l root $host' # $Conf{TarClientPath} = ''; +########################################################################### +# Rsync/Rsyncd Configuration +# (can be overwritten in the per-PC log file) +########################################################################### # # Path to rsync executable on the client # @@ -1324,6 +1336,73 @@ $Conf{RsyncRestoreArgs} = [ # ]; +########################################################################### +# FTP Configuration +# (can be overwritten in the per-PC log file) +########################################################################## +# +# Name of the host share that is backed up when using FTP. This can be a +# string or an array of strings if there are multiple shares per host. +# Examples: +# +# $Conf{FtpShareName} = 'c'; # backup 'c' share +# $Conf{FtpShareName} = ['c', 'd']; # backup 'c' and 'd' shares +# +# This setting only matters if $Conf{XferMethod} = 'ftp'. +# +$Conf{FtpShareName} = ''; + +# +# FTP user name. This is used to log into the server. +# +# This setting is used only if $Conf{XferMethod} = 'ftp'. +# +$Conf{FtpUserName} = ''; + +# +# FTP user password. This is used to log into the server. +# +# This setting is used only if $Conf{XferMethod} = 'ftp'. +# +$Conf{FtpPasswd} = ''; + +# +# Transfer block size. This sets the size of the amounts of data in +# each frame. While undefined, this value takes the default value. +# +# This setting is used only if $Conf{XferMethod} = 'ftp'. +# +$Conf{FtpBlockSize} = 10240; + +# +# The port of the ftp server. If undefined, 21 is used. +# +# This setting is used only if $Conf{XferMethod} = 'ftp'. +# +$Conf{FtpPort} = 21; + +# +# Connection timeout for FTP. When undefined, the default is 120 seconds. +# +# This setting is used only if $Conf{XferMethod} = 'ftp'. +# +$Conf{FtpTimeout} = 120; + +# +# Behaviour when BackupPC encounters symlinks on the FTP share. +# +# Symlinks cannot be restored via FTP, so the desired behaviour will +# be different depending on the setup of the share. The default for +# this behavor is 1. Directory shares with more complicated directory +# structures should consider other protocols. +# +$Conf{FtpFollowSymlinks} = 0; + + +########################################################################### +# BackupPCd Configuration +# (can be overwritten in the per-PC log file) +########################################################################### # # Share name to backup. For $Conf{XferMethod} = "backuppcd" this should # be a file system path, eg '/' or '/home'. @@ -1384,7 +1463,10 @@ $Conf{BackupPCdCmd} = '$bpcdPath $host $shareName $poolDir XXXX $poolCompress $t # $Conf{BackupPCdRestoreCmd} = '$bpcdPath TODO'; - +########################################################################### +# Archive Configuration +# (can be overwritten in the per-PC log file) +########################################################################### # # Archive Destination # @@ -2128,6 +2210,16 @@ $Conf{CgiUserConfigEdit} = { RsyncClientCmd => 0, RsyncClientRestoreCmd => 0, RsyncClientPath => 0, + FtpShareName => 1, + FtpUserName => 1, + FtpPasswd => 1, + FtpBlockSize => 1, + FtpPort => 1, + FtpTimeout => 1, + BackupPCdPath => 1, + BackupPCdShareName => 1, + BackupPCdCmd => 1, + BackupPCdRestoreCmd => 1, ArchiveDest => 1, ArchiveComp => 1, ArchivePar => 1, diff --git a/configure.pl b/configure.pl index 7ec545c..e0923d2 100755 --- a/configure.pl +++ b/configure.pl @@ -1,4 +1,4 @@ -#!/bin/perl +#!/usr/bin/env perl #============================================================= -*-perl-*- # # configure.pl: Configuration and installation program for BackupPC @@ -97,6 +97,7 @@ if ( !GetOptions( "cgi-dir=s", "compress-level=i", "config-path=s", + "config-override=s%", "config-dir=s", "data-dir=s", "dest-dir=s", @@ -230,10 +231,15 @@ EOF # Create defaults for FHS setup # if ( $opts{fhs} ) { - $Conf{TopDir} ||= "/data/BackupPC"; - $Conf{ConfDir} ||= $opts{"config-dir"} || "/etc/BackupPC"; - $Conf{InstallDir} ||= "/usr/local/BackupPC"; - $Conf{LogDir} ||= $opts{"log-dir"} || "/var/log/BackupPC"; + $Conf{TopDir} ||= $opts{"data-dir"} || "/data/BackupPC"; + $Conf{ConfDir} ||= $opts{"config-dir"} || "/etc/BackupPC"; + $Conf{InstallDir} ||= $opts{"install-dir"} || "/usr/local/BackupPC"; + $Conf{LogDir} ||= $opts{"log-dir"} || "/var/log/BackupPC"; +} else { + $Conf{TopDir} ||= $opts{"data-dir"} || "/data/BackupPC"; + $Conf{ConfDir} ||= $opts{"config-dir"} || "$Conf{TopDir}/conf"; + $Conf{InstallDir} ||= $opts{"install-dir"} || "/usr/local/BackupPC"; + $Conf{LogDir} ||= $opts{"log-dir"} || "$Conf{TopDir}/log"; } # @@ -528,6 +534,7 @@ foreach my $dir ( qw(bin doc lib/BackupPC/Storage lib/BackupPC/Xfer lib/BackupPC/Zip + lib/Net/FTP ) ) { next if ( -d "$DestDir$Conf{InstallDir}/$dir" ); mkpath("$DestDir$Conf{InstallDir}/$dir", 0, 0755); @@ -616,6 +623,9 @@ foreach my $init ( qw(gentoo-backuppc gentoo-backuppc.conf linux-backuppc InstallFile("init.d/src/$init", "init.d/$init", 0444); } +printf("Making Apache configuration file for suid-perl\n"); +InstallFile("httpd/src/BackupPC.conf", "httpd/BackupPC.conf", 0644); + printf("Installing docs in $DestDir$Conf{InstallDir}/doc\n"); foreach my $doc ( qw(BackupPC.pod BackupPC.html) ) { InstallFile("doc/$doc", "$DestDir$Conf{InstallDir}/doc/$doc", 0444); @@ -792,6 +802,23 @@ if ( defined($Conf{CgiUserConfigEdit}) ) { =~ s/(\s*\$Conf\{.*?\}\s*=\s*).*/$1$value/s; } +# +# Apply any command-line configuration parameter settings +# +foreach my $param ( keys(%{$opts{"config-override"}}) ) { + my $val = eval { $opts{"config-override"}{$param} }; + if ( @$ ) { + printf("Can't eval --config-override setting %s=%s\n", + $param, $opts{"config-override"}{$param}); + exit(1); + } + if ( !defined($newVars->{$param}) ) { + printf("Unkown config parameter %s in --config-override\n", $param); + exit(1); + } + $newConf->[$newVars->{$param}]{text} = $opts{"config-override"}{$param}; +} + # # Now backup and write the config file # @@ -930,6 +957,8 @@ sub InstallFile if ( $prog =~ /Lib.pm/ ); s/__BACKUPPCUSER__/$Conf{BackupPCUser}/g; s/__CGIDIR__/$Conf{CgiDir}/g; + s/__IMAGEDIR__/$Conf{CgiImageDir}/g; + s/__IMAGEDIRURL__/$Conf{CgiImageDirURL}/g; if ( $first && /^#.*bin\/perl/ ) { # # Fill in correct path to perl (no taint for >= 2.0.1). diff --git a/httpd/src/BackupPC.conf b/httpd/src/BackupPC.conf new file mode 100644 index 0000000..163bff9 --- /dev/null +++ b/httpd/src/BackupPC.conf @@ -0,0 +1,33 @@ +# +# DESCRIPTION +# +# This file controls access and configuration for the BackupPC CGI +# interface. +# +# Distributed with BackupPC version 3.1.1, released 22 Dec 2008. + + + +# +# This section tells apache which machines can access the interface. +# You can change the allow line to allow access from your local +# network, or comment out this region to allow access from all +# machines. +# +order deny,allow +deny from all +allow from 127.0.0.1 + +# +# You can change the authorization method to LDAP or another method +# besides htaccess here if you are so inclined. +# +AuthType Basic +AuthUserFile __CONFDIR__/BackupPC.users +AuthName "BackupPC Community Edition Administrative Interface" +require valid-user + + + +Alias __IMAGEDIRURL__ __IMAGEDIR__ +ScriptAlias /BackupPC_Admin __CGIDIR__/BackupPC_Admin diff --git a/lib/BackupPC/CGI/EditConfig.pm b/lib/BackupPC/CGI/EditConfig.pm index edc4526..9b78b15 100644 --- a/lib/BackupPC/CGI/EditConfig.pm +++ b/lib/BackupPC/CGI/EditConfig.pm @@ -28,7 +28,7 @@ # #======================================================================== # -# Version 3.1.0, released 25 Nov 2007. +# Version 3.1.1, released 22 Dec 2008. # # See http://backuppc.sourceforge.net. # @@ -173,6 +173,7 @@ our %ConfigMenu = ( {name => "ClientCharset"}, {name => "ClientCharsetLegacy"}, + ### Smb Settings {text => "CfgEdit_Title_Smb_Settings", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, {name => "SmbShareName", @@ -182,11 +183,13 @@ our %ConfigMenu = ( {name => "SmbSharePasswd", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, + ### Tar Settings {text => "CfgEdit_Title_Tar_Settings", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, {name => "TarShareName", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, + ### Rsync Settings {text => "CfgEdit_Title_Rsync_Settings", visible => sub { return $_[0]->{XferMethod} eq "rsync"; } }, {text => "CfgEdit_Title_Rsyncd_Settings", @@ -202,6 +205,25 @@ our %ConfigMenu = ( {name => "RsyncCsumCacheVerifyProb", visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } }, + ### Ftp Settings + {text => "CfgEdit_Title_Ftp_Settings", + visible => sub { return $_[0]->{XferMethod} eq "ftp"; } }, + {name => "FtpShareName", + visible => sub { return $_[0]->{XferMethod} eq "ftp"; } }, + {name => "FtpUserName", + visible => sub { return $_[0]->{XferMethod} eq "ftp"; } }, + {name => "FtpPasswd", + visible => sub { return $_[0]->{XferMethod} eq "ftp"; } }, + {name => "FtpBlockSize", + visible => sub { return $_[0]->{XferMethod} eq "ftp"; } }, + {name => "FtpPort", + visible => sub { return $_[0]->{XferMethod} eq "ftp"; } }, + {name => "FtpTimeout", + visible => sub { return $_[0]->{XferMethod} eq "ftp"; } }, + {name => "FtpFollowSymlinks", + visible => sub { return $_[0]->{XferMethod} eq "ftp"; } }, + + ### BackupPCd Settings {text => "CfgEdit_Title_BackupPCd_Settings", visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } }, {name => "BackupPCdShareName", @@ -213,6 +235,7 @@ our %ConfigMenu = ( {name => "BackupPCdRestoreCmd", visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } }, + ### Archive Settings {text => "CfgEdit_Title_Archive_Settings", visible => sub { return $_[0]->{XferMethod} eq "archive"; } }, {name => "ArchiveDest", @@ -224,6 +247,7 @@ our %ConfigMenu = ( {name => "ArchiveSplit", visible => sub { return $_[0]->{XferMethod} eq "archive"; } }, + ### Include/Exclude Settings {text => "CfgEdit_Title_Include_Exclude", visible => sub { return $_[0]->{XferMethod} ne "archive"; } }, {name => "BackupFilesOnly", @@ -231,6 +255,7 @@ our %ConfigMenu = ( {name => "BackupFilesExclude", visible => sub { return $_[0]->{XferMethod} ne "archive"; } }, + ### Samba paths and commands {text => "CfgEdit_Title_Smb_Paths_Commands", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, {name => "SmbClientPath", @@ -242,6 +267,7 @@ our %ConfigMenu = ( {name => "SmbClientRestoreCmd", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, + ### Tar paths and commands {text => "CfgEdit_Title_Tar_Paths_Commands", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, {name => "TarClientPath", @@ -255,6 +281,7 @@ our %ConfigMenu = ( {name => "TarClientRestoreCmd", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, + ### Rsync paths and commands {text => "CfgEdit_Title_Rsync_Paths_Commands_Args", visible => sub { return $_[0]->{XferMethod} eq "rsync"; } }, {text => "CfgEdit_Title_Rsyncd_Port_Args", @@ -272,6 +299,7 @@ our %ConfigMenu = ( {name => "RsyncRestoreArgs", visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } }, + ### Archive paths and commands {text => "CfgEdit_Title_Archive_Paths_Commands", visible => sub { return $_[0]->{XferMethod} eq "archive"; } }, {name => "ArchiveClientCmd", diff --git a/lib/BackupPC/CGI/RSS.pm b/lib/BackupPC/CGI/RSS.pm index 2c63508..f773157 100644 --- a/lib/BackupPC/CGI/RSS.pm +++ b/lib/BackupPC/CGI/RSS.pm @@ -135,7 +135,7 @@ sub action $incrSizeTot = sprintf("%.2f", $incrSizeTot / 1000); my $now = timeStamp2(time); - print 'Content-type: text/xml', "\n\n", + print 'Content-type: text/xml', "\r\n\r\n", $rss->as_string; } diff --git a/lib/BackupPC/Config/Meta.pm b/lib/BackupPC/Config/Meta.pm index 133ddd0..17833c7 100644 --- a/lib/BackupPC/Config/Meta.pm +++ b/lib/BackupPC/Config/Meta.pm @@ -28,7 +28,7 @@ # #======================================================================== # -# Version 3.1.0, released 25 Nov 2007. +# Version 3.1.1, released 22 Dec 2008. # # See http://backuppc.sourceforge.net. # @@ -194,13 +194,16 @@ use vars qw(%ConfigMeta); ###################################################################### XferMethod => { type => "select", - values => [qw(archive rsync rsyncd smb tar)], + values => [qw(archive ftp rsync rsyncd smb tar)], }, XferLogLevel => "integer", ClientCharset => "string", ClientCharsetLegacy => "string", + ###################################################################### + # Smb Configuration + ###################################################################### SmbShareName => { type => "list", child => "string", @@ -212,6 +215,9 @@ use vars qw(%ConfigMeta); SmbClientIncrCmd => "string", SmbClientRestoreCmd => {type => "string", undefIfEmpty => 1}, + ###################################################################### + # Tar Configuration + ###################################################################### TarShareName => { type => "list", child => "string", @@ -222,6 +228,9 @@ use vars qw(%ConfigMeta); TarClientRestoreCmd => {type => "string", undefIfEmpty => 1}, TarClientPath => {type => "string", undefIfEmpty => 1}, + ###################################################################### + # Rsync Configuration + ###################################################################### RsyncShareName => { type => "list", child => "string", @@ -230,11 +239,17 @@ use vars qw(%ConfigMeta); RsyncClientCmd => "string", RsyncClientRestoreCmd => "string", + ###################################################################### + # Rsyncd Configuration + ###################################################################### RsyncdClientPort => "integer", RsyncdUserName => "string", RsyncdPasswd => "string", RsyncdAuthRequired => "boolean", + ###################################################################### + # Rsync(d) Options + ###################################################################### RsyncCsumCacheVerifyProb => "float", RsyncArgs => { type => "list", @@ -248,11 +263,34 @@ use vars qw(%ConfigMeta); child => "string", }, + ###################################################################### + # FTP Configuration + ###################################################################### + FtpShareName => { + type => "list", + child => "string", + }, + FtpUserName => "string", + FtpPasswd => "string", + FtpBlockSize => "integer", + FtpPort => "integer", + FtpTimeout => "integer", + FtpFollowSymlinks => "boolean", + + ###################################################################### + # BackupPCd Configuration + ###################################################################### + BackupPCdShareName => { + type => "list", + child => "string", + }, BackupPCdCmd => "string", BackupPCdPath => "string", - BackupPCdShareName => "string", BackupPCdRestoreCmd => "string", + ###################################################################### + # Archive Configuration + ###################################################################### ArchiveDest => "string", ArchiveComp => { type => "select", @@ -262,6 +300,9 @@ use vars qw(%ConfigMeta); ArchiveSplit => "float", ArchiveClientCmd => "string", + ###################################################################### + # Other Client Configuration + ###################################################################### NmbLookupCmd => "string", NmbLookupFindHostCmd => "string", @@ -408,6 +449,16 @@ use vars qw(%ConfigMeta); RsyncClientCmd => "boolean", RsyncClientPath => "boolean", RsyncClientRestoreCmd => "boolean", + FtpShareName => "boolean", + FtpUserName => "boolean", + FtpPasswd => "boolean", + FtpBlockSize => "boolean", + FtpPort => "boolean", + FtpTimeout => "boolean", + BackupPCdShareName => "boolean", + BackupPCdCmd => "boolean", + BackupPCdPath => "boolean", + BackupPCdRestoreCmd => "boolean", ArchiveDest => "boolean", ArchiveComp => "boolean", ArchivePar => "boolean", diff --git a/lib/BackupPC/Lang/de.pm b/lib/BackupPC/Lang/de.pm index 06a1b6b..06673ad 100644 --- a/lib/BackupPC/Lang/de.pm +++ b/lib/BackupPC/Lang/de.pm @@ -141,8 +141,8 @@ $Lang{BackupPC_Summary}=<Dieser Status wurde am \$now generiert.
  • Das Pool Filesystem (Backup-Speicherplatz) ist zu \$Info{DUlastValue}% (\$DUlastTime) voll, das Maximum heute ist \$Info{DUDailyMax}% (\$DUmaxTime) - und das Maximum gestern war \$Info{DUDailyMaxPrev}%. (Hinweis: Sollten ca. 70% ?berschritten werden, so - ist evtl. bald eine Erweiterung des Backupspeichers erforderlich. Ist weitere Planung n?tig?) + und das Maximum gestern war \$Info{DUDailyMaxPrev}%. (Hinweis: Sollten ca. 70% überschritten werden, so + ist evtl. bald eine Erweiterung des Backupspeichers erforderlich. Ist weitere Planung nötig?)

    @@ -1391,6 +1391,7 @@ $Lang{CfgEdit_Title_Smb_Settings} = "Smb Einstellungen"; $Lang{CfgEdit_Title_Tar_Settings} = "Tar Einstellungen"; $Lang{CfgEdit_Title_Rsync_Settings} = "Rsync Einstellungen"; $Lang{CfgEdit_Title_Rsyncd_Settings} = "Rsyncd Einstellungen"; +$Lang{CfgEdit_Title_Ftp_Settings} = "FTP Einstellungen"; $Lang{CfgEdit_Title_BackupPCd_Settings} = "BackupPCd Einstellungen"; $Lang{CfgEdit_Title_Archive_Settings} = "Archive Einstellungen"; $Lang{CfgEdit_Title_Include_Exclude} = "Include/Exclude"; diff --git a/lib/BackupPC/Lang/en.pm b/lib/BackupPC/Lang/en.pm index 1b72d71..6154232 100644 --- a/lib/BackupPC/Lang/en.pm +++ b/lib/BackupPC/Lang/en.pm @@ -1383,6 +1383,7 @@ $Lang{CfgEdit_Title_Smb_Settings} = "Smb Settings"; $Lang{CfgEdit_Title_Tar_Settings} = "Tar Settings"; $Lang{CfgEdit_Title_Rsync_Settings} = "Rsync Settings"; $Lang{CfgEdit_Title_Rsyncd_Settings} = "Rsyncd Settings"; +$Lang{CfgEdit_Title_Ftp_Settings} = "FTP Settings"; $Lang{CfgEdit_Title_BackupPCd_Settings} = "BackupPCd Settings"; $Lang{CfgEdit_Title_Archive_Settings} = "Archive Settings"; $Lang{CfgEdit_Title_Include_Exclude} = "Include/Exclude"; diff --git a/lib/BackupPC/Lang/es.pm b/lib/BackupPC/Lang/es.pm index 63cd9c2..25bb73a 100644 --- a/lib/BackupPC/Lang/es.pm +++ b/lib/BackupPC/Lang/es.pm @@ -1387,6 +1387,7 @@ $Lang{CfgEdit_Title_Smb_Settings} = "Smb Settings"; $Lang{CfgEdit_Title_Tar_Settings} = "Tar Settings"; $Lang{CfgEdit_Title_Rsync_Settings} = "Rsync Settings"; $Lang{CfgEdit_Title_Rsyncd_Settings} = "Rsyncd Settings"; +$Lang{CfgEdit_Title_Ftp_Settings} = "FTP Settings"; $Lang{CfgEdit_Title_BackupPCd_Settings} = "BackupPCd Settings"; $Lang{CfgEdit_Title_Archive_Settings} = "Archive Settings"; $Lang{CfgEdit_Title_Include_Exclude} = "Include/Exclude"; diff --git a/lib/BackupPC/Lang/fr.pm b/lib/BackupPC/Lang/fr.pm index c9fc8ff..0109692 100644 --- a/lib/BackupPC/Lang/fr.pm +++ b/lib/BackupPC/Lang/fr.pm @@ -1384,6 +1384,7 @@ $Lang{CfgEdit_Title_Smb_Settings} = "Param $Lang{CfgEdit_Title_Tar_Settings} = "Paramètres de Tar"; $Lang{CfgEdit_Title_Rsync_Settings} = "Paramètres de Rsync"; $Lang{CfgEdit_Title_Rsyncd_Settings} = "Paramètres de Rsyncd"; +$Lang{CfgEdit_Title_Ftp_Settings} = "Paramètres de FTP"; $Lang{CfgEdit_Title_BackupPCd_Settings} = "Paramètres de BackupPCd"; $Lang{CfgEdit_Title_Archive_Settings} = "Paramètres d'archivage"; $Lang{CfgEdit_Title_Include_Exclude} = "Inclure/Exclure"; diff --git a/lib/BackupPC/Lang/it.pm b/lib/BackupPC/Lang/it.pm index d26dcf4..f9b81fb 100644 --- a/lib/BackupPC/Lang/it.pm +++ b/lib/BackupPC/Lang/it.pm @@ -1398,6 +1398,7 @@ $Lang{CfgEdit_Title_Smb_Settings} = "Configurazione Smb"; $Lang{CfgEdit_Title_Tar_Settings} = "Configurazione Tar"; $Lang{CfgEdit_Title_Rsync_Settings} = "Configurazione Rsync"; $Lang{CfgEdit_Title_Rsyncd_Settings} = "Configurazione Rsyncd"; +$Lang{CfgEdit_Title_Ftp_Settings} = "Configurazione FTP"; $Lang{CfgEdit_Title_BackupPCd_Settings} = "Configurazione BackupPCd"; $Lang{CfgEdit_Title_Archive_Settings} = "Configurazione Archivi"; $Lang{CfgEdit_Title_Include_Exclude} = "Includi/Escludi"; diff --git a/lib/BackupPC/Lang/nl.pm b/lib/BackupPC/Lang/nl.pm index 36adada..943ebe5 100644 --- a/lib/BackupPC/Lang/nl.pm +++ b/lib/BackupPC/Lang/nl.pm @@ -1396,6 +1396,7 @@ $Lang{CfgEdit_Title_Smb_Settings} = "Smb instellingen"; $Lang{CfgEdit_Title_Tar_Settings} = "Tar instellingen"; $Lang{CfgEdit_Title_Rsync_Settings} = "Rsync instellingen"; $Lang{CfgEdit_Title_Rsyncd_Settings} = "Rsyncd instellingen"; +$Lang{CfgEdit_Title_Ftp_Settings} = "FTP instellingen"; $Lang{CfgEdit_Title_BackupPCd_Settings} = "BackupPCd instellingen"; $Lang{CfgEdit_Title_Archive_Settings} = "Archivering instellingen"; $Lang{CfgEdit_Title_Include_Exclude} = "Inclusief/Exclusief"; diff --git a/lib/BackupPC/Lang/pl.pm b/lib/BackupPC/Lang/pl.pm index b4cec29..9963371 100644 --- a/lib/BackupPC/Lang/pl.pm +++ b/lib/BackupPC/Lang/pl.pm @@ -1377,6 +1377,7 @@ $Lang{CfgEdit_Title_Smb_Settings} = "Ustawienia Smb"; $Lang{CfgEdit_Title_Tar_Settings} = "Ustawienia Tar"; $Lang{CfgEdit_Title_Rsync_Settings} = "Ustawienia Rsync"; $Lang{CfgEdit_Title_Rsyncd_Settings} = "Ustawienia Rsyncd"; +$Lang{CfgEdit_Title_Ftp_Settings} = "Ustawienia FTP"; $Lang{CfgEdit_Title_BackupPCd_Settings} = "Ustawienia BackupPCd"; $Lang{CfgEdit_Title_Archive_Settings} = "Ustawienia Archiwizacji"; $Lang{CfgEdit_Title_Include_Exclude} = "Dodaj/Usuń"; diff --git a/lib/BackupPC/Lang/pt_br.pm b/lib/BackupPC/Lang/pt_br.pm index 7086d8e..ec3c06b 100644 --- a/lib/BackupPC/Lang/pt_br.pm +++ b/lib/BackupPC/Lang/pt_br.pm @@ -1384,9 +1384,10 @@ $Lang{CfgEdit_Title_User_Config_Editing} = "Edi $Lang{CfgEdit_Title_Xfer} = "Transferência"; $Lang{CfgEdit_Title_Xfer_Settings} = "Configurações de transferência"; $Lang{CfgEdit_Title_Smb_Settings} = "Configurações do Smb"; -$Lang{CfgEdit_Title_Tar_Settings} = "Configurações doTar"; +$Lang{CfgEdit_Title_Tar_Settings} = "Configurações do Tar"; $Lang{CfgEdit_Title_Rsync_Settings} = "Configurações do Rsync"; $Lang{CfgEdit_Title_Rsyncd_Settings} = "Configurações do Rsyncd"; +$Lang{CfgEdit_Title_Ftp_Settings} = "Configurações do FTP"; $Lang{CfgEdit_Title_BackupPCd_Settings} = "Configurações do BackupPCd"; $Lang{CfgEdit_Title_Archive_Settings} = "Configurações do Archive"; $Lang{CfgEdit_Title_Include_Exclude} = "Inclui/Exclui"; diff --git a/lib/BackupPC/Lang/zh_CN.pm b/lib/BackupPC/Lang/zh_CN.pm index 62aedb1..24c97e6 100644 --- a/lib/BackupPC/Lang/zh_CN.pm +++ b/lib/BackupPC/Lang/zh_CN.pm @@ -1351,6 +1351,7 @@ $Lang{CfgEdit_Title_Smb_Settings} = "Smb 设置"; $Lang{CfgEdit_Title_Tar_Settings} = "Tar 设置"; $Lang{CfgEdit_Title_Rsync_Settings} = "Rsync 设置"; $Lang{CfgEdit_Title_Rsyncd_Settings} = "Rsyncd 设置"; +$Lang{CfgEdit_Title_Ftp_Settings} = "FTP 设置"; $Lang{CfgEdit_Title_BackupPCd_Settings} = "BackupPCd 设置"; $Lang{CfgEdit_Title_Archive_Settings} = "备档设置"; $Lang{CfgEdit_Title_Include_Exclude} = "包含/排除"; diff --git a/lib/BackupPC/Lib.pm b/lib/BackupPC/Lib.pm index 3e320ba..8665c29 100644 --- a/lib/BackupPC/Lib.pm +++ b/lib/BackupPC/Lib.pm @@ -29,7 +29,7 @@ # #======================================================================== # -# Version 3.1.0, released 25 Nov 2007. +# Version 3.2.0, released 31 Dec 2008. # # See http://backuppc.sourceforge.net. # @@ -51,7 +51,7 @@ use Digest::MD5; use Config; use Encode qw/from_to encode_utf8/; -use vars qw( $IODirentOk ); +use vars qw( $IODirentOk $IODirentLoaded ); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); require Exporter; @@ -72,20 +72,7 @@ require DynaLoader; BEGIN { eval "use IO::Dirent qw( readdirent DT_DIR );"; - if ( !$@ && opendir(my $fh, ".") ) { - # - # Make sure the IO::Dirent really works - some installs - # on certain file systems don't return a valid type. - # - my $dt_dir = eval("DT_DIR"); - foreach my $e ( readdirent($fh) ) { - if ( $e->{name} eq "." && $e->{type} == $dt_dir ) { - $IODirentOk = 1; - last; - } - } - closedir($fh); - } + $IODirentLoaded = 1 if ( !$@ ); }; # @@ -115,7 +102,7 @@ sub new # # Set defaults for $topDir and $installDir. # - $topDir = '/tera0/backup/BackupPC' if ( $topDir eq "" ); + $topDir = '/data/BackupPC' if ( $topDir eq "" ); $installDir = '/usr/local/BackupPC' if ( $installDir eq "" ); # @@ -128,7 +115,7 @@ sub new useFHS => $useFHS, TopDir => $topDir, InstallDir => $installDir, - ConfDir => $confDir eq "" ? '/tera0/backup/BackupPC/conf' : $confDir, + ConfDir => $confDir eq "" ? '/data/BackupPC/conf' : $confDir, LogDir => '/var/log/BackupPC', }; } else { @@ -143,7 +130,7 @@ sub new my $bpc = bless { %$paths, - Version => '3.1.0', + Version => '3.2.0', }, $class; $bpc->{storage} = BackupPC::Storage->new($paths); @@ -152,8 +139,6 @@ sub new # Clean up %ENV and setup other variables. # delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; - $bpc->{PoolDir} = "$bpc->{TopDir}/pool"; - $bpc->{CPoolDir} = "$bpc->{TopDir}/cpool"; if ( defined(my $error = $bpc->ConfigRead()) ) { print(STDERR $error, "\n"); return; @@ -167,6 +152,8 @@ sub new $paths->{$dir} = $bpc->{$dir} = $bpc->{Conf}{$dir}; } $bpc->{storage}->setPaths($paths); + $bpc->{PoolDir} = "$bpc->{TopDir}/pool"; + $bpc->{CPoolDir} = "$bpc->{TopDir}/cpool"; # # Verify we are running as the correct user @@ -486,6 +473,26 @@ sub dirRead from_to($path, "utf8", $need->{charsetLegacy}) if ( $need->{charsetLegacy} ne "" ); return if ( !opendir(my $fh, $path) ); + if ( $IODirentLoaded && !$IODirentOk ) { + # + # Make sure the IO::Dirent really works - some installs + # on certain file systems (eg: XFS) don't return a valid type. + # + if ( opendir(my $fh, $bpc->{TopDir}) ) { + my $dt_dir = eval("DT_DIR"); + foreach my $e ( readdirent($fh) ) { + if ( $e->{name} eq "." && $e->{type} == $dt_dir ) { + $IODirentOk = 1; + last; + } + } + closedir($fh); + } + # + # if it isn't ok then don't check again. + # + $IODirentLoaded = 0 if ( !$IODirentOk ); + } if ( $IODirentOk ) { @entries = sort({ $a->{inode} <=> $b->{inode} } readdirent($fh)); map { $_->{type} = 0 + $_->{type} } @entries; # make type numeric @@ -1463,7 +1470,40 @@ sub sortedPCLogFiles } closedir(DIR); } - return sort(compareLOGName @files); + return sort compareLOGName @files; +} + +# +# converts a glob-style pattern into a perl regular expression. +# +sub glob2re +{ + my ( $bpc, $glob ) = @_; + my ( $char, $subst ); + + # $escapeChars escapes characters with no special glob meaning but + # have meaning in regexps. + my $escapeChars = [ '.', '/', ]; + + # $charMap is where we implement the special meaning of glob + # patterns and translate them to regexps. + my $charMap = { + '?' => '[^/]', + '*' => '[^/]*', }; + + # multiple forward slashes are equivalent to one slash. We should + # never have to use this. + $glob =~ s/\/+/\//; + + foreach $char (@$escapeChars) { + $glob =~ s/\Q$char\E/\\$char/g; + } + + while ( ( $char, $subst ) = each(%$charMap) ) { + $glob =~ s/(? +# +# COPYRIGHT +# Copyright (C) 2008 Zmanda +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +#======================================================================== +# +# Version 3.1.0+ +# +# See http://backuppc.sourceforge.net. +# +#======================================================================== + + +package BackupPC::Xfer; + +use strict; +use Encode qw/from_to encode/; + +use BackupPC::Xfer::Archive; +use BackupPC::Xfer::BackupPCd; +use BackupPC::Xfer::Ftp; +use BackupPC::Xfer::Protocol; +use BackupPC::Xfer::Rsync; +use BackupPC::Xfer::Smb; +use BackupPC::Xfer::Tar; + +use vars qw( $errStr ); + +sub create +{ + my($protocol, $bpc, $args) = @_; + my $xfer; + + $errStr = undef; + + if ( $protocol eq 'archive' ) { + + $xfer = BackupPC::Xfer::Archive->new( $bpc, $args ); + $errStr = BackupPC::Xfer::Archive::errStr() if ( !defined($xfer) ); + return $xfer; + + } elsif ( $protocol eq 'backuppcd' ) { + + $xfer = BackupPC::Xfer::BackupPCd->new( $bpc, $args ); + $errStr = BackupPC::Xfer::BackupPCd::errStr() if ( !defined($xfer) ); + return $xfer; + + } elsif ( $protocol eq 'ftp' ) { + + $xfer = BackupPC::Xfer::Ftp->new( $bpc, $args ); + $errStr = BackupPC::Xfer::Ftp::errStr() if ( !defined($xfer) ); + return $xfer; + + } elsif ( $protocol eq 'rsync' || $protocol eq 'rsyncd' ) { + + $xfer = BackupPC::Xfer::Rsync->new( $bpc, $args ); + $errStr = BackupPC::Xfer::Rsync::errStr() if ( !defined($xfer) ); + return $xfer; + + } elsif ( $protocol eq 'smb' ) { + + $xfer = BackupPC::Xfer::Smb->new( $bpc, $args ); + $errStr = BackupPC::Xfer::Smb::errStr() if ( !defined($xfer) ); + return $xfer; + + } elsif ( $protocol eq 'tar' ) { + + $xfer = BackupPC::Xfer::Tar->new( $bpc, $args ); + $errStr = BackupPC::Xfer::Tar::errStr() if ( !defined($xfer) ); + return $xfer; + + } elsif ( $protocol eq 'protocol') { + + $xfer = BackupPC::Xfer::Protocol->new( $bpc, $args ); + $errStr = BackupPC::Xfer::Protocol::errStr() if ( !defined($xfer) ); + return $xfer; + + } else { + + $xfer = undef; + $errStr = "$protocol is not a supported protocol."; + return $xfer; + } +} + +# +# getShareNames() loads the correct shares dependent on the +# transfer type. +# +sub getShareNames +{ + my($conf) = @_; + my $ShareNames; + + if ( $conf->{XferMethod} eq "tar" ) { + $ShareNames = $conf->{TarShareName}; + + } elsif ( $conf->{XferMethod} eq "ftp" ) { + $ShareNames = $conf->{FtpShareName}; + + } elsif ( $conf->{XferMethod} eq "rsync" || $conf->{XferMethod} eq "rsyncd" ) { + $ShareNames = $conf->{RsyncShareName}; + + } elsif ( $conf->{XferMethod} eq "backuppcd" ) { + $ShareNames = $conf->{BackupPCdShareName}; + + } elsif ( $conf->{XferMethod} eq "smb" ) { + $ShareNames = $conf->{SmbShareName}; + + } else { + # + # default to smb shares + # + $ShareNames = $conf->{SmbShareName}; + } + + $ShareNames = [$ShareNames] unless ref($ShareNames) eq "ARRAY"; + return $ShareNames; +} + +sub errStr +{ + return $errStr; +} + +1; diff --git a/lib/BackupPC/Xfer/Archive.pm b/lib/BackupPC/Xfer/Archive.pm index e7b0e2b..cb18db7 100644 --- a/lib/BackupPC/Xfer/Archive.pm +++ b/lib/BackupPC/Xfer/Archive.pm @@ -38,40 +38,7 @@ package BackupPC::Xfer::Archive; use strict; - -sub new -{ - my($class, $bpc, $args) = @_; - - $args ||= {}; - my $t = bless { - bpc => $bpc, - conf => { $bpc->Conf }, - host => "", - hostIP => "", - shareName => "", - pipeRH => undef, - pipeWH => undef, - badFiles => [], - %$args, - }, $class; - - return $t; -} - -sub args -{ - my($t, $args) = @_; - - foreach my $arg ( keys(%$args) ) { - $t->{$arg} = $args->{$arg}; - } -} - -sub useArchive -{ - return 1; -} +use base qw(BackupPC::Xfer::Protocol); sub start { @@ -130,36 +97,4 @@ sub run return "Completed Archive"; } -sub errStr -{ - my($t) = @_; - - return $t->{_errStr}; -} - -sub abort -{ -} - -sub xferPid -{ - my($t) = @_; - - return ($t->{xferPid}); -} - -sub logMsg -{ - my($t, $msg) = @_; - - push(@{$t->{_logMsg}}, $msg); -} - -sub logMsgGet -{ - my($t) = @_; - - return shift(@{$t->{_logMsg}}); -} - 1; diff --git a/lib/BackupPC/Xfer/BackupPCd.pm b/lib/BackupPC/Xfer/BackupPCd.pm index f8552c0..e6dd5b0 100644 --- a/lib/BackupPC/Xfer/BackupPCd.pm +++ b/lib/BackupPC/Xfer/BackupPCd.pm @@ -38,6 +38,7 @@ package BackupPC::Xfer::BackupPCd; use strict; +use base qw(BackupPC::Xfer::Protocol); sub new { @@ -71,20 +72,6 @@ sub new return $t; } -sub args -{ - my($t, $args) = @_; - - foreach my $arg ( keys(%$args) ) { - $t->{$arg} = $args->{$arg}; - } -} - -sub useTar -{ - return 0; -} - sub start { my($t) = @_; @@ -221,47 +208,4 @@ sub run } } -sub abort -{ - my($t, $reason) = @_; - - # TODO - return 1; -} - -sub errStr -{ - my($t) = @_; - - return $t->{_errStr}; -} - -sub xferPid -{ - my($t) = @_; - - return (); -} - -# -# Returns a hash ref giving various status information about -# the transfer. -# -sub getStats -{ - my($t) = @_; - - return { map { $_ => $t->{$_} } - qw(byteCnt fileCnt xferErrCnt xferBadShareCnt xferBadFileCnt - xferOK hostAbort hostError lastOutputLine) - }; -} - -sub getBadFiles -{ - my($t) = @_; - - return @{$t->{badFiles}}; -} - 1; diff --git a/lib/BackupPC/Xfer/Ftp.pm b/lib/BackupPC/Xfer/Ftp.pm new file mode 100644 index 0000000..2b3c001 --- /dev/null +++ b/lib/BackupPC/Xfer/Ftp.pm @@ -0,0 +1,1019 @@ +#============================================================= -*-perl-*- +# +# BackupPC::Xfer::Ftp package +# +# DESCRIPTION +# +# This library defines a BackupPC::Xfer::Ftp class for transferring +# data from a FTP client. +# +# AUTHOR +# Paul Mantz +# +# COPYRIGHT +# (C) 2008, Zmanda Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +# 02111-1307 USA +# +# +#======================================================================== +# +# Unreleased, planned release in 3.2 (or 3.1.1) +# +# See http://backuppc.sourceforge.net. +# +#======================================================================== + + +package BackupPC::Xfer::Ftp; + +use strict; + +use BackupPC::View; +use BackupPC::Attrib qw(:all); + +use Encode qw/from_to encode/; +use File::Listing qw/parse_dir/; +use File::Path; +use Data::Dumper; +use base qw(BackupPC::Xfer::Protocol); + +use vars qw( $FTPLibOK $FTPLibErr $ARCLibOK ); + +use constant S_IFMT => 0170000; + +BEGIN { + + $FTPLibOK = 1; + $ARCLibOK = 0; + + # + # clear eval error variable + # + my @FTPLibs = qw( Net::FTP Net::FTP::RetrHandle ); + + foreach my $module ( @FTPLibs ) { + + undef $@; + eval "use $module;"; + + if ( $@ ) { + $FTPLibOK = 0; + $FTPLibErr = "module $module doesn't exist: $@"; + last; + } + } + + eval "use Net::FTP::AutoReconnect;"; + $ARCLibOK = (defined($@)) ? 1 : 0; +}; + +############################################################################## +# Constructor +############################################################################## + + +# +# usage: +# $xfer = new BackupPC::Xfer::Ftp( $bpc, %args ); +# +# new() is your default class constructor. it also calls the +# constructor for Protocol as well. +# +sub new +{ + my ( $class, $bpc, $args ) = @_; + $args ||= {}; + + my $t = BackupPC::Xfer::Protocol->new( + $bpc, + { + ftp => undef, + stats => { + errorCnt => 0, + TotalFileCnt => 0, + TotalFileSize => 0, + ExistFileCnt => 0, + ExistFileSize => 0, + ExistFileCompSize => 0, + }, + %$args, + } ); + return bless( $t, $class ); +} + + +############################################################################## +# Methods +############################################################################## + +# +# usage: +# $xfer->start(); +# +# start() is called to configure and initiate a dump or restore, +# depending on the configured options. +# +sub start +{ + my ($t) = @_; + + my $bpc = $t->{bpc}; + my $conf = $t->{conf}; + + my ( @fileList, $logMsg, $incrDate, $args, $dumpText ); + + # + # initialize the statistics returned by getStats() + # + foreach ( qw/byteCnt fileCnt xferErrCnt xferBadShareCnt + xferBadFileCnt xferOK hostAbort hostError + lastOutputLine/ ) + { + $t->{$_} = 0; + } + + # + # Net::FTP::RetrHandle is necessary. + # + if ( !$FTPLibOK ) { + $t->{_errStr} = "Error: FTP transfer selected but module" + . " Net::FTP::RetrHandle is not installed."; + $t->{xferErrCnt}++; + return; + } + + # + # standardize the file include/exclude settings if necessary + # + unless ( $t->{type} eq 'restore' ) { + $bpc->backupFileConfFix( $conf, "FtpShareName" ); + $t->loadInclExclRegexps("FtpShareName"); + } + + # + # Convert the encoding type of the names if at all possible + # + from_to( $args->{shareName}, "utf8", $conf->{ClientCharset} ) + if ( $conf->{ClientCharset} ne "" ); + + # + # Collect FTP configuration arguments and translate them for + # passing to the FTP module. + # + $args = $t->getFTPArgs(); + + # + # Create the Net::FTP::AutoReconnect or Net::FTP object. + # + unless ( $t->{ftp} = ($ARCLibOK) ? Net::FTP::AutoReconnect->new(%$args) + : Net::FTP->new(%$args) ) + { + $t->{_errStr} = "Can't open connection to $args->{Host}"; + $t->{xferErrCnt}++; + return; + } + + # + # Log in to the ftp server and set appropriate path information. + # + unless ( $t->{ftp}->login( $conf->{FtpUserName}, $conf->{FtpPasswd} ) ) { + $t->{_errStr} = "Can't login to $args->{Host}"; + $t->{xferErrCnt}++; + return; + } + + unless ( $t->{ftp}->binary() ) { + $t->{_errStr} = "Can't enable binary transfer mode to $args->{Host}"; + $t->{xferErrCnt}++; + return; + } + + unless ( ( $t->{shareName} =~ m/^\.?$/ ) + || ( $t->{ftp}->cwd( $t->{shareName} ) ) ) + { + $t->{_errStr} = "Can't change working directory to $t->{shareName}"; + $t->{xferErrCnt}++; + return; + } + + unless ( $t->{sharePath} = $t->{ftp}->pwd() ) { + $t->{_errStr} = "Can't retrieve full working directory of $t->{shareName}"; + $t->{xferErrCnt}++; + return; + } + + # + # log the beginning of action based on type + # + if ( $t->{type} eq 'restore' ) { + $logMsg = "restore started on directory $t->{shareName}"; + + } elsif ( $t->{type} eq 'full' ) { + $logMsg = "full backup started on directory $t->{shareName}"; + + } elsif ( $t->{type} eq 'incr' ) { + + $incrDate = $bpc->timeStamp( $t->{incrBaseTime} - 3600, 1 ); + $logMsg = "incremental backup started back to $incrDate" . + " (backup #$t->{incrBaseBkupNum}) for directory" . " + $t->{shareName}"; + } + + # + # call the recursive function based on the type of action + # + if ( $t->{type} eq 'restore' ) { + + $t->restore(); + $logMsg = "Restore of $args->{Host} complete"; + + } elsif ( $t->{type} eq 'incr' ) { + + $t->backup(); + $logMsg = "Incremental backup of $args->{Host} complete"; + + } elsif ( $t->{type} eq 'full' ) { + + $t->backup(); + $logMsg = "Full backup of $args->{Host} complete"; + } + + delete $t->{_errStr}; + return $logMsg; +} + + +# +# +# +sub run +{ + my ($t) = @_; + my $stats = $t->{stats}; + + my ( $tarErrs, $nFilesExist, $sizeExist, + $sizeExistCom, $nFilesTotal, $sizeTotal ); + + # + # TODO: replace the $stats array with variables at the top level, + # ones returned by $getStats. They should be identical. + # + $tarErrs = 0; + $nFilesExist = $stats->{ExistFileCnt}; + $sizeExist = $stats->{ExistFileSize}; + $sizeExistCom = $stats->{ExistFileCompSize}; + $nFilesTotal = $stats->{TotalFileCnt}; + $sizeTotal = $stats->{TotalFileSize}; + + if ( $t->{type} eq "restore" ) { + return ( $t->{fileCnt}, $t->{byteCnt}, 0, 0 ); + + } else { + return \( $tarErrs, $nFilesExist, $sizeExist, + $sizeExistCom, $nFilesTotal, $sizeTotal ); + } +} + + +# +# usage: +# $t->restore(); +# +# TODO: finish or scuttle this function. It is not necessary for a +# release. +# +sub restore +{ + my $t = @_; + + my $bpc = $t->{bpc}; + my $fileList = $t->{fileList}; + + my ($path, $fileName, $fileAttr, $fileType ); + + #print STDERR "BackupPC::Xfer::Ftp->restore()"; + + # + # Prepare the view object + # + $t->{view} = BackupPC::View->new( $bpc, $t->{bkupSrcHost}, + $t->{backups} ); + my $view = $t->{view}; + + SCAN: foreach my $f ( @$fileList ) { + + #print STDERR "restoring $f...\n"; + + $f =~ /(.*)\/([^\/]*)/; + $path = $1; + $fileName = $2; + + $view->dirCache($path); + + $fileAttr = $view->fileAttrib($fileName); + $fileType = fileType2Text( $fileAttr->{type} ); + + if ( $fileType eq "dir") { + $t->restoreDir($fileName, $fileAttr); + + } elsif ( $fileType eq "file" ) { + $t->restoreFile($fileName, $fileAttr); + + } elsif ( $fileType eq "symlink" ) { + # + # ignore + # + } else { + # + # ignore + # + } + } # end SCAN +} + + +sub restoreDir +{ + my ( $t, $dirName, $dirAttr ) = @_; + + my $ftp = $t->{ftp}; + my $bpc = $t->{bpc}; + my $conf = $t->{conf}; + my $view = $t->{view}; + my $TopDir = $bpc->TopDir(); + + my $path = "$dirAttr->{relPath}/$dirName"; + my $dirList = $view->dirAttrib( -1, $t->{shareName}, $path ); + + my ( $fileName, $fileAttr, $fileType ); + + #print STDERR "BackupPC::Xfer::Ftp->restore($dirName)\n"; + + # + # Create the remote directory + # + unless ( $ftp->mkdir( $path, 1 ) ) { + + $t->logFileAction( "fail", $dirName, $dirAttr ); + return; + } + + SCAN: while ( ($fileName, $fileAttr ) = each %$dirList ) { + + $fileType = fileType2Text( $fileAttr->{type} ); + + if ( $fileType eq "dir" ) { + if ( $t->restoreDir( $fileName, $fileAttr ) ) { + $t->logWrite( "restored: $path/$fileName\n", 5 ); + } else { + $t->logWrite( "restore failed: $path/$fileName\n", 3 ); + } + + } elsif ( $fileType eq "file" ) { + $t->restoreFile( $fileName, $fileAttr ); + + } elsif ( $fileType eq "hardlink" ) { + # + # Hardlinks cannot be restored. however, if we have the + # target file in the pool, we can restore that. + # + $t->restoreFile( $fileName, $fileAttr ); + + next SCAN; + + } elsif ( $fileType eq "symlink" ) { + # + # Symlinks cannot be restored + # + next SCAN; + + } else { + # + # Ignore all other types (devices, doors, etc) + # + next SCAN; + } + } +} + + +sub restoreFile +{ + my ($t, $fileName, $fileAttr ) = @_; + + my $conf = $t->{conf}; + my $ftp = $t->{ftp}; + + my $poolFile = $fileAttr->{fullPath}; + my $fileDest = ( $conf->{ClientCharset} ne "" ) + ? from_to( "$fileAttr->{relPath}/$fileName", + "utf8", $conf->{ClientCharset} ) + : "$fileAttr->{relPath}/$fileName"; + + #print STDERR "BackupPC::Xfer::Ftp->restoreFile($fileName)\n"; + + # + # Note: is logging necessary here? + # + if ( $ftp->put( $poolFile, $fileDest ) ) { + $t->logFileAction("restore", $fileName, $fileAttr); + + } else { + $t->logFileAction("fail", $fileName, $fileAttr); + } +} + + +# +# usage: +# $t->backup($path); +# +# $t->backup() is a recursive function that takes a path as an +# argument, and performs a backup on that folder consistent with the +# configuration parameters. $path is considered rooted at +# $t->{shareName}, so no $ftp->cwd() command is necessary. +# +sub backup +{ + my ($t) = @_; + + my $ftp = $t->{ftp}; + my $bpc = $t->{bpc}; + my $conf = $t->{conf}; + my $TopDir = $bpc->TopDir(); + my $OutDir = "$TopDir/pc/$t->{client}/new/" + . $bpc->fileNameEltMangle( $t->{shareName} ); + + # + # Prepare the view object + # + $t->{view} = BackupPC::View->new( $bpc, $t->{client}, $t->{backups} ); + + # + # Prepare backup folder + # + unless ( mkpath( $OutDir, 0, 0755 ) ) { + $t->{_errStr} = "can't create OutDir: $OutDir"; + $t->{xferErrCnt}++; + return; + } + + # + # determine the filetype of the shareName and back it up + # appropriately. For now, assume that $t->{shareName} is a + # directory. + # + my $f = { + relPath => "", + fullName => $t->{shareName}, + }; + + if ( $t->handleDir( $f, $OutDir ) ) { + + $t->{xferOK} = 1; + return 1; + + } else { + + $t->{xferBadShareCnt}++; + return; + } +} + + +#################################################################################### +# FTP-specific functions +#################################################################################### + + +# +# This is an encapulation of the logic necessary to grab the arguments +# from %Conf and throw it in a hash pointer to be passed to the +# Net::FTP object. +# +sub getFTPArgs +{ + my ($t) = @_; + my $bpc = $t->{bpc}; + my $conf = $t->{conf}; + + # + # accepted default key => value pairs to Net::FTP + # + my $args = { + Host => undef, + Firewall => undef, # not used + FirewallType => undef, # not used + BlockSize => 10240, + Port => 21, + Timeout => 120, + Debug => 0, # do not touch + Passive => 1, # do not touch + Hash => undef, # do not touch + LocalAddr => "localhost", # do not touch + }; + + # + # This is mostly to fool makeDist + # + exists( $conf->{ClientNameAlias} ) && exists( $conf->{FtpBlockSize} ) && + exists( $conf->{FtpPort} ) && exists( $conf->{FtpTimeout} ) + or die "Configuration variables for FTP not present in config.pl"; + + # + # map of options from %Conf in the config.pl scripts to options + # the Net::FTP::AutoReconnect object. + # + my $argMap = { + "Host" => "ClientNameAlias", + "BlockSize" => "FtpBlockSize", + "Port" => "FtpPort", + "Timeout" => "FtpTimeout", + }; + + foreach my $key ( keys(%$args) ) { + $args->{$key} = $conf->{ $argMap->{$key} } || $args->{$key}; + } + + # + # Fix for $args->{Host} since it can be in more than one location. + # Note the precedence here, this may need to be fixed. Order of + # precedence: + # $conf->{ClientNameAlias} + # $t->{hostIP} + # $t->{host} + # + $args->{Host} ||= $t->{hostIP}; + $args->{Host} ||= $t->{host}; + + # + # return the reference to the hash of items + # + return $args; +} + + +# +# usage: +# $dirList = $t->remotels($path); +# +# remotels() returns a reference to a list of hash references that +# describe the contents of each file in the directory of the path +# specified. +# +# In the future, I would like to make this function return objects in +# Attrib format. That would be very optimal, and I could probably +# release the code to CPAN. +# +sub remotels +{ + my ( $t, $path ) = @_; + + my $ftp = $t->{ftp}; + my $bpc = $t->{bpc}; + my $conf = $t->{conf}; + + my ( $dirContents, $remoteDir, $f ); + + unless ( $dirContents = ($path =~ /^\.?$/ ) ? $ftp->dir() : + $ftp->dir("$path/") ) + { + $t->{xferErrCnt}++; + return "can't retrieve remote directory contents of $path"; + } + + foreach my $info ( @{parse_dir($dirContents)} ) { + + $f = { + name => $info->[0], + type => $info->[1], + size => $info->[2], + mtime => $info->[3], + mode => $info->[4], + }; + + # + # convert & store utf8 version of filename + # + $f->{utf8name} = $f->{name}; + from_to( $f->{utf8name}, $conf->{ClientCharset}, "utf8" ); + + # + # construct the full name + # + $f->{fullName} = "$t->{sharePath}/$path/$f->{name}"; + $f->{fullName} =~ s/\/+/\//g; + + $f->{relPath} = ($path eq "") ? $f->{name} : "$path/$f->{name}"; + $f->{relPath} =~ s/\/+/\//g; + + push( @$remoteDir, $f ); + } + + return $remoteDir; +} + + +# +# ignoreFileCheck() looks at the attributes of the arguments and the +# backup types, and determines if the file should be skipped in this +# backup. +# +sub ignoreFileCheck +{ + my ( $t, $f, $attrib ) = @_; + + # + # case for ignoring the files '.' & '..' + # + if ( $f->{name} =~ /^\.\.?$/ ) { + return 1; + } + + # + # Check the include/exclude lists. the function returns true if + # the file should be backed up, so return the opposite. + # + return ( !$t->checkIncludeExclude( $f->{fullName} ) ); +} + + +# +# handleSymlink() backs up a symlink. +# +sub handleSymlink +{ + my ( $t, $f, $OutDir, $attrib ) = @_; + + my $conf = $t->{conf}; + my $ftp = $t->{ftp}; + my ( $target, $targetDesc ); + + my $attribInfo = { + type => BPC_FTYPE_SYMLINK, + mode => $f->{mode}, + uid => undef, # unsupported + gid => undef, # unsupported + size => 0, + mtime => $f->{mtime}, + }; + + # + # If we are following symlinks, back them up as the type of file + # they point to. Otherwise, backup the symlink. + # + if ( $conf->{FtpFollowSymlinks} ) { + + # + # handle nested symlinks by recurring on the target until a + # file or directory is found. + # + $f->{type} =~ /^l (.*)/; + $target = $1; + + if ( $targetDesc = $ftp->dir("$target/") ) { + $t->handleSymDir( $f, $OutDir, $attrib, $targetDesc ); + + } elsif ( $targetDesc = $ftp->dir($target) ) { + if ( $targetDesc->[4] eq 'file' ) { + $t->handleSymFile( $f, $OutDir, $attrib ); + + } elsif ( $targetDesc->[4] =~ /l (.*)/) { + + $t->logFileAction("fail", $f->{utf8name}, $attribInfo); + return; + } + } else { + + $t->("fail", $f); + return; + } + + } else { + + # + # If we are not following symlinks, record them normally. + # + $attrib->set( $f->{utf8name}, $attribInfo ); + $t->logFileAction("create", $f->{utf8name}, $attribInfo); + } + return 1; +} + + +sub handleSymDir +{ + my ($t, $fSym, $OutDir, $attrib, $targetDesc) = @_; + + return 1; + } + + +sub handleSymFile +{ + my ( $t, $fSym, $OutDir, $attrib, $targetDesc ) = @_; + + my $bpc = $t->{bpc}; + my $conf = $t->{conf}; + + my $f = { + name => $fSym->{name}, + type => $targetDesc->[1], + size => $targetDesc->[2], + mtime => $targetDesc->[3], + mode => $targetDesc->[4] + }; + + $f->{utf8name} = $fSym->{name}; + from_to( $f->{utf8name}, $conf->{ClientCharset}, "utf8" ); + + $f->{relPath} = $fSym->{relPath}; + + $f->{fullName} = "$t->{shareName}/$fSym->{relPath}/$fSym->{name}"; + $f->{fullName} =~ s/\/+/\//g; + + # + # since FTP servers follow symlinks, we can jsut do this: + # + return $t->handleFile( $f, $OutDir, $attrib ); +} + + +# +# handleDir() backs up a directory, and initiates a backup of its +# contents. +# +sub handleDir +{ + my ( $t, $dir, $OutDir ) = @_; + + my $ftp = $t->{ftp}; + my $bpc = $t->{bpc}; + my $conf = $t->{conf}; + my $view = $t->{view}; + my $stats = $t->{stats}; + + my ( $exists, $digest, $outSize, $errs ); + my ( $poolWrite, $poolFile, $attribInfo ); + my ( $localDir, $remoteDir, $attrib, %expectedFiles ); + + if ( exists($dir->{utf8name})) { + $OutDir .= "/" . $bpc->fileNameMangle( $dir->{utf8name} ); + } + + unless ( -d $OutDir ) { + + mkpath( $OutDir, 0, 0755 ); + $t->logFileAction( "create", $dir->{utf8name}, $dir ); + } + + $attrib = BackupPC::Attrib->new( { compress => $t->{Compress} } ); + $remoteDir = $t->remotels( $dir->{relPath} ); + + if ( $t->{type} eq "incr" ) { + $localDir = $view->dirAttrib( $t->{incrBaseBkupNum}, + $t->{shareName}, $dir->{relPath} ); + %expectedFiles = map { $_ => 0 } sort keys %$localDir + } + + # + # take care of each file in the directory + # + SCAN: foreach my $f ( @{$remoteDir} ) { + + next SCAN if $t->ignoreFileCheck( $f, $attrib ); + + # + # handle based on filetype + # + if ( $f->{type} eq 'f' ) { + $t->handleFile( $f, $OutDir, $attrib ); + + } elsif ( $f->{type} eq 'd' ) { + + $attribInfo = { + type => BPC_FTYPE_DIR, + mode => $f->{mode}, + uid => undef, # unsupported + gid => undef, # unsupported + size => $f->{size}, + mtime => $f->{mtime}, + }; + + #print STDERR "$f->{utf8name}: ". Dumper($attribInfo); + + if ( $t->handleDir($f, $OutDir) ) { + $attrib->set( $f->{utf8name}, $attribInfo); + } + + } elsif ( $f->{type} =~ /^l (.*)/ ) { + $t->handleSymlink( $f, $OutDir, $attrib ); + + } else { + # + # do nothing + # + } + + # + # Mark file as seen in expected files hash + # + $expectedFiles{ $f->{utf8name} }++ if ( $t->{type} eq "incr" ); + + } # end foreach (@{$remoteDir}) + + # + # If the backup type is incremental, mark the files that are not + # present on the server as deleted. + # + if ( $t->{type} eq "incr" ) { + while ( my ($f, $seen) = each %expectedFiles ) { + $attrib->set( $f, { type => BPC_FTYPE_DELETED } ) + unless ($seen); + } + } + + # + # print the directory attributes, now that the directory is done. + # + my $fileName = $attrib->fileName($OutDir); + my $data = $attrib->writeData(); + + $poolWrite = BackupPC::PoolWrite->new( $bpc, $fileName, length($data), + $t->{Compress} ); + $poolWrite->write( \$data ); + ( $exists, $digest, $outSize, $errs ) = $poolWrite->close(); + + # + # Explicit success + # + return 1; +} + + +# +# handleFile() backs up a file. +# +sub handleFile +{ + my ( $t, $f, $OutDir, $attrib ) = @_; + + my $bpc = $t->{bpc}; + my $ftp = $t->{ftp}; + my $view = $t->{view}; + my $stats = $t->{stats}; + my $newFilesFH = $t->{newFilesFH}; + + my ( $poolFile, $poolWrite, $data, $localSize ); + my ( $exists, $digest, $outSize, $errs ); + my ( $oldAttrib ); + local *FTP; + + # + # If this is an incremental backup and the file exists in a + # previous backup unchanged, write the attribInfo for the file + # accordingly. + # + if ( $t->{type} eq "incr" ) { + return 1 if $t->incrFileExistCheck( $f, $attrib ); + } + + my $attribInfo = { + type => BPC_FTYPE_FILE, + mode => $f->{mode}, + uid => undef, # unsupported + gid => undef, # unsupported + size => $f->{size}, + mtime => $f->{mtime}, + }; + + # + # If this is a full backup or the file has changed on the host, + # back it up. + # + unless ( tie( *FTP, 'Net::FTP::RetrHandle', $ftp, $f->{fullName} ) ) { + + $t->handleFileAction( "fail", $attribInfo ); + $t->{xferBadFileCnt}++; + $stats->{errCnt}++; + return; + } + + $poolFile = $OutDir . "/" . $bpc->fileNameMangle( $f->{name} ); + $poolWrite = BackupPC::PoolWrite->new( $bpc, $poolFile, $f->{size}, + $bpc->{xfer}{compress} ); + + $localSize = 0; + while () { + + $localSize += length($_); + $poolWrite->write( \$_ ); + } + ( $exists, $digest, $outSize, $errs ) = $poolWrite->close(); + + # + # calculate the file statistics + # + if (@$errs) { + + $t->logFileAction( "fail", $f->{utf8name}, $attribInfo ); + unlink($poolFile); + $t->{xferBadFileCnt}++; + $t->{errCnt} += scalar(@$errs); + return; + } + + # + # this should never happen + # + if ( $localSize != $f->{size} ) { + + $t->logFileAction( "fail", $f->{utf8name}, $attribInfo ); + unklink($poolFile); + $stats->{xferBadFileCnt}++; + $stats->{errCnt}++; + return; + } + + # + # Perform logging + # + $attrib->set( $f->{utf8name}, $attribInfo ); + $t->logFileAction( $exists ? "pool" : "create", $f->{utf8name}, $attribInfo ); + print $newFilesFH "$digest $f->{size} $poolFile\n" unless $exists; + + # + # Cumulate the stats + # + $stats->{TotalFileCnt}++; + $stats->{ExistFileCnt}++; + $stats->{ExistFileCompSize} += -s $poolFile; + $stats->{ExistFileSize} += $f->{size}; + $stats->{TotalFileSize} += $f->{size}; + + $t->{byteCnt} += $localSize; + $t->{fileCnt}++; +} + + +# +# this function checks if the file has been modified on disk, and if +# it has, returns. Otherwise, it updates the attrib values. +# +sub incrFileExistCheck +{ + my ($t, $f, $attrib) = @_; + + my $view = $t->{view}; + + my $oldAttribInfo = $view->fileAttrib( $t->{incrBaseBkupNum}, + $t->{shareName}, $f->{relPath} ); + + #print STDERR "*" x 50 . "\n"; + #print STDERR "Old data:\n" . Dumper($oldAttribInfo); + #print STDERR "New data:\n" . Dumper($f); + #print STDERR "$f->{fullName}: $oldAttribInfo->{mtime} ?= $f->{mtime}, $oldAttribInfo->{size} ?= $f->{size}\n"; + + return ( $oldAttribInfo->{mtime} == $f->{mtime} + && $oldAttribInfo->{size} == $f->{size} ); +} + + +# +# Generate a log file message for a completed file. Taken from +# BackupPC_tarExtract. $f should be an attrib object. +# +sub logFileAction +{ + my ( $t, $action, $name, $attrib ) = @_; + + my $owner = "$attrib->{uid}/$attrib->{gid}"; + my $type = + ( ( "", "p", "c", "", "d", "", "b", "", "", "", "l", "", "s" ) ) + [ ( $attrib->{mode} & S_IFMT ) >> 12 ]; + + $name = "." if ( $name eq "" ); + $owner = "-/-" if ( $owner eq "/" ); + + my $fileAction = sprintf( " %-6s %1s%4o %9s %11.0f %s\n", + $action, $type, $attrib->{mode} & 07777, + $owner, $attrib->{size}, $name ); + + return $t->logWrite( $fileAction, 1 ); +} + +1; diff --git a/lib/BackupPC/Xfer/Protocol.pm b/lib/BackupPC/Xfer/Protocol.pm new file mode 100644 index 0000000..ae6b4c4 --- /dev/null +++ b/lib/BackupPC/Xfer/Protocol.pm @@ -0,0 +1,452 @@ +#============================================================= -*-perl-*- +# +# BackupPC::Xfer::Protocol package +# +# DESCRIPTION +# +# This library defines a BackupPC::Xfer::Protocol class which +# defines standard methods for the transfer protocols in BackupPC. +# +# AUTHOR +# Paul Mantz +# +# COPYRIGHT +# Copyright (C) 2001-2007 Craig Barratt +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +#======================================================================== +# +# Version 3.1.0, released 25 Nov 2007. +# +# See http://backuppc.sourceforge.net. +# +#======================================================================== + +package BackupPC::Xfer::Protocol; + +use strict; +use Data::Dumper; +use Encode qw/from_to encode/; + +use BackupPC::Attrib qw(:all); + +# +# usage: +# $t = BackupPC::Xfer::Protocol->new($args); +# +# new() is the constructor. There's nothing special going on here. +# +sub new +{ + my($class, $bpc, $args) = @_; + + $args ||= {}; + my $t = bless { + bpc => $bpc, + conf => $bpc->{Conf}, + host => "", + hostIP => "", + shareName => "", + pipeRH => undef, + pipeWH => undef, + badFiles => [], + + # + # Various stats + # + byteCnt => 0, + fileCnt => 0, + xferErrCnt => 0, + xferBadShareCnt => 0, + xferBadFileCnt => 0, + xferOK => 0, + + # + # User's args + # + %$args, + }, $class; + + return $t; +} + +# +# usage: +# $t->args($args); +# +# args() can be used to send additional argument to the Xfer object +# via a hash reference. +# +sub args +{ + my($t, $args) = @_; + + foreach my $arg ( keys(%$args) ) { + $t->{$arg} = $args->{$arg}; + } +} + +# +# usage: +# $t->start(); +# +# start() executes the actual data transfer. Must be implemented by +# the derived class. +# +sub start +{ + my($t) = @_; + + $t->{_errStr} = "start() not implemented by ".ref($t); + return; +} + +# +# +# +sub run +{ + my($t) = @_; + + $t->{_errStr} = "run() not implemented by ".ref($t); + return; +} + +# +# usage: +# $t->readOutput(); +# +# This function is only used when $t->useTar() == 1. +# +sub readOutput +{ + my($t) = @_; + + $t->{_errStr} = "readOutput() not implemented by " . ref($t); + return; +} + +# +# usage: +# $t->abort($reason); +# +# Aborts the current job. +# +sub abort +{ + my($t, $reason) = @_; + my @xferPid = $t->xferPid; + + $t->{abort} = 1; + $t->{abortReason} = $reason; + if ( @xferPid ) { + kill($t->{bpc}->sigName2num("INT"), @xferPid); + } +} + +# +# usage: +# $t->subSelectMask +# +# This function sets a mask for files when ($t->useTar == 1). +# +sub setSelectMask +{ + my($t) = @_; + + $t->{_errStr} = "readOutput() not implemented by " . ref($t); +} + +# +# usage: +# $t->errStr(); +# +sub errStr +{ + my($t) = @_; + + return $t->{_errStr}; +} + +# +# usage: +# $pid = $t->xferPid(); +# +# xferPid() returns the process id of the child forked process. +# +sub xferPid +{ + my($t) = @_; + + return ($t->{xferPid}); +} + +# +# usage: +# $t->logMsg($msg); +# +sub logMsg +{ + my ($t, $msg) = @_; + + push(@{$t->{_logMsg}}, $msg); +} + +# +# usage: +# $t->logMsgGet(); +# +sub logMsgGet +{ + my($t) = @_; + + return shift(@{$t->{_logMsg}}); +} + +# +# usage: +# $t->getStats(); +# +# This function returns xfer statistics. It Returns a hash ref giving +# various status information about the transfer. +# +sub getStats +{ + my ($t) = @_; + + return { + map { $_ => $t->{$_} } + qw(byteCnt fileCnt xferErrCnt xferBadShareCnt xferBadFileCnt + xferOK hostAbort hostError lastOutputLine) + }; +} + +sub getBadFiles +{ + my ($t) = @_; + + return @{$t->{badFiles}}; +} + +# +# useTar function. In order to work correctly, the protocol in +# question should overwrite the function if it needs to return true. +# +sub useTar +{ + return 0; +} + +############################################################################## +# Logging Functions +############################################################################## + +# +# usage: +# $t->logWrite($msg [, $level]) +# +# This function writes to XferLOG. +# +sub logWrite +{ + my($t, $msg, $level) = @_; + + my $XferLOG = $t->{XferLOG}; + $level = 3 if ( !defined($level) ); + + return ( $XferLOG->write(\$msg) ) if ( $level <= $t->{logLevel} ); +} + + +############################################################################## +# File Inclusion/Exclusion +############################################################################## + +# +# loadInclExclRegexps() places the appropriate file include/exclude regexps +# +sub loadInclExclRegexps +{ + my ( $t, $shareType ) = @_; + my $bpc = $t->{bpc}; + my $conf = $t->{conf}; + + my @BackupFilesOnly = (); + my @BackupFilesExclude = (); + my ($shareName, $shareNameRE); + + # + # $conf->{$shareType} shold be a reference to an array with one + # element, thanks to BackupFileConfFix(). + # + $shareName = @{ $conf->{$shareType} }[0]; + $shareName =~ s/\/*$//; # remove trailing slashes + + $t->{shareName} = $shareName; + $t->{shareNameRE} = $bpc->glob2re($shareName); + + # + # load all relevant values into @BackupFilesOnly + # + if ( ref( $conf->{BackupFilesOnly} ) eq "HASH" ) { + + foreach my $share ( ( '*', $shareName ) ) { + push @BackupFilesOnly, @{ $conf->{BackupFilesOnly}{$share} } + if ( defined( $conf->{BackupFilesOnly}{$share} ) ); + } + + } elsif ( ref( $conf->{BackupFilesOnly} ) eq "ARRAY" ) { + + push( @BackupFilesOnly, @{ $conf->{BackupFilesOnly} } ); + + } elsif ( !defined( $conf->{BackupFilesOnly} ) ) { + + # + # do nothing + # + + } else { + + # + # not a legitimate entry for $conf->{BackupFilesOnly} + # + $t->{_errStr} = "Incorrect syntax in BackupFilesOnly for host $t->{Host}"; + + return; + } + + # + # load all relevant values into @BackupFilesExclude + # + if ( ref( $conf->{BackupFilesExclude} ) eq "HASH" ) { + + foreach my $share ( ( '*', $shareName ) ) { + push( @BackupFilesExclude, + map { + ( $_ =~ /^\// ) + ? ( $t->{shareNameRE} . $bpc->glob2re($_) ) + : ( '.*\/' . $bpc->glob2re($_) . '(?=\/.*)?' ) + } @{ $conf->{BackupFilesExclude}{$share} } + ) if ( defined( $conf->{BackupFilesExclude}{$share} ) ) ; + } + + } elsif ( ref( $conf->{BackupFilesExclude} ) eq "ARRAY" ) { + + push( @BackupFilesExclude, + map { + ( $_ =~ /\// ) + ? ( $bpc->glob2re($_) ) + : ( '.*\/' . $bpc->glob2re($_) . '(?<=\/.*)?' ) + } @{ $conf->{BackupFilesExclude} } ); + + } elsif ( !defined( $conf->{BackupFilesOnly} ) ) { + + # + # do nothing here + # + + } else { + + # + # not a legitimate entry for $conf->{BackupFilesExclude} + # + $t->{_errStr} = + "Incorrect syntax in BackupFilesExclude for host $t->{Host}"; + return; + } + + # + # load the regular expressions into the xfer object + # + $t->{BackupFilesOnly} = ( @BackupFilesOnly > 0 ) ? \@BackupFilesOnly : undef; + $t->{BackupFilesExclude} = ( @BackupFilesExclude > 0 ) ? \@BackupFilesExclude : undef; + + return 1; +} + + +sub checkIncludeExclude +{ + my ($t, $file) = @_; + + return ( $t->checkIncludeMatch($file) && !$t->checkExcludeMatch($file) ); +} + +sub checkIncludeMatch +{ + my ($t, $file) = @_; + + my $shareName = $t->{shareName}; + my $includes = $t->{BackupFilesOnly} || return 1; + my $match = ""; + + foreach my $include ( @{$includes} ) { + + # + # construct regexp elsewhere to avoid syntactical evil + # + $match = '^' . quotemeta( $shareName . $include ) . '(?=\/.*)?'; + + # + # return true if the include folder is a parent of the file, + # or the folder itself. + # + return 1 if ( $file =~ /$match/ ); + + $match = '^' . quotemeta($file) . '(?=\/.*)?'; + + # + # return true if the file is a parent of the include folder, + # or the folder itself. + # + return 1 if ( "$shareName$include" =~ /$match/ ); + } + return 0; +} + +sub checkExcludeMatch +{ + my ($t, $file) = @_; + + my $shareName = $t->{shareName}; + my $includes = $t->{BackupFilesOnly} || return 0; + my $match = ""; + + foreach my $include ( @{$includes} ) { + + # + # construct regexp elsewhere to avoid syntactical evil + # + $match = '^' . quotemeta( $shareName . $include ) . '(?=\/.*)?'; + + # + # return true if the include folder is a parent of the file, + # or the folder itself. + # + return 1 if ( $file =~ /$match/ ); + + $match = '^' . quotemeta($file) . '(?=\/.*)?'; + + # + # return true if the file is a parent of the include folder, + # or the folder itself. + # + return 1 if ( "$shareName$include" =~ /$match/ ); + } + return 0; +} + +1; diff --git a/lib/BackupPC/Xfer/Rsync.pm b/lib/BackupPC/Xfer/Rsync.pm index 3110f87..ff24cd5 100644 --- a/lib/BackupPC/Xfer/Rsync.pm +++ b/lib/BackupPC/Xfer/Rsync.pm @@ -41,6 +41,7 @@ use strict; use BackupPC::View; use BackupPC::Xfer::RsyncFileIO; use Encode qw/from_to encode/; +use base qw(BackupPC::Xfer::Protocol); use vars qw( $RsyncLibOK $RsyncLibErr ); @@ -59,7 +60,7 @@ BEGIN { if ( $File::RsyncP::VERSION < 0.68 ) { $RsyncLibOK = 0; $RsyncLibErr = "File::RsyncP module version" - . " ($File::RsyncP::VERSION) too old: need 0.68"; + . " ($File::RsyncP::VERSION) too old: need >= 0.68"; } else { $RsyncLibOK = 1; } @@ -71,46 +72,8 @@ sub new my($class, $bpc, $args) = @_; return if ( !$RsyncLibOK ); - $args ||= {}; - my $t = bless { - bpc => $bpc, - conf => { $bpc->Conf }, - host => "", - hostIP => "", - shareName => "", - badFiles => [], - - # - # Various stats - # - byteCnt => 0, - fileCnt => 0, - xferErrCnt => 0, - xferBadShareCnt => 0, - xferBadFileCnt => 0, - xferOK => 0, - - # - # User's args - # - %$args, - }, $class; - - return $t; -} - -sub args -{ - my($t, $args) = @_; - - foreach my $arg ( keys(%$args) ) { - $t->{$arg} = $args->{$arg}; - } -} - -sub useTar -{ - return 0; + my $t = BackupPC::Xfer::Protocol->new($bpc, $args); + return bless($t, $class); } sub start @@ -480,11 +443,6 @@ sub abort return 1; } -sub setSelectMask -{ - my($t, $FDreadRef) = @_; -} - sub errStr { my($t) = @_; @@ -500,39 +458,4 @@ sub xferPid return (); } -sub logMsg -{ - my($t, $msg) = @_; - - push(@{$t->{_logMsg}}, $msg); -} - -sub logMsgGet -{ - my($t) = @_; - - return shift(@{$t->{_logMsg}}); -} - -# -# Returns a hash ref giving various status information about -# the transfer. -# -sub getStats -{ - my($t) = @_; - - return { map { $_ => $t->{$_} } - qw(byteCnt fileCnt xferErrCnt xferBadShareCnt xferBadFileCnt - xferOK hostAbort hostError lastOutputLine) - }; -} - -sub getBadFiles -{ - my($t) = @_; - - return @{$t->{badFiles}}; -} - 1; diff --git a/lib/BackupPC/Xfer/Smb.pm b/lib/BackupPC/Xfer/Smb.pm index acfe195..baaeccb 100644 --- a/lib/BackupPC/Xfer/Smb.pm +++ b/lib/BackupPC/Xfer/Smb.pm @@ -39,35 +39,7 @@ package BackupPC::Xfer::Smb; use strict; use Encode qw/from_to encode/; - -sub new -{ - my($class, $bpc, $args) = @_; - - $args ||= {}; - my $t = bless { - bpc => $bpc, - conf => { $bpc->Conf }, - host => "", - hostIP => "", - shareName => "", - pipeRH => undef, - pipeWH => undef, - badFiles => [], - %$args, - }, $class; - - return $t; -} - -sub args -{ - my($t, $args) = @_; - - foreach my $arg ( keys(%$args) ) { - $t->{$arg} = $args->{$arg}; - } -} +use base qw(BackupPC::Xfer::Protocol); sub useTar { @@ -284,6 +256,7 @@ sub readOutput $t->{XferLOG}->write(\"$_\n") if ( $t->{logLevel} >= 0 ); } elsif ( /^\s*NT_STATUS_ACCESS_DENIED listing (.*)/ || /^\s*ERRDOS - ERRnoaccess \(Access denied\.\) listing (.*)/ ) { + $t->{xferErrCnt}++; my $badDir = $1; $badDir =~ s{\\}{/}g; $badDir =~ s{/+}{/}g; @@ -349,14 +322,6 @@ sub readOutput return 1; } -sub abort -{ - my($t, $reason) = @_; - - $t->{abort} = 1; - $t->{abortReason} = $reason; -} - sub setSelectMask { my($t, $FDreadRef) = @_; @@ -364,53 +329,4 @@ sub setSelectMask vec($$FDreadRef, fileno($t->{pipeSMB}), 1) = 1; } -sub errStr -{ - my($t) = @_; - - return $t->{_errStr}; -} - -sub xferPid -{ - my($t) = @_; - - return ($t->{xferPid}); -} - -sub logMsg -{ - my($t, $msg) = @_; - - push(@{$t->{_logMsg}}, $msg); -} - -sub logMsgGet -{ - my($t) = @_; - - return shift(@{$t->{_logMsg}}); -} - -# -# Returns a hash ref giving various status information about -# the transfer. -# -sub getStats -{ - my($t) = @_; - - return { map { $_ => $t->{$_} } - qw(byteCnt fileCnt xferErrCnt xferBadShareCnt xferBadFileCnt - xferOK hostAbort hostError lastOutputLine) - }; -} - -sub getBadFiles -{ - my($t) = @_; - - return @{$t->{badFiles}}; -} - 1; diff --git a/lib/BackupPC/Xfer/Tar.pm b/lib/BackupPC/Xfer/Tar.pm index f6f0be1..4ab61e7 100644 --- a/lib/BackupPC/Xfer/Tar.pm +++ b/lib/BackupPC/Xfer/Tar.pm @@ -39,35 +39,7 @@ package BackupPC::Xfer::Tar; use strict; use Encode qw/from_to encode/; - -sub new -{ - my($class, $bpc, $args) = @_; - - $args ||= {}; - my $t = bless { - bpc => $bpc, - conf => { $bpc->Conf }, - host => "", - hostIP => "", - shareName => "", - pipeRH => undef, - pipeWH => undef, - badFiles => [], - %$args, - }, $class; - - return $t; -} - -sub args -{ - my($t, $args) = @_; - - foreach my $arg ( keys(%$args) ) { - $t->{$arg} = $args->{$arg}; - } -} +use base qw(BackupPC::Xfer::Protocol); sub useTar { @@ -270,18 +242,6 @@ sub readOutput return 1; } -sub abort -{ - my($t, $reason) = @_; - my @xferPid = $t->xferPid; - - $t->{abort} = 1; - $t->{abortReason} = $reason; - if ( @xferPid ) { - kill($t->{bpc}->sigName2num("INT"), @xferPid); - } -} - sub setSelectMask { my($t, $FDreadRef) = @_; @@ -289,53 +249,4 @@ sub setSelectMask vec($$FDreadRef, fileno($t->{pipeTar}), 1) = 1; } -sub errStr -{ - my($t) = @_; - - return $t->{_errStr}; -} - -sub xferPid -{ - my($t) = @_; - - return ($t->{xferPid}); -} - -sub logMsg -{ - my($t, $msg) = @_; - - push(@{$t->{_logMsg}}, $msg); -} - -sub logMsgGet -{ - my($t) = @_; - - return shift(@{$t->{_logMsg}}); -} - -# -# Returns a hash ref giving various status information about -# the transfer. -# -sub getStats -{ - my($t) = @_; - - return { map { $_ => $t->{$_} } - qw(byteCnt fileCnt xferErrCnt xferBadShareCnt xferBadFileCnt - xferOK hostAbort hostError lastOutputLine) - }; -} - -sub getBadFiles -{ - my($t) = @_; - - return @{$t->{badFiles}}; -} - 1; diff --git a/lib/Net/FTP/AutoReconnect.pm b/lib/Net/FTP/AutoReconnect.pm new file mode 100644 index 0000000..b2c82d7 --- /dev/null +++ b/lib/Net/FTP/AutoReconnect.pm @@ -0,0 +1,509 @@ +package Net::FTP::AutoReconnect; +our $VERSION = '0.2'; + +use warnings; +use strict; + +use Net::FTP; + +=head1 NAME + +Net::FTP::AutoReconnect - FTP client class with automatic reconnect on failure + +=head1 SYNOPSIS + +C is a wrapper module around C. +For many commands, if anything goes wrong on the first try, it tries +to disconnect and reconnect to the server, restore the state to the +same as it was when the command was executed, then execute it again. +The state includes login credentials, authorize credentials, transfer +mode (ASCII or binary), current working directory, and any restart, +passive, or port commands sent. + +=head1 DESCRIPTION + +The goal of this method is to hide some implementation details of FTP +server systems from the programmer. In particular, many FTP systems +will automatically disconnect a user after a relatively short idle +time or after a transfer is aborted. In this case, +C will simply reconnect, send the commands +necessary to return your session to its previous state, then resend +the command. If that fails, it will return the error. + +It makes no effort to determine what sorts of errors are likely to +succeed when they're retried. Partly that's because it's hard to +know; if you're retreiving a file from an FTP site with several +mirrors and the file is not found, for example, maybe on the next try +you'll connect to a different server and find it. But mostly it's +from laziness; if you have some good ideas about how to determine when +to retry and when not to bother, by all means send patches. + +This module contains an instance of C, which it passes most +method calls along to. + +These methods also record their state: C, C, +C, C, C, C, C, +C,C, C, C. Directory changing commands +execute a C afterwards and store their new working directory. + +These methods are automatically retried: C, C, C, +C, C, C, C, C, C, C, +C, C, C, C, C, C, C, C, +C, C, C, C, C, C, C, +C. + +These methods are tried just once: C, C, C, +C, C, C, C, C, +C, C, C. From C: C, +C, C, C. C doesn't actually send any +FTP commands (they're sent along with the command they apply to), +which is why it's not restarted. + +Any other commands are unimplemented (or possibly misdocumented); if I +missed one you'd like, please send a patch. + +=head2 CONSTRUCTOR + +=head3 new + +All parameters are passed along verbatim to C, as well as +stored in case we have to reconnect. + +=cut + ; + +sub new { + my $self = {}; + my $class = shift; + bless $self,$class; + + $self->{newargs} = \@_; + $self->reconnect(); + + $self; +} + +=head2 METHODS + +Most of the methods are those of L. One additional +method is available: + +=head3 reconnect() + +Abandon the current FTP connection and create a new one, restoring all +the state we can. + +=cut + ; + +sub reconnect +{ + my $self = shift; + + warn "Reconnecting!\n" + if ($ENV{DEBUG}); + + $self->{ftp} = Net::FTP->new(@{$self->{newargs}}) + or die "Couldn't create new FTP object\n"; + + if ($self->{login}) + { + $self->{ftp}->login(@{$self->{login}}); + } + if ($self->{authorize}) + { + $self->{ftp}->authorize(@{$self->{authorize}}); + } + if ($self->{mode}) + { + if ($self->{mode} eq 'ascii') + { + $self->{ftp}->ascii(); + } + else + { + $self->{ftp}->binary(); + } + } + if ($self->{cwd}) + { + $self->{ftp}->cwd($self->{cwd}); + } + if ($self->{hash}) + { + $self->{ftp}->hash(@{$self->{hash}}); + } + if ($self->{restart}) + { + $self->{ftp}->restart(@{$self->{restart}}); + } + if ($self->{alloc}) + { + $self->{ftp}->restart(@{$self->{alloc}}); + } + if ($self->{pasv}) + { + $self->{ftp}->pasv(@{$self->{pasv}}); + } + if ($self->{port}) + { + $self->{ftp}->port(@{$self->{port}}); + } +} + +sub _auto_reconnect +{ + my $self = shift; + my($code)=@_; + + my $ret = $code->(); + if (!defined($ret)) + { + $self->reconnect(); + $ret = $code->(); + } + $ret; +} + +sub _after_pcmd +{ + my $self = shift; + my($r) = @_; + if ($r) + { + # succeeded + delete $self->{port}; + delete $self->{pasv}; + delete $self->{restart}; + delete $self->{alloc}; + } + $r; +} + + +sub login +{ + my $self = shift; + + $self->{login} = \@_; + $self->{ftp}->login(@_); +} + +sub authorize +{ + my $self = shift; + $self->{authorize} = \@_; + $self->{ftp}->authorize(@_); +} + +sub site +{ + my $self = shift; + $self->{ftp}->site(@_); +} + +sub ascii +{ + my $self = shift; + $self->{mode} = 'ascii'; + $self->_auto_reconnect(sub { $self->{ftp}->ascii() }); +} + +sub binary +{ + my $self = shift; + $self->{mode} = 'binary'; + $self->_auto_reconnect(sub { $self->{ftp}->binary() }); +} + +sub rename +{ + my $self = shift; + my @a = @_; + $self->_auto_reconnect(sub { $self->{ftp}->rename(@a) }); +} + +sub delete +{ + my $self = shift; + my @a = @_; + $self->_auto_reconnect(sub { $self->{ftp}->delete(@a) }); +} + +sub cwd +{ + my $self = shift; + my @a = @_; + my $ret = $self->_auto_reconnect(sub { $self->{ftp}->cwd(@a) }); + if (defined($ret)) + { + $self->{cwd} = $self->{ftp}->pwd() + or die "Couldn't get directory after cwd\n"; + } + $ret; +} + +sub cdup +{ + my $self = shift; + my @a = @_; + my $ret = $self->_auto_reconnect(sub { $self->{ftp}->cdup(@a) }); + if (defined($ret)) + { + $self->{cwd} = $self->{ftp}->pwd() + or die "Couldn't get directory after cdup\n"; + } + $ret; +} + +sub pwd +{ + my $self = shift; + my @a = @_; + $self->_auto_reconnect(sub { $self->{ftp}->pwd(@a) }); +} + +sub rmdir +{ + my $self = shift; + my @a = @_; + $self->_auto_reconnect(sub { $self->{ftp}->rmdir(@a) }); +} + +sub mkdir +{ + my $self = shift; + my @a = @_; + $self->_auto_reconnect(sub { $self->{ftp}->mkdir(@a) }); +} + +sub ls +{ + my $self = shift; + my @a = @_; + my $ret = $self->_auto_reconnect(sub { $self->{ftp}->ls(@a) }); + return $ret ? (wantarray ? @$ret : $ret) : undef; +} + +sub dir +{ + my $self = shift; + my @a = @_; + my $ret = $self->_auto_reconnect(sub { $self->{ftp}->dir(@a) }); + return $ret ? (wantarray ? @$ret : $ret) : undef; +} + +sub restart +{ + my $self = shift; + my @a = @_; + $self->{restart} = \@a; + $self->{ftp}->restart(@_); +} + +sub retr +{ + my $self = shift; + my @a = @_; + $self->_after_pcmd($self->_auto_reconnect(sub { $self->{ftp}->retr(@a) })); +} + +sub get +{ + my $self = shift; + my @a = @_; + $self->_auto_reconnect(sub { $self->{ftp}->get(@a) }); +} + +sub mdtm +{ + my $self = shift; + my @a = @_; + $self->_auto_reconnect(sub { $self->{ftp}->mdtm(@a) }); +} + +sub size +{ + my $self = shift; + my @a = @_; + $self->_auto_reconnect(sub { $self->{ftp}->size(@a) }); +} + +sub abort +{ + my $self = shift; + $self->{ftp}->abort(); +} + +sub quit +{ + my $self = shift; + $self->{ftp}->quit(); +} + +sub hash +{ + my $self = shift; + my @a = @_; + $self->{hash} = \@a; + $self->{ftp}->hash(@_); +} + +sub alloc +{ + my $self = shift; + my @a = @_; + $self->{alloc} = \@a; + $self->_auto_reconnect(sub { $self->{ftp}->alloc(@a) }); +} + +sub put +{ + my $self = shift; + my @a = @_; + $self->_auto_reconnect(sub { $self->{ftp}->put(@a) }); +} + +sub put_unique +{ + my $self = shift; + my @a = @_; + $self->_auto_reconnect(sub { $self->{ftp}->put_unique(@a) }); +} + +sub append +{ + my $self = shift; + my @a = @_; + $self->_auto_reconnect(sub { $self->{ftp}->append(@a) }); +} + +sub unique_name +{ + my $self = shift; + $self->{ftp}->unique_name(@_); +} + +sub supported +{ + my $self = shift; + my @a = @_; + $self->_auto_reconnect(sub { $self->{ftp}->supported(@a) }); +} + +sub port +{ + my $self = shift; + my @a = @_; + $self->{port} = \@a; + $self->_auto_reconnect(sub { $self->{ftp}->port(@a) }); +} + +sub pasv +{ + my $self = shift; + my @a = @_; + $self->{pasv} = \@a; + $self->_auto_reconnect(sub { $self->{ftp}->pasv(@a) }); +} + +sub nlst +{ + my $self = shift; + my @a = @_; + $self->_after_pcmd($self->_auto_reconnect(sub { $self->{ftp}->nlst(@a) })); +} + +sub stou +{ + my $self = shift; + my @a = @_; + $self->_after_pcmd($self->_auto_reconnect(sub { $self->{ftp}->stou(@a) })); +} + +sub appe +{ + my $self = shift; + my @a = @_; + $self->_after_pcmd($self->_auto_reconnect(sub { $self->{ftp}->appe(@a) })); +} + +sub list +{ + my $self = shift; + my @a = @_; + $self->_after_pcmd($self->_auto_reconnect(sub { $self->{ftp}->list(@a) })); +} + +sub pasv_xfer +{ + my $self = shift; + $self->{ftp}->pasv_xfer(@_); +} + +sub pasv_xfer_unique +{ + my $self = shift; + $self->{ftp}->pasv_xfer_unique(@_); +} + +sub pasv_wait +{ + my $self = shift; + $self->{ftp}->pasv_wait(@_); +} + +sub message +{ + my $self = shift; + $self->{ftp}->message(@_); +} + +sub code +{ + my $self = shift; + $self->{ftp}->code(@_); +} + +sub ok +{ + my $self = shift; + $self->{ftp}->ok(@_); +} + +sub status +{ + my $self = shift; + $self->{ftp}->status(@_); +} + +=head1 AUTHOR + +Scott Gifford + +=head1 BUGS + +We should really be smarter about when to retry. + +We shouldn't be hardwired to use C, but any FTP-compatible +class; that would allow all modules similar to this one to be chained +together. + +Much of this is only lightly tested; it's hard to find an FTP server +unreliable enough to test all aspects of it. It's mostly been tested +with a server that dicsonnects after an aborted transfer, and the +module seems to work OK. + +=head1 SEE ALSO + +L. + +=head1 COPYRIGHT + +Copyright (c) 2006 Scott Gifford. All rights reserved. This program +is free software; you can redistribute it and/or modify it under the +same terms as Perl itself. + +=cut + +1; diff --git a/lib/Net/FTP/RetrHandle.pm b/lib/Net/FTP/RetrHandle.pm new file mode 100644 index 0000000..70ae6fe --- /dev/null +++ b/lib/Net/FTP/RetrHandle.pm @@ -0,0 +1,692 @@ +package Net::FTP::RetrHandle; +our $VERSION = '0.2'; + +use warnings; +use strict; + +use constant DEFAULT_MAX_SKIPSIZE => 1024 * 1024 * 2; +use constant DEFAULT_BLOCKSIZE => 10240; # Net::FTP's default + +use base 'IO::Seekable'; +# We don't use base 'IO::Handle'; it currently confuses Archive::Zip. + +use Carp; +use Scalar::Util; + + +=head1 NAME + +Net::FTP::RetrHandle - Tied or IO::Handle-compatible interface to a file retrieved by FTP + +=head1 SYNOPSIS + +Provides a file reading interface for reading all or parts of files +located on a remote FTP server, including emulation of C and +support for downloading only the parts of the file requested. + +=head1 DESCRIPTION + +Support for skipping the beginning of the file is implemented with the +FTP C command, which starts a retrieval at any point in the +file. Support for skipping the end of the file is implemented with +the FTP C command, which stops the transfer. With these two +commands and some careful tracking of the current file position, we're +able to reliably emulate a C pair, and get only the parts +of the file that are actually read. + +This was originally designed for use with +L; it's reliable enough that the table of +contents and individual files can be extracted from a remote ZIP +archive without downloading the whole thing. See L below. + +An interface compatible with L is provided, +along with a C-based interface. + +Remember that an FTP server can only do one thing at a time, so make +sure to C your connection before asking the FTP server to do +nything else. + +=head1 CONSTRUCTOR + +=head2 new ( $ftp, $filename, options... ) + +Creates a new L-compatible object to fetch all +or parts of C<$filename> using the FTP connection C<$ftp>. + +Available options: + +=over 4 + +=item MaxSkipSize => $size + +If we need to move forward in a file or close the connection, +sometimes it's faster to just read the bytes we don't need than to +abort the connection and restart. This setting tells how many +unnecessary bytes we're willing to read rather than abort. An +appropriate setting depends on the speed of transferring files and the +speed of reconnecting to the server. + +=item BlockSize => $size + +When doing buffered reads, how many bytes to read at once. The +default is the same as the default for L, so it's +generally best to leave it alone. + +=item AlreadyBinary => $bool + +If set to a true value, we assume the server is already in binary +mode, and don't try to set it. + +=back + +=cut +use constant USAGE => "Usage: Net::FTP::RetrHandle\->new(ftp => \$ftp_obj, filename => \$filename)\n"; +sub new +{ + my $class = shift; + my $ftp = shift + or croak USAGE; + my $filename = shift + or croak USAGE; + my $self = { MaxSkipSize => DEFAULT_MAX_SKIPSIZE, + BlockSize => DEFAULT_BLOCKSIZE, + @_, + ftp => $ftp, filename => $filename, + pos => 0, nextpos => 0}; + $self->{size} = $self->{ftp}->size($self->{filename}) + or return undef; + $self->{ftp}->binary() + unless ($self->{AlreadyBinary}); + + bless $self,$class; +} + +=head1 METHODS + +Most of the methods implemented behave exactly like those from +L. + +These methods are implemented: C, C, C, C, +C, C, C, C, C, C, +C, C, C, C, C, C. + +=cut ; + +sub opened { 1; } + +sub seek +{ + my $self = shift; + my $pos = shift || 0; + my $whence = shift || 0; + warn " SEEK: self=$self, pos=$pos, whence=$whence\n" + if ($ENV{DEBUG}); + my $curpos = $self->tell(); + my $newpos = _newpos($self->tell(),$self->{size},$pos,$whence); + my $ret; + if ($newpos == $curpos) + { + return $curpos; + } + elsif (defined($self->{_buf}) and ($newpos > $curpos) and ($newpos < ($curpos + length($self->{_buf})))) + { + # Just seeking within the buffer (or not at all) + substr($self->{_buf},0,$newpos - $curpos,''); + $ret = $newpos; + } + else + { + $ret = $self->sysseek($newpos,0); + $self->{_buf} = ''; + } + return $ret; +} + +sub _newpos +{ + + my($curpos,$size,$pos,$whence)=@_; + if ($whence == 0) # seek_set + { + return $pos; + } + elsif ($whence == 1) # seek_cur + { + return $curpos + $pos; + } + elsif ($whence == 2) # seek_end + { + return $size + $pos; + } + else + { + die "Invalid value $whence for whence!"; + } +} + +sub sysseek +{ + my $self = shift; + my $pos = shift || 0; + my $whence = shift || 0; + warn "SYSSEEK: self=$self, pos=$pos, whence=$whence\n" + if ($ENV{DEBUG}); + my $newpos = _newpos($self->{nextpos},$self->{size},$pos,$whence); + + $self->{eof}=undef; + return $self->{nextpos}=$newpos; +} + +sub tell +{ + my $self = shift; + return $self->{nextpos} - (defined($self->{_buf}) ? length($self->{_buf}) : 0); +} + +# WARNING: ASCII mode probably breaks seek. +sub binmode +{ + my $self = shift; + my $mode = shift || ':raw'; + return if (defined($self->{curmode}) && ($self->{curmode} eq $mode)); + if (defined($mode) and $mode eq ':crlf') + { + $self->_finish_connection(); + $self->{ftp}->ascii() + or return $self->seterr(); + } + else + { + $self->_finish_connection(); + $self->{ftp}->binary() + or return $self->seterr(); + } + $self->{curmode} = $mode; +} + +sub _min +{ + return $_[0] < $_[1] ? $_[0] : $_[1]; +} + +sub _max +{ + return $_[0] > $_[1] ? $_[0] : $_[1]; +} + +sub read +{ + my $self = shift; +# return $self->sysread(@_); + + my(undef,$len,$offset)=@_; + $offset ||= 0; + warn "READ(buf,$len,$offset)\n" + if ($ENV{DEBUG}); + + if (!defined($self->{_buf}) || length($self->{_buf}) <= 0) + { + $self->sysread($self->{_buf},_max($len,$self->{BlockSize})) + or return 0; + } + elsif (length($self->{_buf}) < $len) + { + $self->sysread($self->{_buf},_max($len-length($self->{_buf}),$self->{BlockSize}),length($self->{_buf})); + } + my $ret = _min($len,length($self->{_buf})); + if (!defined($_[0])) { $_[0] = '' } + substr($_[0],$offset) = substr($self->{_buf},0,$len,''); + $self->{read_count}++; + + return $ret; +} + +sub sysread +{ + my $self = shift; + if ($self->{eof}) + { + return 0; + } + + my(undef,$len,$offset) = @_; + $offset ||= 0; + + warn "SYSREAD(buf,$len,$offset)\n" + if ($ENV{DEBUG}); + if ($self->{nextpos} >= $self->{size}) + { + $self->{eof} = 1; + $self->{pos} = $self->{nextpos}; + return 0; + } + + if ($self->{pos} != $self->{nextpos}) + { + # They seeked. + if ($self->{ftp_running}) + { + warn "Seek detected, nextpos=$self->{nextpos}, pos=$self->{pos}, MaxSkipSize=$self->{MaxSkipSize}\n" + if ($ENV{DEBUG}); + if ($self->{nextpos} > $self->{pos} and ($self->{nextpos} - $self->{pos}) < $self->{MaxSkipSize}) + { + my $br = $self->{nextpos}-$self->{pos}; + warn "Reading $br bytes to skip ahead\n" + if ($ENV{DEBUG}); + my $junkbuff; + while ($br > 0) + { + warn "Trying to read $br more bytes\n" + if ($ENV{DEBUG}); + my $b = $self->{ftp_data}->read($junkbuff,$br); + if ($b == 0) + { + $self->_at_eof(); + return 0; + } + elsif (!defined($b) || $b < 0) + { + return $self->seterr(); + } + else + { + $br -= $b; + } + } + $self->{pos}=$self->{nextpos}; + } + else + { + warn "Aborting connection to move to new position\n" + if ($ENV{DEBUG}); + $self->_finish_connection(); + } + } + } + + if (!$self->{ftp_running}) + { + $self->{ftp}->restart($self->{nextpos}); + $self->{ftp_data} = $self->{ftp}->retr($self->{filename}) + or return $self->seterr(); + $self->{ftp_running} = 1; + $self->{pos}=$self->{nextpos}; + } + + my $tmpbuf; + my $rb = $self->{ftp_data}->read($tmpbuf,$len); + if ($rb == 0) + { + $self->_at_eof(); + return 0; + } + elsif (!defined($rb) || $rb < 0) + { + return $self->seterr(); + } + + if (!defined($_[0])) { $_[0] = '' } + substr($_[0],$offset) = $tmpbuf; + $self->{pos} += $rb; + $self->{nextpos} += $rb; + + $self->{sysread_count}++; + $rb; +} + +sub _at_eof +{ + my $self = shift; + $self->{eof}=1; + $self->_finish_connection(); +# $self->{ftp_data}->_close(); + $self->{ftp_running} = $self->{ftp_data} = undef; +} + +sub _finish_connection +{ + my $self = shift; + warn "_finish_connection\n" + if ($ENV{DEBUG}); + return unless ($self->{ftp_running}); + + if ($self->{size} - $self->{pos} < $self->{MaxSkipSize}) + { + warn "Skipping " . ($self->{size}-$self->{pos}) . " bytes\n" + if ($ENV{DEBUG}); + my $junkbuff; + my $br; + while(($br = $self->{ftp_data}->read($junkbuff,8192))) + { + # Read until EOF or error + } + defined($br) + or $self->seterr(); + } + warn "Shutting down existing FTP DATA session...\n" + if ($ENV{DEBUG}); + + my $closeret; + { + eval { + $closeret = $self->{ftp_data}->close(); + }; + # Work around a timeout bug in Net::FTP + if ($@ && $@ =~ /^Timeout /) + { + warn "Timeout closing connection, retrying...\n" + if ($ENV{DEBUG}); + select(undef,undef,undef,1); + redo; + } + } + + $self->{ftp_running} = $self->{ftp_data} = undef; + return $closeret ? 1 : $self->seterr(); +} + +sub write +{ + die "Only reading currently supported"; +} + +sub close +{ + my $self = shift; + return $self->{ftp_data} ? $self->_finish_connection() + : 1; +} + +sub eof +{ + my $self = shift; + if ($self->{eof}) + { + return 1; + } + + my $c = $self->getc; + if (!defined($c)) + { + return 1; + } + $self->ungetc(ord($c)); + return undef; +} + +sub getc +{ + my $self = shift; + my $c; + my $rb = $self->read($c,1); + if ($rb < 1) + { + return undef; + } + return $c; +} + +sub ungetc +{ + my $self = shift; + # Note that $c is the ordinal value of a character, not the + # character itself (for some reason) + my($c)=@_; + $self->{_buf} = chr($c) . $self->{_buf}; +} + +sub getline +{ + my $self = shift; + if (!defined($/)) + { + my $buf; + while($self->read($buf,$self->{BlockSize},length($buf)) > 0) + { + # Keep going + } + return $buf; + } + elsif (ref($/) && looks_like_number ${$/} ) + { + my $buf; + $self->read($buf,${$/}) + or return undef; + return $buf; + } + + my $rs; + if ($/ eq '') + { + $rs = "\n\n"; + } + else + { + $rs = $/; + } + my $eol; + if (!defined($self->{_buf})) { $self->{_buf} = '' } + while (($eol=index($self->{_buf},$rs)) < $[) + { + if ($self->{eof}) + { + # return what's left + if (length($self->{_buf}) == 0) + { + return undef; + } + else + { + return substr($self->{_buf},0,length($self->{_buf}),''); + } + } + else + { + $self->sysread($self->{_buf},$self->{BlockSize},length($self->{_buf})); + } + } + # OK, we should have a match. + my $tmpbuf = substr($self->{_buf},0,$eol+length($rs),''); + while ($/ eq '' and substr($self->{_buf},0,1) eq "\n") + { + substr($self->{_buf},0,1)=''; + } + return $tmpbuf; +} + +sub getlines +{ + my $self = shift; + my @lines; + my $line; + while (defined($line = $self->getline())) + { + push(@lines,$line); + } + @lines; +} + +sub error +{ + return undef; +} + +sub seterr +{ + my $self = shift; + $self->{_error} = 1; + return undef; +} + +sub clearerr +{ + my $self = shift; + $self->{_error} = undef; + return 0; +} + +sub getpos +{ + my $self = shift; + return $self->tell(); +} + +sub setpos +{ + my $self = shift; + return $self->seek(@_); +} + +sub DESTROY +{ + my $self = shift; + if (UNIVERSAL::isa($self,'GLOB')) + { + $self = tied *$self + or die "$self not tied?..."; + } + if ($self->{ftp_data}) + { + $self->_finish_connection(); + } + warn "sysread called ".$self->{sysread_count}." times.\n" + if ($ENV{DEBUG}); +} + +=head1 TIED INTERFACE + +Instead of a L-compatible interface, you can +use a C-based interface to use the standard Perl I/O operators. +You can use it like this: + + use Net::FTP::RetrHandle; + # Create FTP object in $ftp + # Store filename in $filename + tie *FH, 'Net::FTP::RetrHandle', $ftp, $filename + or die "Error in tie!\n"; + +=cut + ; +sub TIEHANDLE +{ + my $class = shift; + my $obj = $class->new(@_); + $obj; +} + +sub READ +{ + my $self = shift; + $self->read(@_); +} + +sub READLINE +{ + my $self = shift; + return wantarray ? $self->getlines(@_) + : $self->getline(@_); +} + +sub GETC +{ + my $self = shift; + return $self->getc(@_); +} + +sub SEEK +{ + my $self = shift; + return $self->seek(@_); +} + +sub SYSSEEK +{ + my $self = shift; + return $self->sysseek(@_); +} + +sub TELL +{ + my $self = shift; + return $self->tell(); +} + +sub CLOSE +{ + my $self = shift; + return $self->close(@_); +} + +sub EOF +{ + my $self = shift; + return $self->eof(@_); + +} +sub UNTIE +{ + tied($_[0])->close(@_); +} + +=head1 EXAMPLE + +Here's an example of listing a Zip file without downloading the whole +thing: + + #!/usr/bin/perl + + use warnings; + use strict; + + use Net::FTP; + use Net::FTP::AutoReconnect; + use Net::FTP::RetrHandle; + use Archive::Zip; + + my $ftp = Net::FTP::AutoReconnect->new("ftp.info-zip.com", Debug => $ENV{DEBUG}) + or die "connect error\n"; + $ftp->login('anonymous','example@example.com') + or die "login error\n"; + $ftp->cwd('/pub/infozip/UNIX/LINUX') + or die "cwd error\n"; + my $fh = Net::FTP::RetrHandle->new($ftp,'unz551x-glibc.zip') + or die "Couldn't get handle to remote file\n"; + my $zip = Archive::Zip->new($fh) + or die "Couldn't create Zip object\n"; + foreach my $fn ($zip->memberNames()) + { + print "unz551-glibc.zip: $fn\n"; + } + + +=head1 AUTHOR + +Scott Gifford + +=head1 BUGS + +The distinction between tied filehandles and C-compatible +filehandles should be blurrier. It seems like other file handle +objects you can freely mix method calls and traditional Perl +operations, but I can't figure out how to do it. + +Many FTP servers don't like frequent connection aborts. If that's the +case, try L, which will hide much of that +from you. + +If the filehandle is tied and created with C, C +doesn't work with older versions of Perl. No idea why. + +=head1 SEE ALSO + +L, L, L. + +=head1 COPYRIGHT + +Copyright (c) 2006 Scott Gifford. All rights reserved. This program +is free software; you can redistribute it and/or modify it under the +same terms as Perl itself. + +=cut + +1; diff --git a/makeDist b/makeDist index c247b84..28071a2 100755 --- a/makeDist +++ b/makeDist @@ -1,4 +1,4 @@ -#!/bin/perl +#!/usr/bin/env perl # # makeDist: Build a BackupPC distribution # @@ -53,8 +53,8 @@ die("BackupPC::Lib->new failed\n") umask(0022); -my $Version = "3.1.0"; -my $ReleaseDate = "25 Nov 2007"; +my $Version = "3.2.0"; +my $ReleaseDate = "31 Dec 2008"; my $DistDir = "dist/BackupPC-$Version"; my @PerlSrc = qw( @@ -117,14 +117,19 @@ my @PerlSrc = qw( lib/BackupPC/Lang/pt_br.pm lib/BackupPC/Lang/zh_CN.pm lib/BackupPC/Storage/Text.pm + lib/BackupPC/Xfer.pm lib/BackupPC/Xfer/Archive.pm lib/BackupPC/Xfer/BackupPCd.pm - lib/BackupPC/Xfer/Smb.pm - lib/BackupPC/Xfer/Tar.pm + lib/BackupPC/Xfer/Ftp.pm + lib/BackupPC/Xfer/Protocol.pm lib/BackupPC/Xfer/Rsync.pm lib/BackupPC/Xfer/RsyncDigest.pm lib/BackupPC/Xfer/RsyncFileIO.pm + lib/BackupPC/Xfer/Smb.pm + lib/BackupPC/Xfer/Tar.pm lib/BackupPC/Zip/FileMember.pm + lib/Net/FTP/AutoReconnect.pm + lib/Net/FTP/RetrHandle.pm cgi-bin/BackupPC_Admin ); @@ -169,7 +174,11 @@ $ConfVars->{BlackoutHourEnd} = 2; $ConfVars->{BlackoutWeekDays} = 2; $ConfVars->{RsyncLogLevel} = 2; +system("perl -Ilib -c conf/config.pl >& /dev/null") + && die("$0: conf/config.pl contains a syntax error\n"); foreach my $file ( @PerlSrc ) { + system("perl -Ilib -c $file >& /dev/null") + && die("$0: $file contains a syntax error\n"); $errCnt += CheckConfigParams($file, $ConfVars, 1); } if ( !$opts{l} ) { @@ -195,13 +204,14 @@ if ( $errCnt ) { rmtree($DistDir, 0, 0); mkpath($DistDir, 0, 0777); -foreach my $dir ( qw(bin doc conf images init.d/src cgi-bin +foreach my $dir ( qw(bin doc conf images init.d/src cgi-bin httpd/src lib/BackupPC/CGI lib/BackupPC/Config lib/BackupPC/Lang lib/BackupPC/Storage lib/BackupPC/Xfer lib/BackupPC/Zip + lib/Net/FTP ) ) { mkpath("$DistDir/$dir", 0, 0777); } @@ -237,6 +247,7 @@ foreach my $file ( (@PerlSrc, init.d/src/slackware-backuppc init.d/src/solaris-backuppc init.d/src/suse-backuppc + httpd/src/BackupPC.conf doc/BackupPC.pod doc/BackupPC.html README diff --git a/makePatch b/makePatch index a7681f5..132206d 100755 --- a/makePatch +++ b/makePatch @@ -8,8 +8,8 @@ use strict; use File::Find; use File::Path; -my $BaseVersion = "2.1.2"; -my $PatchLevel = "pl1"; +my $BaseVersion = "3.2.0"; +my $PatchLevel = "pl0"; my $PatchVersion = "$BaseVersion$PatchLevel"; my $Base = "/home/craig/admin/packages/BackupPC-$BaseVersion"; -- 2.20.1