From 546f9691f118c9ea2d164f377994b4a018a60d02 Mon Sep 17 00:00:00 2001 From: cbarratt Date: Tue, 24 Jan 2006 07:10:56 +0000 Subject: [PATCH] - config and host editing pretty much done - added charset conversion - utf8 support pretty much done - added more hardlink limit checking - added scripts bin/BackupPC_attribPrint, bin/BackupPC_fixupBackupSummary and bin/BackupPC_tarPCCopy --- bin/BackupPC | 6 +- bin/BackupPC_attribPrint | 70 +++ bin/BackupPC_dump | 54 ++- bin/BackupPC_fixupBackupSummary | 251 ++++++++++ bin/BackupPC_link | 27 +- bin/BackupPC_nightly | 33 +- bin/BackupPC_tarCreate | 25 +- bin/BackupPC_tarExtract | 34 +- bin/BackupPC_tarPCCopy | 550 ++++++++++++++++++++++ bin/BackupPC_zipCreate | 46 +- conf/BackupPC_stnd.css | 46 +- conf/config.pl | 19 + configure.pl | 237 +++++++--- lib/BackupPC/CGI/EditConfig.pm | 686 +++++++++++++++++++--------- lib/BackupPC/CGI/Lib.pm | 54 +-- lib/BackupPC/CGI/StartStopBackup.pm | 8 +- lib/BackupPC/Config/Meta.pm | 93 ++-- lib/BackupPC/FileZIO.pm | 2 +- lib/BackupPC/Lang/en.pm | 109 +++++ lib/BackupPC/Lang/fr.pm | 126 ++--- lib/BackupPC/Lang/it.pm | 2 +- lib/BackupPC/Lib.pm | 108 ++++- lib/BackupPC/PoolWrite.pm | 49 ++ lib/BackupPC/Storage.pm | 22 + lib/BackupPC/Storage/Text.pm | 355 +++++++++----- lib/BackupPC/View.pm | 1 + lib/BackupPC/Xfer/Rsync.pm | 2 + lib/BackupPC/Xfer/RsyncFileIO.pm | 59 ++- lib/BackupPC/Xfer/Tar.pm | 9 +- makeDist | 7 +- 30 files changed, 2498 insertions(+), 592 deletions(-) create mode 100755 bin/BackupPC_attribPrint create mode 100755 bin/BackupPC_fixupBackupSummary create mode 100755 bin/BackupPC_tarPCCopy diff --git a/bin/BackupPC b/bin/BackupPC index 4c13633..2a81d11 100755 --- a/bin/BackupPC +++ b/bin/BackupPC @@ -416,9 +416,6 @@ sub Main_TryToRun_nightly # # Zero out the data we expect to get from BackupPC_nightly. - # In the future if we want to split BackupPC_nightly over - # more than one night we will only zero out the portion - # that we are running right now. # for my $p ( qw(pool cpool) ) { for ( my $i = $start ; $i < $end ; $i++ ) { @@ -442,7 +439,7 @@ sub Main_TryToRun_nightly # # Now queue the $Conf{MaxBackupPCNightlyJobs} jobs. - # The granularity on start and end is now 0..256. + # The granularity on start and end is now 0..255. # $start *= 16; $end *= 16; @@ -470,7 +467,6 @@ sub Main_TryToRun_nightly $CmdQueueOn{$job} = 1; } $RunNightlyWhenIdle = 2; - } } diff --git a/bin/BackupPC_attribPrint b/bin/BackupPC_attribPrint new file mode 100755 index 0000000..47ecf79 --- /dev/null +++ b/bin/BackupPC_attribPrint @@ -0,0 +1,70 @@ +#!/bin/perl +#============================================================= -*-perl-*- +# +# BackupPC_attribPrint: print the contents of attrib files. +# +# DESCRIPTION +# +# Usage: BackupPC_attribPrint attribPath +# +# Compression status of attrib path is based on $Conf{CompressLevel}. +# +# AUTHOR +# Craig Barratt +# +# COPYRIGHT +# Copyright (C) 2005 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 2.1.0, released 20 Jun 2004. +# +# See http://backuppc.sourceforge.net. +# +#======================================================================== + +use strict; +no utf8; +use lib "/usr/local/BackupPC/lib"; + +use BackupPC::Lib; +use BackupPC::Attrib qw(:all); +use BackupPC::FileZIO; +use Data::Dumper; + +die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) ); +my $TopDir = $bpc->TopDir(); +my $BinDir = $bpc->BinDir(); +my %Conf = $bpc->Conf(); + +if ( @ARGV != 1 ) { + print STDERR "Usage: $0 attribPath\n"; + exit(1); +} +if ( !-f $ARGV[0] ) { + print STDERR "$ARGV[0] does not exist\n"; + exit(1); +} + +my $attrib = BackupPC::Attrib->new({ compress => $Conf{CompressLevel} }); +if ( !$attrib->read(".", $ARGV[0]) ) { + print STDERR "Cannot read attrib file $ARGV[0]\n"; + exit(1); +} +my $info = $attrib->get(); +$Data::Dumper::Indent = 1; +print Dumper($info); diff --git a/bin/BackupPC_dump b/bin/BackupPC_dump index 8151cbd..ef29ed8 100755 --- a/bin/BackupPC_dump +++ b/bin/BackupPC_dump @@ -81,6 +81,7 @@ no utf8; 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; @@ -1064,7 +1065,8 @@ sub BackupExpire my($client) = @_; my($Dir) = "$TopDir/pc/$client"; my(@Backups) = $bpc->BackupInfoRead($client); - my($cntFull, $cntIncr, $firstFull, $firstIncr, $oldestIncr, $oldestFull); + my($cntFull, $cntIncr, $firstFull, $firstIncr, $oldestIncr, + $oldestFull, $changes); if ( $Conf{FullKeepCnt} <= 0 ) { print(LOG $bpc->timeStamp, @@ -1106,6 +1108,7 @@ sub BackupExpire print(LOG $bpc->timeStamp, "removing incr backup $Backups[$firstIncr]{num}\n"); BackupRemove($client, \@Backups, $firstIncr); + $changes++; next; } @@ -1145,6 +1148,7 @@ sub BackupExpire print(LOG $bpc->timeStamp, "removing old full backup $Backups[$firstFull]{num}\n"); BackupRemove($client, \@Backups, $firstFull); + $changes++; next; } @@ -1154,7 +1158,7 @@ sub BackupExpire # last if ( !BackupFullExpire($client, \@Backups) ); } - $bpc->BackupInfoWrite($client, @Backups); + $bpc->BackupInfoWrite($client, @Backups) if ( $changes ); } # @@ -1248,6 +1252,7 @@ sub BackupSave { my @Backups = $bpc->BackupInfoRead($client); my $num = -1; + my $newFilesFH; # # Since we got a good backup we should remove any partial dumps @@ -1290,7 +1295,18 @@ sub BackupSave $Backups[$i]{noFill} = $type eq "incr" ? 1 : 0; $Backups[$i]{level} = $type eq "incr" ? 1 : 0; $Backups[$i]{mangle} = 1; # name mangling always on for v1.04+ + $Backups[$i]{xferMethod} = $Conf{XferMethod}; + $Backups[$i]{charset} = $Conf{ClientCharset}; + # + # Save the main backups file + # $bpc->BackupInfoWrite($client, @Backups); + # + # Save just this backup's info in case the main backups file + # gets corrupted + # + BackupPC::Storage->backupInfoWrite($Dir, $Backups[$i]{num}, + $Backups[$i]); unlink("$Dir/timeStamp.level0") if ( -f "$Dir/timeStamp.level0" ); foreach my $ext ( qw(bad bad.z) ) { @@ -1316,11 +1332,34 @@ sub BackupSave $file = "$f->{share}/$f->{file}"; } next if ( !-f "$Dir/$Backups[$j]{num}/$file" ); - if ( !link("$Dir/$Backups[$j]{num}/$file", - "$Dir/$num/$shareM/$fileM") ) { - my $str = \"Unable to link $num/$f->{share}/$f->{file} to" - . " $Backups[$j]{num}/$f->{share}/$f->{file}\n"; - $XferLOG->write(\$str); + + my($exists, $digest, $origSize, $outSize, $errs) + = BackupPC::PoolWrite::LinkOrCopy( + $bpc, + "$Dir/$Backups[$j]{num}/$file", + $Backups[$j]{compress}, + "$Dir/$num/$shareM/$fileM", + $Conf{CompressLevel}); + if ( !$exists ) { + # + # the hard link failed, most likely because the target + # file has too many links. We have copied the file + # instead, so add this to the new file list. + # + if ( !defined($newFilesFH) ) { + my $str = "Appending to NewFileList for $shareM/$fileM\n"; + $XferLOG->write(\$str); + open($newFilesFH, ">>", "$TopDir/pc/$client/NewFileList") + || die("can't open $TopDir/pc/$client/NewFileList"); + binmode($newFilesFH); + } + if ( -f "$Dir/$num/$shareM/$fileM" ) { + print($newFilesFH "$digest $origSize $shareM/$fileM\n"); + } else { + my $str = "Unable to link/copy $num/$f->{share}/$f->{file}" + . " to $Backups[$j]{num}/$f->{share}/$f->{file}\n"; + $XferLOG->write(\$str); + } } else { my $str = "Bad file $num/$f->{share}/$f->{file} replaced" . " by link to" @@ -1335,6 +1374,7 @@ sub BackupSave $XferLOG->write(\$str); } } + close($newFilesFH) if ( defined($newFilesFH) ); $XferLOG->close(); rename("$Dir/XferLOG$fileExt", "$Dir/XferLOG.$num$fileExt"); rename("$Dir/NewFileList", "$Dir/NewFileList.$num"); diff --git a/bin/BackupPC_fixupBackupSummary b/bin/BackupPC_fixupBackupSummary new file mode 100755 index 0000000..5bc5318 --- /dev/null +++ b/bin/BackupPC_fixupBackupSummary @@ -0,0 +1,251 @@ +#!/bin/perl +#============================================================= -*-perl-*- +# +# BackupPC_fixupBackupSummary: recreate backups file in case +# it was lost. +# +# DESCRIPTION +# +# Usage: BackupPC_fixupBackupSummary [clients...] +# +# AUTHOR +# Craig Barratt +# +# COPYRIGHT +# Copyright (C) 2005 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 2.1.0, released 20 Jun 2004. +# +# See http://backuppc.sourceforge.net. +# +#======================================================================== + +use strict; +no utf8; +use lib "/usr/local/BackupPC/lib"; +use Getopt::Std; +use Data::Dumper; +use Time::ParseDate; + +use BackupPC::Lib; +use BackupPC::Attrib qw(:all); +use BackupPC::FileZIO; +use BackupPC::Storage; + +die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) ); +my $TopDir = $bpc->TopDir(); +my $BinDir = $bpc->BinDir(); +my %Conf = $bpc->Conf(); +my $Hosts = $bpc->HostInfoRead(); +my @hostList; + +our(%backupInfo); + +if ( !@ARGV ) { + @hostList = sort(keys(%$Hosts)); +} else { + @hostList = @ARGV; +} + +foreach my $host ( @hostList ) { + my(@Backups, $BkupFromLOG, $BkupFromInfo, $BkupNums, @LogFiles); + + $BkupFromInfo = {}; + $BkupFromLOG = {}; + if ( !defined($Hosts->{$host}) ) { + print("$host doesn't exist in BackupPC's host file... skipping\n"); + next; + } + + my $dir = "$TopDir/pc/$host"; + print("Doing host $host\n"); + + if ( !opendir(DIR, $dir) ) { + print("$host: Can't open $dir... skipping $host\n"); + next; + } + + # + # Read the backups file + # + @Backups = $bpc->BackupInfoRead($host); + + # + # Temporary: create backupInfo files in each backup + # directory + # + foreach ( my $i = 0 ; $i < @Backups ; $i++ ) { + BackupPC::Storage->backupInfoWrite($dir, $Backups[$i]{num}, + $Backups[$i]); + if ( 0 ) { + my $bkupNum = $Backups[$i]{num}; + if ( !-f "$dir/$bkupNum/backupInfo" ) { + my($dump) = Data::Dumper->new( + [ $Backups[$i]], + [qw(*backupInfo)]); + $dump->Indent(1); + if ( open(BKUPINFO, ">", "$dir/$bkupNum/backupInfo") ) { + print(BKUPINFO $dump->Dump); + close(BKUPINFO); + } + } + } + } + + # + # Look through the LOG files to get information about + # completed backups. The data from the LOG file is + # incomplete, but enough to get some useful info. + # + # Also, try to pick up the new-style of information + # that is kept in each backup tree. This info is + # complete. This data is only saved after version + # 2.1.2. + # + my @files = readdir(DIR); + closedir(DIR); + foreach my $file ( @files ) { + if ( $file =~ /^LOG(.\d+\.z)?/ ) { + push(@LogFiles, $file); + } elsif ( $file =~ /^(\d+)$/ ) { + my $bkupNum = $1; + $BkupNums->{$bkupNum} = 1; + + next if ( !-f "$dir/$bkupNum/backupInfo" ); + + # + # Read backup info + # + %backupInfo = (); + print(" Reading $dir/$bkupNum/backupInfo\n"); + if ( !(my $ret = do "$dir/$bkupNum/backupInfo") ) { + print(" couldn't parse $dir/$bkupNum/backupInfo: $@\n") if $@; + print(" couldn't do $dir/$bkupNum/backupInfo: $!\n") + unless defined $ret; + print(" couldn't run $dir/$bkupNum/backupInfo\n"); + next; + } + if ( !keys(%backupInfo) || !defined($backupInfo{num}) ) { + print(" $dir/$bkupNum/backupInfo is empty\n"); + next; + } + %{$BkupFromInfo->{$backupInfo{num}}} = %backupInfo; + } + } + + # + # Read through LOG files from oldest to newest + # + @LogFiles = sort({-M "$dir/$a" <=> -M "$dir/$b"} @LogFiles); + my $startTime; + foreach my $file ( @LogFiles ) { + my $f = BackupPC::FileZIO->open("$dir/$file", 0, $file =~ /\.z/); + + if ( !defined($f) ) { + print("$host: unable to open file $dir/$file\n"); + next; + } + print(" Reading $file\n"); + while ( (my $str = $f->readLine()) ne "" ) { + if ( $str =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (full|incr|partial) backup started for directory / ) { + $startTime = $str; + next; + } + next if ( $str !~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (full|incr|partial) backup (\d+) complete, (\d+) files, (\d+) bytes, (\d+) xferErrs \((\d+) bad files, (\d+) bad shares, (\d+) other\)/ ); + + my $endTime = $1; + my $type = $2; + my $bkupNum = $3; + my $nFilesTotal = $4; + my $sizeTotal = $5; + my $xferErrs = $6; + my $badFiles = $7; + my $badShare = $8; + print(" Got $type backup $bkupNum at $endTime\n"); + next if ( !-d "$dir/$2" ); + $BkupFromLOG->{$bkupNum} = { + num => $bkupNum, + type => $type, + startTime => parsedate($startTime), + endTime => parsedate($endTime), + size => $sizeTotal, + nFiles => $nFilesTotal, + xferErrs => $xferErrs, + xferBadFile => $badFiles, + xferBadShare => $badShare, + nFilesExist => 0, + sizeExist => 0, + sizeExistComp => 0, + tarErrs => 0, + compress => $Conf{CompressLevel}, + noFill => $type eq "incr" ? 1 : 0, + level => $type eq "incr" ? 1 : 0, + mangle => 1, + noFill => $noFill; + fillFromNum => $fillFromNum; + }; + } + } + + splice(@Backups, 2, 1); + + # + # Now merge any info from $BkupFromInfo and $BkupFromLOG + # that is missing from @Backups. + # + # First, anything in @Backups overrides the other data + # + # + foreach ( my $i = 0 ; $i < @Backups ; $i++ ) { + my $bkupNum = $Backups[$i]{num}; + delete($BkupFromLOG->{$bkupNum}); + delete($BkupFromInfo->{$bkupNum}); + delete($BkupNums->{$bkupNum}); + } + + # + # Now merge in data from the LOG and backupInfo files. + # backupInfo files override LOG files. + # + my $changes; + + foreach my $bkupNum ( keys(%$BkupFromLOG) ) { + next if ( defined($BkupFromInfo->{$bkupNum}) ); + print(" Adding info for backup $bkupNum from LOG file\n"); + push(@Backups, $BkupFromLOG->{$bkupNum}); + delete($BkupNums->{$bkupNum}); + $changes++; + } + foreach my $bkupNum ( keys(%$BkupFromInfo) ) { + print(" Adding info for backup $bkupNum from backupInfo file\n"); + push(@Backups, $BkupFromInfo->{$bkupNum}); + delete($BkupNums->{$bkupNum}); + $changes++; + } + foreach my $bkupNum ( keys(%$BkupNums) ) { + print(" *** No info for backup number $bkupNum\n"); + } + + if ( $changes ) { + @Backups = sort({$a->{num} <=> $b->{num}} @Backups); + print Dumper \@Backups; + } else { + print(" No changes for host $host\n"); + } +} diff --git a/bin/BackupPC_link b/bin/BackupPC_link index c7e925d..b3cc51a 100755 --- a/bin/BackupPC_link +++ b/bin/BackupPC_link @@ -51,6 +51,7 @@ use lib "/usr/local/BackupPC/lib"; use BackupPC::Lib; use BackupPC::Attrib; use BackupPC::PoolWrite; +use BackupPC::Storage; use File::Find; use File::Path; @@ -156,6 +157,16 @@ while ( 1 ) { $Backups[$num]{sizeNewComp} += $sizeNewComp; $Backups[$num]{noFill} = $noFill; $Backups[$num]{fillFromNum} = $fillFromNum; + # + # Save just this backup's info in case the main backups file + # gets corrupted + # + BackupPC::Storage->backupInfoWrite($Dir, + $Backups[$num]{num}, + $Backups[$num], 1); + # + # Save the main backups file + # $bpc->BackupInfoWrite($host, @Backups); } @@ -211,7 +222,21 @@ sub FillIncr # # Exists in the older filled backup, and not in the new, so link it # - link($name, $newName); + my($exists, $digest, $origSize, $outSize, $errs) + = BackupPC::PoolWrite::LinkOrCopy( + $bpc, + $name, $Compress, + $newName, $Compress); + if ( $exists ) { + $nFilesExist++; + $sizeExist += $origSize; + $sizeExistComp += $outSize; + } elsif ( $outSize > 0 ) { + $nFilesNew++; + $sizeNew += $origSize; + $sizeNewComp += -s $outSize; + LinkNewFile($digest, $origSize, $newName); + } } } diff --git a/bin/BackupPC_nightly b/bin/BackupPC_nightly index 560115b..9f6f4eb 100755 --- a/bin/BackupPC_nightly +++ b/bin/BackupPC_nightly @@ -20,6 +20,9 @@ # # -m Do monthly aging of per-PC log files and sending of email. # Otherise, BackupPC_nightly just does pool pruning. +# Since several BackupPC_nightly processes might run +# concurrently, just the first one is given the -m flag +# by BackupPC. # # The poolRangeStart and poolRangeEnd arguments are integers from 0 to 255. # These specify which parts of the pool to process. There are 256 2nd-level @@ -208,11 +211,12 @@ for my $pool ( qw(pool cpool) ) { printf("BackupPC_nightly lock_off\n"); ########################################################################### -# Send email +# Send email and generation of backupInfo files for each backup ########################################################################### if ( $opts{m} ) { print("log BackupPC_nightly now running BackupPC_sendEmail\n"); - system("$BinDir/BackupPC_sendEmail") + system("$BinDir/BackupPC_sendEmail"); + doBackupInfoUpdate(); } # @@ -251,6 +255,31 @@ sub doPerPCLogFileAging } } +# +# Update the backupInfo files based on the backups file. +# We do this just once a week (on Sun) since it is only +# needed for old backups with BackupPC <= 2.1.2. +# +sub doBackupInfoUpdate +{ + my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + return if ( $wday != 0 ); + + foreach my $host ( sort(keys(%{$bpc->HostInfoRead()})) ) { + my @Backups = $bpc->BackupInfoRead($host); + + for ( my $i = 0 ; $i < @Backups ; $i++ ) { + # + # BackupPC::Storage->backupInfoWrite won't overwrite + # an existing file + # + BackupPC::Storage->backupInfoWrite("$TopDir/pc/$host", + $Backups[$i]{num}, + $Backups[$i]); + } + } +} + sub GetPoolStats { my($nlinks, $nblocks) = (lstat($_))[3, 12]; diff --git a/bin/BackupPC_tarCreate b/bin/BackupPC_tarCreate index ce67747..8d8d134 100755 --- a/bin/BackupPC_tarCreate +++ b/bin/BackupPC_tarCreate @@ -23,6 +23,8 @@ # -p pathAdd new path prefix # -b BLOCKS BLOCKS x 512 bytes per record (default 20; same as tar) # -w writeBufSz write buffer size (default 1MB) +# -e charset charset for encoding file names (default: value of +# $Conf{ClientCharset} when backup was done) # # The -h, -n and -s options specify which dump is used to generate # the tar archive. The -r and -p options can be used to relocate @@ -62,19 +64,17 @@ no utf8; use lib "/usr/local/BackupPC/lib"; use File::Path; use Getopt::Std; +use Encode qw/from_to/; use BackupPC::Lib; use BackupPC::Attrib qw(:all); use BackupPC::FileZIO; use BackupPC::View; die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) ); -my $TopDir = $bpc->TopDir(); -my $BinDir = $bpc->BinDir(); -my %Conf = $bpc->Conf(); my %opts; -if ( !getopts("th:n:p:r:s:b:w:", \%opts) || @ARGV < 1 ) { +if ( !getopts("te:h:n:p:r:s:b:w:", \%opts) || @ARGV < 1 ) { print STDERR <= @Backups ) { exit(1); } +my $Charset = $Backups[$i]{charset}; +$Charset = $opts{e} if ( $opts{e} ne "" ); + my $PathRemove = $1 if ( $opts{r} =~ /(.+)/ ); my $PathAdd = $1 if ( $opts{p} =~ /(.+)/ ); -if ( $opts{s} !~ /^([\w\s\.\/\$-]+)$/ && $opts{s} ne "*" ) { +if ( $opts{s} !~ /^([\w\s.\/$(){}[\]-]+)$/ && $opts{s} ne "*" ) { print(STDERR "$0: bad share name '$opts{s}'\n"); exit(1); } + our $ShareName = $opts{s}; our $view = BackupPC::View->new($bpc, $Host, \@Backups); @@ -360,6 +366,14 @@ sub TarWriteFileInfo { my($fh, $hdr) = @_; + # + # Convert path names to requested (eg: client) charset + # + if ( $Charset ne "" ) { + from_to($hdr->{name}, "utf8", $Charset); + from_to($hdr->{linkname}, "utf8", $Charset); + } + # # Handle long link names (symbolic links) # @@ -373,6 +387,7 @@ sub TarWriteFileInfo TarWrite($fh, \$data); TarWritePad($fh, length($data)); } + # # Handle long file names # diff --git a/bin/BackupPC_tarExtract b/bin/BackupPC_tarExtract index 32044f6..1686ffd 100755 --- a/bin/BackupPC_tarExtract +++ b/bin/BackupPC_tarExtract @@ -36,6 +36,7 @@ use strict; no utf8; use lib "/usr/local/BackupPC/lib"; +use Encode qw/from_to/; use BackupPC::Lib; use BackupPC::Attrib qw(:all); use BackupPC::FileZIO; @@ -101,7 +102,7 @@ $SIG{TTIN} = \&catch_signal; # Copyright 1998 Stephen Zander. All rights reserved. # my $tar_unpack_header - = 'Z100 A8 A8 A8 A12 A12 A8 A1 Z100 A6 A2 Z32 Z32 A8 A8 A155 x12'; + = 'Z100 A8 A8 A8 a12 A12 A8 A1 Z100 A6 A2 Z32 Z32 A8 A8 A155 x12'; my $tar_header_length = 512; my $BufSize = 1048576; # 1MB or 2^20 @@ -250,8 +251,19 @@ sub TarReadFileInfo $name, $mode, $size, $type) if ( $Conf{XferLogLevel} >= 3 ); $name = $longName if ( defined($longName) ); $linkname = $longLink if ( defined($longLink) ); + + # + # Map client charset encodings to utf8 + # + # printf("File $name (hex: %s)\n", unpack("H*", $name)); + if ( $Conf{ClientCharset} ne "" ) { + from_to($name, $Conf{ClientCharset}, "utf8"); + from_to($linkname, $Conf{ClientCharset}, "utf8"); + } + printf("File now $name (hex: %s)\n", unpack("H*", $name)); + $name =~ s{^\./+}{}; - $name =~ s{/+$}{}; + $name =~ s{/+\.?$}{}; $name =~ s{//+}{/}g; return { name => $name, @@ -313,7 +325,7 @@ sub TarReadFile # my($nRead); #print("Reading $f->{name}, $f->{size} bytes, type $f->{type}\n"); - pathCreate($dir, "$OutDir/$ShareName/$f->{mangleName}", $file, $f); + pathCreate($dir, "$OutDir/$ShareName/$f->{mangleName}", $f); my $poolWrite = BackupPC::PoolWrite->new($bpc, "$OutDir/$ShareName/$f->{mangleName}", $f->{size}, $Compress); @@ -351,7 +363,7 @@ sub TarReadFile # a plain file. # $f->{size} = length($f->{linkname}); - pathCreate($dir, "$OutDir/$ShareName/$f->{mangleName}", $file, $f); + pathCreate($dir, "$OutDir/$ShareName/$f->{mangleName}", $f); my $poolWrite = BackupPC::PoolWrite->new($bpc, "$OutDir/$ShareName/$f->{mangleName}", $f->{size}, $Compress); @@ -369,7 +381,7 @@ sub TarReadFile # contents. # $f->{size} = length($f->{linkname}); - pathCreate($dir, "$OutDir/$ShareName/$f->{mangleName}", $file, $f); + pathCreate($dir, "$OutDir/$ShareName/$f->{mangleName}", $f); my $poolWrite = BackupPC::PoolWrite->new($bpc, "$OutDir/$ShareName/$f->{mangleName}", $f->{size}, $Compress); @@ -393,7 +405,7 @@ sub TarReadFile } else { $data = "$f->{devmajor},$f->{devminor}"; } - pathCreate($dir, "$OutDir/$ShareName/$f->{mangleName}", $file, $f); + pathCreate($dir, "$OutDir/$ShareName/$f->{mangleName}", $f); my $poolWrite = BackupPC::PoolWrite->new($bpc, "$OutDir/$ShareName/$f->{mangleName}", length($data), $Compress); @@ -487,17 +499,21 @@ sub logFileAction # sub pathCreate { - my($dir, $fullPath, $file, $f) = @_; + my($dir, $fullPath, $f) = @_; # # Get parent directory of each of $dir and $fullPath # - $dir =~ s{/[^/]*$}{}; + # print("pathCreate: dir = $dir, fullPath = $fullPath\n"); + $dir =~ s{/([^/]*)$}{}; + my $file = $bpc->fileNameUnmangle($1); $fullPath =~ s{/[^/]*$}{}; - return if ( -d $fullPath ); + return if ( -d $fullPath || $file eq "" ); + unlink($fullPath) if ( -e $fullPath ); mkpath($fullPath, 0, 0777); $Attrib{$dir} = BackupPC::Attrib->new({ compress => $Compress }) if ( !defined($Attrib{$dir}) ); + # print("pathCreate: adding file = $file to dir = $dir\n"); $Attrib{$dir}->set($file, { type => BPC_FTYPE_DIR, mode => 0755, diff --git a/bin/BackupPC_tarPCCopy b/bin/BackupPC_tarPCCopy new file mode 100755 index 0000000..efb96a8 --- /dev/null +++ b/bin/BackupPC_tarPCCopy @@ -0,0 +1,550 @@ +#!/bin/perl +#============================================================= -*-perl-*- +# +# BackupPC_tarPCCopy: create a tar archive of the PC directory +# for copying the entire PC data directory. The archive will +# contain hardlinks to the pool directory, which should be copied +# before BackupPC_tarPCCopy is run. +# +# DESCRIPTION +# +# Usage: BackupPC_tarPCCopy [options] files/directories... +# +# Flags: +# -c don't cache inode data (reduces memory usage at the +# expense of longer run time) +# +# AUTHOR +# Craig Barratt +# +# COPYRIGHT +# Copyright (C) 2005 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 2.1.0, released 20 Jun 2004. +# +# See http://backuppc.sourceforge.net. +# +#======================================================================== + +use strict; +no utf8; +use lib "/usr/local/BackupPC/lib"; +use File::Find; +use File::Path; +use Getopt::Std; + +use BackupPC::Lib; +use BackupPC::Attrib qw(:all); +use BackupPC::FileZIO; +use BackupPC::View; + +use constant S_IFMT => 0170000; # type of file + +die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) ); +my $TopDir = $bpc->TopDir(); +my $BinDir = $bpc->BinDir(); +my %Conf = $bpc->Conf(); + +my %opts; + +if ( !getopts("c", \%opts) || @ARGV < 1 ) { + print STDERR < sub { archiveFile($fh) } }, $path); + + # + # To avoid using too much memory for the inode cache, + # remove it after each top-level directory is done. + # + %Inode2Path = (); + + # + # Print some stats + # + print STDERR "Done $path ($argCnt of $argMax): $DirCnt dirs," + . " $FileCnt files, $HLinkCnt hardlinks\n"; + + $FileCnt = 0; + $HLinkCnt = 0; + $ByteCnt = 0; + $DirCnt = 0; + + $argCnt++; +} + +# +# Finish with two null 512 byte headers, and then round out a full +# block. +# +my $data = "\0" x ($tar_header_length * 2); +TarWrite($fh, \$data); +TarWrite($fh, undef); + +if ( $ErrorCnt ) { + # + # Got errors so exit with a non-zero status + # + print STDERR "Got $ErrorCnt warnings/errors\n"; + exit(1); +} +exit(0); + +########################################################################### +# Subroutines +########################################################################### + +sub archiveFile +{ + my($fh) = @_; + my($hdr); + + my @s = stat($_); + + # + # We just handle directories and files; no symlinks or + # char/block special files. + # + $hdr->{type} = -d _ ? BPC_FTYPE_DIR + : -f _ ? BPC_FTYPE_FILE + : -1; + $hdr->{fullPath} = $File::Find::name; + $hdr->{inode} = $s[1]; + $hdr->{nlink} = $s[3]; + $hdr->{size} = $s[7]; + $hdr->{devmajor} = $s[6] >> 8; + $hdr->{devminor} = $s[6] & 0xff; + $hdr->{uid} = $s[4]; + $hdr->{gid} = $s[5]; + $hdr->{mode} = $s[2]; + $hdr->{mtime} = $s[9]; + $hdr->{compress} = 1; + + if ( $hdr->{fullPath} !~ m{\Q$TopDir\E/pc/(.*)} ) { + print STDERR "Can't extract TopDir ($TopDir) from" + . " $hdr->{fullPath}\n"; + $ErrorCnt++; + return; + } + $hdr->{relPath} = $1; + if ( $hdr->{relPath} =~ m{(.*)/(.*)} ) { + $hdr->{name} = $2; + } else { + $hdr->{name} = $hdr->{relPath}; + } + + if ( $hdr->{relPath} =~ m{(.*?)/} ) { + my $clientName = $1; + if ( $ClientName ne $clientName ) { + $ClientName = $clientName; + $ClientBackups = [ $bpc->BackupInfoRead($ClientName) ]; + #print STDERR "Setting Client to $ClientName\n"; + } + if ( $hdr->{relPath} =~ m{(.*?)/(\d+)/} + || $hdr->{relPath} =~ m{(.*?)/(\d+)$} ) { + my $backupNum = $2; + if ( $ClientBkupNum != $backupNum ) { + my $i; + $ClientBkupNum = $backupNum; + # print STDERR "Setting ClientBkupNum to $ClientBkupNum\n"; + for ( $i = 0 ; $i < @$ClientBackups ; $i++ ) { + if ( $ClientBackups->[$i]{num} == $ClientBkupNum ) { + $ClientBkupCompress = $ClientBackups->[$i]{compress}; + $ClientBkupMangle = $ClientBackups->[$i]{mangle}; + # print STDERR "Setting $ClientBkupNum compress to $ClientBkupCompress, mangle to $ClientBkupMangle\n"; + last; + } + } + } + $hdr->{compress} = $ClientBkupCompress; + if ( $hdr->{type} == BPC_FTYPE_FILE && $hdr->{nlink} > 1 + && $hdr->{name} =~ /^f/ ) { + (my $dir = $hdr->{fullPath}) =~ s{(.*)/.*}{$1}; + if ( $ClientDir ne $dir ) { + $ClientDir = $dir; + $ClientDirAttr = BackupPC::Attrib->new( + { compress => $ClientBkupCompress } + ); + if ( -f $ClientDirAttr->fileName($dir) + && !$ClientDirAttr->read($dir) ) { + print STDERR "Can't read attrib file in $dir\n"; + $ErrorCnt++; + } + } + my $name = $hdr->{name}; + $name = $bpc->fileNameUnmangle($name) if ( $ClientBkupMangle ); + my $attr = $ClientDirAttr->get($name); + $hdr->{realSize} = $attr->{size} if ( defined($attr) ); + #print STDERR "$hdr->{fullPath} has real size $hdr->{realSize}\n"; + } + } + } else { + $hdr->{compress} = 0; + $hdr->{realSize} = $hdr->{size}; + } + + #print STDERR "$File::Find::name\n"; + + TarWriteFile($hdr, $fh); +} + +sub UidLookup +{ + my($uid) = @_; + + $UidCache{$uid} = (getpwuid($uid))[0] if ( !exists($UidCache{$uid}) ); + return $UidCache{$uid}; +} + +sub GidLookup +{ + my($gid) = @_; + + $GidCache{$gid} = (getgrgid($gid))[0] if ( !exists($GidCache{$gid}) ); + return $GidCache{$gid}; +} + +sub TarWrite +{ + my($fh, $dataRef) = @_; + + if ( !defined($dataRef) ) { + # + # do flush by padding to a full $WriteBufSz + # + my $data = "\0" x ($WriteBufSz - length($WriteBuf)); + $dataRef = \$data; + } + if ( length($WriteBuf) + length($$dataRef) < $WriteBufSz ) { + # + # just buffer and return + # + $WriteBuf .= $$dataRef; + return; + } + my $done = $WriteBufSz - length($WriteBuf); + if ( (my $n = syswrite($fh, $WriteBuf . substr($$dataRef, 0, $done))) + != $WriteBufSz ) { + print(STDERR "Unable to write to output file ($!) ($n vs $WriteBufSz)\n"); + exit(1); + } + while ( $done + $WriteBufSz <= length($$dataRef) ) { + if ( (my $n = syswrite($fh, substr($$dataRef, $done, $WriteBufSz))) + != $WriteBufSz ) { + print(STDERR "Unable to write to output file ($!) ($n v $WriteBufSz)\n"); + exit(1); + } + $done += $WriteBufSz; + } + $WriteBuf = substr($$dataRef, $done); +} + +sub TarWritePad +{ + my($fh, $size) = @_; + + if ( $size % $tar_header_length ) { + my $data = "\0" x ($tar_header_length - ($size % $tar_header_length)); + TarWrite($fh, \$data); + } +} + +sub TarWriteHeader +{ + my($fh, $hdr) = @_; + + $hdr->{uname} = UidLookup($hdr->{uid}) if ( !defined($hdr->{uname}) ); + $hdr->{gname} = GidLookup($hdr->{gid}) if ( !defined($hdr->{gname}) ); + my $devmajor = defined($hdr->{devmajor}) ? sprintf("%07o", $hdr->{devmajor}) + : ""; + my $devminor = defined($hdr->{devminor}) ? sprintf("%07o", $hdr->{devminor}) + : ""; + my $sizeStr; + if ( $hdr->{size} >= 2 * 65536 * 65536 ) { + # + # GNU extension for files >= 8GB: send size in big-endian binary + # + $sizeStr = pack("c4 N N", 0x80, 0, 0, 0, + $hdr->{size} / (65536 * 65536), + $hdr->{size} % (65536 * 65536)); + } elsif ( $hdr->{size} >= 1 * 65536 * 65536 ) { + # + # sprintf octal only handles up to 2^32 - 1 + # + $sizeStr = sprintf("%03o", $hdr->{size} / (1 << 24)) + . sprintf("%08o", $hdr->{size} % (1 << 24)); + } else { + $sizeStr = sprintf("%011o", $hdr->{size}); + } + my $data = pack($tar_pack_header, + substr($hdr->{name}, 0, 99), + sprintf("%07o", $hdr->{mode}), + sprintf("%07o", $hdr->{uid}), + sprintf("%07o", $hdr->{gid}), + $sizeStr, + sprintf("%011o", $hdr->{mtime}), + "", #checksum field - space padded by pack("A8") + $hdr->{type}, + substr($hdr->{linkname}, 0, 99), + $hdr->{magic} || 'ustar ', + $hdr->{version} || ' ', + $hdr->{uname}, + $hdr->{gname}, + $devmajor, + $devminor, + "" # prefix is empty + ); + substr($data, 148, 7) = sprintf("%06o\0", unpack("%16C*",$data)); + TarWrite($fh, \$data); +} + +sub TarWriteFileInfo +{ + my($fh, $hdr) = @_; + + # + # Handle long link names (symbolic links) + # + if ( length($hdr->{linkname}) > 99 ) { + my %h; + my $data = $hdr->{linkname} . "\0"; + $h{name} = "././\@LongLink"; + $h{type} = "K"; + $h{size} = length($data); + TarWriteHeader($fh, \%h); + TarWrite($fh, \$data); + TarWritePad($fh, length($data)); + } + # + # Handle long file names + # + if ( length($hdr->{name}) > 99 ) { + my %h; + my $data = $hdr->{name} . "\0"; + $h{name} = "././\@LongLink"; + $h{type} = "L"; + $h{size} = length($data); + TarWriteHeader($fh, \%h); + TarWrite($fh, \$data); + TarWritePad($fh, length($data)); + } + TarWriteHeader($fh, $hdr); +} + +my $Attr; +my $AttrDir; + +sub TarWriteFile +{ + my($hdr, $fh) = @_; + + my $tarPath = $hdr->{relPath}; + + $tarPath =~ s{//+}{/}g; + $tarPath = "./" . $tarPath if ( $tarPath !~ /^\.\// ); + $tarPath =~ s{//+}{/}g; + $hdr->{name} = $tarPath; + + if ( $hdr->{type} == BPC_FTYPE_DIR ) { + # + # Directory: just write the header + # + $hdr->{name} .= "/" if ( $hdr->{name} !~ m{/$} ); + TarWriteFileInfo($fh, $hdr); + $DirCnt++; + } elsif ( $hdr->{type} == BPC_FTYPE_FILE ) { + # + # Regular file: write the header and file + # + my($data, $dataMD5, $size, $linkName); + + if ( $hdr->{type} == BPC_FTYPE_FILE && $hdr->{nlink} > 1 ) { + if ( defined($Inode2Path{$hdr->{inode}}) ) { + $linkName = $Inode2Path{$hdr->{inode}}; + #print STDERR "Got cache hit for $linkName\n"; + } else { + my $f = BackupPC::FileZIO->open($hdr->{fullPath}, 0, + $hdr->{compress}); + if ( !defined($f) ) { + print(STDERR "Unable to open file $hdr->{fullPath}\n"); + $ErrorCnt++; + return; + } + # + # Try to find the hardlink it points to by computing + # the pool file digest. + # + $f->read(\$dataMD5, $BufSize); + if ( !defined($hdr->{realSize}) ) { + # + # Need to get the real size + # + $size = length($dataMD5); + while ( $f->read(\$data, $BufSize) > 0 ) { + $size += length($data); + } + $hdr->{realSize} = $size; + } + $f->close(); + my $md5 = Digest::MD5->new; + if ( $hdr->{realSize} < 1048576 + && length($dataMD5) != $hdr->{realSize} ) { + printf(STDERR "File $hdr->{fullPath} has bad size" + . " (expect $hdr->{realSize}, got %d)\n", + length($dataMD5)); + } else { + my $digest = $bpc->Buffer2MD5($md5, $hdr->{realSize}, + \$dataMD5); + my $path = $bpc->MD52Path($digest, $hdr->{compress}); + my $i = -1; + + # print(STDERR "Looking up $hdr->{fullPath} at $path\n"); + while ( 1 ) { + my $testPath = $path; + $testPath .= "_$i" if ( $i >= 0 ); + last if ( !-f $testPath ); + my $inode = (stat(_))[1]; + if ( $inode == $hdr->{inode} ) { + # + # Found it! Just emit a tar hardlink + # + $testPath =~ s{\Q$TopDir\E}{..}; + $linkName = $testPath; + last; + } + $i++; + } + } + } + if ( defined($linkName) ) { + $hdr->{type} = BPC_FTYPE_HARDLINK; + $hdr->{linkname} = $linkName; + TarWriteFileInfo($fh, $hdr); + $HLinkCnt++; + #print STDERR "$hdr->{relPath} matches $testPath\n"; + if ( !$opts{c} && $hdr->{nlink} > 2 ) { + # + # add it to the cache if there are more + # than 2 links (pool + current file), + # since there are more to go + # + $Inode2Path{$hdr->{inode}} = $linkName; + } + return; + } + $size = 0; + print STDERR "Can't find $hdr->{relPath} in pool, will copy file\n"; + $ErrorCnt++; + } + + my $f = BackupPC::FileZIO->open($hdr->{fullPath}, 0, 0); + if ( !defined($f) ) { + print(STDERR "Unable to open file $hdr->{fullPath}\n"); + $ErrorCnt++; + return; + } + TarWriteFileInfo($fh, $hdr); + while ( $f->read(\$data, $BufSize) > 0 ) { + if ( $size + length($data) > $hdr->{size} ) { + print(STDERR "Error: truncating $hdr->{fullPath} to" + . " $hdr->{size} bytes\n"); + $data = substr($data, 0, $hdr->{size} - $size); + $ErrorCnt++; + } + TarWrite($fh, \$data); + $size += length($data); + } + $f->close; + if ( $size != $hdr->{size} ) { + print(STDERR "Error: padding $hdr->{fullPath} to $hdr->{size}" + . " bytes from $size bytes\n"); + $ErrorCnt++; + while ( $size < $hdr->{size} ) { + my $len = $hdr->{size} - $size; + $len = $BufSize if ( $len > $BufSize ); + $data = "\0" x $len; + TarWrite($fh, \$data); + $size += $len; + } + } + TarWritePad($fh, $size); + $FileCnt++; + $ByteCnt += $size; + } else { + print(STDERR "Got unknown type $hdr->{type} for $hdr->{name}\n"); + $ErrorCnt++; + } +} diff --git a/bin/BackupPC_zipCreate b/bin/BackupPC_zipCreate index 7c859d2..82061af 100755 --- a/bin/BackupPC_zipCreate +++ b/bin/BackupPC_zipCreate @@ -6,15 +6,14 @@ # # DESCRIPTION # -# Usage: BackupPC_zipCreate [-t] [-h host] [-n dumpNum] [-s shareName] -# [-r pathRemove] [-p pathAdd] [-c compressionLevel] -# files/directories... +# Usage: BackupPC_zipCreate [options] files/directories... # # Flags: # Required options: -# # -h host host from which the zip archive is created # -n dumpNum dump number from which the zip archive is created +# A negative number means relative to the end (eg -1 +# means the most recent dump, -2 2nd most recent etc). # -s shareName share name from which the zip archive is created # # Other options: @@ -22,6 +21,8 @@ # -r pathRemove path prefix that will be replaced with pathAdd # -p pathAdd new path prefix # -c level compression level (default is 0, no compression) +# -e charset charset for encoding file names (default: value of +# $Conf{ClientCharset} when backup was done) # # The -h, -n and -s options specify which dump is used to generate # the zip archive. The -r and -p options can be used to relocate @@ -63,6 +64,7 @@ use lib "/usr/local/BackupPC/lib"; use Archive::Zip qw(:ERROR_CODES); use File::Path; use Getopt::Std; +use Encode qw/from_to/; use IO::Handle; use BackupPC::Lib; use BackupPC::Attrib qw(:all); @@ -77,10 +79,24 @@ my %Conf = $bpc->Conf(); my %opts; -if ( !getopts("th:n:p:r:s:c:", \%opts) || @ARGV < 1 ) { - print(STDERR "usage: $0 [-t] [-h host] [-n dumpNum] [-s shareName]" - . " [-r pathRemove] [-p pathAdd] [-c compressionLevel]" - . " files/directories...\n"); +if ( !getopts("te:h:n:p:r:s:c:", \%opts) || @ARGV < 1 ) { + print STDERR <BackupInfoRead($Host); -my($i); my $FileCnt = 0; my $ByteCnt = 0; my $DirCnt = 0; my $SpecialCnt = 0; my $ErrorCnt = 0; +my $i; +$Num = $Backups[@Backups + $Num]{num} if ( -@Backups <= $Num && $Num < 0 ); for ( $i = 0 ; $i < @Backups ; $i++ ) { last if ( $Backups[$i]{num} == $Num ); } @@ -118,6 +136,9 @@ if ( $i >= @Backups ) { exit(1); } +my $Charset = $Backups[$i]{charset}; +$Charset = $opts{e} if ( $opts{e} ne "" ); + my $PathRemove = $1 if ( $opts{r} =~ /(.+)/ ); my $PathAdd = $1 if ( $opts{p} =~ /(.+)/ ); if ( $opts{s} !~ /^([\w\s.\/$(){}[\]-]+)$/ ) { @@ -200,9 +221,10 @@ sub ZipWriteFile && substr($tarPath, 0, length($PathRemove)) eq $PathRemove ) { substr($tarPath, 0, length($PathRemove)) = $PathAdd; } - $tarPath = "./" . $tarPath if ( $tarPath !~ /^\.\// ); + $tarPath = $1 if ( $tarPath =~ m{^\.?/+(.*)} ); $tarPath =~ s{//+}{/}g; $hdr->{name} = $tarPath; + return if ( $tarPath eq "." || $tarPath eq "./" || $tarPath eq "" ); my $zipmember; # Container to hold the file/directory to zip. @@ -211,12 +233,14 @@ sub ZipWriteFile # Directory: just write the header # $hdr->{name} .= "/" if ( $hdr->{name} !~ m{/$} ); + from_to($hdr->{name}, "utf8", $Charset) if ( $Charset ne "" ); $zipmember = Archive::Zip::Member->newDirectoryNamed($hdr->{name}); $DirCnt++; } elsif ( $hdr->{type} == BPC_FTYPE_FILE ) { # # Regular file: write the header and file # + from_to($hdr->{name}, "utf8", $Charset) if ( $Charset ne "" ); $zipmember = BackupPC::Zip::FileMember->newFromFileNamed( $hdr->{fullPath}, $hdr->{name}, diff --git a/conf/BackupPC_stnd.css b/conf/BackupPC_stnd.css index 9beca1a..19e77e3 100644 --- a/conf/BackupPC_stnd.css +++ b/conf/BackupPC_stnd.css @@ -1,7 +1,7 @@ /* * BackupPC standard CSS definitions * - * Version 2.1.0, released 13 Jun 2004. + * Version 2.1.0, released 20 Jun 2004. * * See http://backuppc.sourceforge.net. * @@ -92,6 +92,50 @@ a.navbar { font-size:10pt; } +.editHeader { + font-family:arial,sans-serif; + font-size:12pt; + color:#000000; + font-weight:bold; + background-color:#99cc33; +} + +.editTabSel { + font-family:arial,sans-serif; + font-size:14pt; + color:#000000; + font-weight:bold; + background-color:#99cc33; + padding:3px; + padding-left:6px; + margin-bottom:5px; +} + +.editTabNoSel { + font-family:arial,sans-serif; + font-size:14pt; + color:#000000; + font-weight:bold; + padding:3px; + padding-left:6px; + margin-bottom:5px; +} + +.editSaveButton { + color:#ff0000; + font-size:14pt; + font-weight:bold; +} + +.editError { + color:#ff0000; + font-weight:bold; +} + +.editComment { + font-size:10pt; +} + .fviewheader { font-weight:bold; font-size:10pt; diff --git a/conf/config.pl b/conf/config.pl index 1284c5d..03c28b5 100644 --- a/conf/config.pl +++ b/conf/config.pl @@ -547,6 +547,9 @@ $Conf{ArchiveInfoKeepCnt} = 10; # array, and $Conf{SmbShareName} contains multiple share names, then # the setting is assumed to apply all shares. # +# If a hash is used, a special key "*" means it applies to all +# shares. +# # Examples: # $Conf{BackupFilesOnly} = '/myFiles'; # $Conf{BackupFilesOnly} = ['/myFiles']; # same as first example @@ -555,6 +558,10 @@ $Conf{ArchiveInfoKeepCnt} = 10; # 'c' => ['/myFiles', '/important'], # these are for 'c' share # 'd' => ['/moreFiles', '/archive'], # these are for 'd' share # }; +# $Conf{BackupFilesOnly} = { +# 'c' => ['/myFiles', '/important'], # these are for 'c' share +# '*' => ['/myFiles', '/important'], # these are other shares +# }; # $Conf{BackupFilesOnly} = undef; @@ -590,6 +597,9 @@ $Conf{BackupFilesOnly} = undef; # Users report that for smbclient you should specify a directory # followed by "/*", eg: "/proc/*", instead of just "/proc". # +# If a hash is used, a special key "*" means it applies to all +# shares. +# # Examples: # $Conf{BackupFilesExclude} = '/temp'; # $Conf{BackupFilesExclude} = ['/temp']; # same as first example @@ -598,6 +608,10 @@ $Conf{BackupFilesOnly} = undef; # 'c' => ['/temp', '/winnt/tmp'], # these are for 'c' share # 'd' => ['/junk', '/dont_back_this_up'], # these are for 'd' share # }; +# $Conf{BackupFilesExclude} = { +# 'c' => ['/temp', '/winnt/tmp'], # these are for 'c' share +# '*' => ['/junk', '/dont_back_this_up'], # these are for other shares +# }; # $Conf{BackupFilesExclude} = undef; @@ -1759,6 +1773,11 @@ $Conf{CgiImageDirURL} = ''; # $Conf{CgiCSSFile} = 'BackupPC_stnd.css'; +# +# Whether the user is allowed to edit their per-PC config. +# +$Conf{CgiUserConfigEditEnable} = 1; + # # Which per-host config variables a non-admin user is allowed # to edit. diff --git a/configure.pl b/configure.pl index ec63ebb..0b8e030 100755 --- a/configure.pl +++ b/configure.pl @@ -78,21 +78,28 @@ EOF } my %opts; +$opts{fhs} = 1; +$opts{"set-perms"} = 1; +$opts{"backuppc-user"} = "backuppc"; if ( !GetOptions( \%opts, "batch", + "backuppc-user=s", "bin-path=s%", - "config-path=s", "cgi-dir=s", + "compress-level=i", + "config-path=s", "data-dir=s", "dest-dir=s", + "fhs!", "help|?", "hostname=s", "html-dir=s", "html-dir-url=s", "install-dir=s", "man", - "uid-ignore", + "set-perms!", + "uid-ignore!", ) || @ARGV ) { pod2usage(2); } @@ -101,7 +108,7 @@ pod2usage(-exitstatus => 0, -verbose => 2) if $opts{man}; my $DestDir = $opts{"dest-dir"}; -if ( $< != 0 ) { +if ( !$opts{"uid-ignore"} && $< != 0 ) { print < Full path to existing conf/config.pl", - $ConfigPath, - "config-path"); + if ( $opts{fhs} && -f "/etc/BackupPC/config.pl" ) { + $ConfigPath = "/etc/BackupPC/config.pl"; + } else { + $ConfigPath = prompt("--> Full path to existing main config.pl", + $ConfigPath, + "config-path"); + } last if ( $ConfigPath eq "" || ($ConfigPath =~ /^\// && -r $ConfigPath && -w $ConfigPath) ); my $problem = "is not an absolute path"; - $problem = "is not writable" if ( !-w $ConfigPath ); - $problem = "is not readable" if ( !-r $ConfigPath ); - $problem = "doesn't exist" if ( !-f $ConfigPath ); + $problem = "is not writable" if ( !-w $ConfigPath ); + $problem = "is not readable" if ( !-r $ConfigPath ); + $problem = "is not a regular file" if ( !-f $ConfigPath ); + $problem = "doesn't exist" if ( !-e $ConfigPath ); print("The file '$ConfigPath' $problem.\n"); if ( $opts{batch} ) { print("Need to specify a valid --config-path for upgrade\n"); exit(1); } } + my $bpc; if ( $ConfigPath ne "" && -r $ConfigPath ) { - (my $topDir = $ConfigPath) =~ s{/[^/]+/[^/]+$}{}; + (my $confDir = $ConfigPath) =~ s{/[^/]+$}{}; die("BackupPC::Lib->new failed\n") - if ( !($bpc = BackupPC::Lib->new($topDir, ".", 1)) ); + if ( !($bpc = BackupPC::Lib->new(".", ".", $confDir, 1)) ); %Conf = $bpc->Conf(); %OrigConf = %Conf; - $Conf{TopDir} = $topDir; + if ( !$opts{fhs} ) { + ($Conf{TopDir} = $ConfigPath) =~ s{/[^/]+/[^/]+$}{}; + } + $Conf{ConfDir} = $confDir; my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort}, 1); if ( $err eq "" ) { print < BackupPC should run as user", $Conf{BackupPCUser} || "backuppc", - "username"); - ($name, $passwd, $Uid, $Gid) = getpwnam($Conf{BackupPCUser}); - last if ( $name ne "" ); - print <compOk ? 3 : 0; if ( $ConfigPath eq "" && $Conf{CompressLevel} ) { @@ -435,7 +485,7 @@ Ok, we're about to: - install the binaries, lib and docs in $Conf{InstallDir}, - create the data directory $Conf{TopDir}, - - create/update the config.pl file $Conf{TopDir}/conf, + - create/update the config.pl file $Conf{ConfDir}/config.pl, - optionally install the cgi-bin interface. EOF @@ -447,16 +497,14 @@ exit unless prompt("--> Do you want to continue?", "y") =~ /y/i; # foreach my $dir ( qw(bin doc lib/BackupPC/CGI - lib/BackupPC/Config lib/BackupPC/Lang - lib/BackupPC/Storage lib/BackupPC/Xfer lib/BackupPC/Zip ) ) { next if ( -d "$DestDir$Conf{InstallDir}/$dir" ); mkpath("$DestDir$Conf{InstallDir}/$dir", 0, 0775); if ( !-d "$DestDir$Conf{InstallDir}/$dir" - || !chown($Uid, $Gid, "$DestDir$Conf{InstallDir}/$dir") ) { + || !my_chown($Uid, $Gid, "$DestDir$Conf{InstallDir}/$dir") ) { die("Failed to create or chown $DestDir$Conf{InstallDir}/$dir\n"); } else { print("Created $DestDir$Conf{InstallDir}/$dir\n"); @@ -467,9 +515,9 @@ foreach my $dir ( qw(bin doc # Create CGI image directory # foreach my $dir ( ($Conf{CgiImageDir}) ) { - next if ( $dir eq "" || -d "$DestDir$dir" ); + next if ( $dir eq "" || -d $dir ); mkpath("$DestDir$dir", 0, 0775); - if ( !-d "$DestDir$dir" || !chown($Uid, $Gid, "$DestDir$dir") ) { + if ( !-d "$DestDir$dir" || !my_chown($Uid, $Gid, "$DestDir$dir") ) { die("Failed to create or chown $DestDir$dir"); } else { print("Created $DestDir$dir\n"); @@ -477,15 +525,24 @@ foreach my $dir ( ($Conf{CgiImageDir}) ) { } # -# Create $TopDir's top-level directories +# Create other directories # -foreach my $dir ( qw(. conf pool cpool pc trash log) ) { - mkpath("$DestDir$Conf{TopDir}/$dir", 0, 0750) if ( !-d "$DestDir$Conf{TopDir}/$dir" ); - if ( !-d "$DestDir$Conf{TopDir}/$dir" - || !chown($Uid, $Gid, "$DestDir$Conf{TopDir}/$dir") ) { - die("Failed to create or chown $DestDir$Conf{TopDir}/$dir\n"); +foreach my $dir ( ( + "$Conf{TopDir}", + "$Conf{TopDir}/pool", + "$Conf{TopDir}/cpool", + "$Conf{TopDir}/pc", + "$Conf{TopDir}/trash", + "$Conf{ConfDir}", + "$Conf{LogDir}", + "$Conf{StatusDir}", + ) ) { + mkpath("$DestDir/$dir", 0, 0750) if ( !-d "$DestDir/$dir" ); + if ( !-d "$DestDir/$dir" + || !my_chown($Uid, $Gid, "$DestDir/$dir") ) { + die("Failed to create or chown $DestDir/$dir\n"); } else { - print("Created $DestDir$Conf{TopDir}/$dir\n"); + print("Created $DestDir/$dir\n"); } } @@ -501,12 +558,23 @@ foreach my $prog ( qw(BackupPC BackupPC_dump BackupPC_link BackupPC_nightly printf("Installing library in $DestDir$Conf{InstallDir}/lib\n"); foreach my $lib ( qw( BackupPC/Lib.pm - BackupPC/Attrib.pm BackupPC/FileZIO.pm - BackupPC/Config.pm + BackupPC/Attrib.pm BackupPC/PoolWrite.pm - BackupPC/Storage.pm BackupPC/View.pm + BackupPC/Xfer/Archive.pm + BackupPC/Xfer/Tar.pm + BackupPC/Xfer/Smb.pm + BackupPC/Xfer/Rsync.pm + BackupPC/Xfer/RsyncDigest.pm + BackupPC/Xfer/RsyncFileIO.pm + BackupPC/Zip/FileMember.pm + BackupPC/Lang/en.pm + BackupPC/Lang/fr.pm + BackupPC/Lang/es.pm + BackupPC/Lang/de.pm + BackupPC/Lang/it.pm + BackupPC/Lang/nl.pm BackupPC/CGI/AdminOptions.pm BackupPC/CGI/Archive.pm BackupPC/CGI/ArchiveInfo.pm @@ -527,22 +595,6 @@ foreach my $lib ( qw( BackupPC/CGI/StopServer.pm BackupPC/CGI/Summary.pm BackupPC/CGI/View.pm - BackupPC/Config/Meta.pm - BackupPC/Lang/en.pm - BackupPC/Lang/fr.pm - BackupPC/Lang/es.pm - BackupPC/Lang/de.pm - BackupPC/Lang/it.pm - BackupPC/Lang/nl.pm - BackupPC/Lang/pt_br.pm - BackupPC/Storage/Text.pm - BackupPC/Xfer/Archive.pm - BackupPC/Xfer/Tar.pm - BackupPC/Xfer/Smb.pm - BackupPC/Xfer/Rsync.pm - BackupPC/Xfer/RsyncDigest.pm - BackupPC/Xfer/RsyncFileIO.pm - BackupPC/Zip/FileMember.pm ) ) { InstallFile("lib/$lib", "$DestDir$Conf{InstallDir}/lib/$lib", 0444); } @@ -577,16 +629,16 @@ foreach my $doc ( qw(BackupPC.pod BackupPC.html) ) { InstallFile("doc/$doc", "$DestDir$Conf{InstallDir}/doc/$doc", 0444); } -printf("Installing config.pl and hosts in $DestDir$Conf{TopDir}/conf\n"); -InstallFile("conf/hosts", "$DestDir$Conf{TopDir}/conf/hosts", 0644) - if ( !-f "$DestDir$Conf{TopDir}/conf/hosts" ); +printf("Installing config.pl and hosts in $DestDir$Conf{ConfDir}\n"); +InstallFile("conf/hosts", "$DestDir$Conf{ConfDir}/hosts", 0644) + if ( !-f "$DestDir$Conf{ConfDir}/hosts" ); # # Now do the config file. If there is an existing config file we # merge in the new config file, adding any new configuration # parameters and deleting ones that are no longer needed. # -my $dest = "$DestDir$Conf{TopDir}/conf/config.pl"; +my $dest = "$DestDir$Conf{ConfDir}/config.pl"; my ($newConf, $newVars) = ConfigParse("conf/config.pl"); my ($oldConf, $oldVars); if ( -f $dest ) { @@ -711,10 +763,12 @@ if ( -f $dest && !-f $confCopy ) { my $mode = $stat[2]; my $uid = $stat[4]; my $gid = $stat[5]; - die("can't copy($dest, $confCopy)\n") unless copy($dest, $confCopy); + die("can't copy($dest, $confCopy)\n") + unless copy($dest, $confCopy); die("can't chown $uid, $gid $confCopy\n") - unless chown($uid, $gid, $confCopy); - die("can't chmod $mode $confCopy\n") unless chmod($mode, $confCopy); + unless my_chown($uid, $gid, $confCopy); + die("can't chmod $mode $confCopy\n") + unless my_chmod($mode, $confCopy); } open(OUT, ">", $dest) || die("can't open $dest for writing\n"); binmode(OUT); @@ -735,8 +789,8 @@ foreach my $var ( @$newConf ) { } close(OUT); if ( !defined($oldConf) ) { - die("can't chmod 0640 mode $dest\n") unless chmod(0640, $dest); - die("can't chown $Uid, $Gid $dest\n") unless chown($Uid, $Gid, $dest); + die("can't chmod 0640 mode $dest\n") unless my_chmod(0640, $dest); + die("can't chown $Uid, $Gid $dest\n") unless my_chown($Uid, $Gid, $dest); } if ( $Conf{CgiDir} ne "" ) { @@ -751,12 +805,12 @@ print < ) { s/__INSTALLDIR__/$Conf{InstallDir}/g; + s/__LOGDIR__/$Conf{LogDir}/g; + s/__CONFDIR__/$Conf{ConfDir}/g; s/__TOPDIR__/$Conf{TopDir}/g; s/__BACKUPPCUSER__/$Conf{BackupPCUser}/g; s/__CGIDIR__/$Conf{CgiDir}/g; @@ -841,8 +897,8 @@ sub InstallFile close(PROG); close(OUT); } - die("can't chown $uid, $gid $dest") unless chown($uid, $gid, $dest); - die("can't chmod $mode $dest") unless chmod($mode, $dest); + die("can't chown $uid, $gid $dest") unless my_chown($uid, $gid, $dest); + die("can't chmod $mode $dest") unless my_chmod($mode, $dest); } sub FindProgram @@ -954,6 +1010,22 @@ sub ConfigMerge return $res; } +sub my_chown +{ + my($uid, $gid, $file) = @_; + + return 1 if ( !$opts{"set-perms"} ); + return chown($uid, $gid, $file); +} + +sub my_chmod +{ + my ($mode, $file) = @_; + + return 1 if ( !$opts{"set-perms"} ); + return chmod($mode, $file); +} + sub prompt { my($question, $default, $option) = @_; @@ -998,6 +1070,11 @@ Run configure.pl in batch mode. configure.pl will run without prompting the user. The other command-line options are used to specify the settings that the user is usually prompted for. +=item B<--backuppc-user=USER> + +Specify the BackupPC user name that owns all the BackupPC +files and runs the BackupPC programs. Default is backuppc. + =item B<--bin-path PROG=PATH> Specify the path for various external programs that BackupPC @@ -1015,6 +1092,11 @@ Examples --bin-path cat=/bin/cat --bin-path bzip2=/home/user/bzip2 +=item B<--compress-level=N> + +Set the configuration compression level to N. Default is 3 +if Compress::Zlib is installed. + =item B<--config-path CONFIG_PATH> Path to the existing config.pl configuration file for BackupPC. @@ -1050,6 +1132,12 @@ to run it from below the --dest-dir directory, since all the paths are set assuming BackupPC is installed in the intended final locations. +=item B<--fhs> + +Use locations specified by the Filesystem Hierarchy Standard +for installing BackupPC. This is enabled by default. To +use the pre-3.0 installation locations, specify --no-fhs. + =item B<--help|?> Print a brief help message and exits. @@ -1094,6 +1182,13 @@ Example: Prints the manual page and exits. +=item B<--set-perms> + +When installing files and creating directories, chown them to +the BackupPC user and chmod them too. This is enabled by default. +To disable (for example, if staging a destination directory) +then specify --no-set-perms. + =item B<--uid-ignore> configure.pl verifies that the script is being run as the super user diff --git a/lib/BackupPC/CGI/EditConfig.pm b/lib/BackupPC/CGI/EditConfig.pm index dc831c2..6633f59 100644 --- a/lib/BackupPC/CGI/EditConfig.pm +++ b/lib/BackupPC/CGI/EditConfig.pm @@ -44,31 +44,31 @@ use Data::Dumper; our %ConfigMenu = ( server => { - text => "Server", + text => "CfgEdit_Title_Server", param => [ - {text => "General Parameters"}, + {text => "CfgEdit_Title_General_Parameters"}, {name => "ServerHost"}, {name => "BackupPCUser"}, {name => "BackupPCUserVerify"}, {name => "MaxOldLogFiles"}, {name => "TrashCleanSleepSec"}, - {text => "Wakeup Schedule"}, + {text => "CfgEdit_Title_Wakeup_Schedule"}, {name => "WakeupSchedule"}, - {text => "Concurrent Jobs"}, + {text => "CfgEdit_Title_Concurrent_Jobs"}, {name => "MaxBackups"}, {name => "MaxUserBackups"}, {name => "MaxPendingCmds"}, {name => "MaxBackupPCNightlyJobs"}, {name => "BackupPCNightlyPeriod"}, - {text => "Pool Filesystem Limits"}, + {text => "CfgEdit_Title_Pool_Filesystem_Limits"}, {name => "DfCmd"}, {name => "DfMaxUsagePct"}, {name => "HardLinkMax"}, - {text => "Other Parameters"}, + {text => "CfgEdit_Title_Other_Parameters"}, {name => "UmaskMode"}, {name => "MyPath"}, {name => "DHCPAddressRanges"}, @@ -76,11 +76,11 @@ our %ConfigMenu = ( {name => "ServerInitdPath"}, {name => "ServerInitdStartCmd"}, - {text => "Remote Apache Settings"}, + {text => "CfgEdit_Title_Remote_Apache_Settings"}, {name => "ServerPort"}, {name => "ServerMesgSecret"}, - {text => "Program Paths"}, + {text => "CfgEdit_Title_Program_Paths"}, {name => "SshPath"}, {name => "NmbLookupPath"}, {name => "PingPath"}, @@ -91,22 +91,25 @@ our %ConfigMenu = ( {name => "GzipPath"}, {name => "Bzip2Path"}, - {text => "Install Paths"}, + {text => "CfgEdit_Title_Install_Paths"}, + {name => "TopDir"}, + {name => "ConfDir"}, + {name => "LogDir"}, {name => "CgiDir"}, {name => "InstallDir"}, ], }, email => { - text => "Email", + text => "CfgEdit_Title_Email", param => [ - {text => "Email settings"}, + {text => "CfgEdit_Title_Email_settings"}, {name => "SendmailPath"}, {name => "EMailNotifyMinDays"}, {name => "EMailFromUserName"}, {name => "EMailAdminUserName"}, {name => "EMailUserDestDomain"}, - {text => "Email User Messages"}, + {text => "CfgEdit_Title_Email_User_Messages"}, {name => "EMailNoBackupEverSubj"}, {name => "EMailNoBackupEverMesg"}, {name => "EMailNotifyOldBackupDays"}, @@ -115,19 +118,17 @@ our %ConfigMenu = ( {name => "EMailNotifyOldOutlookDays"}, {name => "EMailOutlookBackupSubj"}, {name => "EMailOutlookBackupMesg"}, + {name => "EMailHeaders"}, ], }, cgi => { - text => "CGI", + text => "CfgEdit_Title_CGI", param => [ - {text => "Admin Privileges"}, + {text => "CfgEdit_Title_Admin_Privileges"}, {name => "CgiAdminUserGroup"}, {name => "CgiAdminUsers"}, - {text => "Config Editing"}, - {name => "CgiUserConfigEdit"}, - - {text => "Page Rendering"}, + {text => "CfgEdit_Title_Page_Rendering"}, {name => "Language"}, {name => "CgiNavBarAdminAllHosts"}, {name => "CgiSearchBoxEnable"}, @@ -138,25 +139,29 @@ our %ConfigMenu = ( {name => "CgiExt2ContentType"}, {name => "CgiCSSFile"}, - {text => "Paths"}, + {text => "CfgEdit_Title_Paths"}, {name => "CgiURL"}, {name => "CgiImageDir"}, {name => "CgiImageDirURL"}, - {text => "User URLs"}, + {text => "CfgEdit_Title_User_URLs"}, {name => "CgiUserHomePageCheck"}, {name => "CgiUserUrlCreate"}, + {text => "CfgEdit_Title_User_Config_Editing"}, + {name => "CgiUserConfigEditEnable"}, + {name => "CgiUserConfigEdit"}, ], }, xfer => { - text => "Xfer", + text => "CfgEdit_Title_Xfer", param => [ - {text => "Xfer Settings"}, + {text => "CfgEdit_Title_Xfer_Settings"}, {name => "XferMethod", onchangeSubmit => 1}, {name => "XferLogLevel"}, + {name => "ClientCharset"}, - {text => "Smb Settings", + {text => "CfgEdit_Title_Smb_Settings", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, {name => "SmbShareName", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, @@ -165,14 +170,14 @@ our %ConfigMenu = ( {name => "SmbSharePasswd", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, - {text => "Tar Settings", + {text => "CfgEdit_Title_Tar_Settings", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, {name => "TarShareName", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, - {text => "Rsync Settings", + {text => "CfgEdit_Title_Rsync_Settings", visible => sub { return $_[0]->{XferMethod} eq "rsync"; } }, - {text => "Rsyncd Settings", + {text => "CfgEdit_Title_Rsyncd_Settings", visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } }, {name => "RsyncShareName", visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } }, @@ -183,7 +188,7 @@ our %ConfigMenu = ( {name => "RsyncCsumCacheVerifyProb", visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } }, - {text => "Archive Settings", + {text => "CfgEdit_Title_Archive_Settings", visible => sub { return $_[0]->{XferMethod} eq "archive"; } }, {name => "ArchiveDest", visible => sub { return $_[0]->{XferMethod} eq "archive"; } }, @@ -194,14 +199,14 @@ our %ConfigMenu = ( {name => "ArchiveSplit", visible => sub { return $_[0]->{XferMethod} eq "archive"; } }, - {text => "Include/Exclude", + {text => "CfgEdit_Title_Include_Exclude", visible => sub { return $_[0]->{XferMethod} ne "archive"; } }, {name => "BackupFilesOnly", visible => sub { return $_[0]->{XferMethod} ne "archive"; } }, {name => "BackupFilesExclude", visible => sub { return $_[0]->{XferMethod} ne "archive"; } }, - {text => "Smb Paths/Commands", + {text => "CfgEdit_Title_Smb_Paths_Commands", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, {name => "SmbClientPath", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, @@ -212,7 +217,7 @@ our %ConfigMenu = ( {name => "SmbClientRestoreCmd", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, - {text => "Tar Paths/Commands", + {text => "CfgEdit_Title_Tar_Paths_Commands", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, {name => "TarClientPath", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, @@ -225,9 +230,9 @@ our %ConfigMenu = ( {name => "TarClientRestoreCmd", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, - {text => "Rsync Paths/Commands/Args", + {text => "CfgEdit_Title_Rsync_Paths_Commands_Args", visible => sub { return $_[0]->{XferMethod} eq "rsync"; } }, - {text => "Rsyncd Port/Args", + {text => "CfgEdit_Title_Rsyncd_Port_Args", visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } }, {name => "RsyncClientPath", visible => sub { return $_[0]->{XferMethod} eq "rsync"; } }, @@ -242,7 +247,7 @@ our %ConfigMenu = ( {name => "RsyncRestoreArgs", visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } }, - {text => "Archive Paths/Commands", + {text => "CfgEdit_Title_Archive_Paths_Commands", visible => sub { return $_[0]->{XferMethod} eq "archive"; } }, {name => "ArchiveClientCmd", visible => sub { return $_[0]->{XferMethod} eq "archive"; } }, @@ -250,27 +255,27 @@ our %ConfigMenu = ( ], }, schedule => { - text => "Schedule", + text => "CfgEdit_Title_Schedule", param => [ - {text => "Full Backups"}, + {text => "CfgEdit_Title_Full_Backups"}, {name => "FullPeriod"}, {name => "FullKeepCnt"}, {name => "FullKeepCntMin"}, {name => "FullAgeMax"}, - {text => "Incremental Backups"}, + {text => "CfgEdit_Title_Incremental_Backups"}, {name => "IncrPeriod"}, {name => "IncrKeepCnt"}, {name => "IncrKeepCntMin"}, {name => "IncrAgeMax"}, {name => "IncrFill"}, - {text => "Blackouts"}, + {text => "CfgEdit_Title_Blackouts"}, {name => "BlackoutBadPingLimit"}, {name => "BlackoutGoodCnt"}, {name => "BlackoutPeriods"}, - {text => "Other"}, + {text => "CfgEdit_Title_Other"}, {name => "PartialAgeMax"}, {name => "RestoreInfoKeepCnt"}, {name => "ArchiveInfoKeepCnt"}, @@ -278,9 +283,9 @@ our %ConfigMenu = ( ], }, backup => { - text => "Backup Settings", + text => "CfgEdit_Title_Backup_Settings", param => [ - {text => "Client Lookup"}, + {text => "CfgEdit_Title_Client_Lookup"}, {name => "ClientNameAlias"}, {name => "NmbLookupCmd"}, {name => "NmbLookupFindHostCmd"}, @@ -288,12 +293,12 @@ our %ConfigMenu = ( {name => "PingCmd"}, {name => "PingMaxMsec"}, - {text => "Other"}, + {text => "CfgEdit_Title_Other"}, {name => "ClientTimeout"}, {name => "MaxOldPerPCLogFiles"}, {name => "CompressLevel"}, - {text => "User Commands"}, + {text => "CfgEdit_Title_User_Commands"}, {name => "DumpPreUserCmd"}, {name => "DumpPostUserCmd"}, {name => "DumpPreShareCmd"}, @@ -304,6 +309,14 @@ our %ConfigMenu = ( {name => "ArchivePostUserCmd"}, ], }, + hosts => { + text => "CfgEdit_Title_Hosts", + param => [ + {text => "CfgEdit_Title_Hosts"}, + {name => "Hosts", + comment => "CfgEdit_Hosts_Comment"}, + ], + }, ); sub action @@ -318,14 +331,16 @@ sub action my $config_path = $host eq "" ? "$TopDir/conf/config.pl" : "$TopDir/pc/$host/config.pl"; - my $Privileged = CheckPermission(); - my $userHost = 1 if ( $Privileged && !$PrivAdmin && defined($host) ); + my $Privileged = CheckPermission($host) + && ($PrivAdmin || $Conf{CgiUserConfigEditEnable}); + my $userHost = 1 if ( defined($host) ); + my $debugText; if ( !$Privileged ) { ErrorExit(eval("qq{$Lang->{Only_privileged_users_can_edit_config_files}}")); } - if ( defined($In{menu}) || $In{editAction} eq "Save" ) { + if ( defined($In{menu}) || $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) { $errors = errorCheck(); if ( %$errors ) { # @@ -334,18 +349,22 @@ sub action $In{editAction} = ""; $In{newMenu} = ""; } + if ( (my $var = $In{overrideUncheck}) ne "" ) { + # + # a compound variable was unchecked; delete extra + # variables to make the shape the same. + # + #print STDERR Dumper(\%In); + foreach my $v ( keys(%In) ) { + next if ( $v !~ /^v_z_(\Q$var\E(_z_.*|$))/ ); + delete($In{$v}) if ( !defined($In{"orig_z_$1"}) ); + } + delete($In{"vflds.$var"}); + } + ($newConf, $override) = inputParse($bpc, $userHost); $override = undef if ( $host eq "" ); - # - # Copy all the orig_ input parameters - # - foreach my $var ( keys(%In) ) { - next if ( $var !~ /^orig_/ ); - $contentHidden .= < -EOF - } } else { # # First time: pick up the current config settings @@ -358,28 +377,14 @@ EOF $override->{$param} = 1; } } else { + my $hostInfo = $bpc->HostInfoRead(); $hostConf = {}; + $mainConf->{Hosts} = [map($hostInfo->{$_}, sort(keys(%$hostInfo)))]; } $newConf = { %$mainConf, %$hostConf }; - - # - # Emit all the original config settings - # - my $doneParam = {}; - foreach my $param ( keys(%ConfigMeta) ) { - next if ( $doneParam->{$param} ); - next if ( $userHost && !$bpc->{Conf}{CgiUserConfigEdit}{$param} ); - $contentHidden .= fieldHiddenBuild($ConfigMeta{$param}, - $param, - $mainConf->{$param}, - "orig", - ); - $doneParam->{$param} = 1; - } - } - if ( $In{editAction} ne "Save" && $In{newMenu} ne "" + if ( $In{editAction} ne $Lang->{CfgEdit_Button_Save} && $In{newMenu} ne "" && defined($ConfigMenu{$In{newMenu}}) ) { $menu = $In{newMenu}; } @@ -389,7 +394,8 @@ EOF # # For a non-admin user editing the host config, we need to # figure out which subsets of the menu tree will be visible, - # based on what is enabled + # based on what is enabled. Admin users can edit all the + # available per-host settings. # foreach my $m ( keys(%ConfigMenu) ) { my $enabled = 0; @@ -403,10 +409,12 @@ EOF $text = $n; $mask[$text] = 1; } else { - if ( $bpc->{Conf}{CgiUserConfigEdit}{$param} ) { + if ( $bpc->{Conf}{CgiUserConfigEdit}{$param} + || (defined($bpc->{Conf}{CgiUserConfigEdit}{$param}) + && $PrivAdmin) ) { $mask[$text] = 0 if ( $text >= 0 ); $mask[$n] = 0; - $enabled = 1; + $enabled ||= 1; } else { $mask[$n] = 1; } @@ -432,47 +440,48 @@ EOF my $groupText; foreach my $m ( keys(%ConfigMenu) ) { next if ( $menuDisable{$m}{top} ); - my $text = $ConfigMenu{$m}{text}; + my $text = eval("qq($Lang->{$ConfigMenu{$m}{text}})"); if ( $m eq $menu ) { $groupText .= <$text +$text EOF } else { $groupText .= <$text +$text EOF } } if ( $host eq "" ) { - $content .= <{CfgEdit_Header_Main})"); } else { - $content .= < -Note: Check Override if you want to modify a value specific to this host. -EOF + $content .= eval("qq($Lang->{CfgEdit_Header_Host})"); } my $saveDisplay = "block"; - $saveDisplay = "none" if ( !$In{modified} ); + $saveDisplay = "none" if ( !$In{modified} + || $In{editAction} eq $Lang->{CfgEdit_Button_Save} ); + # + # Add action and host to the URL so the nav bar link is + # highlighted + # + my $url = "$MyURL?action=editConfig"; + $url .= "&host=$host" if ( $host ne "" ); $content .= < $groupText -
+ + - -$contentHidden + - +$debugText EOF @@ -611,7 +624,7 @@ EOF if ( $In{deleteVar} ne "" && %$errors > 0 ) { my $matchAll = 1; foreach my $v ( keys(%$errors) ) { - if ( $v ne $In{deleteVar} && $v !~ /^\Q$In{deleteVar}_/ ) { + if ( $v ne $In{deleteVar} && $v !~ /^\Q$In{deleteVar}_z_/ ) { $matchAll = 0; last; } @@ -621,28 +634,81 @@ EOF my $isError = %$errors; - if ( !$isError && $In{editAction} eq "Save" ) { - my $mesg; + if ( !$isError && $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) { + my($mesg, $err); if ( $host ne "" ) { $hostConf = $bpc->ConfigDataRead($host) if ( !defined($hostConf) ); - $mesg = configDiffMesg($host, $hostConf, $newConf); - foreach my $param ( %$newConf ) { - $hostConf->{$param} = $newConf->{$param} - if ( $override->{param} ); + my %hostConf2 = %$hostConf; + foreach my $param ( keys(%$newConf) ) { + if ( $override->{$param} ) { + $hostConf->{$param} = $newConf->{$param} + } else { + delete($hostConf->{$param}); + } } - $bpc->ConfigDataWrite($host, $hostConf); + $mesg = configDiffMesg($host, \%hostConf2, $hostConf); + $err .= $bpc->ConfigDataWrite($host, $hostConf); } else { $mainConf = $bpc->ConfigDataRead() if ( !defined($mainConf) ); - $mesg = configDiffMesg(undef, $mainConf, $newConf); + + my $hostsSave = []; + my($hostsNew, $allHosts, $copyConf); + foreach my $entry ( @{$newConf->{Hosts}} ) { + next if ( $entry->{host} eq "" ); + $allHosts->{$entry->{host}} = 1; + $allHosts->{$1} = 1 if ( $entry->{host} =~ /(.+?)\s*=/ ); + } + foreach my $entry ( @{$newConf->{Hosts}} ) { + next if ( $entry->{host} eq "" + || defined($hostsNew->{$entry->{host}}) ); + if ( $entry->{host} =~ /(.+?)\s*=\s*(.+)/ ) { + if ( defined($allHosts->{$2}) ) { + $entry->{host} = $1; + $copyConf->{$1} = $2; + } else { + my $fullHost = $entry->{host}; + my $copyHost = $2; + $err .= eval("qq($Lang->{CfgEdit_Error_Copy_host_does_not_exist})"); + } + } + push(@$hostsSave, $entry); + $hostsNew->{$entry->{host}} = $entry; + } + ($mesg, my $hostChange) = hostsDiffMesg($hostsNew); + $bpc->HostInfoWrite($hostsNew) if ( $hostChange ); + foreach my $host ( keys(%$copyConf) ) { + my $confData = $bpc->ConfigDataRead($copyConf->{$host}); + my $fromHost = $copyConf->{$host}; + $err .= $bpc->ConfigDataWrite($host, $confData); + $mesg .= eval("qq($Lang->{CfgEdit_Log_Copy_host_config})"); + } + + delete($newConf->{Hosts}); + $mesg .= configDiffMesg(undef, $mainConf, $newConf); $mainConf = { %$mainConf, %$newConf }; - $bpc->ConfigDataWrite(undef, $mainConf); + $err .= $bpc->ConfigDataWrite(undef, $mainConf); + $newConf->{Hosts} = $hostsSave; } + if ( defined($err) ) { + $content .= <$err +EOF + } + $bpc->ServerConnect(); if ( $mesg ne "" ) { - $bpc->ServerConnect(); + (my $mesgBR = $mesg) =~ s/\n/
\n/g; + $content .= <$mesgBR +EOF foreach my $str ( split(/\n/, $mesg) ) { - $bpc->ServerMesg($str); + $bpc->ServerMesg("log $str") if ( $str ne "" ); } } + # + # Tell the server to reload, unless we only changed + # a client config + # + $bpc->ServerMesg("server reload") if ( $host eq "" ); } my @mask = @{$menuDisable{$menu}{mask} || []}; @@ -658,7 +724,8 @@ EOF next; } - if ( defined(my $text = $paramInfo->{text}) ) { + if ( defined($paramInfo->{text}) ) { + my $text = eval("qq($Lang->{$paramInfo->{text}})"); $content .= <$text EOF @@ -669,23 +736,30 @@ EOF # TODO: get parameter documentation # my $comment = ""; - $comment =~ s/\'//g; - $comment =~ s/\"//g; - $comment =~ s/\n/ /g; + #$comment =~ s/\'//g; + #$comment =~ s/\"//g; + #$comment =~ s/\n/ /g; $doneParam->{$param} = 1; $content .= fieldEditBuild($ConfigMeta{$param}, - $param, - $newConf->{$param}, - $errors, - 0, - $comment, - $isError, - $paramInfo->{onchangeSubmit}, - defined($override) ? $param : undef, - defined($override) ? $override->{$param} : undef + $param, + $newConf->{$param}, + $errors, + 0, + $comment, + $isError, + $paramInfo->{onchangeSubmit}, + defined($override) ? $param : undef, + defined($override) ? $override->{$param} : undef ); + if ( defined($paramInfo->{comment}) ) { + my $topDir = $bpc->TopDir; + my $text = eval("qq($Lang->{$paramInfo->{comment}})"); + $content .= <$text +EOF + } } # @@ -693,7 +767,7 @@ EOF # foreach my $param ( sort(keys(%$errors)) ) { $content .= <$errors->{$param} +$errors->{$param} EOF delete($errors->{$param}); } @@ -707,7 +781,10 @@ EOF # foreach my $param ( keys(%ConfigMeta) ) { next if ( $doneParam->{$param} ); - next if ( $userHost && !$bpc->{Conf}{CgiUserConfigEdit}{$param} ); + next if ( $userHost + && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param}) + || (!$PrivAdmin + && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) ); $content .= fieldHiddenBuild($ConfigMeta{$param}, $param, $newConf->{$param}, @@ -721,7 +798,60 @@ EOF $doneParam->{$param} = 1; } + if ( defined($In{menu}) || $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) { + if ( $In{editAction} eq $Lang->{CfgEdit_Button_Save} + && !$userHost ) { + # + # Emit the new settings as orig_z_ parameters + # + $doneParam = {}; + foreach my $param ( keys(%ConfigMeta) ) { + next if ( $doneParam->{$param} ); + next if ( $userHost + && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param}) + || (!$PrivAdmin + && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) ); + $contentHidden .= fieldHiddenBuild($ConfigMeta{$param}, + $param, + $newConf->{$param}, + "orig", + ); + $doneParam->{$param} = 1; + $In{modified} = 0; + } + } else { + # + # Just switching menus: copy all the orig_z_ input parameters + # + foreach my $var ( keys(%In) ) { + next if ( $var !~ /^orig_z_/ ); + $contentHidden .= < +EOF + } + } + } else { + # + # First time: emit all the original config settings + # + $doneParam = {}; + foreach my $param ( keys(%ConfigMeta) ) { + next if ( $doneParam->{$param} ); + next if ( $userHost + && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param}) + || (!$PrivAdmin + && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) ); + $contentHidden .= fieldHiddenBuild($ConfigMeta{$param}, + $param, + $mainConf->{$param}, + "orig", + ); + $doneParam->{$param} = 1; + } + } + $content .= < @@ -743,14 +873,16 @@ sub fieldHiddenBuild $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" ); for ( my $i = 0 ; $i < @$varValue ; $i++ ) { - $content .= fieldHiddenBuild($type->{child}, "${varName}_$i", + $content .= fieldHiddenBuild($type->{child}, "${varName}_z_$i", $varValue->[$i], $prefix); } - } elsif ( $type->{type} eq "hash" ) { + } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) { $varValue = {} if ( ref($varValue) ne "HASH" ); my(@order, $childType); - if ( defined($type->{child}) ) { + if ( defined($type->{order}) ) { + @order = @{$type->{order}}; + } elsif ( defined($type->{child}) ) { @order = sort(keys(%{$type->{child}})); } else { @order = sort(keys(%$varValue)); @@ -769,18 +901,18 @@ sub fieldHiddenBuild EOF } - $content .= fieldHiddenBuild($childType, "${varName}_$fld", + $content .= fieldHiddenBuild($childType, "${varName}_z_$fld", $varValue->{$fld}, $prefix); } } elsif ( $type->{type} eq "shortlist" ) { $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" ); $varValue = join(", ", @$varValue); $content .= < + EOF } else { $content .= < + EOF } return $content; @@ -795,33 +927,42 @@ sub fieldEditBuild my $size = 50 - 10 * $level; $type = { type => $type } if ( ref($type) ne "HASH" ); + $size = $type->{size} if ( defined($type->{size}) ); + + # + # These fragments allow inline conent to be turned on and off + # + # + # $varName + # + if ( $level == 0 ) { + my $lcVarName = lc($varName); $content .= <$comment -$varName +$varName EOF if ( defined($overrideVar) ) { my $override_checked = ""; - if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_/ - || !$isError && $In{insertVar} =~ /^\Q${varName}\E(_|$)/ - || !$isError && $In{addVar} =~ /^\Q${varName}\E(_|$)/ ) { + if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_z_/ + || !$isError && $In{insertVar} =~ /^\Q${varName}\E(_z_|$)/ + || !$isError && $In{addVar} =~ /^\Q${varName}\E(_z_|$)/ ) { $overrideSet = 1; } if ( $overrideSet ) { $override_checked = "checked"; } $content .= <\ Override +
\ ${EscHTML($Lang->{CfgEdit_Button_Override})} EOF } $content .= "\n"; } - $content .= "\n"; if ( $type->{type} eq "list" ) { + $content .= "\n"; $varValue = [] if ( !defined($varValue) ); $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" ); - if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_\E(\d+)$/ + if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_z_\E(\d+)$/ && $1 < @$varValue ) { # # User deleted entry in this array @@ -829,7 +970,7 @@ EOF splice(@$varValue, $1, 1) if ( @$varValue > 1 || $type->{emptyOk} ); $In{deleteVar} = ""; } - if ( !$isError && $In{insertVar} =~ /^\Q${varName}_\E(\d+)$/ + if ( !$isError && $In{insertVar} =~ /^\Q${varName}_z_\E(\d+)$/ && $1 < @$varValue ) { # # User inserted entry in this array @@ -847,36 +988,71 @@ EOF } $content .= "\n"; - for ( my $i = 0 ; $i < @$varValue ; $i++ ) { - $content .= "\n"; - $content .= fieldEditBuild($type->{child}, "${varName}_$i", - $varValue->[$i], $errors, $level + 1, undef, - $isError, $onchangeSubmit, - $overrideVar, $overrideSet); + if ( ref($type) eq "HASH" && ref($type->{child}) eq "HASH" + && $type->{child}{type} eq "horizHash" ) { + my @order; + if ( defined($type->{child}{order}) ) { + @order = @{$type->{child}{order}}; + } else { + @order = sort(keys(%{$type->{child}{child}})); + } + $content .= "\n"; + for ( my $i = 0 ; $i < @order ; $i++ ) { + $content .= "\n"; + } $content .= "\n"; + for ( my $i = 0 ; $i < @$varValue ; $i++ ) { + if ( @$varValue > 1 || $type->{emptyOk} ) { + $content .= < + + +EOF + } + $content .= fieldEditBuild($type->{child}, "${varName}_z_$i", + $varValue->[$i], $errors, $level + 1, undef, + $isError, $onchangeSubmit, + $overrideVar, $overrideSet); + $content .= "\n"; + } + } else { + for ( my $i = 0 ; $i < @$varValue ; $i++ ) { + $content .= <\n"; + $content .= fieldEditBuild($type->{child}, "${varName}_z_$i", + $varValue->[$i], $errors, $level + 1, undef, + $isError, $onchangeSubmit, + $overrideVar, $overrideSet); + $content .= "\n"; + } } $content .= <
\n"; - if ( @$varValue > 1 || $type->{emptyOk} ) { - $content .= < - -EOF - } - $content .= "
$order[$i]
+ +EOF + if ( @$varValue > 1 || $type->{emptyOk} ) { + $content .= < +EOF + } + $content .= "
EOF + $content .= "\n"; } elsif ( $type->{type} eq "hash" ) { + $content .= "\n"; $content .= "\n"; $varValue = {} if ( ref($varValue) ne "HASH" ); if ( !$isError && !$type->{noKeyEdit} - && $In{deleteVar} =~ /^\Q${varName}_\E(\w+)$/ ) { + && $In{deleteVar} !~ /^\Q${varName}_z_\E.*_z_/ + && $In{deleteVar} =~ /^\Q${varName}_z_\E(\w+)$/ ) { # - # User deleted entry in this array + # User deleted entry in this hash # delete($varValue->{$1}) if ( keys(%$varValue) > 1 || $type->{emptyOk} ); @@ -887,13 +1063,15 @@ EOF # # User added entry to this array # - $varValue->{$In{addVarKey}} = "" - if ( !defined($varValue->{$In{addVarKey}}) ); + $varValue->{$In{"addVarKey_$varName"}} = "" + if ( !defined($varValue->{$In{"addVarKey_$varName"}}) ); $In{addVar} = ""; } my(@order, $childType); - if ( defined($type->{child}) ) { + if ( defined($type->{order}) ) { + @order = @{$type->{order}}; + } elsif ( defined($type->{child}) ) { @order = sort(keys(%{$type->{child}})); } else { @order = sort(keys(%$varValue)); @@ -906,8 +1084,8 @@ EOF if ( !$type->{noKeyEdit} && (keys(%$varValue) > 1 || $type->{emptyOk}) ) { $content .= < + EOF } if ( defined($type->{child}) ) { @@ -923,7 +1101,7 @@ EOF EOF } $content .= "\n"; - $content .= fieldEditBuild($childType, "${varName}_$fld", + $content .= fieldEditBuild($childType, "${varName}_z_$fld", $varValue->{$fld}, $errors, $level + 1, undef, $isError, $onchangeSubmit, $overrideVar, $overrideSet); @@ -933,24 +1111,56 @@ EOF if ( !$type->{noKeyEdit} ) { $content .= < EOF } $content .= "
-New key: - +New key: +
\n"; + $content .= "\n"; + } elsif ( $type->{type} eq "horizHash" ) { + $varValue = {} if ( ref($varValue) ne "HASH" ); + my(@order, $childType); + + if ( defined($type->{order}) ) { + @order = @{$type->{order}}; + } elsif ( defined($type->{child}) ) { + @order = sort(keys(%{$type->{child}})); + } else { + @order = sort(keys(%$varValue)); + } + + foreach my $fld ( @order ) { + if ( defined($type->{child}) ) { + $childType = $type->{child}{$fld}; + } else { + $childType = $type->{childType}; + # + # emit list of fields since they are user-defined + # rather than hard-coded + # + $content .= < +EOF + } + $content .= fieldEditBuild($childType, "${varName}_z_$fld", + $varValue->{$fld}, $errors, $level + 1, undef, + $isError, $onchangeSubmit, + $overrideVar, $overrideSet); + } } else { + $content .= "\n"; if ( $isError ) { # # If there was an error, we use the original post values # in %In, rather than the parsed values in $varValue. # This is so that the user's erroneous input is preserved. # - $varValue = $In{"v_$varName"} if ( defined($In{"v_$varName"}) ); + $varValue = $In{"v_z_$varName"} if ( defined($In{"v_z_$varName"}) ); } if ( defined($errors->{$varName}) ) { $content .= <{$varName}
+$errors->{$varName}
EOF delete($errors->{$varName}); } @@ -969,6 +1179,7 @@ EOF if ( $varValue !~ /\n/ && ($type->{type} eq "integer" || $type->{type} eq "string" + || $type->{type} eq "execPath" || $type->{type} eq "shortlist" || $type->{type} eq "float") ) { # simple input box @@ -977,17 +1188,17 @@ EOF $varValue = join(", ", @$varValue); } $content .= < + EOF } elsif ( $type->{type} eq "boolean" ) { # checkbox my $checked = "checked" if ( $varValue ); $content .= < + EOF } elsif ( $type->{type} eq "select" ) { $content .= < + EOF } + $content .= "\n"; } - $content .= "\n"; return $content; } @@ -1025,13 +1236,15 @@ sub fieldErrorCheck if ( $type->{type} eq "list" ) { for ( my $i = 0 ; ; $i++ ) { - last if ( fieldErrorCheck($type->{child}, "${varName}_$i", $errors) ); + last if ( fieldErrorCheck($type->{child}, "${varName}_z_$i", $errors) ); } - } elsif ( $type->{type} eq "hash" ) { + } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) { my(@order, $childType); my $ret; - if ( defined($type->{child}) ) { + if ( defined($type->{order}) ) { + @order = @{$type->{order}}; + } elsif ( defined($type->{child}) ) { @order = sort(keys(%{$type->{child}})); } else { @order = split(/\0/, $In{"vflds.$varName"}); @@ -1042,51 +1255,58 @@ sub fieldErrorCheck } else { $childType = $type->{childType}; } - $ret ||= fieldErrorCheck($childType, "${varName}_$fld", $errors); + $ret ||= fieldErrorCheck($childType, "${varName}_z_$fld", $errors); } return $ret; } else { - return 1 if ( !exists($In{"v_$varName"}) ); + $In{"v_z_$varName"} = "0" if ( $type->{type} eq "boolean" + && $In{"v_z_$varName"} eq "" ); + + return 1 if ( !exists($In{"v_z_$varName"}) ); + + (my $var = $varName) =~ s/_z_/./g; if ( $type->{type} eq "integer" || $type->{type} eq "boolean" ) { - if ( $In{"v_$varName"} !~ /^-?\d+\s*$/s - && $In{"v_$varName"} ne "" ) { - $errors->{$varName} = "Error: $varName must be an integer"; + if ( $In{"v_z_$varName"} !~ /^-?\d+\s*$/s + && $In{"v_z_$varName"} ne "" ) { + $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_an_integer}}"); } } elsif ( $type->{type} eq "float" ) { - if ( $In{"v_$varName"} !~ /^-?\d*(\.\d*)?\s*$/s - && $In{"v_$varName"} ne "" ) { + if ( $In{"v_z_$varName"} !~ /^-?\d*(\.\d*)?\s*$/s + && $In{"v_z_$varName"} ne "" ) { $errors->{$varName} - = "Error: $varName must be a real-valued number"; + = eval("qq{$Lang->{CfgEdit_Error__must_be_real_valued_number}}"); } } elsif ( $type->{type} eq "shortlist" ) { - my @vals = split(/[,\s]+/, $In{"v_$varName"}); + my @vals = split(/[,\s]+/, $In{"v_z_$varName"}); for ( my $i = 0 ; $i < @vals ; $i++ ) { if ( $type->{child} eq "integer" && $vals[$i] !~ /^-?\d+\s*$/s && $vals[$i] ne "" ) { my $k = $i + 1; - $errors->{$varName} = "Error: $varName entry $k must" - . " be an integer"; + $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_an_integer}}"); } elsif ( $type->{child} eq "float" && $vals[$i] !~ /^-?\d*(\.\d*)?\s*$/s && $vals[$i] ne "" ) { my $k = $i + 1; - $errors->{$varName} = "Error: $varName entry $k must" - . " be a real-valued number"; + $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_real_valued_number}}"); } } } elsif ( $type->{type} eq "select" ) { my $match = 0; foreach my $option ( @{$type->{values}} ) { - if ( $In{"v_$varName"} eq $option ) { + if ( $In{"v_z_$varName"} eq $option ) { $match = 1; last; } } - $errors->{$varName} = "Error: $varName must be a valid option" + $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_valid_option}}") if ( !$match ); + } elsif ( $type->{type} eq "execPath" ) { + if ( $In{"v_z_$varName"} ne "" && !-x $In{"v_z_$varName"} ) { + $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_executable_program}}"); + } } else { # # $type->{type} eq "string": no error checking @@ -1104,11 +1324,14 @@ sub inputParse foreach my $param ( keys(%ConfigMeta) ) { my $value; - next if ( $userHost && !$bpc->{Conf}{CgiUserConfigEdit}{$param} ); + next if ( $userHost + && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param}) + || (!$PrivAdmin + && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) ); fieldInputParse($ConfigMeta{$param}, $param, \$value); $conf->{$param} = $value; $override->{$param} = $In{"override_$param"}; -} + } return ($conf, $override); } @@ -1122,16 +1345,18 @@ sub fieldInputParse $$value = []; for ( my $i = 0 ; ; $i++ ) { my $val; - last if ( fieldInputParse($type->{child}, "${varName}_$i", \$val) ); + last if ( fieldInputParse($type->{child}, "${varName}_z_$i", \$val) ); push(@$$value, $val); } $$value = undef if ( $type->{undefIfEmpty} && @$$value == 0 ); - } elsif ( $type->{type} eq "hash" ) { + } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) { my(@order, $childType); my $ret; $$value = {}; - if ( defined($type->{child}) ) { + if ( defined($type->{order}) ) { + @order = @{$type->{order}}; + } elsif ( defined($type->{child}) ) { @order = sort(keys(%{$type->{child}})); } else { @order = split(/\0/, $In{"vflds.$varName"}); @@ -1144,24 +1369,24 @@ sub fieldInputParse } else { $childType = $type->{childType}; } - $ret ||= fieldInputParse($childType, "${varName}_$fld", \$val); + $ret ||= fieldInputParse($childType, "${varName}_z_$fld", \$val); last if ( $ret ); $$value->{$fld} = $val; } return $ret; } else { if ( $type->{type} eq "boolean" ) { - $$value = 0 + $In{"v_$varName"}; - } elsif ( !exists($In{"v_$varName"}) ) { + $$value = 0 + $In{"v_z_$varName"}; + } elsif ( !exists($In{"v_z_$varName"}) ) { return 1; } if ( $type->{type} eq "integer" ) { - $$value = 0 + $In{"v_$varName"}; + $$value = 0 + $In{"v_z_$varName"}; } elsif ( $type->{type} eq "float" ) { - $$value = 0 + $In{"v_$varName"}; + $$value = 0 + $In{"v_z_$varName"}; } elsif ( $type->{type} eq "shortlist" ) { - $$value = [split(/[,\s]+/, $In{"v_$varName"})]; + $$value = [split(/[,\s]+/, $In{"v_z_$varName"})]; if ( $type->{child} eq "float" || $type->{child} eq "integer" || $type->{child} eq "boolean" ) { @@ -1170,7 +1395,8 @@ sub fieldInputParse } } } else { - $$value = $In{"v_$varName"}; + $$value = $In{"v_z_$varName"}; + $$value =~ s/\r\n/\n/g; } $$value = undef if ( $type->{undefIfEmpty} && $$value eq "" ); } @@ -1193,14 +1419,16 @@ sub configDiffMesg if ( !exists($oldConf->{$p}) && !exists($newConf->{$p}) ) { next; } elsif ( exists($oldConf->{$p}) && !exists($newConf->{$p}) ) { - $mesg .= "log Deleted $p from $conf\n"; + $mesg .= eval("qq($Lang->{CfgEdit_Log_Delete_param})"); } elsif ( !exists($oldConf->{$p}) && exists($newConf->{$p}) ) { my $dump = Data::Dumper->new([$newConf->{$p}]); $dump->Indent(0); $dump->Sortkeys(1); $dump->Terse(1); my $value = $dump->Dump; - $mesg .= "log Added $p to $conf, set to $value\n"; + $value =~ s/\n/\\n/g; + $value =~ s/\r/\\r/g; + $mesg .= eval("qq($Lang->{CfgEdit_Log_Add_param_value})"); } else { my $dump = Data::Dumper->new([$newConf->{$p}]); $dump->Indent(0); @@ -1218,11 +1446,53 @@ sub configDiffMesg $dump->Terse(1); my $valueOld = $dump->Dump; - $mesg .= "log Changed $p in $conf to $valueNew from $valueOld\n" - if ( $valueOld ne $valueNew ); + (my $valueNew2 = $valueNew) =~ s/['\n\r]//g; + (my $valueOld2 = $valueOld) =~ s/['\n\r]//g; + $valueNew =~ s/\n/\\n/g; + $valueOld =~ s/\n/\\n/g; + $valueNew =~ s/\r/\\r/g; + $valueOld =~ s/\r/\\r/g; + $mesg .= eval("qq($Lang->{CfgEdit_Log_Change_param_value})") + if ( $valueOld2 ne $valueNew2 ); } } return $mesg; } +sub hostsDiffMesg +{ + my($hostsNew) = @_; + my $hostsOld = $bpc->HostInfoRead(); + my($mesg, $hostChange); + + foreach my $host ( keys(%$hostsOld) ) { + if ( !defined($hostsNew->{$host}) ) { + $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Delete})"); + $hostChange++; + next; + } + foreach my $key ( keys(%{$hostsNew->{$host}}) ) { + next if ( $hostsNew->{$host}{$key} eq $hostsOld->{$host}{$key} ); + my $valueOld = $hostsOld->{$host}{$key}; + my $valueNew = $hostsNew->{$host}{$key}; + $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Change})"); + $hostChange++; + } + } + + foreach my $host ( keys(%$hostsNew) ) { + next if ( defined($hostsOld->{$host}) ); + my $dump = Data::Dumper->new([$hostsNew->{$host}]); + $dump->Indent(0); + $dump->Sortkeys(1); + $dump->Terse(1); + my $value = $dump->Dump; + $value =~ s/\n/\\n/g; + $value =~ s/\r/\\r/g; + $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Add})"); + $hostChange++; + } + return ($mesg, $hostChange); +} + 1; diff --git a/lib/BackupPC/CGI/Lib.pm b/lib/BackupPC/CGI/Lib.pm index 70a7e49..dcabdb9 100644 --- a/lib/BackupPC/CGI/Lib.pm +++ b/lib/BackupPC/CGI/Lib.pm @@ -95,7 +95,7 @@ sub NewRequest if ( !defined($bpc) ) { ErrorExit($Lang->{BackupPC__Lib__new_failed__check_apache_error_log}) - if ( !($bpc = BackupPC::Lib->new(undef, undef, 1)) ); + if ( !($bpc = BackupPC::Lib->new(undef, undef, undef, 1)) ); $TopDir = $bpc->TopDir(); $BinDir = $bpc->BinDir(); %Conf = $bpc->Conf(); @@ -220,7 +220,7 @@ sub EscHTML $s =~ s/\"/"/g; $s =~ s/>/>/g; $s =~ s/ "", name => $Lang->{Status}}, - { link => "?action=adminOpts", name => $Lang->{Admin_Options}, - priv => 1}, - { link => "?action=editConfig", name => "Edit Config", - priv => 1}, - { link => "?action=editHosts", name => "Edit Hosts", - priv => 1}, - { link => "?action=summary", name => $Lang->{PC_Summary}}, - { link => "?action=view&type=LOG", name => $Lang->{LOG_file}, - priv => 1}, - { link => "?action=LOGlist", name => $Lang->{Old_LOGs}, - priv => 1}, - { link => "?action=emailSummary", name => $Lang->{Email_summary}, - priv => 1}, - { link => "?action=view&type=config", name => $Lang->{Config_file}, - priv => 1}, - { link => "?action=view&type=hosts", name => $Lang->{Hosts_file}, - priv => 1}, - { link => "?action=queue", name => $Lang->{Current_queues}, - priv => 1}, + { link => "", name => $Lang->{Status}}, + { link => "?action=adminOpts", name => $Lang->{Admin_Options}, + priv => 1}, + { link => "?action=editConfig", name => $Lang->{CfgEdit_Edit_Config}, + priv => 1}, + { link => "?action=editConfig&newMenu=hosts", + name => $Lang->{CfgEdit_Edit_Hosts}, + priv => 1}, + { link => "?action=summary", name => $Lang->{PC_Summary}}, + { link => "?action=view&type=LOG", name => $Lang->{LOG_file}, + priv => 1}, + { link => "?action=LOGlist", name => $Lang->{Old_LOGs}, + priv => 1}, + { link => "?action=emailSummary", name => $Lang->{Email_summary}, + priv => 1}, + { link => "?action=queue", name => $Lang->{Current_queues}, + priv => 1}, @{$Conf{CgiNavBarLinks} || []}, ); my $host = $In{host}; - print $Cgi->header(); + print $Cgi->header(-charset => "utf-8"); print < @@ -469,11 +466,14 @@ EOF $Lang->{Last_bad_XferLOG_errors_only}, " class=\"navbar\""); } - if ( -f "$TopDir/pc/$host/config.pl" + if ( $Conf{CgiUserConfigEditEnable} || $PrivAdmin ) { + NavLink("?action=editConfig&host=${EscURI($host)}", + $Lang->{CfgEdit_Edit_Config}, " class=\"navbar\""); + } elsif ( -f "$TopDir/pc/$host/config.pl" || ($host ne "config" && -f "$TopDir/conf/$host.pl") ) { - NavLink("?action=editConfig&host=${EscURI($host)}", - "Edit Config", " class=\"navbar\""); - } + NavLink("?action=view&type=config&host=${EscURI($host)}", + $Lang->{Config_file}, " class=\"navbar\""); + } print "\n"; } print("
\n$content\n"); diff --git a/lib/BackupPC/CGI/StartStopBackup.pm b/lib/BackupPC/CGI/StartStopBackup.pm index e852c99..a07efeb 100644 --- a/lib/BackupPC/CGI/StartStopBackup.pm +++ b/lib/BackupPC/CGI/StartStopBackup.pm @@ -76,7 +76,13 @@ sub action Trailer(); } else { if ( $start ) { - my $ipAddr = ConfirmIPAddress($host); + $bpc->ConfigRead($host); + %Conf = $bpc->Conf(); + + my $checkHost = $host; + $checkHost = $Conf{ClientNameAlias} + if ( $Conf{ClientNameAlias} ne "" ); + my $ipAddr = ConfirmIPAddress($checkHost); my $content = eval("qq{$Lang->{Are_you_sure_start}}"); Header(eval("qq{$Lang->{BackupPC__Start_Backup_Confirm_on__host}}"),$content); } else { diff --git a/lib/BackupPC/Config/Meta.pm b/lib/BackupPC/Config/Meta.pm index 7995169..3d651be 100644 --- a/lib/BackupPC/Config/Meta.pm +++ b/lib/BackupPC/Config/Meta.pm @@ -81,16 +81,16 @@ use vars qw(%ConfigMeta); BackupPCNightlyPeriod => "integer", MaxOldLogFiles => "integer", - SshPath => {type => "string", undefIfEmpty => 1}, - NmbLookupPath => {type => "string", undefIfEmpty => 1}, - PingPath => {type => "string", undefIfEmpty => 1}, - DfPath => {type => "string", undefIfEmpty => 1}, + SshPath => {type => "execPath", undefIfEmpty => 1}, + NmbLookupPath => {type => "execPath", undefIfEmpty => 1}, + PingPath => {type => "execPath", undefIfEmpty => 1}, + DfPath => {type => "execPath", undefIfEmpty => 1}, DfCmd => "string", - SplitPath => {type => "string", undefIfEmpty => 1}, - ParPath => {type => "string", undefIfEmpty => 1}, - CatPath => {type => "string", undefIfEmpty => 1}, - GzipPath => {type => "string", undefIfEmpty => 1}, - Bzip2Path => {type => "string", undefIfEmpty => 1}, + SplitPath => {type => "execPath", undefIfEmpty => 1}, + ParPath => {type => "execPath", undefIfEmpty => 1}, + CatPath => {type => "execPath", undefIfEmpty => 1}, + GzipPath => {type => "execPath", undefIfEmpty => 1}, + Bzip2Path => {type => "execPath", undefIfEmpty => 1}, DfMaxUsagePct => "float", TrashCleanSleepSec => "integer", DHCPAddressRanges => { @@ -99,6 +99,7 @@ use vars qw(%ConfigMeta); child => { type => "hash", noKeyEdit => 1, + order => [qw(ipAddrBase first last)], child => { ipAddrBase => "string", first => "integer", @@ -109,7 +110,10 @@ use vars qw(%ConfigMeta); BackupPCUser => "string", CgiDir => "string", InstallDir => "string", - BackupPCUserVerify => "integer", + TopDir => "string", + ConfDir => "string", + LogDir => "string", + BackupPCUserVerify => "boolean", HardLinkMax => "integer", PerlModuleLoad => { type => "list", @@ -136,21 +140,27 @@ use vars qw(%ConfigMeta); IncrKeepCntMin => "integer", IncrAgeMax => "float", PartialAgeMax => "float", - IncrFill => "integer", + IncrFill => "boolean", RestoreInfoKeepCnt => "integer", ArchiveInfoKeepCnt => "integer", BackupFilesOnly => { - type => "list", - emptyOk => 1, - undefIfEmpty => 1, - child => "string", + type => "hash", + emptyOk => 1, + childType => { + type => "list", + emptyOk => 1, + child => "string", + }, }, BackupFilesExclude => { - type => "list", - emptyOk => 1, - undefIfEmpty => 1, - child => "string", + type => "hash", + emptyOk => 1, + childType => { + type => "list", + emptyOk => 1, + child => "string", + }, }, BlackoutBadPingLimit => "integer", @@ -172,7 +182,7 @@ use vars qw(%ConfigMeta); }, }, - BackupZeroFilesIsFatal => "integer", + BackupZeroFilesIsFatal => "boolean", ###################################################################### # How to backup a client @@ -183,6 +193,8 @@ use vars qw(%ConfigMeta); }, XferLogLevel => "integer", + ClientCharset => "string", + SmbShareName => { type => "list", child => "string", @@ -214,7 +226,7 @@ use vars qw(%ConfigMeta); RsyncdClientPort => "integer", RsyncdPasswd => "string", - RsyncdAuthRequired => "integer", + RsyncdAuthRequired => "boolean", RsyncCsumCacheVerifyProb => "float", RsyncArgs => { @@ -233,14 +245,14 @@ use vars qw(%ConfigMeta); type => "select", values => [qw(none bzip2 gzip)], }, - ArchivePar => "integer", + ArchivePar => "boolean", ArchiveSplit => "float", ArchiveClientCmd => "string", NmbLookupCmd => "string", NmbLookupFindHostCmd => "string", - FixedIPNetBiosNameCheck => "integer", + FixedIPNetBiosNameCheck => "boolean", PingCmd => "string", PingMaxMsec => "float", @@ -265,7 +277,7 @@ use vars qw(%ConfigMeta); # Email reminders, status and messages # (can be overridden in the per-PC config.pl) ###################################################################### - SendmailPath => {type => "string", undefIfEmpty => 1}, + SendmailPath => {type => "execPath", undefIfEmpty => 1}, EMailNotifyMinDays => "float", EMailFromUserName => "string", EMailAdminUserName => "string", @@ -278,6 +290,7 @@ use vars qw(%ConfigMeta); EMailNotifyOldOutlookDays => "float", EMailOutlookBackupSubj => {type => "string", undefIfEmpty => 1}, EMailOutlookBackupMesg => {type => "bigstring", undefIfEmpty => 1}, + EMailHeaders => {type => "bigstring", undefIfEmpty => 1}, ###################################################################### # CGI user interface configuration settings @@ -285,12 +298,15 @@ use vars qw(%ConfigMeta); CgiAdminUserGroup => "string", CgiAdminUsers => "string", CgiURL => "string", - Language => "string", + Language => { + type => "select", + values => [qw(de en es fr it nl pt_br)], + }, CgiUserHomePageCheck => "string", CgiUserUrlCreate => "string", - CgiDateFormatMMDD => "integer", - CgiNavBarAdminAllHosts => "integer", - CgiSearchBoxEnable => "integer", + CgiDateFormatMMDD => "boolean", + CgiNavBarAdminAllHosts => "boolean", + CgiSearchBoxEnable => "boolean", CgiNavBarLinks => { type => "list", emptyOk => 1, @@ -324,6 +340,7 @@ use vars qw(%ConfigMeta); }, CgiImageDirURL => "string", CgiCSSFile => "string", + CgiUserConfigEditEnable => "boolean", CgiUserConfigEdit => { type => "hash", noKeyEdit => 1, @@ -348,6 +365,7 @@ use vars qw(%ConfigMeta); BackupZeroFilesIsFatal => "boolean", XferMethod => "boolean", XferLogLevel => "boolean", + ClientCharset => "boolean", SmbShareName => "boolean", SmbShareUserName => "boolean", SmbSharePasswd => "boolean", @@ -385,6 +403,25 @@ use vars qw(%ConfigMeta); EMailOutlookBackupMesg => "boolean", }, }, + + ###################################################################### + # Fake config setting for editing the hosts + ###################################################################### + Hosts => { + type => "list", + emptyOk => 1, + child => { + type => "horizHash", + order => [qw(host dhcp user moreUsers)], + noKeyEdit => 1, + child => { + host => { type => "string", size => 20 }, + dhcp => { type => "boolean" }, + user => { type => "string", size => 20 }, + moreUsers => { type => "string", size => 30 }, + }, + }, + }, ); 1; diff --git a/lib/BackupPC/FileZIO.pm b/lib/BackupPC/FileZIO.pm index dc6e480..a5359a0 100644 --- a/lib/BackupPC/FileZIO.pm +++ b/lib/BackupPC/FileZIO.pm @@ -29,7 +29,7 @@ # #======================================================================== # -# Version 2.1.0, released 20 Jun 2004. +# Version 2.1.1, released 13 Mar 2005. # # See http://backuppc.sourceforge.net. # diff --git a/lib/BackupPC/Lang/en.pm b/lib/BackupPC/Lang/en.pm index c3fdb68..7b351c3 100644 --- a/lib/BackupPC/Lang/en.pm +++ b/lib/BackupPC/Lang/en.pm @@ -1303,4 +1303,113 @@ EOF $Lang{howLong_not_been_backed_up} = "not been backed up successfully"; $Lang{howLong_not_been_backed_up_for_days_days} = "not been backed up for \$days days"; +####################################################################### +# Configuration editor strings +####################################################################### + +$Lang{Only_privileged_users_can_edit_config_files} = "Only privileged users can edit configuation settings."; +$Lang{CfgEdit_Edit_Config} = "Edit Config"; +$Lang{CfgEdit_Edit_Hosts} = "Edit Hosts"; + +$Lang{CfgEdit_Title_Server} = "Server"; +$Lang{CfgEdit_Title_General_Parameters} = "General Parameters"; +$Lang{CfgEdit_Title_Wakeup_Schedule} = "Wakeup Schedule"; +$Lang{CfgEdit_Title_Concurrent_Jobs} = "Concurrent Jobs"; +$Lang{CfgEdit_Title_Pool_Filesystem_Limits} = "Pool Filesystem Limits"; +$Lang{CfgEdit_Title_Other_Parameters} = "Other Parameters"; +$Lang{CfgEdit_Title_Remote_Apache_Settings} = "Remote Apache Settings"; +$Lang{CfgEdit_Title_Program_Paths} = "Program Paths"; +$Lang{CfgEdit_Title_Install_Paths} = "Install Paths"; +$Lang{CfgEdit_Title_Email} = "Email"; +$Lang{CfgEdit_Title_Email_settings} = "Email settings"; +$Lang{CfgEdit_Title_Email_User_Messages} = "Email User Messages"; +$Lang{CfgEdit_Title_CGI} = "CGI"; +$Lang{CfgEdit_Title_Admin_Privileges} = "Admin Privileges"; +$Lang{CfgEdit_Title_Page_Rendering} = "Page Rendering"; +$Lang{CfgEdit_Title_Paths} = "Paths"; +$Lang{CfgEdit_Title_User_URLs} = "User URLs"; +$Lang{CfgEdit_Title_User_Config_Editing} = "User Config Editing"; +$Lang{CfgEdit_Title_Xfer} = "Xfer"; +$Lang{CfgEdit_Title_Xfer_Settings} = "Xfer Settings"; +$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_Archive_Settings} = "Archive Settings"; +$Lang{CfgEdit_Title_Include_Exclude} = "Include/Exclude"; +$Lang{CfgEdit_Title_Smb_Paths_Commands} = "Smb Paths/Commands"; +$Lang{CfgEdit_Title_Tar_Paths_Commands} = "Tar Paths/Commands"; +$Lang{CfgEdit_Title_Rsync_Paths_Commands_Args} = "Rsync Paths/Commands/Args"; +$Lang{CfgEdit_Title_Rsyncd_Port_Args} = "Rsyncd Port/Args"; +$Lang{CfgEdit_Title_Archive_Paths_Commands} = "Archive Paths/Commands"; +$Lang{CfgEdit_Title_Schedule} = "Schedule"; +$Lang{CfgEdit_Title_Full_Backups} = "Full Backups"; +$Lang{CfgEdit_Title_Incremental_Backups} = "Incremental Backups"; +$Lang{CfgEdit_Title_Blackouts} = "Blackouts"; +$Lang{CfgEdit_Title_Other} = "Other"; +$Lang{CfgEdit_Title_Backup_Settings} = "Backup Settings"; +$Lang{CfgEdit_Title_Client_Lookup} = "Client Lookup"; +$Lang{CfgEdit_Title_Other} = "Other"; +$Lang{CfgEdit_Title_User_Commands} = "User Commands"; +$Lang{CfgEdit_Title_Hosts} = "Hosts"; + +$Lang{CfgEdit_Hosts_Comment} = < +Note: Check Override if you want to modify a value specific to this host. +EOF + +$Lang{CfgEdit_Button_Save} = "Save"; +$Lang{CfgEdit_Button_Insert} = "Insert"; +$Lang{CfgEdit_Button_Delete} = "Delete"; +$Lang{CfgEdit_Button_Add} = "Add"; +$Lang{CfgEdit_Button_Override} = "Override"; + +$Lang{CfgEdit_Error__must_be_an_integer} + = "Error: \$var must be an integer"; +$Lang{CfgEdit_Error__must_be_real_valued_number} + = "Error: \$var must be a real-valued number"; +$Lang{CfgEdit_Error__entry__must_be_an_integer} + = "Error: \$var entry \$k must be an integer"; +$Lang{CfgEdit_Error__entry__must_be_real_valued_number} + = "Error: \$var entry \$k must be a real-valued number"; +$Lang{CfgEdit_Error__must_be_executable_program} + = "Error: \$var must be a valid executable path"; +$Lang{CfgEdit_Error__must_be_valid_option} + = "Error: \$var must be a valid option"; +$Lang{CfgEdit_Error_Copy_host_does_not_exist} + = "Copy host \$copyHost doesn't exist; creating full host name \$fullHost. Delete this host if that is not what you wanted."; + +$Lang{CfgEdit_Log_Copy_host_config} + = "\$User copied config from host \$fromHost to \$host\n"; +$Lang{CfgEdit_Log_Delete_param} + = "\$User deleted \$p from \$conf\n"; +$Lang{CfgEdit_Log_Add_param_value} + = "\$User added \$p to \$conf, set to \$value\n"; +$Lang{CfgEdit_Log_Change_param_value} + = "\$User changed \$p in \$conf to \$valueNew from \$valueOld\n"; +$Lang{CfgEdit_Log_Host_Delete} + = "\$User deleted host \$host\n"; +$Lang{CfgEdit_Log_Host_Change} + = "\$User host \$host changed \$key from \$valueOld to \$valueNew\n"; +$Lang{CfgEdit_Log_Host_Add} + = "\$User added host \$host: \$value\n"; + #end of lang_en.pm diff --git a/lib/BackupPC/Lang/fr.pm b/lib/BackupPC/Lang/fr.pm index 080261e..8e93c32 100644 --- a/lib/BackupPC/Lang/fr.pm +++ b/lib/BackupPC/Lang/fr.pm @@ -40,16 +40,16 @@ $Lang{Admin_Options_Page} = < -Le serveur BackupPC sur \$Conf{ServerHost}, port \$Conf{ServerPort} +Le serveur BackupPC sur \$Conf{ServerHost} aur port \$Conf{ServerPort} n'est pas en fonction (vous l'avez peut-être arrêté, ou vous ne l'avez pas encore démarré).
-Voulez-vous le démarrer? +Voulez-vous le démarrer @@ -75,7 +75,7 @@ $Lang{BackupPC_Server_Status_General_Info}= <\$numCmdQueue requêtes de commandes en attente, \$poolInfo
  • L\'espace de stockage a été récemment rempli à \$Info{DUlastValue}% - (\$DUlastTime), le maximum aujourd\'hui a été de \$Info{DUDailyMax}% (\$DUmaxTime) + (\$DUlastTime), le maximum d\'aujourd\'hui est \$Info{DUDailyMax}% (\$DUmaxTime) et hier le maximum était \$Info{DUDailyMaxPrev}%. @@ -117,7 +117,7 @@ $Lang{BackupPC_Server_Status} = < -Il y a \$hostCntGood hôtes qui ont été sauvegardés représentant \${fullSizeTot} Go +Il y a \$hostCntGood hôtes qui ont étés sauvegardés représentant \${fullSizeTot} Go

    @@ -291,7 +291,7 @@ La r Retourner à la page d\'accueil de \$host. EOF # -------------------------------- -$Lang{BackupPC__Start_Backup_Confirm_on__host} = "BackupPC: Confirmation du démarrage de la sauvegarde de \$host"; +$Lang{BackupPC__Start_Backup_Confirm_on__host} = "BackupPC: Confirmation du départ de la sauvegarde de \$host"; # -------------------------------- $Lang{Are_you_sure_start} = < -Voulez-vous vraiment le faire ? +Voulez vous vraiment le faire ? @@ -323,7 +323,7 @@ Vous En outre, prière de ne pas démarrer d\'autres sauvegarde pour heures.

    -Voulez-vous vraiment le faire ? +Voulez vous vraiment le faire ? @@ -493,9 +493,9 @@ $Lang{Option_2__Download_Zip_archive} = <

    -Attention: en fonction des fichiers/répertoires que vous avez sélectionnés, -cette archive peut devenir très très volumineuse. Cela peut prendre plusieurs minutes pour créer -et transférer cette archive, et vous aurez besoin d\'assez d\'espace disque pour la stocker. +Attention: en fonction de quels fichiers/répertoires vous avez sélectionné, +cette archive peut devenir très très large. Cela peut prendre plusieurs minutes pour créer +et transférer cette archive, et vous aurez besoin d\'assez d\'espace disque pour le stocker.

    @@ -538,8 +538,8 @@ que vous avez s comme tar ou winzip pour voir ou extraire n\'importe quel fichier.

    Attention: en fonction des fichiers/répertoires que vous avez sélectionnés, -cette archive peut devenir très très volumineuse. Cela peut prendre plusieurs minutes -pour créer et transférer l\'archive, et vous aurez besoin d\'assez +cette archive peut devenir très très large. Cela peut prendre plusieurs minutes +pour créer et transférer l\'archive, et vous aurez besoin d'assez d\'espace disque local pour la stocker.

    @@ -646,7 +646,7 @@ Cliquer sur le num \${h2("Résumé des erreurs de transfert")}

    - + @@ -662,7 +662,7 @@ Cliquer sur le num

    Les fichiers existants sont ceux qui sont déjà sur le serveur; Les nouveaux fichiers sont ceux qui ont été ajoutés au serveur. -Les fichiers vides et les erreurs de SMB ne sont pas comptabilisés dans les fichiers nouveaux ou réutilisés. +Les fichiers vides et les erreurs de SMB ne sont pas comptabilisés parmi les nouveaux et les réutilisés.

    Sauvegarde n°
    Nb sauvegarde Type Voir Nb erreurs transfert
    @@ -671,7 +671,7 @@ Les fichiers vides et les erreurs de SMB ne sont pas comptabilis - + @@ -699,10 +699,10 @@ r - + - + \$compStr @@ -712,7 +712,7 @@ EOF $Lang{Host__host_Archive_Summary} = "BackupPC: Résumé de l'archivage pour l'hôte \$host"; $Lang{Host__host_Archive_Summary2} = < \$warnStr
      @@ -745,7 +745,7 @@ $Lang{NavSectionTitle_} = "Serveur"; # ------------------------- $Lang{Backup_browse_for__host} = <
    Nouveaux fichiers
    Sauvegarde n° Nb de sauvegarde Type Nb de Fichiers Taille/Mo Type Niveau de Compression Taille/Mo Taille compressée/Mo Comp/Mo Compression Taille/Mo Taille compressée/Mo Comp/Mo Compression