2017-11-27 01:43:51 +02:00
|
|
|
####################################################################################################################################
|
|
|
|
# pgBackRest - Reliable PostgreSQL Backup & Restore
|
|
|
|
####################################################################################################################################
|
|
|
|
package pgBackRest::Main;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings FATAL => qw(all);
|
|
|
|
use Carp qw(confess);
|
|
|
|
use English '-no_match_vars';
|
|
|
|
|
|
|
|
# Convert die to confess to capture the stack trace
|
2018-04-13 02:42:26 +02:00
|
|
|
$SIG{__DIE__} = sub {Carp::confess @_};
|
2017-11-27 01:43:51 +02:00
|
|
|
|
|
|
|
use File::Basename qw(dirname);
|
|
|
|
|
2019-06-18 21:19:20 +02:00
|
|
|
use pgBackRest::Backup::Info;
|
2017-11-27 01:43:51 +02:00
|
|
|
use pgBackRest::Common::Exception;
|
|
|
|
use pgBackRest::Common::Lock;
|
|
|
|
use pgBackRest::Common::Log;
|
|
|
|
use pgBackRest::Config::Config;
|
|
|
|
use pgBackRest::Protocol::Helper;
|
2019-06-18 21:19:20 +02:00
|
|
|
use pgBackRest::Protocol::Storage::Helper;
|
2018-08-23 02:05:49 +02:00
|
|
|
use pgBackRest::Storage::Helper;
|
2017-11-27 01:43:51 +02:00
|
|
|
use pgBackRest::Version;
|
|
|
|
|
2018-03-08 23:24:16 +02:00
|
|
|
####################################################################################################################################
|
|
|
|
# Set config JSON separately to avoid exposing secrets in the stack trace
|
|
|
|
####################################################################################################################################
|
|
|
|
my $strConfigJson;
|
2018-04-13 02:42:26 +02:00
|
|
|
my $strConfigBin;
|
|
|
|
my $bConfigLoaded = false;
|
2018-03-08 23:24:16 +02:00
|
|
|
|
2018-04-13 02:42:26 +02:00
|
|
|
sub mainConfigSet
|
2018-03-08 23:24:16 +02:00
|
|
|
{
|
2018-04-13 02:42:26 +02:00
|
|
|
$strConfigBin = shift;
|
2018-03-08 23:24:16 +02:00
|
|
|
$strConfigJson = shift;
|
|
|
|
}
|
|
|
|
|
2017-11-27 01:43:51 +02:00
|
|
|
####################################################################################################################################
|
|
|
|
# Main entry point for the library
|
|
|
|
####################################################################################################################################
|
|
|
|
sub main
|
|
|
|
{
|
2018-01-29 04:37:09 +02:00
|
|
|
my $strCommand = shift;
|
|
|
|
my @stryCommandArg = @_;
|
2017-11-27 01:43:51 +02:00
|
|
|
|
|
|
|
# Run in eval block to catch errors
|
2018-04-13 02:42:26 +02:00
|
|
|
# ------------------------------------------------------------------------------------------------------------------------------
|
2018-07-20 14:03:44 +02:00
|
|
|
my $iResult = 0;
|
2018-07-20 14:11:34 +02:00
|
|
|
my $bErrorC = false;
|
2018-07-20 14:03:44 +02:00
|
|
|
my $strMessage = '';
|
2018-04-13 02:42:26 +02:00
|
|
|
|
2017-11-27 01:43:51 +02:00
|
|
|
eval
|
|
|
|
{
|
2018-03-08 23:24:16 +02:00
|
|
|
# Load command line parameters and config -- pass config by reference to hide secrets more than for efficiency
|
2018-04-13 02:42:26 +02:00
|
|
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
|
|
if (!$bConfigLoaded)
|
|
|
|
{
|
|
|
|
configLoad(undef, $strConfigBin, $strCommand, \$strConfigJson);
|
|
|
|
|
|
|
|
# Set test options
|
|
|
|
if (cfgOptionTest(CFGOPT_TEST) && cfgOption(CFGOPT_TEST))
|
|
|
|
{
|
|
|
|
testSet(cfgOption(CFGOPT_TEST), cfgOption(CFGOPT_TEST_DELAY), cfgOption(CFGOPT_TEST_POINT, false));
|
|
|
|
}
|
2017-11-27 01:43:51 +02:00
|
|
|
|
2018-04-13 02:42:26 +02:00
|
|
|
$bConfigLoaded = true;
|
|
|
|
}
|
|
|
|
else
|
2017-11-27 01:43:51 +02:00
|
|
|
{
|
2018-04-13 02:42:26 +02:00
|
|
|
cfgCommandSet(cfgCommandId($strCommand));
|
2017-11-27 01:43:51 +02:00
|
|
|
}
|
|
|
|
|
2018-04-13 02:42:26 +02:00
|
|
|
# Process remote command
|
|
|
|
# --------------------------------------------------------------------------------------------------------------------------
|
2019-03-29 15:26:33 +02:00
|
|
|
if (cfgCommandTest(CFGCMD_REMOTE))
|
2017-11-27 01:43:51 +02:00
|
|
|
{
|
|
|
|
# Set log levels
|
|
|
|
cfgOptionSet(CFGOPT_LOG_LEVEL_STDERR, PROTOCOL, true);
|
2018-08-23 02:05:49 +02:00
|
|
|
logLevelSet(cfgOption(CFGOPT_LOG_LEVEL_FILE), OFF, cfgOption(CFGOPT_LOG_LEVEL_STDERR));
|
|
|
|
|
|
|
|
logFileSet(
|
|
|
|
storageLocal(),
|
|
|
|
cfgOption(CFGOPT_LOG_PATH) . '/' . (cfgOptionTest(CFGOPT_STANZA) ? cfgOption(CFGOPT_STANZA) : 'all') . '-' .
|
|
|
|
lc(cfgOption(CFGOPT_COMMAND)) . '-' . lc(cfgCommandName(cfgCommandGet())) . '-' .
|
2018-08-31 17:31:13 +02:00
|
|
|
sprintf("%03d", cfgOption(CFGOPT_PROCESS)));
|
2017-11-27 01:43:51 +02:00
|
|
|
|
|
|
|
if (cfgOptionTest(CFGOPT_TYPE, CFGOPTVAL_REMOTE_TYPE_BACKUP) &&
|
|
|
|
!cfgOptionTest(CFGOPT_REPO_TYPE, CFGOPTVAL_REPO_TYPE_S3) &&
|
|
|
|
!-e cfgOption(CFGOPT_REPO_PATH))
|
|
|
|
{
|
2018-02-04 01:27:38 +02:00
|
|
|
confess &log(ERROR,
|
|
|
|
cfgOptionName(CFGOPT_REPO_PATH) . ' \'' . cfgOption(CFGOPT_REPO_PATH) . '\' does not exist',
|
|
|
|
ERROR_PATH_MISSING);
|
2017-11-27 01:43:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Load module dynamically
|
|
|
|
require pgBackRest::Protocol::Remote::Minion;
|
|
|
|
pgBackRest::Protocol::Remote::Minion->import();
|
|
|
|
|
|
|
|
# Create the remote object
|
|
|
|
my $oRemote = new pgBackRest::Protocol::Remote::Minion(
|
|
|
|
cfgOption(CFGOPT_BUFFER_SIZE), cfgOption(CFGOPT_PROTOCOL_TIMEOUT));
|
|
|
|
|
|
|
|
# Process remote requests
|
2018-08-31 17:31:13 +02:00
|
|
|
$oRemote->process(
|
|
|
|
cfgOption(CFGOPT_LOCK_PATH), cfgOption(CFGOPT_COMMAND), cfgOption(CFGOPT_STANZA, false), cfgOption(CFGOPT_PROCESS));
|
2017-11-27 01:43:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Process check command
|
2018-04-13 02:42:26 +02:00
|
|
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
|
|
elsif (cfgCommandTest(CFGCMD_CHECK))
|
2017-11-27 01:43:51 +02:00
|
|
|
{
|
|
|
|
# Load module dynamically
|
|
|
|
require pgBackRest::Check::Check;
|
|
|
|
pgBackRest::Check::Check->import();
|
|
|
|
|
2018-07-20 14:03:44 +02:00
|
|
|
$iResult = new pgBackRest::Check::Check()->process();
|
2017-11-27 01:43:51 +02:00
|
|
|
}
|
2018-04-13 02:42:26 +02:00
|
|
|
else
|
2018-01-03 19:23:33 +02:00
|
|
|
{
|
2018-04-13 02:42:26 +02:00
|
|
|
# Check that the repo path exists
|
|
|
|
require pgBackRest::Protocol::Storage::Helper;
|
|
|
|
pgBackRest::Protocol::Storage::Helper->import();
|
2018-01-03 19:23:33 +02:00
|
|
|
|
2018-04-13 02:42:26 +02:00
|
|
|
if (isRepoLocal() && !cfgOptionTest(CFGOPT_REPO_TYPE, CFGOPTVAL_REPO_TYPE_S3) && !storageRepo()->pathExists(''))
|
2017-11-27 01:43:51 +02:00
|
|
|
{
|
|
|
|
confess &log(ERROR,
|
2018-04-13 02:42:26 +02:00
|
|
|
cfgOptionName(CFGOPT_REPO_PATH) . ' \'' . cfgOption(CFGOPT_REPO_PATH) . '\' does not exist',
|
|
|
|
ERROR_PATH_MISSING);
|
2017-11-27 01:43:51 +02:00
|
|
|
}
|
|
|
|
|
2019-08-20 03:57:14 +02:00
|
|
|
logFileSet(
|
|
|
|
storageLocal(),
|
|
|
|
cfgOption(CFGOPT_LOG_PATH) . '/' . cfgOption(CFGOPT_STANZA) . '-' . lc(cfgCommandName(cfgCommandGet())));
|
|
|
|
|
|
|
|
# Process restore command
|
2018-04-13 02:42:26 +02:00
|
|
|
# ----------------------------------------------------------------------------------------------------------------------
|
2019-08-21 22:26:28 +02:00
|
|
|
if (cfgCommandTest(CFGCMD_RESTORE))
|
2017-11-27 01:43:51 +02:00
|
|
|
{
|
2019-08-20 03:57:14 +02:00
|
|
|
# Check locality
|
|
|
|
if (!isDbLocal())
|
|
|
|
{
|
|
|
|
confess &log(ERROR,
|
|
|
|
cfgCommandName(cfgCommandGet()) . ' command must be run on the PostgreSQL host', ERROR_HOST_INVALID);
|
|
|
|
}
|
|
|
|
|
2017-11-27 01:43:51 +02:00
|
|
|
# Load module dynamically
|
2019-08-20 03:57:14 +02:00
|
|
|
require pgBackRest::Restore;
|
|
|
|
pgBackRest::Restore->import();
|
2017-11-27 01:43:51 +02:00
|
|
|
|
2019-08-20 03:57:14 +02:00
|
|
|
# Do the restore
|
|
|
|
new pgBackRest::Restore()->process();
|
2017-11-27 01:43:51 +02:00
|
|
|
}
|
2018-04-13 02:42:26 +02:00
|
|
|
else
|
2017-11-27 01:43:51 +02:00
|
|
|
{
|
2019-08-20 03:57:14 +02:00
|
|
|
# Check if processes have been stopped
|
|
|
|
lockStopTest();
|
2018-04-13 02:42:26 +02:00
|
|
|
|
2019-08-20 03:57:14 +02:00
|
|
|
# Check locality
|
|
|
|
if (!isRepoLocal())
|
|
|
|
{
|
|
|
|
confess &log(ERROR,
|
|
|
|
cfgCommandName(cfgCommandGet()) . ' command must be run on the repository host', ERROR_HOST_INVALID);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Process backup command
|
2018-04-13 02:42:26 +02:00
|
|
|
# ------------------------------------------------------------------------------------------------------------------
|
2019-08-21 22:26:28 +02:00
|
|
|
if (cfgCommandTest(CFGCMD_BACKUP))
|
2018-04-13 02:42:26 +02:00
|
|
|
{
|
|
|
|
# Load module dynamically
|
2019-08-20 03:57:14 +02:00
|
|
|
require pgBackRest::Backup::Backup;
|
|
|
|
pgBackRest::Backup::Backup->import();
|
2018-04-13 02:42:26 +02:00
|
|
|
|
2019-08-20 03:57:14 +02:00
|
|
|
new pgBackRest::Backup::Backup()->process();
|
2018-04-13 02:42:26 +02:00
|
|
|
}
|
2019-08-20 03:57:14 +02:00
|
|
|
|
|
|
|
# Process expire command
|
|
|
|
# --------------------------------------------------------------------------------------------------------------
|
|
|
|
elsif (cfgCommandTest(CFGCMD_EXPIRE))
|
2018-04-13 02:42:26 +02:00
|
|
|
{
|
2019-08-20 03:57:14 +02:00
|
|
|
new pgBackRest::Backup::Info(storageRepo()->pathGet(STORAGE_REPO_BACKUP));
|
2018-04-13 02:42:26 +02:00
|
|
|
}
|
2017-11-27 01:43:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-13 02:42:26 +02:00
|
|
|
return 1;
|
2017-11-27 01:43:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Check for errors
|
2018-04-13 02:42:26 +02:00
|
|
|
# ------------------------------------------------------------------------------------------------------------------------------
|
2017-11-27 01:43:51 +02:00
|
|
|
or do
|
|
|
|
{
|
|
|
|
# Perl 5.10 seems to have a problem propogating errors up through a large call stack, so in the case that the error arrives
|
|
|
|
# blank just use the last logged error instead. Don't do this in all cases because newer Perls seem to work fine and there
|
|
|
|
# are other errors that could be arriving in $EVAL_ERROR.
|
2018-04-13 02:42:26 +02:00
|
|
|
my $oException = defined($EVAL_ERROR) && length($EVAL_ERROR) > 0 ? $EVAL_ERROR : logErrorLast();
|
|
|
|
|
2018-04-30 23:27:39 +02:00
|
|
|
# If a backrest exception
|
2018-04-13 02:42:26 +02:00
|
|
|
if (isException(\$oException))
|
|
|
|
{
|
2018-07-20 14:03:44 +02:00
|
|
|
$iResult = $oException->code();
|
2018-07-20 14:11:34 +02:00
|
|
|
$bErrorC = $oException->errorC();
|
2018-04-30 23:27:39 +02:00
|
|
|
|
|
|
|
# Only return message if we are in an async process since this will not be logged to the console
|
|
|
|
if (!$bConfigLoaded && cfgOption(CFGOPT_ARCHIVE_ASYNC))
|
|
|
|
{
|
2018-07-20 14:03:44 +02:00
|
|
|
$strMessage = $oException->message();
|
2018-04-30 23:27:39 +02:00
|
|
|
}
|
2018-04-13 02:42:26 +02:00
|
|
|
}
|
|
|
|
# Else a regular Perl exception
|
|
|
|
else
|
|
|
|
{
|
2018-07-20 14:03:44 +02:00
|
|
|
$iResult = ERROR_UNHANDLED;
|
|
|
|
$strMessage =
|
2018-04-13 02:42:26 +02:00
|
|
|
'process terminated due to an unhandled exception' .
|
|
|
|
(defined($oException) ? ":\n${oException}" : ': [exception not defined]');
|
|
|
|
}
|
2017-11-27 01:43:51 +02:00
|
|
|
};
|
|
|
|
|
2018-04-13 02:42:26 +02:00
|
|
|
# Return result and error message if the result is an error
|
2018-07-20 14:11:34 +02:00
|
|
|
return $iResult, $bErrorC, $strMessage;
|
2018-04-13 02:42:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
####################################################################################################################################
|
|
|
|
# Do any cleanup required when the perl process is about to be shut down
|
|
|
|
####################################################################################################################################
|
|
|
|
sub mainCleanup
|
|
|
|
{
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
|
|
my
|
|
|
|
(
|
|
|
|
$strOperation,
|
|
|
|
$iExitCode,
|
|
|
|
) =
|
|
|
|
logDebugParam
|
|
|
|
(
|
|
|
|
__PACKAGE__ . '::mainCleanup', \@_,
|
|
|
|
{name => 'iExitCode', required => false},
|
|
|
|
);
|
|
|
|
|
|
|
|
# Don't fail if the remote can't be closed
|
|
|
|
eval
|
|
|
|
{
|
|
|
|
protocolDestroy(undef, undef, defined($iExitCode) && ($iExitCode == 0 || $iExitCode == 1));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
# this eval exists only to suppress protocol shutdown errors so original error will not be lost
|
|
|
|
or do {};
|
|
|
|
|
|
|
|
# Don't fail if the lock can't be released (it will be freed by the system though the file will remain)
|
|
|
|
eval
|
|
|
|
{
|
|
|
|
lockRelease(false);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
# this eval exists only to suppress lock errors so original error will not be lost
|
|
|
|
or do {};
|
|
|
|
|
|
|
|
# Log return values if any
|
|
|
|
return logDebugReturn($strOperation);
|
2017-11-27 01:43:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
1;
|