show scheduled and burned columns
[BackupPC.git] / lib / BackupPC / Lib.pm
index e1dda82..6e1fe8f 100644 (file)
@@ -11,7 +11,7 @@
 #   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
@@ -29,7 +29,7 @@
 #
 #========================================================================
 #
-# Version 3.0.0, released 28 Jan 2007.
+# Version 3.2.0, released 31 Jul 2010.
 #
 # See http://backuppc.sourceforge.net.
 #
@@ -49,9 +49,9 @@ use Socket;
 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;
@@ -72,7 +72,7 @@ require DynaLoader;
 
 BEGIN {
     eval "use IO::Dirent qw( readdirent DT_DIR );";
-    $IODirentOk = 1 if ( !$@ );
+    $IODirentLoaded = 1 if ( !$@ );
 };
 
 #
@@ -96,15 +96,17 @@ sub new
     # 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
@@ -115,7 +117,7 @@ sub new
             useFHS     => $useFHS,
             TopDir     => $topDir,
             InstallDir => $installDir,
-            ConfDir    => $confDir eq "" ? '/etc/BackupPC' : $confDir,
+            ConfDir    => $confDir eq "" ? '/data/BackupPC/conf' : $confDir,
             LogDir     => '/var/log/BackupPC',
         };
     } else {
@@ -130,7 +132,7 @@ sub new
 
     my $bpc = bless {
        %$paths,
-        Version => '3.0.0',
+        Version => '3.2.0',
     }, $class;
 
     $bpc->{storage} = BackupPC::Storage->new($paths);
@@ -139,8 +141,6 @@ sub new
     # Clean up %ENV and setup other variables.
     #
     delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
-    $bpc->{PoolDir}  = "$bpc->{TopDir}/pool";
-    $bpc->{CPoolDir} = "$bpc->{TopDir}/cpool";
     if ( defined(my $error = $bpc->ConfigRead()) ) {
         print(STDERR $error, "\n");
         return;
@@ -154,6 +154,8 @@ sub new
         $paths->{$dir} = $bpc->{$dir} = $bpc->{Conf}{$dir};
     }
     $bpc->{storage}->setPaths($paths);
+    $bpc->{PoolDir}  = "$bpc->{TopDir}/pool";
+    $bpc->{CPoolDir} = "$bpc->{TopDir}/cpool";
 
     #
     # Verify we are running as the correct user
@@ -364,9 +366,9 @@ sub ConfigRead
     # 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;
     }
 
     #
@@ -396,6 +398,12 @@ sub ConfigRead
        return $mesg;
     }
     $bpc->{Lang} = \%Lang;
+
+    #
+    # Make sure IncrLevels is defined
+    #
+    $bpc->{Conf}{IncrLevels} = [1] if ( !defined($bpc->{Conf}{IncrLevels}) );
+
     return;
 }
 
@@ -467,6 +475,26 @@ sub dirRead
     from_to($path, "utf8", $need->{charsetLegacy})
                         if ( $need->{charsetLegacy} ne "" );
     return if ( !opendir(my $fh, $path) );
+    if ( $IODirentLoaded && !$IODirentOk ) {
+        #
+        # Make sure the IO::Dirent really works - some installs
+        # on certain file systems (eg: XFS) don't return a valid type.
+        #
+        if ( opendir(my $fh, $bpc->{TopDir}) ) {
+            my $dt_dir = eval("DT_DIR");
+            foreach my $e ( readdirent($fh) ) {
+                if ( $e->{name} eq "." && $e->{type} == $dt_dir ) {
+                    $IODirentOk = 1;
+                    last;
+                }
+            }
+            closedir($fh);
+        }
+        #
+        # if it isn't ok then don't check again.
+        #
+        $IODirentLoaded = 0 if ( !$IODirentOk );
+    }
     if ( $IODirentOk ) {
         @entries = sort({ $a->{inode} <=> $b->{inode} } readdirent($fh));
         map { $_->{type} = 0 + $_->{type} } @entries;   # make type numeric
@@ -598,7 +626,15 @@ sub RmTreeDefer
     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 );
@@ -877,7 +913,10 @@ sub MakeFileLink
         } 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 {
@@ -886,6 +925,33 @@ sub MakeFileLink
     }
 }
 
+#
+# 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) = @_;
@@ -982,6 +1048,10 @@ sub NetBiosInfoGet
     };
     $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
@@ -1165,22 +1235,27 @@ sub cmdVarSubstitute
         }
     }
     #
-    # 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;
@@ -1417,7 +1492,40 @@ sub sortedPCLogFiles
         }
         closedir(DIR);
     }
-    return sort(compareLOGName @files);
+    return sort compareLOGName @files;
+}
+
+#
+# converts a glob-style pattern into a perl regular expression.
+#
+sub glob2re
+{
+    my ( $bpc, $glob ) = @_;
+    my ( $char, $subst );
+
+    # $escapeChars escapes characters with no special glob meaning but
+    # have meaning in regexps.
+    my $escapeChars = [ '.', '/', ];
+
+    # $charMap is where we implement the special meaning of glob
+    # patterns and translate them to regexps.
+    my $charMap = {
+                    '?' => '[^/]',
+                    '*' => '[^/]*', };
+
+    # multiple forward slashes are equivalent to one slash.  We should
+    # never have to use this.
+    $glob =~ s/\/+/\//;
+
+    foreach $char (@$escapeChars) {
+        $glob =~ s/\Q$char\E/\\$char/g;
+    }
+
+    while ( ( $char, $subst ) = each(%$charMap) ) {
+        $glob =~ s/(?<!\\)\Q$char\E/$subst/g;
+    }
+
+    return $glob;
 }
 
 1;