# Craig Barratt <cbarratt@users.sourceforge.net>
#
# COPYRIGHT
-# Copyright (C) 2001-2003 Craig Barratt
+# Copyright (C) 2001-2009 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
#
#========================================================================
#
-# Version 3.0.0, released 28 Jan 2007.
+# Version 3.2.0, released 31 Jul 2010.
#
# See http://backuppc.sourceforge.net.
#
use Cwd;
use Digest::MD5;
use Config;
-use Encode;
+use Encode qw/from_to encode_utf8/;
-use vars qw( $IODirentOk );
+use vars qw( $IODirentOk $IODirentLoaded );
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
require Exporter;
BEGIN {
eval "use IO::Dirent qw( readdirent DT_DIR );";
- $IODirentOk = 1 if ( !$@ );
+ $IODirentLoaded = 1 if ( !$@ );
};
#
# Whether to use filesystem hierarchy standard for file layout.
# If set, text config files are below /etc/BackupPC.
#
- my $useFHS = 0;
+ my $useFHS = 1;
my $paths;
#
# Set defaults for $topDir and $installDir.
#
- $topDir = '/tera0/backup/BackupPC' if ( $topDir eq "" );
+ $topDir = '/data/BackupPC' if ( $topDir eq "" );
$installDir = '/usr/local/BackupPC' if ( $installDir eq "" );
+ $confDir = '/etc/BackupPC'; # FIXME remove this! XXX
+
#
# Pick some initial defaults. For FHS the only critical
# path is the ConfDir, since we get everything else out
useFHS => $useFHS,
TopDir => $topDir,
InstallDir => $installDir,
- ConfDir => $confDir eq "" ? '/etc/BackupPC' : $confDir,
+ ConfDir => $confDir eq "" ? '/data/BackupPC/conf' : $confDir,
LogDir => '/var/log/BackupPC',
};
} else {
my $bpc = bless {
%$paths,
- Version => '3.0.0',
+ Version => '3.2.0',
}, $class;
$bpc->{storage} = BackupPC::Storage->new($paths);
# Clean up %ENV and setup other variables.
#
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
- $bpc->{PoolDir} = "$bpc->{TopDir}/pool";
- $bpc->{CPoolDir} = "$bpc->{TopDir}/cpool";
if ( defined(my $error = $bpc->ConfigRead()) ) {
print(STDERR $error, "\n");
return;
$paths->{$dir} = $bpc->{$dir} = $bpc->{Conf}{$dir};
}
$bpc->{storage}->setPaths($paths);
+ $bpc->{PoolDir} = "$bpc->{TopDir}/pool";
+ $bpc->{CPoolDir} = "$bpc->{TopDir}/cpool";
#
# Verify we are running as the correct user
# Read host config file
#
if ( $host ne "" ) {
- ($mesg, $config) = $bpc->{storage}->ConfigDataRead($host);
+ ($mesg, $config) = $bpc->{storage}->ConfigDataRead($host, $config);
return $mesg if ( defined($mesg) );
- $bpc->{Conf} = { %{$bpc->{Conf}}, %$config };
+ $bpc->{Conf} = $config;
}
#
return $mesg;
}
$bpc->{Lang} = \%Lang;
+
+ #
+ # Make sure IncrLevels is defined
+ #
+ $bpc->{Conf}{IncrLevels} = [1] if ( !defined($bpc->{Conf}{IncrLevels}) );
+
return;
}
from_to($path, "utf8", $need->{charsetLegacy})
if ( $need->{charsetLegacy} ne "" );
return if ( !opendir(my $fh, $path) );
+ if ( $IODirentLoaded && !$IODirentOk ) {
+ #
+ # Make sure the IO::Dirent really works - some installs
+ # on certain file systems (eg: XFS) don't return a valid type.
+ #
+ if ( opendir(my $fh, $bpc->{TopDir}) ) {
+ my $dt_dir = eval("DT_DIR");
+ foreach my $e ( readdirent($fh) ) {
+ if ( $e->{name} eq "." && $e->{type} == $dt_dir ) {
+ $IODirentOk = 1;
+ last;
+ }
+ }
+ closedir($fh);
+ }
+ #
+ # if it isn't ok then don't check again.
+ #
+ $IODirentLoaded = 0 if ( !$IODirentOk );
+ }
if ( $IODirentOk ) {
@entries = sort({ $a->{inode} <=> $b->{inode} } readdirent($fh));
map { $_->{type} = 0 + $_->{type} } @entries; # make type numeric
my($i, $f);
return if ( !-e $file );
- mkpath($trashDir, 0, 0777) if ( !-d $trashDir );
+ if ( !-d $trashDir ) {
+ eval { mkpath($trashDir, 0, 0777) };
+ if ( $@ ) {
+ #
+ # There's no good place to send this error - use stderr
+ #
+ print(STDERR "RmTreeDefer: can't create directory $trashDir");
+ }
+ }
for ( $i = 0 ; $i < 1000 ; $i++ ) {
$f = sprintf("%s/%d_%d_%d", $trashDir, time, $$, $i);
next if ( -e $f );
} elsif ( $newFile && -f $name && (stat($name))[3] == 1 ) {
my($newDir);
($newDir = $rawFile) =~ s{(.*)/.*}{$1};
- mkpath($newDir, 0, 0777) if ( !-d $newDir );
+ if ( !-d $newDir ) {
+ eval { mkpath($newDir, 0, 0777) };
+ return -5 if ( $@ );
+ }
return -4 if ( !link($name, $rawFile) );
return 2;
} else {
}
}
+#
+# Tests if we can create a hardlink from a file in directory
+# $newDir to a file in directory $targetDir. A temporary
+# file in $targetDir is created and an attempt to create a
+# hardlink of the same name in $newDir is made. The temporary
+# files are removed.
+#
+# Like link(), returns true on success and false on failure.
+#
+sub HardlinkTest
+{
+ my($bpc, $targetDir, $newDir) = @_;
+
+ my($targetFile, $newFile, $fd);
+ for ( my $i = 0 ; ; $i++ ) {
+ $targetFile = "$targetDir/.TestFileLink.$$.$i";
+ $newFile = "$newDir/.TestFileLink.$$.$i";
+ last if ( !-e $targetFile && !-e $newFile );
+ }
+ return 0 if ( !open($fd, ">", $targetFile) );
+ close($fd);
+ my $ret = link($targetFile, $newFile);
+ unlink($targetFile);
+ unlink($newFile);
+ return $ret;
+}
+
sub CheckHostAlive
{
my($bpc, $host) = @_;
};
$nmbCmd = $bpc->cmdVarSubstitute($bpc->{Conf}{NmbLookupCmd}, $args);
foreach ( split(/[\n\r]+/, $bpc->cmdSystemOrEval($nmbCmd, undef, $args)) ) {
+ #
+ # skip <GROUP> and other non <ACTIVE> entries
+ #
+ next if ( /<\w{2}> - <GROUP>/i );
next if ( !/^\s*([\w\s-]+?)\s*<(\w{2})\> - .*<ACTIVE>/i );
$netBiosHostName ||= $1 if ( $2 eq "00" ); # host is first 00
$netBiosUserName = $1 if ( $2 eq "03" ); # user is last 03
}
}
#
- # Merge variables into @tarClientCmd
+ # Merge variables into @cmd
#
foreach my $arg ( @$template ) {
+ #
+ # Replace $VAR with ${VAR} so that both types of variable
+ # substitution are supported
+ #
+ $arg =~ s[\$(\w+)]{\${$1}}g;
#
# Replace scalar variables first
#
- $arg =~ s{\$(\w+)(\+?)}{
+ $arg =~ s[\${(\w+)}(\+?)]{
exists($vars->{$1}) && ref($vars->{$1}) ne "ARRAY"
? ($2 eq "+" ? $bpc->shellEscape($vars->{$1}) : $vars->{$1})
- : "\$$1$2"
+ : "\${$1}$2"
}eg;
#
# Now replicate any array arguments; this just works for just one
# array var in each argument.
#
- if ( $arg =~ m{(.*)\$(\w+)(\+?)(.*)} && ref($vars->{$2}) eq "ARRAY" ) {
+ if ( $arg =~ m[(.*)\${(\w+)}(\+?)(.*)] && ref($vars->{$2}) eq "ARRAY" ) {
my $pre = $1;
my $var = $2;
my $esc = $3;
}
closedir(DIR);
}
- return sort(compareLOGName @files);
+ return sort compareLOGName @files;
+}
+
+#
+# converts a glob-style pattern into a perl regular expression.
+#
+sub glob2re
+{
+ my ( $bpc, $glob ) = @_;
+ my ( $char, $subst );
+
+ # $escapeChars escapes characters with no special glob meaning but
+ # have meaning in regexps.
+ my $escapeChars = [ '.', '/', ];
+
+ # $charMap is where we implement the special meaning of glob
+ # patterns and translate them to regexps.
+ my $charMap = {
+ '?' => '[^/]',
+ '*' => '[^/]*', };
+
+ # multiple forward slashes are equivalent to one slash. We should
+ # never have to use this.
+ $glob =~ s/\/+/\//;
+
+ foreach $char (@$escapeChars) {
+ $glob =~ s/\Q$char\E/\\$char/g;
+ }
+
+ while ( ( $char, $subst ) = each(%$charMap) ) {
+ $glob =~ s/(?<!\\)\Q$char\E/$subst/g;
+ }
+
+ return $glob;
}
1;