2014-12-18 18:42:54 +02:00
|
|
|
####################################################################################################################################
|
|
|
|
# RESTORE MODULE
|
|
|
|
####################################################################################################################################
|
|
|
|
package BackRest::Restore;
|
|
|
|
|
|
|
|
use threads;
|
2014-12-22 18:24:32 +02:00
|
|
|
use threads::shared;
|
|
|
|
use Thread::Queue;
|
2014-12-18 18:42:54 +02:00
|
|
|
use strict;
|
2015-03-03 07:57:20 +02:00
|
|
|
use warnings FATAL => qw(all);
|
|
|
|
use Carp qw(confess);
|
2014-12-18 18:42:54 +02:00
|
|
|
|
2015-01-08 22:43:43 +02:00
|
|
|
use File::Basename qw(dirname);
|
|
|
|
use File::stat qw(lstat);
|
2014-12-18 18:42:54 +02:00
|
|
|
|
|
|
|
use lib dirname($0);
|
2015-08-29 20:20:46 +02:00
|
|
|
use BackRest::Common::Exception;
|
|
|
|
use BackRest::Common::Log;
|
2015-09-08 13:31:24 +02:00
|
|
|
use BackRest::Config::Config;
|
2015-03-12 18:15:19 +02:00
|
|
|
use BackRest::Db;
|
2015-06-14 00:25:49 +02:00
|
|
|
use BackRest::File;
|
|
|
|
use BackRest::Manifest;
|
|
|
|
use BackRest::RestoreFile;
|
2015-08-29 20:20:46 +02:00
|
|
|
|
|
|
|
####################################################################################################################################
|
|
|
|
# Operation constants
|
|
|
|
####################################################################################################################################
|
|
|
|
use constant OP_RESTORE => 'Restore';
|
|
|
|
|
|
|
|
use constant OP_RESTORE_BUILD => OP_RESTORE . '->build';
|
|
|
|
use constant OP_RESTORE_CLEAN => OP_RESTORE . '->clean';
|
2015-10-08 17:43:56 +02:00
|
|
|
use constant OP_RESTORE_DESTROY => OP_RESTORE . '->destroy';
|
2015-08-29 20:20:46 +02:00
|
|
|
use constant OP_RESTORE_MANIFEST_LOAD => OP_RESTORE . '->manifestLoad';
|
|
|
|
use constant OP_RESTORE_MANIFEST_OWNERSHIP_CHECK => OP_RESTORE . '->manifestOwnershipCheck';
|
|
|
|
use constant OP_RESTORE_NEW => OP_RESTORE . '->new';
|
|
|
|
use constant OP_RESTORE_PROCESS => OP_RESTORE . '->process';
|
|
|
|
use constant OP_RESTORE_RECOVERY => OP_RESTORE . '->recovery';
|
2015-03-12 18:15:19 +02:00
|
|
|
|
|
|
|
####################################################################################################################################
|
2015-08-06 22:36:55 +02:00
|
|
|
# File constants
|
2015-03-12 18:15:19 +02:00
|
|
|
####################################################################################################################################
|
2015-08-29 20:20:46 +02:00
|
|
|
# Conf file with settings for recovery after restore
|
|
|
|
use constant FILE_RECOVERY_CONF => 'recovery.conf';
|
|
|
|
|
|
|
|
# Tablespace map introduced in 9.5
|
|
|
|
use constant FILE_TABLESPACE_MAP => 'tablespace_map';
|
2014-12-22 18:24:32 +02:00
|
|
|
|
2014-12-18 18:42:54 +02:00
|
|
|
####################################################################################################################################
|
|
|
|
# CONSTRUCTOR
|
|
|
|
####################################################################################################################################
|
|
|
|
sub new
|
|
|
|
{
|
|
|
|
my $class = shift; # Class name
|
|
|
|
|
|
|
|
# Create the class hash
|
|
|
|
my $self = {};
|
|
|
|
bless $self, $class;
|
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Assign function parameters, defaults, and log debug info
|
2015-10-08 17:43:56 +02:00
|
|
|
my ($strOperation) = logDebugParam(OP_RESTORE_NEW);
|
|
|
|
|
|
|
|
# Initialize default file object
|
|
|
|
$self->{oFile} = new BackRest::File
|
2015-08-29 20:20:46 +02:00
|
|
|
(
|
2015-10-08 17:43:56 +02:00
|
|
|
optionGet(OPTION_STANZA),
|
|
|
|
optionRemoteTypeTest(BACKUP) ? optionGet(OPTION_REPO_REMOTE_PATH) : optionGet(OPTION_REPO_PATH),
|
|
|
|
optionRemoteType(),
|
|
|
|
protocolGet()
|
|
|
|
);
|
2015-08-29 20:20:46 +02:00
|
|
|
|
2014-12-18 18:42:54 +02:00
|
|
|
# Initialize variables
|
2015-08-29 20:20:46 +02:00
|
|
|
$self->{strDbClusterPath} = optionGet(OPTION_DB_PATH);
|
|
|
|
$self->{strBackupSet} = optionGet(OPTION_SET);
|
|
|
|
|
|
|
|
# Return from function and log return values if any
|
|
|
|
return logDebugReturn
|
|
|
|
(
|
|
|
|
$strOperation,
|
|
|
|
{name => 'self', value => $self}
|
|
|
|
);
|
2014-12-18 18:42:54 +02:00
|
|
|
}
|
|
|
|
|
2015-10-08 17:43:56 +02:00
|
|
|
####################################################################################################################################
|
|
|
|
# DESTROY
|
|
|
|
####################################################################################################################################
|
|
|
|
sub DESTROY
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
|
|
my
|
|
|
|
(
|
|
|
|
$strOperation
|
|
|
|
) =
|
|
|
|
logDebugParam
|
|
|
|
(
|
|
|
|
OP_RESTORE_DESTROY
|
|
|
|
);
|
|
|
|
|
|
|
|
undef($self->{oFile});
|
|
|
|
|
|
|
|
# Return from function and log return values if any
|
|
|
|
return logDebugReturn
|
|
|
|
(
|
|
|
|
$strOperation
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-12-30 18:59:57 +02:00
|
|
|
####################################################################################################################################
|
2015-08-29 20:20:46 +02:00
|
|
|
# manifestOwnershipCheck
|
2014-12-30 18:59:57 +02:00
|
|
|
#
|
|
|
|
# Checks the users and groups that exist in the manifest and emits warnings for ownership that cannot be set properly, either
|
|
|
|
# because the current user does not have permissions or because the user/group does not exist.
|
|
|
|
####################################################################################################################################
|
2015-08-29 20:20:46 +02:00
|
|
|
sub manifestOwnershipCheck
|
2014-12-30 18:59:57 +02:00
|
|
|
{
|
|
|
|
my $self = shift; # Class hash
|
2015-08-29 20:20:46 +02:00
|
|
|
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
|
|
my
|
|
|
|
(
|
|
|
|
$strOperation,
|
|
|
|
$oManifest # Backup manifest
|
|
|
|
) =
|
|
|
|
logDebugParam
|
|
|
|
(
|
|
|
|
OP_RESTORE_MANIFEST_OWNERSHIP_CHECK, \@_,
|
|
|
|
{name => 'oManifest', trace => true}
|
|
|
|
);
|
2014-12-30 18:59:57 +02:00
|
|
|
|
|
|
|
# Create hashes to track valid/invalid users/groups
|
|
|
|
my %oOwnerHash = ();
|
|
|
|
|
|
|
|
# Create hash for each type and owner to be checked
|
2014-12-30 22:41:43 +02:00
|
|
|
my $strDefaultUser = getpwuid($<);
|
|
|
|
my $strDefaultGroup = getgrgid($();
|
|
|
|
|
2015-01-26 21:59:58 +02:00
|
|
|
my %oFileTypeHash = (&MANIFEST_PATH => true, &MANIFEST_LINK => true, &MANIFEST_FILE => true);
|
2015-01-20 21:13:35 +02:00
|
|
|
my %oOwnerTypeHash = (&MANIFEST_SUBKEY_USER => $strDefaultUser, &MANIFEST_SUBKEY_GROUP => $strDefaultGroup);
|
2014-12-30 18:59:57 +02:00
|
|
|
|
2014-12-31 18:20:46 +02:00
|
|
|
# Loop through owner types (user, group)
|
2014-12-30 22:41:43 +02:00
|
|
|
foreach my $strOwnerType (sort (keys %oOwnerTypeHash))
|
2014-12-30 18:59:57 +02:00
|
|
|
{
|
2014-12-31 18:20:46 +02:00
|
|
|
# Loop through all backup paths (base and tablespaces)
|
2015-01-20 21:13:35 +02:00
|
|
|
foreach my $strPathKey ($oManifest->keys(MANIFEST_SECTION_BACKUP_PATH))
|
2014-12-30 18:59:57 +02:00
|
|
|
{
|
2014-12-31 18:20:46 +02:00
|
|
|
# Loop through types (path, link, file)
|
2014-12-30 18:59:57 +02:00
|
|
|
foreach my $strFileType (sort (keys %oFileTypeHash))
|
|
|
|
{
|
2015-01-20 21:13:35 +02:00
|
|
|
my $strSection = "${strPathKey}:${strFileType}";
|
|
|
|
|
2014-12-30 18:59:57 +02:00
|
|
|
# Get users and groups for paths
|
2015-01-20 21:13:35 +02:00
|
|
|
if ($oManifest->test($strSection))
|
2014-12-30 18:59:57 +02:00
|
|
|
{
|
2015-01-20 21:13:35 +02:00
|
|
|
foreach my $strName ($oManifest->keys($strSection))
|
2014-12-30 18:59:57 +02:00
|
|
|
{
|
2015-01-20 21:13:35 +02:00
|
|
|
my $strOwner = $oManifest->get($strSection, $strName, $strOwnerType);
|
2014-12-31 19:28:16 +02:00
|
|
|
|
|
|
|
# If root then test to see if the user/group is valid
|
|
|
|
if ($< == 0)
|
2014-12-30 18:59:57 +02:00
|
|
|
{
|
2014-12-31 19:28:16 +02:00
|
|
|
# If the owner has not been tested yet then test it
|
|
|
|
if (!defined($oOwnerHash{$strOwnerType}{$strOwner}))
|
2014-12-31 18:20:46 +02:00
|
|
|
{
|
2014-12-31 19:28:16 +02:00
|
|
|
my $strOwnerId;
|
|
|
|
|
|
|
|
if ($strOwnerType eq 'user')
|
|
|
|
{
|
|
|
|
$strOwnerId = getpwnam($strOwner);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$strOwnerId = getgrnam($strOwner);
|
|
|
|
}
|
|
|
|
|
|
|
|
$oOwnerHash{$strOwnerType}{$strOwner} = defined($strOwnerId) ? true : false;
|
2014-12-31 18:20:46 +02:00
|
|
|
}
|
2014-12-31 19:28:16 +02:00
|
|
|
|
|
|
|
if (!$oOwnerHash{$strOwnerType}{$strOwner})
|
2014-12-31 18:20:46 +02:00
|
|
|
{
|
2015-01-20 21:13:35 +02:00
|
|
|
$oManifest->set($strSection, $strName, $strOwnerType, $oOwnerTypeHash{$strOwnerType});
|
2014-12-31 18:20:46 +02:00
|
|
|
}
|
2014-12-30 18:59:57 +02:00
|
|
|
}
|
2014-12-31 19:28:16 +02:00
|
|
|
# Else set user/group to current user/group
|
|
|
|
else
|
2014-12-30 18:59:57 +02:00
|
|
|
{
|
2014-12-31 19:28:16 +02:00
|
|
|
if ($strOwner ne $oOwnerTypeHash{$strOwnerType})
|
|
|
|
{
|
|
|
|
$oOwnerHash{$strOwnerType}{$strOwner} = false;
|
2015-01-20 21:13:35 +02:00
|
|
|
$oManifest->set($strSection, $strName, $strOwnerType, $oOwnerTypeHash{$strOwnerType});
|
2014-12-31 19:28:16 +02:00
|
|
|
}
|
2014-12-30 18:59:57 +02:00
|
|
|
}
|
2015-01-20 21:13:35 +02:00
|
|
|
}
|
2014-12-30 18:59:57 +02:00
|
|
|
}
|
|
|
|
}
|
2014-12-30 22:41:43 +02:00
|
|
|
}
|
2014-12-30 18:59:57 +02:00
|
|
|
|
2014-12-30 22:41:43 +02:00
|
|
|
# Output warning for any invalid owners
|
2014-12-31 19:28:16 +02:00
|
|
|
if (defined($oOwnerHash{$strOwnerType}))
|
2014-12-30 22:41:43 +02:00
|
|
|
{
|
2015-06-21 18:06:13 +02:00
|
|
|
foreach my $strOwner (sort (keys(%{$oOwnerHash{$strOwnerType}})))
|
2014-12-31 18:20:46 +02:00
|
|
|
{
|
2014-12-31 19:28:16 +02:00
|
|
|
if (!$oOwnerHash{$strOwnerType}{$strOwner})
|
|
|
|
{
|
|
|
|
&log(WARN, "${strOwnerType} ${strOwner} " . ($< == 0 ? "does not exist" : "cannot be set") .
|
|
|
|
", changed to $oOwnerTypeHash{$strOwnerType}");
|
|
|
|
}
|
2014-12-31 18:20:46 +02:00
|
|
|
}
|
2014-12-30 18:59:57 +02:00
|
|
|
}
|
|
|
|
}
|
2015-08-29 20:20:46 +02:00
|
|
|
|
|
|
|
# Return from function and log return values if any
|
|
|
|
return logDebugReturn($strOperation);
|
2014-12-30 18:59:57 +02:00
|
|
|
}
|
|
|
|
|
2014-12-18 18:42:54 +02:00
|
|
|
####################################################################################################################################
|
2015-08-29 20:20:46 +02:00
|
|
|
# manifestLoad
|
2014-12-23 19:48:25 +02:00
|
|
|
#
|
2014-12-23 22:03:06 +02:00
|
|
|
# Loads the backup manifest and performs requested tablespace remaps.
|
2014-12-18 18:42:54 +02:00
|
|
|
####################################################################################################################################
|
2015-08-29 20:20:46 +02:00
|
|
|
sub manifestLoad
|
2014-12-18 18:42:54 +02:00
|
|
|
{
|
2014-12-23 22:03:06 +02:00
|
|
|
my $self = shift; # Class hash
|
2014-12-23 19:48:25 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
|
|
my ($strOperation) = logDebugParam (OP_RESTORE_MANIFEST_LOAD);
|
|
|
|
|
|
|
|
# Error if the backup set does not exist
|
|
|
|
if (!$self->{oFile}->exists(PATH_BACKUP_CLUSTER, $self->{strBackupSet}))
|
2014-12-18 18:42:54 +02:00
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
confess &log(ERROR, 'backup ' . $self->{strBackupSet} . ' does not exist');
|
|
|
|
}
|
2014-12-18 18:42:54 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Copy the backup manifest to the db cluster path
|
|
|
|
$self->{oFile}->copy(PATH_BACKUP_CLUSTER, $self->{strBackupSet} . '/' . FILE_MANIFEST,
|
|
|
|
PATH_DB_ABSOLUTE, $self->{strDbClusterPath} . '/' . FILE_MANIFEST);
|
2014-12-24 01:52:38 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Load the manifest into a hash
|
|
|
|
my $oManifest = new BackRest::Manifest($self->{oFile}->pathGet(PATH_DB_ABSOLUTE,
|
|
|
|
$self->{strDbClusterPath} . '/' . FILE_MANIFEST));
|
2014-12-31 19:28:16 +02:00
|
|
|
|
2016-01-21 01:34:42 +02:00
|
|
|
# Log the backup set to restore
|
|
|
|
&log(INFO, "restore backup set " . $oManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LABEL));
|
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# If backup is latest then set it equal to backup label, else verify that requested backup and label match
|
|
|
|
my $strBackupLabel = $oManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LABEL);
|
2014-12-24 01:52:38 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
if ($self->{strBackupSet} eq OPTION_DEFAULT_RESTORE_SET)
|
|
|
|
{
|
|
|
|
$self->{strBackupSet} = $strBackupLabel;
|
|
|
|
}
|
|
|
|
elsif ($self->{strBackupSet} ne $strBackupLabel)
|
|
|
|
{
|
|
|
|
confess &log(ASSERT, "request backup $self->{strBackupSet} and label ${strBackupLabel} do not match " .
|
|
|
|
' - this indicates some sort of corruption (at the very least paths have been renamed)');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($self->{strDbClusterPath} ne $oManifest->get(MANIFEST_SECTION_BACKUP_PATH, MANIFEST_KEY_BASE, MANIFEST_SUBKEY_PATH))
|
|
|
|
{
|
|
|
|
&log(INFO, 'remap base path to ' . $self->{strDbClusterPath});
|
|
|
|
$oManifest->set(MANIFEST_SECTION_BACKUP_PATH, MANIFEST_KEY_BASE, MANIFEST_SUBKEY_PATH, $self->{strDbClusterPath});
|
|
|
|
}
|
2015-01-20 21:13:35 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# If no tablespaces are requested
|
|
|
|
if (!optionGet(OPTION_TABLESPACE))
|
|
|
|
{
|
|
|
|
foreach my $strPathKey ($oManifest->keys(MANIFEST_SECTION_BACKUP_PATH))
|
2015-05-09 00:34:27 +02:00
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
if ($oManifest->test(MANIFEST_SECTION_BACKUP_PATH, $strPathKey, MANIFEST_SUBKEY_LINK))
|
2015-05-09 00:34:27 +02:00
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
my $strTablespaceKey =
|
|
|
|
$oManifest->get(MANIFEST_SECTION_BACKUP_PATH, $strPathKey, MANIFEST_SUBKEY_LINK);
|
|
|
|
my $strTablespaceLink = "pg_tblspc/${strTablespaceKey}";
|
|
|
|
my $strTablespacePath =
|
|
|
|
$oManifest->get(MANIFEST_SECTION_BACKUP_PATH, MANIFEST_KEY_BASE, MANIFEST_SUBKEY_PATH) .
|
|
|
|
"/${strTablespaceLink}";
|
|
|
|
|
|
|
|
$oManifest->set(MANIFEST_SECTION_BACKUP_PATH, $strPathKey, MANIFEST_SUBKEY_PATH, $strTablespacePath);
|
|
|
|
|
|
|
|
$oManifest->remove('base:link', $strTablespaceLink);
|
|
|
|
$oManifest->set('base:path', $strTablespaceLink, MANIFEST_SUBKEY_GROUP,
|
|
|
|
$oManifest->get('base:path', '.', MANIFEST_SUBKEY_GROUP));
|
|
|
|
$oManifest->set('base:path', $strTablespaceLink, MANIFEST_SUBKEY_USER,
|
|
|
|
$oManifest->get('base:path', '.', MANIFEST_SUBKEY_USER));
|
|
|
|
$oManifest->set('base:path', $strTablespaceLink, MANIFEST_SUBKEY_MODE,
|
|
|
|
$oManifest->get('base:path', '.', MANIFEST_SUBKEY_MODE));
|
|
|
|
|
|
|
|
&log(INFO, "remap tablespace ${strTablespaceKey} to ${strTablespacePath}");
|
2015-05-09 00:34:27 +02:00
|
|
|
}
|
|
|
|
}
|
2015-08-29 20:20:46 +02:00
|
|
|
}
|
|
|
|
# If tablespaces have been remapped, update the manifest
|
|
|
|
elsif (optionTest(OPTION_RESTORE_TABLESPACE_MAP))
|
|
|
|
{
|
|
|
|
my $oRemapRef = optionGet(OPTION_RESTORE_TABLESPACE_MAP);
|
|
|
|
|
|
|
|
foreach my $strTablespaceKey (sort(keys(%$oRemapRef)))
|
2015-01-23 03:11:33 +02:00
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
my $strRemapPath = $$oRemapRef{$strTablespaceKey};
|
|
|
|
my $strPathKey = "tablespace/${strTablespaceKey}";
|
2015-01-23 03:11:33 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Make sure that the tablespace exists in the manifest
|
|
|
|
if (!$oManifest->test(MANIFEST_SECTION_BACKUP_PATH, $strPathKey, MANIFEST_SUBKEY_LINK))
|
|
|
|
{
|
|
|
|
confess &log(ERROR, "cannot remap invalid tablespace ${strTablespaceKey} to ${strRemapPath}");
|
|
|
|
}
|
2015-01-23 03:11:33 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Remap the tablespace in the manifest
|
|
|
|
&log(INFO, "remap tablespace ${strTablespaceKey} to ${strRemapPath}");
|
2015-01-23 03:11:33 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
my $strTablespaceLink = $oManifest->get(MANIFEST_SECTION_BACKUP_PATH, $strPathKey, MANIFEST_SUBKEY_LINK);
|
2015-01-23 03:11:33 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
$oManifest->set(MANIFEST_SECTION_BACKUP_PATH, $strPathKey, MANIFEST_SUBKEY_PATH, $strRemapPath);
|
|
|
|
$oManifest->set('base:link', "pg_tblspc/${strTablespaceLink}", MANIFEST_SUBKEY_DESTINATION, $strRemapPath);
|
2015-01-23 03:11:33 +02:00
|
|
|
}
|
2014-12-18 18:42:54 +02:00
|
|
|
}
|
2014-12-30 18:59:57 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
$self->manifestOwnershipCheck($oManifest);
|
|
|
|
|
|
|
|
# Return from function and log return values if any
|
|
|
|
return logDebugReturn
|
|
|
|
(
|
|
|
|
$strOperation,
|
|
|
|
{name => 'oManifest', value => $oManifest, trace => true}
|
|
|
|
);
|
2014-12-23 22:03:06 +02:00
|
|
|
}
|
2014-12-19 19:49:56 +02:00
|
|
|
|
2014-12-23 22:03:06 +02:00
|
|
|
####################################################################################################################################
|
2015-08-29 20:20:46 +02:00
|
|
|
# clean
|
2014-12-23 22:03:06 +02:00
|
|
|
#
|
|
|
|
# Checks that the restore paths are empty, or if --force was used then it cleans files/paths/links from the restore directories that
|
|
|
|
# are not present in the manifest.
|
|
|
|
####################################################################################################################################
|
|
|
|
sub clean
|
|
|
|
{
|
|
|
|
my $self = shift; # Class hash
|
2015-08-29 20:20:46 +02:00
|
|
|
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
|
|
my
|
|
|
|
(
|
|
|
|
$strOperation,
|
|
|
|
$oManifest # Backup manifest
|
|
|
|
|
|
|
|
) =
|
|
|
|
logDebugParam
|
|
|
|
(
|
|
|
|
OP_RESTORE_CLEAN, \@_,
|
|
|
|
{name => 'oManifest', trace => true}
|
|
|
|
);
|
2014-12-22 18:24:32 +02:00
|
|
|
|
2015-01-02 21:15:15 +02:00
|
|
|
# Track if files/links/paths where removed
|
2015-01-26 21:59:58 +02:00
|
|
|
my %oRemoveHash = (&MANIFEST_FILE => 0, &MANIFEST_PATH => 0, &MANIFEST_LINK => 0);
|
2015-01-02 21:15:15 +02:00
|
|
|
|
2014-12-19 19:49:56 +02:00
|
|
|
# Check each restore directory in the manifest and make sure that it exists and is empty.
|
|
|
|
# The --force option can be used to override the empty requirement.
|
2015-01-07 17:59:43 +02:00
|
|
|
foreach my $strPathKey ($oManifest->keys(MANIFEST_SECTION_BACKUP_PATH))
|
2014-12-19 19:49:56 +02:00
|
|
|
{
|
2015-06-14 00:25:49 +02:00
|
|
|
my $strPath = $oManifest->get(MANIFEST_SECTION_BACKUP_PATH, $strPathKey, MANIFEST_SUBKEY_PATH);
|
2016-02-06 06:03:29 +02:00
|
|
|
my $bTablespace = $oManifest->test(MANIFEST_SECTION_BACKUP_PATH, $strPathKey, MANIFEST_SUBKEY_LINK);
|
2014-12-19 19:49:56 +02:00
|
|
|
|
2016-02-02 21:33:15 +02:00
|
|
|
# Update path if this is a tablespace
|
2016-02-06 06:03:29 +02:00
|
|
|
if ($bTablespace && $oManifest->numericGet(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION) >= 9.0)
|
2016-02-02 21:33:15 +02:00
|
|
|
{
|
|
|
|
$strPath .= '/' . $oManifest->tablespacePathGet();
|
|
|
|
}
|
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
&log(INFO, "check/clean db path ${strPath}");
|
2014-12-21 17:11:17 +02:00
|
|
|
|
2015-07-16 17:12:48 +02:00
|
|
|
if (!$self->{oFile}->exists(PATH_DB_ABSOLUTE, $strPath))
|
2014-12-19 19:49:56 +02:00
|
|
|
{
|
2015-05-09 00:34:27 +02:00
|
|
|
next;
|
2014-12-19 19:49:56 +02:00
|
|
|
}
|
|
|
|
|
2014-12-23 22:03:06 +02:00
|
|
|
# Load path manifest so it can be compared to deleted files/paths/links that are not in the backup
|
2014-12-19 19:49:56 +02:00
|
|
|
my %oPathManifest;
|
2014-12-23 19:48:25 +02:00
|
|
|
$self->{oFile}->manifest(PATH_DB_ABSOLUTE, $strPath, \%oPathManifest);
|
2014-12-19 19:49:56 +02:00
|
|
|
|
2015-06-21 18:06:13 +02:00
|
|
|
foreach my $strName (sort {$b cmp $a} (keys(%{$oPathManifest{name}})))
|
2014-12-19 19:49:56 +02:00
|
|
|
{
|
|
|
|
# Skip the root path
|
2016-02-06 06:03:29 +02:00
|
|
|
if ($strName eq '.' || (!$bTablespace && $strName eq FILE_MANIFEST))
|
2014-12-19 19:49:56 +02:00
|
|
|
{
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
|
|
|
# If force was not specified then error if any file is found
|
2015-08-29 20:20:46 +02:00
|
|
|
if (!optionGet(OPTION_FORCE) && !optionGet(OPTION_DELTA))
|
2014-12-19 19:49:56 +02:00
|
|
|
{
|
2015-01-24 01:28:39 +02:00
|
|
|
confess &log(ERROR, "cannot restore to path '${strPath}' that contains files - " .
|
|
|
|
'try using --delta if this is what you intended', ERROR_RESTORE_PATH_NOT_EMPTY);
|
2014-12-19 19:49:56 +02:00
|
|
|
}
|
|
|
|
|
2014-12-21 17:11:17 +02:00
|
|
|
my $strFile = "${strPath}/${strName}";
|
|
|
|
|
2014-12-19 19:49:56 +02:00
|
|
|
# Determine the file/path/link type
|
2015-01-26 21:59:58 +02:00
|
|
|
my $strType = MANIFEST_FILE;
|
2014-12-19 19:49:56 +02:00
|
|
|
|
|
|
|
if ($oPathManifest{name}{$strName}{type} eq 'd')
|
|
|
|
{
|
2015-01-26 21:59:58 +02:00
|
|
|
$strType = MANIFEST_PATH;
|
2014-12-19 19:49:56 +02:00
|
|
|
}
|
|
|
|
elsif ($oPathManifest{name}{$strName}{type} eq 'l')
|
|
|
|
{
|
2015-01-26 21:59:58 +02:00
|
|
|
$strType = MANIFEST_LINK;
|
2014-12-19 19:49:56 +02:00
|
|
|
}
|
|
|
|
|
2015-01-07 17:59:43 +02:00
|
|
|
# Build the section name
|
|
|
|
my $strSection = "${strPathKey}:${strType}";
|
|
|
|
|
2014-12-21 17:11:17 +02:00
|
|
|
# Check to see if the file/path/link exists in the manifest
|
2015-01-07 17:59:43 +02:00
|
|
|
if ($oManifest->test($strSection, $strName))
|
2014-12-19 19:49:56 +02:00
|
|
|
{
|
2015-01-08 19:04:56 +02:00
|
|
|
my $strUser = $oManifest->get($strSection, $strName, MANIFEST_SUBKEY_USER);
|
|
|
|
my $strGroup = $oManifest->get($strSection, $strName, MANIFEST_SUBKEY_GROUP);
|
2014-12-21 17:11:17 +02:00
|
|
|
|
|
|
|
# If ownership does not match, fix it
|
|
|
|
if ($strUser ne $oPathManifest{name}{$strName}{user} ||
|
|
|
|
$strGroup ne $oPathManifest{name}{$strName}{group})
|
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
&log(INFO, "set ownership ${strUser}:${strGroup} on ${strFile}");
|
2014-12-21 17:11:17 +02:00
|
|
|
|
2015-01-07 19:58:21 +02:00
|
|
|
$self->{oFile}->owner(PATH_DB_ABSOLUTE, $strFile, $strUser, $strGroup);
|
2014-12-21 17:11:17 +02:00
|
|
|
}
|
2014-12-19 19:49:56 +02:00
|
|
|
|
2014-12-21 17:11:17 +02:00
|
|
|
# If a link does not have the same destination, then delete it (it will be recreated later)
|
2015-01-26 21:59:58 +02:00
|
|
|
if ($strType eq MANIFEST_LINK)
|
2014-12-21 17:11:17 +02:00
|
|
|
{
|
2015-01-26 21:59:58 +02:00
|
|
|
if ($strType eq MANIFEST_LINK && $oManifest->get($strSection, $strName, MANIFEST_SUBKEY_DESTINATION) ne
|
2015-01-07 17:59:43 +02:00
|
|
|
$oPathManifest{name}{$strName}{link_destination})
|
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
&log(INFO, "remove link ${strFile} - destination changed");
|
|
|
|
|
|
|
|
unlink($strFile)
|
|
|
|
or confess &log(ERROR, "unable to remove link ${strFile}");
|
2015-01-07 17:59:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
# Else if file/path mode does not match, fix it
|
|
|
|
else
|
|
|
|
{
|
2015-01-08 19:04:56 +02:00
|
|
|
my $strMode = $oManifest->get($strSection, $strName, MANIFEST_SUBKEY_MODE);
|
2015-01-07 17:59:43 +02:00
|
|
|
|
2015-03-23 23:17:43 +02:00
|
|
|
if ($strType ne MANIFEST_LINK && $strMode ne $oPathManifest{name}{$strName}{mode})
|
2015-01-07 17:59:43 +02:00
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
&log(INFO, "set mode ${strMode} on ${strFile}");
|
2015-01-07 17:59:43 +02:00
|
|
|
|
|
|
|
chmod(oct($strMode), $strFile)
|
2015-08-29 20:20:46 +02:00
|
|
|
or confess 'unable to set mode ${strMode} on ${strFile}';
|
2015-01-07 17:59:43 +02:00
|
|
|
}
|
2014-12-21 17:11:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
# If it does not then remove it
|
|
|
|
else
|
|
|
|
{
|
2014-12-19 19:49:56 +02:00
|
|
|
# If a path then remove it, all the files should have already been deleted since we are going in reverse order
|
2015-01-26 21:59:58 +02:00
|
|
|
if ($strType eq MANIFEST_PATH)
|
2014-12-19 19:49:56 +02:00
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
&log(INFO, "remove path ${strFile}");
|
2014-12-21 17:11:17 +02:00
|
|
|
rmdir($strFile) or confess &log(ERROR, "unable to delete path ${strFile}, is it empty?");
|
2014-12-19 19:49:56 +02:00
|
|
|
}
|
2014-12-21 17:11:17 +02:00
|
|
|
# Else delete a file/link
|
2014-12-19 19:49:56 +02:00
|
|
|
else
|
|
|
|
{
|
2015-05-07 23:56:56 +02:00
|
|
|
# Delete only if this is not the recovery.conf file. This is in case the user wants the recovery.conf file
|
2015-01-26 21:59:58 +02:00
|
|
|
# preserved. It will be written/deleted/preserved as needed in recovery().
|
|
|
|
if (!($strName eq FILE_RECOVERY_CONF && $strType eq MANIFEST_FILE))
|
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
&log(INFO, "remove file/link ${strFile}");
|
2015-01-26 21:59:58 +02:00
|
|
|
unlink($strFile) or confess &log(ERROR, "unable to delete file/link ${strFile}");
|
|
|
|
}
|
2014-12-21 17:11:17 +02:00
|
|
|
}
|
2015-01-02 21:15:15 +02:00
|
|
|
|
|
|
|
$oRemoveHash{$strType} += 1;
|
2014-12-21 17:11:17 +02:00
|
|
|
}
|
|
|
|
}
|
2014-12-23 22:03:06 +02:00
|
|
|
}
|
2015-01-02 21:15:15 +02:00
|
|
|
|
|
|
|
# Loop through types (path, link, file) and emit info if any were removed
|
2015-08-29 20:20:46 +02:00
|
|
|
my @stryMessage;
|
|
|
|
|
2015-01-02 21:15:15 +02:00
|
|
|
foreach my $strFileType (sort (keys %oRemoveHash))
|
|
|
|
{
|
|
|
|
if ($oRemoveHash{$strFileType} > 0)
|
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
push(@stryMessage, "$oRemoveHash{$strFileType} ${strFileType}" . ($oRemoveHash{$strFileType} > 1 ? 's' : ''));
|
2015-01-02 21:15:15 +02:00
|
|
|
}
|
|
|
|
}
|
2015-08-29 20:20:46 +02:00
|
|
|
|
|
|
|
if (@stryMessage)
|
|
|
|
{
|
|
|
|
&log(INFO, 'cleanup removed ' . join(', ', @stryMessage));
|
|
|
|
}
|
|
|
|
|
|
|
|
# Return from function and log return values if any
|
|
|
|
return logDebugReturn($strOperation);
|
2014-12-23 22:03:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
####################################################################################################################################
|
2015-08-29 20:20:46 +02:00
|
|
|
# build
|
2014-12-23 22:03:06 +02:00
|
|
|
#
|
|
|
|
# Creates missing paths and links and corrects ownership/mode on existing paths and links.
|
|
|
|
####################################################################################################################################
|
|
|
|
sub build
|
|
|
|
{
|
2015-01-07 17:59:43 +02:00
|
|
|
my $self = shift; # Class hash
|
2015-08-29 20:20:46 +02:00
|
|
|
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
|
|
my
|
|
|
|
(
|
|
|
|
$strOperation,
|
|
|
|
$oManifest # Backup manifest
|
|
|
|
|
|
|
|
) =
|
|
|
|
logDebugParam
|
|
|
|
(
|
|
|
|
OP_RESTORE_BUILD, \@_,
|
|
|
|
{name => 'oManifest', trace => true}
|
|
|
|
);
|
2014-12-23 22:03:06 +02:00
|
|
|
|
|
|
|
# Build paths/links in each restore path
|
2015-01-20 21:13:35 +02:00
|
|
|
foreach my $strSectionPathKey ($oManifest->keys(MANIFEST_SECTION_BACKUP_PATH))
|
2014-12-23 22:03:06 +02:00
|
|
|
{
|
2016-02-02 21:33:15 +02:00
|
|
|
my $strSection = "${strSectionPathKey}:path";
|
2015-06-14 00:25:49 +02:00
|
|
|
my $strSectionPath = $oManifest->get(MANIFEST_SECTION_BACKUP_PATH, $strSectionPathKey, MANIFEST_SUBKEY_PATH);
|
2014-12-21 17:11:17 +02:00
|
|
|
|
2016-02-02 21:33:15 +02:00
|
|
|
# Update path if this is a tablespace
|
|
|
|
if ($oManifest->numericGet(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION) >= 9.0 &&
|
|
|
|
$oManifest->test(MANIFEST_SECTION_BACKUP_PATH, $strSectionPathKey, MANIFEST_SUBKEY_LINK))
|
|
|
|
{
|
|
|
|
$strSectionPath .= '/' . $oManifest->tablespacePathGet();
|
2015-01-20 21:13:35 +02:00
|
|
|
|
2016-02-02 21:33:15 +02:00
|
|
|
# Create the tablespace if it doesn't exist
|
|
|
|
if (!$self->{oFile}->exists(PATH_DB_ABSOLUTE, $strSectionPath))
|
|
|
|
{
|
|
|
|
$self->{oFile}->pathCreate(PATH_DB_ABSOLUTE, $strSectionPath,
|
|
|
|
$oManifest->get($strSection, '.', MANIFEST_SUBKEY_MODE));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Create all paths in the manifest that do not already exist
|
2015-01-20 21:13:35 +02:00
|
|
|
foreach my $strName ($oManifest->keys($strSection))
|
2014-12-21 17:11:17 +02:00
|
|
|
{
|
|
|
|
# Skip the root path
|
|
|
|
if ($strName eq '.')
|
|
|
|
{
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Create the Path
|
2015-01-20 21:13:35 +02:00
|
|
|
my $strPath = "${strSectionPath}/${strName}";
|
|
|
|
|
|
|
|
if (!$self->{oFile}->exists(PATH_DB_ABSOLUTE, $strPath))
|
2014-12-30 22:41:43 +02:00
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
$self->{oFile}->pathCreate(PATH_DB_ABSOLUTE, $strPath,
|
2015-01-20 21:13:35 +02:00
|
|
|
$oManifest->get($strSection, $strName, MANIFEST_SUBKEY_MODE));
|
2014-12-30 22:41:43 +02:00
|
|
|
}
|
2014-12-21 17:11:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Create all links in the manifest that do not already exist
|
2015-01-20 21:13:35 +02:00
|
|
|
$strSection = "${strSectionPathKey}:link";
|
|
|
|
|
2015-01-23 03:11:33 +02:00
|
|
|
if ($oManifest->test($strSection))
|
2014-12-21 17:11:17 +02:00
|
|
|
{
|
2015-01-23 03:11:33 +02:00
|
|
|
foreach my $strName ($oManifest->keys($strSection))
|
2014-12-21 17:11:17 +02:00
|
|
|
{
|
2015-01-23 03:11:33 +02:00
|
|
|
my $strLink = "${strSectionPath}/${strName}";
|
|
|
|
|
|
|
|
if (!$self->{oFile}->exists(PATH_DB_ABSOLUTE, $strLink))
|
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
$self->{oFile}->linkCreate(PATH_DB_ABSOLUTE,
|
2015-01-23 03:11:33 +02:00
|
|
|
$oManifest->get($strSection, $strName, MANIFEST_SUBKEY_DESTINATION),
|
|
|
|
PATH_DB_ABSOLUTE, $strLink);
|
|
|
|
}
|
2014-12-19 19:49:56 +02:00
|
|
|
}
|
|
|
|
}
|
2014-12-23 22:03:06 +02:00
|
|
|
}
|
2015-05-09 00:34:27 +02:00
|
|
|
|
|
|
|
# Make sure that all paths required for the restore now exist
|
|
|
|
foreach my $strPathKey ($oManifest->keys(MANIFEST_SECTION_BACKUP_PATH))
|
|
|
|
{
|
2015-06-14 00:25:49 +02:00
|
|
|
my $strPath = $oManifest->get(MANIFEST_SECTION_BACKUP_PATH, $strPathKey, MANIFEST_SUBKEY_PATH);
|
2015-05-09 00:34:27 +02:00
|
|
|
|
|
|
|
if (!$self->{oFile}->exists(PATH_DB_ABSOLUTE, $strPath))
|
|
|
|
{
|
|
|
|
confess &log(ERROR, "required db path '${strPath}' does not exist");
|
|
|
|
}
|
|
|
|
}
|
2015-08-29 20:20:46 +02:00
|
|
|
|
|
|
|
# Return from function and log return values if any
|
|
|
|
return logDebugReturn($strOperation);
|
2014-12-23 22:03:06 +02:00
|
|
|
}
|
2014-12-22 18:24:32 +02:00
|
|
|
|
2015-01-26 21:59:58 +02:00
|
|
|
####################################################################################################################################
|
2015-08-29 20:20:46 +02:00
|
|
|
# recovery
|
2015-01-26 21:59:58 +02:00
|
|
|
#
|
|
|
|
# Creates the recovery.conf file.
|
|
|
|
####################################################################################################################################
|
|
|
|
sub recovery
|
|
|
|
{
|
|
|
|
my $self = shift; # Class hash
|
2016-01-14 03:35:12 +02:00
|
|
|
my $fDbVersion = shift; # Version to restore
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
|
|
my ($strOperation) = logDebugParam (OP_RESTORE_RECOVERY);
|
|
|
|
|
2015-01-26 21:59:58 +02:00
|
|
|
# Create recovery.conf path/file
|
|
|
|
my $strRecoveryConf = $self->{strDbClusterPath} . '/' . FILE_RECOVERY_CONF;
|
|
|
|
|
|
|
|
# See if recovery.conf already exists
|
|
|
|
my $bRecoveryConfExists = $self->{oFile}->exists(PATH_DB_ABSOLUTE, $strRecoveryConf);
|
|
|
|
|
2015-05-09 00:34:27 +02:00
|
|
|
# If RECOVERY_TYPE_PRESERVE then warn if recovery.conf does not exist and return
|
2015-08-29 20:20:46 +02:00
|
|
|
if (optionTest(OPTION_TYPE, RECOVERY_TYPE_PRESERVE))
|
2015-01-26 21:59:58 +02:00
|
|
|
{
|
|
|
|
if (!$bRecoveryConfExists)
|
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
&log(WARN, "recovery type is " . optionGet(OPTION_TYPE) . " but recovery file does not exist at ${strRecoveryConf}");
|
2015-01-26 21:59:58 +02:00
|
|
|
}
|
|
|
|
}
|
2015-08-29 20:20:46 +02:00
|
|
|
else
|
2015-01-26 21:59:58 +02:00
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
# In all other cases the old recovery.conf should be removed if it exists
|
|
|
|
if ($bRecoveryConfExists)
|
|
|
|
{
|
|
|
|
$self->{oFile}->remove(PATH_DB_ABSOLUTE, $strRecoveryConf);
|
|
|
|
}
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# If RECOVERY_TYPE_NONE then return
|
|
|
|
if (!optionTest(OPTION_TYPE, RECOVERY_TYPE_NONE))
|
2015-01-26 21:59:58 +02:00
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
# Write recovery options read from the configuration file
|
|
|
|
my $strRecovery = '';
|
|
|
|
my $bRestoreCommandOverride = false;
|
2015-01-31 20:48:09 +02:00
|
|
|
|
2015-09-08 13:31:24 +02:00
|
|
|
if (optionTest(OPTION_RESTORE_RECOVERY_OPTION))
|
2015-01-31 20:48:09 +02:00
|
|
|
{
|
2015-09-08 13:31:24 +02:00
|
|
|
my $oRecoveryRef = optionGet(OPTION_RESTORE_RECOVERY_OPTION);
|
2015-08-29 20:20:46 +02:00
|
|
|
|
|
|
|
foreach my $strKey (sort(keys(%$oRecoveryRef)))
|
|
|
|
{
|
|
|
|
my $strPgKey = $strKey;
|
|
|
|
$strPgKey =~ s/\-/\_/g;
|
|
|
|
|
|
|
|
if ($strPgKey eq 'restore_command')
|
|
|
|
{
|
|
|
|
$bRestoreCommandOverride = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$strRecovery .= "${strPgKey} = '$$oRecoveryRef{$strKey}'\n";
|
|
|
|
}
|
2015-01-31 20:48:09 +02:00
|
|
|
}
|
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Write the restore command
|
|
|
|
if (!$bRestoreCommandOverride)
|
|
|
|
{
|
|
|
|
$strRecovery .= "restore_command = '" . commandWrite(CMD_ARCHIVE_GET) . " %f \"%p\"'\n";
|
|
|
|
}
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2016-01-14 03:35:12 +02:00
|
|
|
# If type is RECOVERY_TYPE_IMMEDIATE
|
|
|
|
if (optionTest(OPTION_TYPE, RECOVERY_TYPE_IMMEDIATE))
|
|
|
|
{
|
|
|
|
$strRecovery .= "recovery_target = '" . RECOVERY_TYPE_IMMEDIATE . "'\n";
|
|
|
|
}
|
|
|
|
# If type is not RECOVERY_TYPE_DEFAULT write target options
|
|
|
|
elsif (!optionTest(OPTION_TYPE, RECOVERY_TYPE_DEFAULT))
|
2015-08-29 20:20:46 +02:00
|
|
|
{
|
|
|
|
# Write the recovery target
|
|
|
|
$strRecovery .= "recovery_target_" . optionGet(OPTION_TYPE) . " = '" . optionGet(OPTION_TARGET) . "'\n";
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Write recovery_target_inclusive
|
|
|
|
if (optionGet(OPTION_TARGET_EXCLUSIVE, false))
|
|
|
|
{
|
|
|
|
$strRecovery .= "recovery_target_inclusive = 'false'\n";
|
|
|
|
}
|
|
|
|
}
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Write pause_at_recovery_target
|
2016-01-14 03:35:12 +02:00
|
|
|
if (optionTest(OPTION_TARGET_ACTION))
|
2015-08-29 20:20:46 +02:00
|
|
|
{
|
2016-01-14 03:35:12 +02:00
|
|
|
my $strTargetAction = optionGet(OPTION_TARGET_ACTION);
|
|
|
|
|
|
|
|
if ($strTargetAction ne OPTION_DEFAULT_RESTORE_TARGET_ACTION)
|
|
|
|
{
|
|
|
|
if ($fDbVersion >= 9.5)
|
|
|
|
{
|
|
|
|
$strRecovery .= "recovery_target_action = '${strTargetAction}'\n";
|
|
|
|
}
|
|
|
|
elsif ($fDbVersion >= 9.1)
|
|
|
|
{
|
|
|
|
$strRecovery .= "pause_at_recovery_target = 'false'\n";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
confess &log(ERROR, OPTION_TARGET_ACTION . ' option is only available in PostgreSQL >= 9.1')
|
|
|
|
}
|
|
|
|
}
|
2015-08-29 20:20:46 +02:00
|
|
|
}
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Write recovery_target_timeline
|
|
|
|
if (optionTest(OPTION_TARGET_TIMELINE))
|
|
|
|
{
|
|
|
|
$strRecovery .= "recovery_target_timeline = '" . optionGet(OPTION_TARGET_TIMELINE) . "'\n";
|
|
|
|
}
|
2015-01-27 18:44:23 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Write recovery.conf
|
|
|
|
my $hFile;
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
open($hFile, '>', $strRecoveryConf)
|
|
|
|
or confess &log(ERROR, "unable to open ${strRecoveryConf}: $!");
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
syswrite($hFile, $strRecovery)
|
|
|
|
or confess "unable to write section ${strRecoveryConf}: $!";
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
close($hFile)
|
|
|
|
or confess "unable to close ${strRecoveryConf}: $!";
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
&log(INFO, "wrote $strRecoveryConf");
|
|
|
|
}
|
|
|
|
}
|
2015-05-09 00:34:27 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Return from function and log return values if any
|
|
|
|
return logDebugReturn($strOperation);
|
2015-01-26 21:59:58 +02:00
|
|
|
}
|
|
|
|
|
2014-12-23 22:03:06 +02:00
|
|
|
####################################################################################################################################
|
2015-08-29 20:20:46 +02:00
|
|
|
# process
|
2014-12-23 22:03:06 +02:00
|
|
|
#
|
|
|
|
# Takes a backup and restores it back to the original or a remapped location.
|
|
|
|
####################################################################################################################################
|
2015-08-29 20:20:46 +02:00
|
|
|
sub process
|
2014-12-23 22:03:06 +02:00
|
|
|
{
|
|
|
|
my $self = shift; # Class hash
|
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
|
|
my ($strOperation) = logDebugParam (OP_RESTORE_PROCESS);
|
|
|
|
|
2014-12-23 22:03:06 +02:00
|
|
|
# Make sure that Postgres is not running
|
|
|
|
if ($self->{oFile}->exists(PATH_DB_ABSOLUTE, $self->{strDbClusterPath} . '/' . FILE_POSTMASTER_PID))
|
|
|
|
{
|
2015-01-28 20:14:46 +02:00
|
|
|
confess &log(ERROR, 'unable to restore while Postgres is running', ERROR_POSTMASTER_RUNNING);
|
2014-12-23 22:03:06 +02:00
|
|
|
}
|
|
|
|
|
2016-02-06 06:03:29 +02:00
|
|
|
# If the restore will be destructive attempt to verify that $PGDATA is valid
|
|
|
|
if ((optionGet(OPTION_DELTA) || optionGet(OPTION_FORCE)) &&
|
|
|
|
!($self->{oFile}->exists(PATH_DB_ABSOLUTE, $self->{strDbClusterPath} . '/' . FILE_PG_VERSION) ||
|
|
|
|
$self->{oFile}->exists(PATH_DB_ABSOLUTE, $self->{strDbClusterPath} . '/' . FILE_MANIFEST)))
|
|
|
|
{
|
|
|
|
&log(WARN, '--delta or --force specified but unable to find \'' . FILE_PG_VERSION . '\' or \'' . FILE_MANIFEST .
|
|
|
|
'\' in \'' . $self->{strDbClusterPath} . '\' to confirm that this is a valid $PGDATA directory.' .
|
|
|
|
' --delta and --force have been disabled and if any files exist in the destination directories the restore' .
|
|
|
|
' will be aborted.');
|
|
|
|
|
|
|
|
optionSet(OPTION_DELTA, false);
|
|
|
|
optionSet(OPTION_FORCE, false);
|
|
|
|
}
|
|
|
|
|
2014-12-23 22:03:06 +02:00
|
|
|
# Make sure the backup path is valid and load the manifest
|
2015-08-29 20:20:46 +02:00
|
|
|
my $oManifest = $self->manifestLoad();
|
2014-12-23 22:03:06 +02:00
|
|
|
|
2015-07-16 17:12:48 +02:00
|
|
|
# Delete pg_control file. This will be copied from the backup at the very end to prevent a partially restored database
|
2015-08-07 16:21:08 +02:00
|
|
|
# from being started by PostgreSQL.
|
2015-07-16 17:12:48 +02:00
|
|
|
$self->{oFile}->remove(PATH_DB_ABSOLUTE,
|
|
|
|
$oManifest->get(MANIFEST_SECTION_BACKUP_PATH, MANIFEST_KEY_BASE, MANIFEST_SUBKEY_PATH) .
|
|
|
|
'/' . FILE_PG_CONTROL);
|
|
|
|
|
2014-12-23 22:03:06 +02:00
|
|
|
# Clean the restore paths
|
2015-01-07 17:59:43 +02:00
|
|
|
$self->clean($oManifest);
|
2014-12-23 22:03:06 +02:00
|
|
|
|
|
|
|
# Build paths/links in the restore paths
|
2015-01-07 17:59:43 +02:00
|
|
|
$self->build($oManifest);
|
2014-12-23 22:03:06 +02:00
|
|
|
|
2015-04-07 13:34:37 +02:00
|
|
|
# Get variables required for restore
|
2015-08-29 20:20:46 +02:00
|
|
|
my $lCopyTimeBegin = $oManifest->numericGet(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TIMESTAMP_COPY_START);
|
|
|
|
my $bSourceCompression = $oManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS);
|
2015-04-07 13:34:37 +02:00
|
|
|
my $strCurrentUser = getpwuid($<);
|
|
|
|
my $strCurrentGroup = getgrgid($();
|
|
|
|
|
2015-05-07 23:56:56 +02:00
|
|
|
# Create hash containing files to restore
|
|
|
|
my %oRestoreHash;
|
|
|
|
my $lSizeTotal = 0;
|
|
|
|
my $lSizeCurrent = 0;
|
2015-04-07 13:34:37 +02:00
|
|
|
|
2015-01-20 21:13:35 +02:00
|
|
|
foreach my $strPathKey ($oManifest->keys(MANIFEST_SECTION_BACKUP_PATH))
|
2014-12-23 22:03:06 +02:00
|
|
|
{
|
2015-01-20 21:13:35 +02:00
|
|
|
my $strSection = "${strPathKey}:file";
|
2016-02-02 21:33:15 +02:00
|
|
|
my $strDestinationPath = $oManifest->get(MANIFEST_SECTION_BACKUP_PATH, $strPathKey, MANIFEST_SUBKEY_PATH);
|
|
|
|
|
|
|
|
# Update path if this is a tablespace
|
|
|
|
if ($oManifest->numericGet(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION) >= 9.0 &&
|
|
|
|
$oManifest->test(MANIFEST_SECTION_BACKUP_PATH, $strPathKey, MANIFEST_SUBKEY_LINK))
|
|
|
|
{
|
|
|
|
$strDestinationPath .= '/' . $oManifest->tablespacePathGet();
|
|
|
|
}
|
2015-01-20 21:13:35 +02:00
|
|
|
|
|
|
|
if ($oManifest->test($strSection))
|
2014-12-22 18:24:32 +02:00
|
|
|
{
|
2015-05-07 23:56:56 +02:00
|
|
|
foreach my $strFile ($oManifest->keys($strSection))
|
2015-04-07 13:34:37 +02:00
|
|
|
{
|
2015-08-07 16:21:08 +02:00
|
|
|
# Skip the tablespace_map file in versions >= 9.5 so Postgres does not rewrite links in the pg_tblspc directory.
|
|
|
|
# The tablespace links have already been created by Restore::build().
|
|
|
|
if ($strPathKey eq MANIFEST_KEY_BASE && $strFile eq FILE_TABLESPACE_MAP &&
|
|
|
|
$oManifest->get(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION) >= 9.5)
|
|
|
|
{
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
my $lSize = $oManifest->numericGet($strSection, $strFile, MANIFEST_SUBKEY_SIZE);
|
2015-05-07 23:56:56 +02:00
|
|
|
$lSizeTotal += $lSize;
|
|
|
|
|
2015-07-16 17:12:48 +02:00
|
|
|
# Preface the file key with the size. This allows for sorting the files to restore by size.
|
|
|
|
my $strFileKey;
|
|
|
|
|
2015-08-07 16:21:08 +02:00
|
|
|
# Skip this for global/pg_control since it will be copied as the last step and needs to named in a way that it
|
|
|
|
# can be found for the copy.
|
2015-07-16 17:12:48 +02:00
|
|
|
if ($strPathKey eq MANIFEST_KEY_BASE && $strFile eq FILE_PG_CONTROL)
|
|
|
|
{
|
|
|
|
$strFileKey = $strFile;
|
2015-08-07 16:21:08 +02:00
|
|
|
$oRestoreHash{$strPathKey}{$strFileKey}{skip} = true;
|
2015-07-16 17:12:48 +02:00
|
|
|
}
|
2015-08-07 16:21:08 +02:00
|
|
|
# Else continue normally
|
2015-07-16 17:12:48 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
$strFileKey = sprintf("%016d-${strFile}", $lSize);
|
2015-08-07 16:21:08 +02:00
|
|
|
$oRestoreHash{$strPathKey}{$strFileKey}{skip} = false;
|
2015-07-16 17:12:48 +02:00
|
|
|
}
|
2015-05-07 23:56:56 +02:00
|
|
|
|
|
|
|
# Get restore information
|
|
|
|
$oRestoreHash{$strPathKey}{$strFileKey}{file} = $strFile;
|
|
|
|
$oRestoreHash{$strPathKey}{$strFileKey}{size} = $lSize;
|
|
|
|
$oRestoreHash{$strPathKey}{$strFileKey}{source_path} = $strPathKey;
|
2016-02-02 21:33:15 +02:00
|
|
|
$oRestoreHash{$strPathKey}{$strFileKey}{destination_path} = $strDestinationPath;
|
2015-05-07 23:56:56 +02:00
|
|
|
$oRestoreHash{$strPathKey}{$strFileKey}{reference} =
|
2015-08-29 20:20:46 +02:00
|
|
|
$oManifest->boolTest(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_HARDLINK, undef, true) ? undef :
|
2015-05-07 23:56:56 +02:00
|
|
|
$oManifest->get($strSection, $strFile, MANIFEST_SUBKEY_REFERENCE, false);
|
|
|
|
$oRestoreHash{$strPathKey}{$strFileKey}{modification_time} =
|
2015-08-29 20:20:46 +02:00
|
|
|
$oManifest->numericGet($strSection, $strFile, MANIFEST_SUBKEY_TIMESTAMP);
|
2015-05-07 23:56:56 +02:00
|
|
|
$oRestoreHash{$strPathKey}{$strFileKey}{mode} =
|
|
|
|
$oManifest->get($strSection, $strFile, MANIFEST_SUBKEY_MODE);
|
|
|
|
$oRestoreHash{$strPathKey}{$strFileKey}{user} =
|
|
|
|
$oManifest->get($strSection, $strFile, MANIFEST_SUBKEY_USER);
|
|
|
|
$oRestoreHash{$strPathKey}{$strFileKey}{group} =
|
|
|
|
$oManifest->get($strSection, $strFile, MANIFEST_SUBKEY_GROUP);
|
|
|
|
|
|
|
|
# Checksum is only stored if size > 0
|
|
|
|
if ($lSize > 0)
|
2015-04-07 13:34:37 +02:00
|
|
|
{
|
2015-05-07 23:56:56 +02:00
|
|
|
$oRestoreHash{$strPathKey}{$strFileKey}{checksum} =
|
|
|
|
$oManifest->get($strSection, $strFile, MANIFEST_SUBKEY_CHECKSUM);
|
2015-04-07 13:34:37 +02:00
|
|
|
}
|
2014-12-22 18:24:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-28 17:23:33 +02:00
|
|
|
# If multi-threaded then create threads to copy files
|
2015-08-29 20:20:46 +02:00
|
|
|
if (optionGet(OPTION_THREAD_MAX) > 1)
|
2015-02-28 17:23:33 +02:00
|
|
|
{
|
2015-09-08 13:31:24 +02:00
|
|
|
# Load module dynamically
|
|
|
|
require BackRest::Protocol::ThreadGroup;
|
|
|
|
BackRest::Protocol::ThreadGroup->import();
|
|
|
|
|
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
logDebugMisc
|
|
|
|
(
|
|
|
|
$strOperation, 'restore with threads',
|
|
|
|
{name => 'iThreadTotal', value => optionGet(OPTION_THREAD_MAX)}
|
|
|
|
);
|
2015-05-07 23:56:56 +02:00
|
|
|
|
|
|
|
# Initialize the thread queues
|
|
|
|
my @oyRestoreQueue;
|
|
|
|
|
|
|
|
foreach my $strPathKey (sort (keys %oRestoreHash))
|
2015-02-28 17:23:33 +02:00
|
|
|
{
|
2015-05-07 23:56:56 +02:00
|
|
|
push(@oyRestoreQueue, Thread::Queue->new());
|
2015-04-07 13:34:37 +02:00
|
|
|
|
2015-06-21 18:06:13 +02:00
|
|
|
foreach my $strFileKey (sort {$b cmp $a} (keys(%{$oRestoreHash{$strPathKey}})))
|
2015-05-07 23:56:56 +02:00
|
|
|
{
|
2015-08-07 16:21:08 +02:00
|
|
|
# Skip files marked to be copied later
|
|
|
|
next if ($oRestoreHash{$strPathKey}{$strFileKey}{skip});
|
2015-07-16 17:12:48 +02:00
|
|
|
|
2015-05-07 23:56:56 +02:00
|
|
|
$oyRestoreQueue[@oyRestoreQueue - 1]->enqueue($oRestoreHash{$strPathKey}{$strFileKey});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Initialize the param hash
|
|
|
|
my %oParam;
|
|
|
|
|
|
|
|
$oParam{copy_time_begin} = $lCopyTimeBegin;
|
|
|
|
$oParam{size_total} = $lSizeTotal;
|
2015-08-29 20:20:46 +02:00
|
|
|
$oParam{delta} = optionGet(OPTION_DELTA);
|
|
|
|
$oParam{force} = optionGet(OPTION_FORCE);
|
|
|
|
$oParam{backup_path} = $self->{strBackupSet};
|
2015-05-07 23:56:56 +02:00
|
|
|
$oParam{source_compression} = $bSourceCompression;
|
|
|
|
$oParam{current_user} = $strCurrentUser;
|
|
|
|
$oParam{current_group} = $strCurrentGroup;
|
|
|
|
$oParam{queue} = \@oyRestoreQueue;
|
|
|
|
|
|
|
|
# Run the threads
|
2015-08-29 20:20:46 +02:00
|
|
|
for (my $iThreadIdx = 0; $iThreadIdx < optionGet(OPTION_THREAD_MAX); $iThreadIdx++)
|
2015-05-07 23:56:56 +02:00
|
|
|
{
|
2015-04-07 13:34:37 +02:00
|
|
|
threadGroupRun($iThreadIdx, 'restore', \%oParam);
|
2015-02-28 17:23:33 +02:00
|
|
|
}
|
2014-12-22 18:24:32 +02:00
|
|
|
|
2015-02-28 17:23:33 +02:00
|
|
|
# Complete thread queues
|
2015-12-24 17:32:25 +02:00
|
|
|
while (!threadGroupComplete())
|
|
|
|
{
|
|
|
|
# Keep the protocol layer from timing out
|
|
|
|
protocolGet()->keepAlive();
|
|
|
|
};
|
2014-12-22 18:24:32 +02:00
|
|
|
}
|
2015-05-07 23:56:56 +02:00
|
|
|
else
|
|
|
|
{
|
2015-08-29 20:20:46 +02:00
|
|
|
logDebugMisc($strOperation, 'restore in main process');
|
2015-05-07 23:56:56 +02:00
|
|
|
|
|
|
|
# Restore file in main process
|
|
|
|
foreach my $strPathKey (sort (keys %oRestoreHash))
|
|
|
|
{
|
2015-06-21 18:06:13 +02:00
|
|
|
foreach my $strFileKey (sort {$b cmp $a} (keys(%{$oRestoreHash{$strPathKey}})))
|
2015-05-07 23:56:56 +02:00
|
|
|
{
|
2015-08-07 16:21:08 +02:00
|
|
|
# Skip files marked to be copied later
|
|
|
|
next if ($oRestoreHash{$strPathKey}{$strFileKey}{skip});
|
2015-07-16 17:12:48 +02:00
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
$lSizeCurrent = restoreFile($oRestoreHash{$strPathKey}{$strFileKey}, $lCopyTimeBegin, optionGet(OPTION_DELTA),
|
|
|
|
optionGet(OPTION_FORCE), $self->{strBackupSet}, $bSourceCompression, $strCurrentUser,
|
2015-05-07 23:56:56 +02:00
|
|
|
$strCurrentGroup, $self->{oFile}, $lSizeTotal, $lSizeCurrent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-12-22 18:24:32 +02:00
|
|
|
|
2015-01-26 21:59:58 +02:00
|
|
|
# Create recovery.conf file
|
2016-01-14 03:35:12 +02:00
|
|
|
$self->recovery($oManifest->get(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION));
|
2015-05-09 00:34:27 +02:00
|
|
|
|
2015-07-16 17:12:48 +02:00
|
|
|
# Copy pg_control last
|
|
|
|
&log(INFO, 'restore ' . FILE_PG_CONTROL . ' (copied last to ensure aborted restores cannot be started)');
|
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
restoreFile($oRestoreHash{&MANIFEST_KEY_BASE}{&FILE_PG_CONTROL}, $lCopyTimeBegin, optionGet(OPTION_DELTA),
|
|
|
|
optionGet(OPTION_FORCE), $self->{strBackupSet}, $bSourceCompression, $strCurrentUser, $strCurrentGroup,
|
|
|
|
$self->{oFile}, $lSizeTotal, $lSizeCurrent);
|
2015-07-16 17:12:48 +02:00
|
|
|
|
2016-02-06 06:03:29 +02:00
|
|
|
# Finally remove the manifest to indicate the restore is complete
|
|
|
|
$self->{oFile}->remove(PATH_DB_ABSOLUTE, $self->{strDbClusterPath} . '/' . FILE_MANIFEST, undef, false);
|
|
|
|
|
2015-08-29 20:20:46 +02:00
|
|
|
# Return from function and log return values if any
|
|
|
|
return logDebugReturn($strOperation);
|
2014-12-22 18:24:32 +02:00
|
|
|
}
|
|
|
|
|
2014-12-18 18:42:54 +02:00
|
|
|
1;
|