- added lib/BackupPC/Storage/Text.pm
authorcbarratt <cbarratt>
Tue, 12 Oct 2004 06:19:42 +0000 (06:19 +0000)
committercbarratt <cbarratt>
Tue, 12 Oct 2004 06:19:42 +0000 (06:19 +0000)
lib/BackupPC/Storage/Text.pm [new file with mode: 0644]

diff --git a/lib/BackupPC/Storage/Text.pm b/lib/BackupPC/Storage/Text.pm
new file mode 100644 (file)
index 0000000..a93fb61
--- /dev/null
@@ -0,0 +1,381 @@
+#============================================================= -*-perl-*-
+#
+# BackupPC::Storage::Text package
+#
+# DESCRIPTION
+#
+#   This library defines a BackupPC::Storage::Text class that implements
+#   BackupPC's persistent state storage (config, host info, backup
+#   and restore info) using text files.
+#
+# AUTHOR
+#   Craig Barratt  <cbarratt@users.sourceforge.net>
+#
+# COPYRIGHT
+#   Copyright (C) 2004  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.
+#
+#========================================================================
+
+package BackupPC::Storage::Text;
+
+use strict;
+use vars qw(%Conf);
+use Data::Dumper;
+use Fcntl qw/:flock/;
+
+sub new
+{
+    my $class = shift;
+    my($flds, $paths) = @_;
+
+    my $s = bless {
+       %$flds,
+       %$paths,
+    }, $class;
+    return $s;
+}
+
+sub BackupInfoRead
+{
+    my($s, $host) = @_;
+    local(*BK_INFO, *LOCK);
+    my(@Backups);
+
+    flock(LOCK, LOCK_EX) if open(LOCK, "$s->{TopDir}/pc/$host/LOCK");
+    if ( open(BK_INFO, "$s->{TopDir}/pc/$host/backups") ) {
+       binmode(BK_INFO);
+        while ( <BK_INFO> ) {
+            s/[\n\r]+//;
+            next if ( !/^(\d+\t(incr|full|partial)[\d\t]*$)/ );
+            $_ = $1;
+            @{$Backups[@Backups]}{@{$s->{BackupFields}}} = split(/\t/);
+        }
+        close(BK_INFO);
+    }
+    close(LOCK);
+    return @Backups;
+}
+
+sub BackupInfoWrite
+{
+    my($s, $host, @Backups) = @_;
+    local(*BK_INFO, *LOCK);
+    my($i);
+
+    flock(LOCK, LOCK_EX) if open(LOCK, "$s->{TopDir}/pc/$host/LOCK");
+    if ( -s "$s->{TopDir}/pc/$host/backups" ) {
+       unlink("$s->{TopDir}/pc/$host/backups.old")
+                   if ( -f "$s->{TopDir}/pc/$host/backups.old" );
+       rename("$s->{TopDir}/pc/$host/backups",
+              "$s->{TopDir}/pc/$host/backups.old")
+                   if ( -f "$s->{TopDir}/pc/$host/backups" );
+    }
+    if ( open(BK_INFO, ">$s->{TopDir}/pc/$host/backups") ) {
+       binmode(BK_INFO);
+        for ( $i = 0 ; $i < @Backups ; $i++ ) {
+            my %b = %{$Backups[$i]};
+            printf(BK_INFO "%s\n", join("\t", @b{@{$s->{BackupFields}}}));
+        }
+        close(BK_INFO);
+    }
+    close(LOCK);
+}
+
+sub RestoreInfoRead
+{
+    my($s, $host) = @_;
+    local(*RESTORE_INFO, *LOCK);
+    my(@Restores);
+
+    flock(LOCK, LOCK_EX) if open(LOCK, "$s->{TopDir}/pc/$host/LOCK");
+    if ( open(RESTORE_INFO, "$s->{TopDir}/pc/$host/restores") ) {
+       binmode(RESTORE_INFO);
+        while ( <RESTORE_INFO> ) {
+            s/[\n\r]+//;
+            next if ( !/^(\d+.*)/ );
+            $_ = $1;
+            @{$Restores[@Restores]}{@{$s->{RestoreFields}}} = split(/\t/);
+        }
+        close(RESTORE_INFO);
+    }
+    close(LOCK);
+    return @Restores;
+}
+
+sub RestoreInfoWrite
+{
+    my($s, $host, @Restores) = @_;
+    local(*RESTORE_INFO, *LOCK);
+    my($i);
+
+    flock(LOCK, LOCK_EX) if open(LOCK, "$s->{TopDir}/pc/$host/LOCK");
+    if ( -s "$s->{TopDir}/pc/$host/restores" ) {
+       unlink("$s->{TopDir}/pc/$host/restores.old")
+                   if ( -f "$s->{TopDir}/pc/$host/restores.old" );
+       rename("$s->{TopDir}/pc/$host/restores",
+              "$s->{TopDir}/pc/$host/restores.old")
+                   if ( -f "$s->{TopDir}/pc/$host/restores" );
+    }
+    if ( open(RESTORE_INFO, ">$s->{TopDir}/pc/$host/restores") ) {
+       binmode(RESTORE_INFO);
+        for ( $i = 0 ; $i < @Restores ; $i++ ) {
+            my %b = %{$Restores[$i]};
+            printf(RESTORE_INFO "%s\n",
+                        join("\t", @b{@{$s->{RestoreFields}}}));
+        }
+        close(RESTORE_INFO);
+    }
+    close(LOCK);
+}
+
+sub ArchiveInfoRead
+{
+    my($s, $host) = @_;
+    local(*ARCHIVE_INFO, *LOCK);
+    my(@Archives);
+
+    flock(LOCK, LOCK_EX) if open(LOCK, "$s->{TopDir}/pc/$host/LOCK");
+    if ( open(ARCHIVE_INFO, "$s->{TopDir}/pc/$host/archives") ) {
+        binmode(ARCHIVE_INFO);
+        while ( <ARCHIVE_INFO> ) {
+            s/[\n\r]+//;
+            next if ( !/^(\d+.*)/ );
+            $_ = $1;
+            @{$Archives[@Archives]}{@{$s->{ArchiveFields}}} = split(/\t/);
+        }
+        close(ARCHIVE_INFO);
+    }
+    close(LOCK);
+    return @Archives;
+}
+
+sub ArchiveInfoWrite
+{
+    my($s, $host, @Archives) = @_;
+    local(*ARCHIVE_INFO, *LOCK);
+    my($i);
+
+    flock(LOCK, LOCK_EX) if open(LOCK, "$s->{TopDir}/pc/$host/LOCK");
+    if ( -s "$s->{TopDir}/pc/$host/archives" ) {
+       unlink("$s->{TopDir}/pc/$host/archives.old")
+                   if ( -f "$s->{TopDir}/pc/$host/archives.old" );
+       rename("$s->{TopDir}/pc/$host/archives",
+              "$s->{TopDir}/pc/$host/archives.old")
+                   if ( -f "$s->{TopDir}/pc/$host/archives" );
+    }
+    if ( open(ARCHIVE_INFO, ">$s->{TopDir}/pc/$host/archives") ) {
+        binmode(ARCHIVE_INFO);
+        for ( $i = 0 ; $i < @Archives ; $i++ ) {
+            my %b = %{$Archives[$i]};
+            printf(ARCHIVE_INFO "%s\n",
+                        join("\t", @b{@{$s->{ArchiveFields}}}));
+        }
+        close(ARCHIVE_INFO);
+    }
+    close(LOCK);
+}
+
+sub ConfigDataRead
+{
+    my($s, $host) = @_;
+    my($ret, $mesg, $config, @configs);
+
+    #
+    # TODO: add lock
+    #
+    my $conf = {};
+
+    if ( defined($host) ) {
+       push(@configs, "$s->{TopDir}/conf/$host.pl")
+               if ( $host ne "config" && -f "$s->{TopDir}/conf/$host.pl" );
+       push(@configs, "$s->{TopDir}/pc/$host/config.pl")
+               if ( -f "$s->{TopDir}/pc/$host/config.pl" );
+    } else {
+       push(@configs, "$s->{TopDir}/conf/config.pl");
+    }
+    foreach $config ( @configs ) {
+        %Conf = ();
+        if ( !defined($ret = do $config) && ($! || $@) ) {
+            $mesg = "Couldn't open $config: $!" if ( $! );
+            $mesg = "Couldn't execute $config: $@" if ( $@ );
+            $mesg =~ s/[\n\r]+//;
+            return ($mesg, $conf);
+        }
+        %$conf = ( %$conf, %Conf );
+    }
+    return (undef, $conf);
+}
+
+sub ConfigDataWrite
+{
+    my($s, $host, $newConf) = @_;
+
+    my($confPath) = $host eq "" ? "$s->{TopDir}/conf/config.pl"
+                               : "$s->{TopDir}/pc/$host/config.pl";
+
+    my $err = $s->ConfigFileMerge($confPath, "$confPath.new", $newConf);
+    #
+    # TODO: add lock and rename
+    #
+}
+
+sub ConfigFileMerge
+{
+    my($s, $inFile, $outFile, $newConf) = @_;
+
+    open(C, $inFile) || return "ConfigFileMerge: can't open/read $inFile";
+    binmode(C);
+
+    open(OUT, ">", $outFile)
+                    || return "ConfigFileMerge: can't open/write $outFile";
+    binmode(OUT);
+
+    my($out);
+    my $comment = 1;
+    my $skipVar = 0;
+    my $endLine = undef;
+    my $done = {};
+
+    while ( <C> ) {
+       if ( $comment && /^\s*#/ ) {
+           $out .= $_;
+       } elsif ( /^\s*\$Conf\{([^}]*)\}\s*=/ ) {
+           my $var = $1;
+           if ( exists($newConf->{$var}) ) { 
+               print OUT $out;
+               my $d = Data::Dumper->new([$newConf->{$var}], [*value]);
+               $d->Indent(1);
+               $d->Terse(1);
+               my $value = $d->Dump;
+               $value =~ s/(.*)\n/$1;\n/s;
+               print OUT "\$Conf{$var} = ", $value;
+               $done->{$var} = 1;
+           }
+           $endLine = $1 if ( /^\s*\$Conf\{[^}]*} *= *<<(.*);/ );
+           $endLine = $1 if ( /^\s*\$Conf\{[^}]*} *= *<<'(.*)';/ );
+           $out = "";
+           $skipVar = 1;
+       } elsif ( $skipVar ) {
+           if ( !defined($endLine) && (/^\s*[\r\n]*$/ || /^\s*#/) ) {
+               $skipVar = 0;
+               $comment = 1;
+               $out .= $_;
+           }
+           if ( defined($endLine) && /^\Q$endLine\E[\n\r]*$/ ) {
+               $endLine = undef;
+               $skipVar = 0;
+               $comment = 1;
+           }
+       } else {
+           $out .= $_;
+       }
+    }
+    if ( $out ne "" ) {
+       print OUT $out;
+    }
+    foreach my $var ( sort(keys(%$newConf)) ) {
+       next if ( $done->{$var} );
+       my $d = Data::Dumper->new([$newConf->{$var}], [*value]);
+       $d->Indent(1);
+       $d->Terse(1);
+       my $value = $d->Dump;
+       $value =~ s/(.*)\n/$1;\n/s;
+       print OUT "\$Conf{$var} = ", $value;
+       $done->{$var} = 1;
+    }
+    close(C);
+    close(OUT);
+}
+
+#
+# Return the mtime of the config file
+#
+sub ConfigMTime
+{
+    my($s) = @_;
+    return (stat("$s->{TopDir}/conf/config.pl"))[9];
+}
+
+#
+# Returns information from the host file in $s->{TopDir}/conf/hosts.
+# With no argument a ref to a hash of hosts is returned.  Each
+# hash contains fields as specified in the hosts file.  With an
+# argument a ref to a single hash is returned with information
+# for just that host.
+#
+sub HostInfoRead
+{
+    my($s, $host) = @_;
+    my(%hosts, @hdr, @fld);
+    local(*HOST_INFO);
+
+    if ( !open(HOST_INFO, "$s->{TopDir}/conf/hosts") ) {
+        print(STDERR $s->timeStamp,
+                     "Can't open $s->{TopDir}/conf/hosts\n");
+        return {};
+    }
+    binmode(HOST_INFO);
+    while ( <HOST_INFO> ) {
+        s/[\n\r]+//;
+        s/#.*//;
+        s/\s+$//;
+        next if ( /^\s*$/ || !/^([\w\.\\-]+\s+.*)/ );
+        #
+        # Split on white space, except if preceded by \
+        # using zero-width negative look-behind assertion
+       # (always wanted to use one of those).
+        #
+        @fld = split(/(?<!\\)\s+/, $1);
+        #
+        # Remove any \
+        #
+        foreach ( @fld ) {
+            s{\\(\s)}{$1}g;
+        }
+        if ( @hdr ) {
+            if ( defined($host) ) {
+                next if ( lc($fld[0]) ne $host );
+                @{$hosts{lc($fld[0])}}{@hdr} = @fld;
+               close(HOST_INFO);
+                return \%hosts;
+            } else {
+                @{$hosts{lc($fld[0])}}{@hdr} = @fld;
+            }
+        } else {
+            @hdr = @fld;
+        }
+    }
+    close(HOST_INFO);
+    return \%hosts;
+}
+
+#
+# Return the mtime of the hosts file
+#
+sub HostsMTime
+{
+    my($s) = @_;
+    return (stat("$s->{TopDir}/conf/hosts"))[9];
+}
+
+1;