1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-12-21 23:47:33 +02:00
Files
pgbackrest/lib/BackRest/Ini.pm
David Steele a59284a4cc v0.80: DBI Support, Stability, and Convenience Features
* Fixed an issue that caused the formatted timestamp for both the oldest and newest backups to be reported as the current time by the info command.  Only text output was affected -- json output reported the correct epoch values.  Reported by Michael Renner.

* Fixed protocol issue that was preventing ssh errors (especially on connection) from being logged.

* Now using Perl DBI and DBD::Pg for connections to PostgreSQL rather than psql.  The cmd-psql and cmd-psql-option settings have been removed and replaced with db-port and db-socket-path.

* Add stop-auto option to allow failed backups to automatically be stopped when a new backup starts.

* Add db-timeout option to limit the amount of time pgBackRest will wait for pg_start_backup() and pg_stop_backup() to return.

* Remove pg_control file at the beginning of the restore and copy it back at the very end.  This prevents the possibility that a partial restore can be started by PostgreSQL.

* The repository is now created and updated with consistent directory and file modes.  By default umask is set to 0000 but this can be disabled with the neutral-umask setting.

* Added checks to be sure the db-path setting is consistent with db-port by comparing the data_directory as reported by the cluster against the db-path setting and the version as reported by the cluster against the value read from pg_control.  The db-socket-path setting is checked to be sure it is an absolute path.

* Experimental support for PostgreSQL 9.5 alpha1.  This may break when the control version or WAL magic changes in future versions but will be updated in each pgBackRest release to keep pace.  All regression tests pass except for --target-resume tests (this functionality has changed in 9.5) and there is no testing yet for .partial WAL segments.

* Major refactoring of the protocol layer to support future development.

* Added vagrant test configurations for Ubuntu 14.04 and CentOS 7.

* Split most of README.md out into USERGUIDE.md and CHANGELOG.md because it was becoming unwieldy.  Changed most references to "database" in the user guide to "database cluster" for clarity.
2015-08-09 11:20:43 -04:00

595 lines
19 KiB
Perl

####################################################################################################################################
# INI MODULE
####################################################################################################################################
package BackRest::Ini;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use Exporter qw(import);
use Fcntl qw(:mode O_WRONLY O_CREAT O_TRUNC);
use File::Basename qw(dirname basename);
use IO::Handle;
use JSON::PP;
use Storable qw(dclone);
use lib dirname($0);
use BackRest::Exception;
use BackRest::FileCommon;
use BackRest::Utility;
use BackRest::Version;
####################################################################################################################################
# Operation constants
####################################################################################################################################
use constant OP_INI => 'Ini';
use constant OP_INI_SET => OP_INI . "->set";
####################################################################################################################################
# Version and Format Constants
####################################################################################################################################
use constant BACKREST_VERSION => "$VERSION";
our @EXPORT = qw(BACKREST_VERSION);
use constant BACKREST_FORMAT => "$FORMAT";
push @EXPORT, qw(BACKREST_FORMAT);
####################################################################################################################################
# Boolean constants
####################################################################################################################################
use constant INI_TRUE => JSON::PP::true;
use constant INI_FALSE => JSON::PP::false;
####################################################################################################################################
# Ini control constants
####################################################################################################################################
use constant INI_SECTION_BACKREST => 'backrest';
push @EXPORT, qw(INI_SECTION_BACKREST);
use constant INI_KEY_CHECKSUM => 'backrest-checksum';
push @EXPORT, qw(INI_KEY_CHECKSUM);
use constant INI_KEY_FORMAT => 'backrest-format';
push @EXPORT, qw(INI_KEY_FORMAT);
use constant INI_KEY_VERSION => 'backrest-version';
push @EXPORT, qw(INI_KEY_VERSION);
use constant INI_COMMENT => '[comment]';
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift; # Class name
my $strFileName = shift; # Manifest filename
my $bLoad = shift; # Load the ini?
# Create the class hash
my $self = {};
bless $self, $class;
# Filename must be specified
if (!defined($strFileName))
{
confess &log(ASSERT, 'filename must be provided');
}
# Set variables
my $oContent = {};
$self->{oContent} = $oContent;
$self->{strFileName} = $strFileName;
# Load the ini if specified
if (!defined($bLoad) || $bLoad)
{
$self->load();
# Make sure the ini is valid by testing checksum
my $strChecksum = $self->get(INI_SECTION_BACKREST, INI_KEY_CHECKSUM);
my $strTestChecksum = $self->hash();
if ($strChecksum ne $strTestChecksum)
{
confess &log(ERROR, "${strFileName} checksum is invalid, should be ${strTestChecksum} but found ${strChecksum}",
ERROR_CHECKSUM);
}
# Make sure that the format is current, otherwise error
my $iFormat = $self->get(INI_SECTION_BACKREST, INI_KEY_FORMAT, undef, false, 0);
if ($iFormat != BACKREST_FORMAT)
{
confess &log(ERROR, "format of ${strFileName} is ${iFormat} but " . BACKREST_FORMAT . ' is required by this ' .
' version of PgBackRest.', ERROR_FORMAT);
}
}
else
{
$self->setNumeric(INI_SECTION_BACKREST, INI_KEY_FORMAT, undef, BACKREST_FORMAT);
$self->set(INI_SECTION_BACKREST, INI_KEY_VERSION, undef, BACKREST_VERSION);
}
return $self;
}
####################################################################################################################################
# load
#
# Load the ini.
####################################################################################################################################
sub load
{
my $self = shift;
iniLoad($self->{strFileName}, $self->{oContent});
}
####################################################################################################################################
# iniLoad
#
# Load file from standard INI format to a hash.
####################################################################################################################################
push @EXPORT, qw(iniLoad);
sub iniLoad
{
my $strFileName = shift;
my $oContent = shift;
my $bRelaxed = shift;
# Open the ini file for reading
my $hFile;
my $strSection;
open($hFile, '<', $strFileName)
or confess &log(ERROR, "unable to open ${strFileName}");
# Create the JSON object
my $oJSON = JSON::PP->new()->allow_nonref();
# Read the INI file
while (my $strLine = readline($hFile))
{
$strLine = trim($strLine);
# Skip lines that are blank or comments
if ($strLine ne '' && $strLine !~ '^[ ]*#.*')
{
# Get the section
if (index($strLine, '[') == 0)
{
$strSection = substr($strLine, 1, length($strLine) - 2);
}
else
{
# Get key and value
my $iIndex = index($strLine, '=');
if ($iIndex == -1)
{
confess &log(ERROR, "unable to read from ${strFileName}: ${strLine}");
}
my $strKey = substr($strLine, 0, $iIndex);
my $strValue = substr($strLine, $iIndex + 1);
# If relaxed then read the value directly
if ($bRelaxed)
{
${$oContent}{$strSection}{$strKey} = $strValue;
}
# Else read the value as stricter JSON
else
{
${$oContent}{$strSection}{$strKey} = $oJSON->decode($strValue);
}
}
}
}
close($hFile);
return($oContent);
}
####################################################################################################################################
# save
#
# Save the manifest.
####################################################################################################################################
sub save
{
my $self = shift;
$self->hash();
iniSave($self->{strFileName}, $self->{oContent});
}
####################################################################################################################################
# iniSave
#
# Save from a hash to standard INI format.
####################################################################################################################################
push @EXPORT, qw(iniSave);
sub iniSave
{
my $strFileName = shift;
my $oContent = shift;
my $bRelaxed = shift;
# Open the ini file for writing
my $hFile;
my $bFirst = true;
sysopen($hFile, $strFileName, O_WRONLY | O_CREAT | O_TRUNC, 0640)
or confess &log(ERROR, "unable to open ${strFileName}");
# Create the JSON object canonical so that fields are alpha ordered to pass unit tests
my $oJSON = JSON::PP->new()->canonical()->allow_nonref();
$bRelaxed = defined($bRelaxed) ? $bRelaxed : false;
# Write the INI file
foreach my $strSection (sort(keys(%$oContent)))
{
# Add a linefeed between sections
if (!$bFirst)
{
syswrite($hFile, "\n")
or confess "unable to write lf: $!";
}
# Write the section comment if present
if (defined(${$oContent}{$strSection}{&INI_COMMENT}))
{
my $strComment = trim(${$oContent}{$strSection}{&INI_COMMENT});
$strComment =~ s/\n/\n# /g;
# syswrite($hFile, ('#' x 80) . "\n# ${strComment}\n" . ('#' x 80) . "\n")
# or confess "unable to comment for section ${strSection}: $!";
syswrite($hFile, "# ${strComment}\n")
or confess "unable to comment for section ${strSection}: $!";
}
# Write the section
syswrite($hFile, "[${strSection}]\n")
or confess "unable to write section ${strSection}: $!";
# Iterate through all keys in the section
foreach my $strKey (sort(keys(%{${$oContent}{"${strSection}"}})))
{
# Skip comments
if ($strKey eq INI_COMMENT)
{
next;
}
# If the value is a hash then convert it to JSON, otherwise store as is
my $strValue = ${$oContent}{"${strSection}"}{"${strKey}"};
# If relaxed then store as old-style config
if ($bRelaxed)
{
syswrite($hFile, "${strKey}=${strValue}\n")
or confess "unable to write relaxed key ${strKey}: $!";
}
# Else write as stricter JSON
else
{
syswrite($hFile, "${strKey}=" . $oJSON->encode($strValue) . "\n")
or confess "unable to write json key ${strKey}: $!";
}
}
$bFirst = false;
}
# Sync and close ini file
$hFile->sync();
filePathSync(dirname($strFileName));
close($hFile);
}
####################################################################################################################################
# hash
#
# Generate hash for the manifest.
####################################################################################################################################
sub hash
{
my $self = shift;
# Remove the old checksum
$self->remove(INI_SECTION_BACKREST, INI_KEY_CHECKSUM);
# Caculate the checksum
my $oChecksumContent = dclone($self->{oContent});
foreach my $strSection (keys(%$oChecksumContent))
{
delete(${$oChecksumContent}{$strSection}{&INI_COMMENT});
}
my $oSHA = Digest::SHA->new('sha1');
my $oJSON = JSON::PP->new()->canonical()->allow_nonref();
$oSHA->add($oJSON->encode($oChecksumContent));
# Set the new checksum
my $strHash = $oSHA->hexdigest();
$self->set(INI_SECTION_BACKREST, INI_KEY_CHECKSUM, undef, $strHash);
return $strHash;
}
####################################################################################################################################
# getBool
#
# Get a numeric value.
####################################################################################################################################
sub getBool
{
my $self = shift;
my $strSection = shift;
my $strValue = shift;
my $strSubValue = shift;
my $bRequired = shift;
my $bDefault = shift;
return $self->get($strSection, $strValue, $strSubValue, $bRequired,
defined($bDefault) ? ($bDefault ? INI_TRUE : INI_FALSE) : undef) ? true : false;
}
####################################################################################################################################
# getNumeric
#
# Get a numeric value.
####################################################################################################################################
sub getNumeric
{
my $self = shift;
my $strSection = shift;
my $strValue = shift;
my $strSubValue = shift;
my $bRequired = shift;
my $nDefault = shift;
return $self->get($strSection, $strValue, $strSubValue, $bRequired,
defined($nDefault) ? $nDefault + 0 : undef) + 0;
}
####################################################################################################################################
# get
#
# Get a value.
####################################################################################################################################
sub get
{
my $self = shift;
my $strSection = shift;
my $strValue = shift;
my $strSubValue = shift;
my $bRequired = shift;
my $oDefault = shift;
my $oContent = $self->{oContent};
# Section must always be defined
if (!defined($strSection))
{
confess &log(ASSERT, 'section is not defined');
}
# Set default for required
$bRequired = defined($bRequired) ? $bRequired : true;
# Store the result
my $oResult = undef;
if (defined($strSubValue))
{
if (!defined($strValue))
{
confess &log(ASSERT, "subvalue '${strSubValue}' requested but value is not defined");
}
if (defined(${$oContent}{$strSection}{$strValue}))
{
$oResult = ${$oContent}{$strSection}{$strValue}{$strSubValue};
}
}
elsif (defined($strValue))
{
if (defined(${$oContent}{$strSection}))
{
$oResult = ${$oContent}{$strSection}{$strValue};
}
}
else
{
$oResult = ${$oContent}{$strSection};
}
if (!defined($oResult) && $bRequired)
{
confess &log(ASSERT, "manifest section '$strSection'" . (defined($strValue) ? ", value '$strValue'" : '') .
(defined($strSubValue) ? ", subvalue '$strSubValue'" : '') . ' is required but not defined');
}
if (!defined($oResult) && defined($oDefault))
{
$oResult = $oDefault;
}
return $oResult
}
####################################################################################################################################
# setBool
#
# Set a boolean value.
####################################################################################################################################
sub setBool
{
my $self = shift;
my $strSection = shift;
my $strKey = shift;
my $strSubKey = shift;
my $bValue = shift;
$self->set($strSection, $strKey, $strSubKey, $bValue ? INI_TRUE : INI_FALSE);
}
####################################################################################################################################
# setNumeric
#
# Set a numeric value.
####################################################################################################################################
sub setNumeric
{
my $self = shift;
my $strSection = shift;
my $strKey = shift;
my $strSubKey = shift;
my $nValue = shift;
$self->set($strSection, $strKey, $strSubKey, $nValue + 0);
}
####################################################################################################################################
# set
#
# Set a value.
####################################################################################################################################
sub set
{
my $self = shift;
my $strSection = shift;
my $strKey = shift;
my $strSubKey = shift;
my $strValue = shift;
my $oContent = $self->{oContent};
if (defined($strSubKey))
{
${$oContent}{$strSection}{$strKey}{$strSubKey} = $strValue;
}
else
{
${$oContent}{$strSection}{$strKey} = $strValue;
}
}
####################################################################################################################################
# setComment
#
# Set a section comment.
####################################################################################################################################
sub setComment
{
my $self = shift;
my $strSection = shift;
my $strComment = shift;
my $oContent = $self->{oContent};
${$oContent}{$strSection}{&INI_COMMENT} = $strComment;
}
####################################################################################################################################
# remove
#
# Remove a value.
####################################################################################################################################
sub remove
{
my $self = shift;
my $strSection = shift;
my $strKey = shift;
my $strSubKey = shift;
my $strValue = shift;
my $oContent = $self->{oContent};
if (defined($strSubKey))
{
delete(${$oContent}{$strSection}{$strKey}{$strSubKey});
}
else
{
delete(${$oContent}{$strSection}{$strKey});
}
}
####################################################################################################################################
# keys
#
# Get a list of keys.
####################################################################################################################################
sub keys
{
my $self = shift;
my $strSection = shift;
my $strKey = shift;
if (defined($strSection))
{
if ($self->test($strSection, $strKey))
{
return sort(keys(%{$self->get($strSection, $strKey)}));
}
my @stryEmptyArray;
return @stryEmptyArray;
}
return sort(keys(%{$self->{oContent}}));
}
####################################################################################################################################
# testBool
#
# Test a value to see if it equals the supplied test boolean value. If no test value is given, tests that it is defined.
####################################################################################################################################
sub testBool
{
my $self = shift;
my $strSection = shift;
my $strValue = shift;
my $strSubValue = shift;
my $bTest = shift;
return $self->test($strSection, $strValue, $strSubValue, defined($bTest) ? ($bTest ? INI_TRUE : INI_FALSE) : undef);
}
####################################################################################################################################
# test
#
# Test a value to see if it equals the supplied test value. If no test value is given, tests that it is defined.
####################################################################################################################################
sub test
{
my $self = shift;
my $strSection = shift;
my $strValue = shift;
my $strSubValue = shift;
my $strTest = shift;
my $strResult = $self->get($strSection, $strValue, $strSubValue, false);
if (defined($strResult))
{
if (defined($strTest))
{
return $strResult eq $strTest ? true : false;
}
return true;
}
return false;
}
1;