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;
|
|
|
|
use warnings;
|
|
|
|
use Carp;
|
|
|
|
|
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-01-24 01:28:39 +02:00
|
|
|
use BackRest::Exception;
|
2014-12-18 18:42:54 +02:00
|
|
|
use BackRest::Utility;
|
2014-12-22 18:24:32 +02:00
|
|
|
use BackRest::ThreadGroup;
|
2014-12-18 18:42:54 +02:00
|
|
|
use BackRest::Config;
|
2015-01-07 17:59:43 +02:00
|
|
|
use BackRest::Manifest;
|
2014-12-18 18:42:54 +02:00
|
|
|
use BackRest::File;
|
2014-12-22 18:24:32 +02:00
|
|
|
|
2014-12-18 18:42:54 +02:00
|
|
|
####################################################################################################################################
|
|
|
|
# CONSTRUCTOR
|
|
|
|
####################################################################################################################################
|
|
|
|
sub new
|
|
|
|
{
|
|
|
|
my $class = shift; # Class name
|
|
|
|
my $strDbClusterPath = shift; # Database cluster path
|
2014-12-23 18:48:51 +02:00
|
|
|
my $strBackupPath = shift; # Backup to restore
|
2014-12-23 22:03:06 +02:00
|
|
|
my $oRemapRef = shift; # Tablespace remaps
|
2014-12-23 19:48:25 +02:00
|
|
|
my $oFile = shift; # Default file object
|
2014-12-22 18:24:32 +02:00
|
|
|
my $iThreadTotal = shift; # Total threads to run for restore
|
2015-01-08 22:43:43 +02:00
|
|
|
my $bDelta = shift; # perform delta restore
|
|
|
|
my $bForce = shift; # force a restore
|
2015-01-26 18:01:24 +02:00
|
|
|
my $strType = shift; # Recovery type
|
|
|
|
my $strTarget = shift; # Recovery target
|
|
|
|
my $bTargetExclusive = shift; # Target exlusive option
|
|
|
|
my $bTargetResume = shift; # Target resume option
|
2015-01-31 20:48:09 +02:00
|
|
|
my $strTargetTimeline = shift; # Target timeline option
|
2015-01-26 18:01:24 +02:00
|
|
|
my $oRecoveryRef = shift; # Other recovery options
|
2015-01-27 18:44:23 +02:00
|
|
|
my $strStanza = shift; # Restore stanza
|
|
|
|
my $strBackRestBin = shift; # Absolute backrest filename
|
|
|
|
my $strConfigFile = shift; # Absolute config filename (optional)
|
2014-12-18 18:42:54 +02:00
|
|
|
|
|
|
|
# Create the class hash
|
|
|
|
my $self = {};
|
|
|
|
bless $self, $class;
|
|
|
|
|
|
|
|
# Initialize variables
|
|
|
|
$self->{strDbClusterPath} = $strDbClusterPath;
|
2015-01-26 18:01:24 +02:00
|
|
|
$self->{oRemapRef} = $oRemapRef;
|
2014-12-23 19:48:25 +02:00
|
|
|
$self->{oFile} = $oFile;
|
2014-12-30 22:41:43 +02:00
|
|
|
$self->{iThreadTotal} = defined($iThreadTotal) ? $iThreadTotal : 1;
|
2015-01-08 22:43:43 +02:00
|
|
|
$self->{bDelta} = $bDelta;
|
2014-12-19 19:49:56 +02:00
|
|
|
$self->{bForce} = $bForce;
|
2015-01-26 18:01:24 +02:00
|
|
|
$self->{strType} = $strType;
|
|
|
|
$self->{strTarget} = $strTarget;
|
|
|
|
$self->{bTargetExclusive} = $bTargetExclusive;
|
|
|
|
$self->{bTargetResume} = $bTargetResume;
|
2015-01-31 20:48:09 +02:00
|
|
|
$self->{strTargetTimeline} = $strTargetTimeline;
|
2015-01-26 18:01:24 +02:00
|
|
|
$self->{oRecoveryRef} = $oRecoveryRef;
|
2015-01-27 18:44:23 +02:00
|
|
|
$self->{strStanza} = $strStanza;
|
|
|
|
$self->{strBackRestBin} = $strBackRestBin;
|
|
|
|
$self->{strConfigFile} = $strConfigFile;
|
2014-12-18 18:42:54 +02:00
|
|
|
|
2014-12-19 19:49:56 +02:00
|
|
|
# If backup path is not specified then default to latest
|
2014-12-23 18:48:51 +02:00
|
|
|
if (defined($strBackupPath))
|
2014-12-18 18:42:54 +02:00
|
|
|
{
|
2014-12-23 18:48:51 +02:00
|
|
|
$self->{strBackupPath} = $strBackupPath;
|
2014-12-18 18:42:54 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-12-23 18:48:51 +02:00
|
|
|
$self->{strBackupPath} = PATH_LATEST;
|
2014-12-18 18:42:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
2014-12-30 18:59:57 +02:00
|
|
|
####################################################################################################################################
|
2014-12-31 18:20:46 +02:00
|
|
|
# MANIFEST_OWNERSHIP_CHECK
|
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.
|
|
|
|
####################################################################################################################################
|
2014-12-31 18:20:46 +02:00
|
|
|
sub manifest_ownership_check
|
2014-12-30 18:59:57 +02:00
|
|
|
{
|
|
|
|
my $self = shift; # Class hash
|
2015-01-20 21:13:35 +02:00
|
|
|
my $oManifest = shift; # Backup manifest
|
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
|
|
|
{
|
2014-12-31 19:28:16 +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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-18 18:42:54 +02:00
|
|
|
####################################################################################################################################
|
2014-12-23 22:03:06 +02:00
|
|
|
# MANIFEST_LOAD
|
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
|
|
|
####################################################################################################################################
|
2014-12-23 22:03:06 +02:00
|
|
|
sub manifest_load
|
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
|
|
|
|
|
|
|
if ($self->{oFile}->exists(PATH_BACKUP_CLUSTER, $self->{strBackupPath}))
|
2014-12-18 18:42:54 +02:00
|
|
|
{
|
|
|
|
# Copy the backup manifest to the db cluster path
|
2014-12-23 19:48:25 +02:00
|
|
|
$self->{oFile}->copy(PATH_BACKUP_CLUSTER, $self->{strBackupPath} . '/' . FILE_MANIFEST,
|
2014-12-19 00:05:06 +02:00
|
|
|
PATH_DB_ABSOLUTE, $self->{strDbClusterPath} . '/' . FILE_MANIFEST);
|
2014-12-18 18:42:54 +02:00
|
|
|
|
2014-12-24 01:52:38 +02:00
|
|
|
# Load the manifest into a hash
|
2015-01-20 21:13:35 +02:00
|
|
|
my $oManifest = new BackRest::Manifest($self->{oFile}->path_get(PATH_DB_ABSOLUTE,
|
|
|
|
$self->{strDbClusterPath} . '/' . FILE_MANIFEST));
|
2014-12-24 01:52:38 +02:00
|
|
|
|
|
|
|
# Remove the manifest now that it is in memory
|
|
|
|
$self->{oFile}->remove(PATH_DB_ABSOLUTE, $self->{strDbClusterPath} . '/' . FILE_MANIFEST);
|
2014-12-31 19:28:16 +02:00
|
|
|
|
2014-12-30 22:41:43 +02:00
|
|
|
# If backup is latest then set it equal to backup label, else verify that requested backup and label match
|
2015-01-20 21:13:35 +02:00
|
|
|
my $strBackupLabel = $oManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LABEL);
|
|
|
|
|
2014-12-30 22:41:43 +02:00
|
|
|
if ($self->{strBackupPath} eq PATH_LATEST)
|
|
|
|
{
|
2015-01-20 21:13:35 +02:00
|
|
|
$self->{strBackupPath} = $strBackupLabel;
|
2014-12-30 22:41:43 +02:00
|
|
|
}
|
2015-01-20 21:13:35 +02:00
|
|
|
elsif ($self->{strBackupPath} ne $strBackupLabel)
|
2014-12-30 22:41:43 +02:00
|
|
|
{
|
2015-01-20 21:13:35 +02:00
|
|
|
confess &log(ASSERT, "request backup $self->{strBackupPath} and label ${strBackupLabel} do not match " .
|
|
|
|
' - this indicates some sort of corruption (at the very least paths have been renamed.');
|
2014-12-30 22:41:43 +02:00
|
|
|
}
|
2014-12-24 01:52:38 +02:00
|
|
|
|
2015-01-23 03:11:33 +02:00
|
|
|
if ($self->{strDbClusterPath} ne $oManifest->get(MANIFEST_SECTION_BACKUP_PATH, MANIFEST_KEY_BASE))
|
2014-12-24 01:52:38 +02:00
|
|
|
{
|
2015-01-23 02:04:55 +02:00
|
|
|
&log(INFO, 'base path remapped to ' . $self->{strDbClusterPath});
|
|
|
|
$oManifest->set(MANIFEST_SECTION_BACKUP_PATH, MANIFEST_KEY_BASE, undef, $self->{strDbClusterPath});
|
2014-12-24 01:52:38 +02:00
|
|
|
}
|
2015-01-20 21:13:35 +02:00
|
|
|
|
2015-01-23 03:11:33 +02:00
|
|
|
# If tablespaces have been remapped, update the manifest
|
|
|
|
if (defined($self->{oRemapRef}))
|
|
|
|
{
|
|
|
|
foreach my $strPathKey (sort(keys $self->{oRemapRef}))
|
|
|
|
{
|
|
|
|
my $strRemapPath = ${$self->{oRemapRef}}{$strPathKey};
|
|
|
|
|
|
|
|
# Make sure that the tablespace exists in the manifest
|
|
|
|
if (!$oManifest->test(MANIFEST_SECTION_BACKUP_TABLESPACE, $strPathKey))
|
|
|
|
{
|
|
|
|
confess &log(ERROR, "cannot remap invalid tablespace ${strPathKey} to ${strRemapPath}");
|
|
|
|
}
|
|
|
|
|
|
|
|
# Remap the tablespace in the manifest
|
|
|
|
&log(INFO, "remapping tablespace ${strPathKey} to ${strRemapPath}");
|
|
|
|
|
|
|
|
my $strTablespaceLink = $oManifest->get(MANIFEST_SECTION_BACKUP_TABLESPACE, $strPathKey, MANIFEST_SUBKEY_LINK);
|
|
|
|
|
|
|
|
$oManifest->set(MANIFEST_SECTION_BACKUP_PATH, "tablespace:${strPathKey}", undef, $strRemapPath);
|
|
|
|
$oManifest->set(MANIFEST_SECTION_BACKUP_TABLESPACE, $strPathKey, MANIFEST_SUBKEY_PATH, $strRemapPath);
|
|
|
|
$oManifest->set('base:link', "pg_tblspc/${strTablespaceLink}", MANIFEST_SUBKEY_DESTINATION, $strRemapPath);
|
|
|
|
}
|
|
|
|
}
|
2015-01-23 02:04:55 +02:00
|
|
|
|
2015-01-20 21:13:35 +02:00
|
|
|
$self->manifest_ownership_check($oManifest);
|
|
|
|
|
|
|
|
return $oManifest;
|
2014-12-18 18:42:54 +02:00
|
|
|
}
|
2014-12-30 18:59:57 +02:00
|
|
|
|
2015-01-20 21:13:35 +02:00
|
|
|
confess &log(ERROR, 'backup ' . $self->{strBackupPath} . ' does not exist');
|
2014-12-23 22:03:06 +02:00
|
|
|
}
|
2014-12-19 19:49:56 +02:00
|
|
|
|
2014-12-23 22:03:06 +02:00
|
|
|
####################################################################################################################################
|
|
|
|
# CLEAN
|
|
|
|
#
|
|
|
|
# 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-01-07 17:59:43 +02:00
|
|
|
my $oManifest = shift; # Backup manifest
|
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-01-07 17:59:43 +02:00
|
|
|
my $strPath = $oManifest->get(MANIFEST_SECTION_BACKUP_PATH, $strPathKey);
|
2014-12-19 19:49:56 +02:00
|
|
|
|
2014-12-23 22:03:06 +02:00
|
|
|
&log(INFO, "checking/cleaning db path ${strPath}");
|
2014-12-21 17:11:17 +02:00
|
|
|
|
2014-12-23 19:48:25 +02:00
|
|
|
if (!$self->{oFile}->exists(PATH_DB_ABSOLUTE, $strPath))
|
2014-12-19 19:49:56 +02:00
|
|
|
{
|
|
|
|
confess &log(ERROR, "required db path '${strPath}' does not exist");
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
|
|
foreach my $strName (sort {$b cmp $a} (keys $oPathManifest{name}))
|
|
|
|
{
|
|
|
|
# Skip the root path
|
|
|
|
if ($strName eq '.')
|
|
|
|
{
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
|
|
|
# If force was not specified then error if any file is found
|
2015-01-08 22:43:43 +02:00
|
|
|
if (!$self->{bForce} && !$self->{bDelta})
|
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})
|
|
|
|
{
|
|
|
|
&log(DEBUG, "setting ${strFile} ownership to ${strUser}:${strGroup}");
|
|
|
|
|
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})
|
|
|
|
{
|
|
|
|
&log(DEBUG, "removing link ${strFile} - destination changed");
|
|
|
|
unlink($strFile) or confess &log(ERROR, "unable to delete file ${strFile}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# 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-01-26 21:59:58 +02:00
|
|
|
if ($strType ne MANIFEST_LINK && $strMode ne $oPathManifest{name}{$strName}{permission})
|
2015-01-07 17:59:43 +02:00
|
|
|
{
|
|
|
|
&log(DEBUG, "setting ${strFile} mode to ${strMode}");
|
|
|
|
|
|
|
|
chmod(oct($strMode), $strFile)
|
|
|
|
or confess 'unable to set mode ${strMode} for ${strFile}';
|
|
|
|
}
|
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
|
|
|
{
|
2014-12-21 17:11:17 +02:00
|
|
|
&log(DEBUG, "removing path ${strFile}");
|
|
|
|
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-01-26 21:59:58 +02:00
|
|
|
# Delete only if this is not the recovery.conf file. This is in case the use wants the recovery.conf file
|
|
|
|
# preserved. It will be written/deleted/preserved as needed in recovery().
|
|
|
|
if (!($strName eq FILE_RECOVERY_CONF && $strType eq MANIFEST_FILE))
|
|
|
|
{
|
|
|
|
&log(DEBUG, "removing file/link ${strFile}");
|
|
|
|
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
|
|
|
|
foreach my $strFileType (sort (keys %oRemoveHash))
|
|
|
|
{
|
|
|
|
if ($oRemoveHash{$strFileType} > 0)
|
|
|
|
{
|
|
|
|
&log(INFO, "$oRemoveHash{$strFileType} ${strFileType}(s) removed during cleanup");
|
|
|
|
}
|
|
|
|
}
|
2014-12-23 22:03:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
####################################################################################################################################
|
|
|
|
# BUILD
|
|
|
|
#
|
|
|
|
# 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
|
|
|
|
my $oManifest = shift; # Backup manifest
|
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
|
|
|
{
|
2015-01-20 21:13:35 +02:00
|
|
|
my $strSectionPath = $oManifest->get(MANIFEST_SECTION_BACKUP_PATH, $strSectionPathKey);
|
2014-12-21 17:11:17 +02:00
|
|
|
|
|
|
|
# Create all paths in the manifest that do not already exist
|
2015-01-20 21:13:35 +02:00
|
|
|
my $strSection = "${strSectionPathKey}:path";
|
|
|
|
|
|
|
|
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-01-20 21:13:35 +02:00
|
|
|
$self->{oFile}->path_create(PATH_DB_ABSOLUTE, $strPath,
|
|
|
|
$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))
|
|
|
|
{
|
|
|
|
$self->{oFile}->link_create(PATH_DB_ABSOLUTE,
|
|
|
|
$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
|
|
|
}
|
|
|
|
}
|
2014-12-22 18:24:32 +02:00
|
|
|
|
2015-01-26 21:59:58 +02:00
|
|
|
####################################################################################################################################
|
|
|
|
# RECOVERY
|
|
|
|
#
|
|
|
|
# Creates the recovery.conf file.
|
|
|
|
####################################################################################################################################
|
|
|
|
sub recovery
|
|
|
|
{
|
|
|
|
my $self = shift; # Class hash
|
|
|
|
|
|
|
|
# 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);
|
|
|
|
|
|
|
|
# If RECOVERY_TYPE_PRESERVE then make sure recovery.conf exists and return
|
|
|
|
if ($self->{strType} eq RECOVERY_TYPE_PRESERVE)
|
|
|
|
{
|
|
|
|
if (!$bRecoveryConfExists)
|
|
|
|
{
|
|
|
|
confess &log(ERROR, "recovery type is $self->{strType} but recovery file does not exist at ${strRecoveryConf}");
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# In all other cases the old recovery.conf should be removed if it exists
|
|
|
|
if ($bRecoveryConfExists)
|
|
|
|
{
|
|
|
|
$self->{oFile}->remove(PATH_DB_ABSOLUTE, $strRecoveryConf);
|
|
|
|
}
|
|
|
|
|
|
|
|
# If RECOVERY_TYPE_NONE then return
|
|
|
|
if ($self->{strType} eq RECOVERY_TYPE_NONE)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Write the recovery options from pg_backrest.conf
|
|
|
|
my $strRecovery = '';
|
2015-01-31 20:48:09 +02:00
|
|
|
my $bRestoreCommandOverride = false;
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2015-01-31 20:48:09 +02:00
|
|
|
if (defined($self->{oRecoveryRef}))
|
2015-01-26 21:59:58 +02:00
|
|
|
{
|
2015-01-31 20:48:09 +02:00
|
|
|
foreach my $strKey (sort(keys $self->{oRecoveryRef}))
|
2015-01-26 21:59:58 +02:00
|
|
|
{
|
2015-01-31 20:48:09 +02:00
|
|
|
my $strPgKey = $strKey;
|
|
|
|
$strPgKey =~ s/\-/\_/g;
|
|
|
|
|
|
|
|
if ($strKey eq CONFIG_KEY_RESTORE_COMMAND)
|
|
|
|
{
|
|
|
|
$bRestoreCommandOverride = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$strRecovery .= "$strPgKey = '${$self->{oRecoveryRef}}{$strKey}'\n";
|
2015-01-26 21:59:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-27 18:44:23 +02:00
|
|
|
# Write the restore command
|
2015-01-31 20:48:09 +02:00
|
|
|
if (!$bRestoreCommandOverride)
|
|
|
|
{
|
|
|
|
$strRecovery .= "restore_command = '$self->{strBackRestBin} --stanza=$self->{strStanza}" .
|
|
|
|
(defined($self->{strConfigFile}) ? " --config=$self->{strConfigFile}" : '') .
|
|
|
|
" archive-get %f \"%p\"'\n";
|
|
|
|
}
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2015-01-27 18:44:23 +02:00
|
|
|
# If RECOVERY_TYPE_DEFAULT do not write target options
|
|
|
|
if ($self->{strType} ne RECOVERY_TYPE_DEFAULT)
|
2015-01-26 21:59:58 +02:00
|
|
|
{
|
2015-01-27 18:44:23 +02:00
|
|
|
# Write the recovery target
|
2015-01-28 05:59:59 +02:00
|
|
|
$strRecovery .= "recovery_target_$self->{strType} = '$self->{strTarget}'\n";
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2015-01-27 18:44:23 +02:00
|
|
|
# Write recovery_target_inclusive
|
|
|
|
if ($self->{bTargetExclusive})
|
|
|
|
{
|
|
|
|
$strRecovery .= "recovery_target_inclusive = false\n";
|
|
|
|
}
|
2015-01-31 20:48:09 +02:00
|
|
|
}
|
2015-01-26 21:59:58 +02:00
|
|
|
|
2015-01-31 20:48:09 +02:00
|
|
|
# Write pause_at_recovery_target
|
|
|
|
if ($self->{bTargetResume})
|
|
|
|
{
|
|
|
|
$strRecovery .= "pause_at_recovery_target = false\n";
|
|
|
|
}
|
2015-01-27 18:44:23 +02:00
|
|
|
|
2015-01-31 20:48:09 +02:00
|
|
|
# Write recovery_target_timeline
|
|
|
|
if (defined($self->{strTargetTimeline}))
|
|
|
|
{
|
|
|
|
$strRecovery .= "recovery_target_timeline = $self->{strTargetTimeline}\n";
|
2015-01-26 21:59:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Write recovery.conf
|
|
|
|
my $hFile;
|
|
|
|
|
|
|
|
open($hFile, '>', $strRecoveryConf)
|
|
|
|
or confess &log(ERROR, "unable to open ${strRecoveryConf}: $!");
|
|
|
|
|
|
|
|
syswrite($hFile, $strRecovery)
|
|
|
|
or confess "unable to write section ${strRecoveryConf}: $!";
|
|
|
|
|
|
|
|
close($hFile)
|
|
|
|
or confess "unable to close ${strRecoveryConf}: $!";
|
|
|
|
}
|
|
|
|
|
2014-12-23 22:03:06 +02:00
|
|
|
####################################################################################################################################
|
|
|
|
# RESTORE
|
|
|
|
#
|
|
|
|
# Takes a backup and restores it back to the original or a remapped location.
|
|
|
|
####################################################################################################################################
|
|
|
|
sub restore
|
|
|
|
{
|
|
|
|
my $self = shift; # Class hash
|
|
|
|
|
|
|
|
# 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
|
|
|
}
|
|
|
|
|
2014-12-24 01:52:38 +02:00
|
|
|
# Log the backup set to restore
|
|
|
|
&log(INFO, "Restoring backup set " . $self->{strBackupPath});
|
|
|
|
|
2014-12-23 22:03:06 +02:00
|
|
|
# Make sure the backup path is valid and load the manifest
|
2015-01-20 21:13:35 +02:00
|
|
|
my $oManifest = $self->manifest_load();
|
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-02-03 02:34:34 +02:00
|
|
|
# Create thread queues
|
2014-12-23 22:03:06 +02:00
|
|
|
my @oyRestoreQueue;
|
|
|
|
|
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";
|
|
|
|
|
|
|
|
if ($oManifest->test($strSection))
|
2014-12-22 18:24:32 +02:00
|
|
|
{
|
2014-12-23 19:48:25 +02:00
|
|
|
$oyRestoreQueue[@oyRestoreQueue] = Thread::Queue->new();
|
2014-12-22 18:24:32 +02:00
|
|
|
|
2015-01-20 21:13:35 +02:00
|
|
|
foreach my $strName ($oManifest->keys($strSection))
|
2014-12-22 18:24:32 +02:00
|
|
|
{
|
2014-12-23 19:48:25 +02:00
|
|
|
$oyRestoreQueue[@oyRestoreQueue - 1]->enqueue("${strPathKey}|${strName}");
|
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
|
|
|
|
if ($self->{iThreadTotal} > 1)
|
|
|
|
{
|
|
|
|
# Create threads to process the thread queues
|
|
|
|
my $oThreadGroup = thread_group_create();
|
|
|
|
|
|
|
|
for (my $iThreadIdx = 0; $iThreadIdx < $self->{iThreadTotal}; $iThreadIdx++)
|
|
|
|
{
|
|
|
|
&log(DEBUG, "starting restore thread ${iThreadIdx}");
|
|
|
|
thread_group_add($oThreadGroup, threads->create(\&restore_thread, true, $self,
|
|
|
|
$iThreadIdx, \@oyRestoreQueue, $oManifest));
|
|
|
|
}
|
2014-12-22 18:24:32 +02:00
|
|
|
|
2015-02-28 17:23:33 +02:00
|
|
|
# Complete thread queues
|
|
|
|
thread_group_complete($oThreadGroup);
|
|
|
|
}
|
|
|
|
# Else copy in the main process
|
|
|
|
else
|
2014-12-22 18:24:32 +02:00
|
|
|
{
|
2015-02-28 17:23:33 +02:00
|
|
|
&log(DEBUG, "starting restore in main process");
|
|
|
|
$self->restore_thread(false, 0, \@oyRestoreQueue, $oManifest);
|
2014-12-22 18:24:32 +02:00
|
|
|
}
|
|
|
|
|
2015-01-26 21:59:58 +02:00
|
|
|
# Create recovery.conf file
|
|
|
|
$self->recovery();
|
2014-12-22 18:24:32 +02:00
|
|
|
}
|
|
|
|
|
2014-12-23 19:48:25 +02:00
|
|
|
####################################################################################################################################
|
|
|
|
# RESTORE_THREAD
|
|
|
|
#
|
|
|
|
# Worker threads for the restore process.
|
|
|
|
####################################################################################################################################
|
2014-12-22 18:24:32 +02:00
|
|
|
sub restore_thread
|
|
|
|
{
|
2014-12-23 19:48:25 +02:00
|
|
|
my $self = shift; # Class hash
|
2015-02-28 17:23:33 +02:00
|
|
|
my $bMulti = shift; # Is this thread one of many?
|
2014-12-23 19:48:25 +02:00
|
|
|
my $iThreadIdx = shift; # Defines the index of this thread
|
2014-12-23 22:03:06 +02:00
|
|
|
my $oyRestoreQueueRef = shift; # Restore queues
|
2015-01-08 22:43:43 +02:00
|
|
|
my $oManifest = shift; # Backup manifest
|
2014-12-22 18:24:32 +02:00
|
|
|
|
2014-12-23 19:48:25 +02:00
|
|
|
my $iDirection = $iThreadIdx % 2 == 0 ? 1 : -1; # Size of files currently copied by this thread
|
2015-02-28 17:23:33 +02:00
|
|
|
my $oFileThread = $self->{oFile}; # Thread local file object
|
|
|
|
|
|
|
|
# If multi-threaded, then clone the file object
|
|
|
|
if ($bMulti)
|
|
|
|
{
|
|
|
|
$oFileThread = $self->{oFile}->clone($iThreadIdx);
|
|
|
|
}
|
|
|
|
# Else use the master file object
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$oFileThread = $self->{oFile};
|
|
|
|
}
|
2014-12-22 18:24:32 +02:00
|
|
|
|
2014-12-23 18:48:51 +02:00
|
|
|
# Initialize the starting and current queue index based in the total number of threads in relation to this thread
|
2014-12-30 22:41:43 +02:00
|
|
|
my $iQueueStartIdx = int((@{$oyRestoreQueueRef} / $self->{iThreadTotal}) * $iThreadIdx);
|
2014-12-22 18:24:32 +02:00
|
|
|
my $iQueueIdx = $iQueueStartIdx;
|
|
|
|
|
2015-01-08 22:43:43 +02:00
|
|
|
# Time when the backup copying began - used for size/timestamp deltas
|
|
|
|
my $lCopyTimeBegin = $oManifest->epoch(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TIMESTAMP_COPY_START);
|
|
|
|
|
2014-12-23 22:03:06 +02:00
|
|
|
# Set source compression
|
2015-01-20 21:13:35 +02:00
|
|
|
my $bSourceCompression = $oManifest->get(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS) eq 'y' ? true : false;
|
2014-12-23 22:03:06 +02:00
|
|
|
|
2014-12-22 18:24:32 +02:00
|
|
|
# When a KILL signal is received, immediately abort
|
|
|
|
$SIG{'KILL'} = sub {threads->exit();};
|
|
|
|
|
2014-12-30 18:59:57 +02:00
|
|
|
# Get the current user and group to compare with stored permissions
|
|
|
|
my $strCurrentUser = getpwuid($<);
|
|
|
|
my $strCurrentGroup = getgrgid($();
|
|
|
|
|
2014-12-23 18:48:51 +02:00
|
|
|
# Loop through all the queues to restore files (exit when the original queue is reached
|
2014-12-22 18:24:32 +02:00
|
|
|
do
|
|
|
|
{
|
2015-02-03 02:34:34 +02:00
|
|
|
while (my $strMessage = ${$oyRestoreQueueRef}[$iQueueIdx]->dequeue_nb())
|
2014-12-22 18:24:32 +02:00
|
|
|
{
|
2014-12-30 18:59:57 +02:00
|
|
|
my $strSourcePath = (split(/\|/, $strMessage))[0]; # Source path from backup
|
|
|
|
my $strSection = "${strSourcePath}:file"; # Backup section with file info
|
2015-01-20 21:13:35 +02:00
|
|
|
my $strDestinationPath = $oManifest->get(MANIFEST_SECTION_BACKUP_PATH, # Destination path stored in manifest
|
|
|
|
$strSourcePath);
|
2014-12-30 18:59:57 +02:00
|
|
|
$strSourcePath =~ s/\:/\//g; # Replace : with / in source path
|
|
|
|
my $strName = (split(/\|/, $strMessage))[1]; # Name of file to be restored
|
2014-12-22 18:24:32 +02:00
|
|
|
|
2014-12-30 22:41:43 +02:00
|
|
|
# If the file is a reference to a previous backup and hardlinks are off, then fetch it from that backup
|
2015-01-20 21:13:35 +02:00
|
|
|
my $strReference = $oManifest->test(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_HARDLINK, undef, 'y') ? undef :
|
|
|
|
$oManifest->get($strSection, $strName, MANIFEST_SUBKEY_REFERENCE, false);
|
2014-12-30 22:41:43 +02:00
|
|
|
|
2014-12-22 18:24:32 +02:00
|
|
|
# Generate destination file name
|
|
|
|
my $strDestinationFile = $oFileThread->path_get(PATH_DB_ABSOLUTE, "${strDestinationPath}/${strName}");
|
|
|
|
|
|
|
|
if ($oFileThread->exists(PATH_DB_ABSOLUTE, $strDestinationFile))
|
|
|
|
{
|
2015-01-08 22:43:43 +02:00
|
|
|
# Perform delta if requested
|
|
|
|
if ($self->{bDelta})
|
2014-12-22 18:24:32 +02:00
|
|
|
{
|
2015-01-08 22:43:43 +02:00
|
|
|
# Do checksum delta if --force was not requested and checksums exist
|
2015-01-20 21:13:35 +02:00
|
|
|
my $strChecksum = $oManifest->get($strSection, $strName, MANIFEST_SUBKEY_CHECKSUM, false);
|
|
|
|
|
2015-01-08 22:43:43 +02:00
|
|
|
if (!$self->{bForce} && defined($strChecksum) &&
|
|
|
|
$oFileThread->hash(PATH_DB_ABSOLUTE, $strDestinationFile) eq $strChecksum)
|
|
|
|
{
|
|
|
|
&log(DEBUG, "${strDestinationFile} exists and matches backup checksum ${strChecksum}");
|
2015-02-01 06:04:24 +02:00
|
|
|
|
|
|
|
# Even if hash is the same set the time back to backup time. This helps with unit testing, but also
|
|
|
|
# presents a pristine version of the database.
|
|
|
|
utime($oManifest->get($strSection, $strName, MANIFEST_SUBKEY_MODIFICATION_TIME),
|
|
|
|
$oManifest->get($strSection, $strName, MANIFEST_SUBKEY_MODIFICATION_TIME),
|
|
|
|
$strDestinationFile)
|
|
|
|
or confess &log(ERROR, "unable to set time for ${strDestinationFile}");
|
|
|
|
|
2015-01-08 22:43:43 +02:00
|
|
|
next;
|
|
|
|
}
|
|
|
|
# Else use size/timestamp delta
|
|
|
|
else
|
|
|
|
{
|
|
|
|
my $oStat = lstat($strDestinationFile);
|
|
|
|
|
|
|
|
# Make sure that timestamp/size are equal and that timestamp is before the copy start time of the backup
|
|
|
|
if (defined($oStat) &&
|
|
|
|
$oStat->size == $oManifest->get($strSection, $strName, MANIFEST_SUBKEY_SIZE) &&
|
|
|
|
$oStat->mtime == $oManifest->get($strSection, $strName, MANIFEST_SUBKEY_MODIFICATION_TIME) &&
|
|
|
|
$oStat->mtime < $lCopyTimeBegin)
|
|
|
|
{
|
|
|
|
&log(DEBUG, "${strDestinationFile} exists and matches size " . $oStat->size .
|
|
|
|
" and modification time " . $oStat->mtime);
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
}
|
2014-12-22 18:24:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$oFileThread->remove(PATH_DB_ABSOLUTE, $strDestinationFile);
|
|
|
|
}
|
|
|
|
|
2014-12-30 18:59:57 +02:00
|
|
|
# Set user and group if running as root (otherwise current user and group will be used for restore)
|
2014-12-22 18:24:32 +02:00
|
|
|
# Copy the file from the backup to the database
|
2014-12-30 22:41:43 +02:00
|
|
|
$oFileThread->copy(PATH_BACKUP_CLUSTER, (defined($strReference) ? $strReference : $self->{strBackupPath}) .
|
|
|
|
"/${strSourcePath}/${strName}" .
|
2014-12-23 22:03:06 +02:00
|
|
|
($bSourceCompression ? '.' . $oFileThread->{strCompressExtension} : ''),
|
2014-12-22 18:24:32 +02:00
|
|
|
PATH_DB_ABSOLUTE, $strDestinationFile,
|
2014-12-23 22:03:06 +02:00
|
|
|
$bSourceCompression, # Source is compressed based on backup settings
|
2014-12-23 18:48:51 +02:00
|
|
|
undef, undef,
|
2015-01-20 21:13:35 +02:00
|
|
|
$oManifest->get($strSection, $strName, MANIFEST_SUBKEY_MODIFICATION_TIME),
|
|
|
|
$oManifest->get($strSection, $strName, MANIFEST_SUBKEY_MODE),
|
2014-12-31 18:20:46 +02:00
|
|
|
undef,
|
2015-01-20 21:13:35 +02:00
|
|
|
$oManifest->get($strSection, $strName, MANIFEST_SUBKEY_USER),
|
|
|
|
$oManifest->get($strSection, $strName, MANIFEST_SUBKEY_GROUP));
|
2014-12-22 18:24:32 +02:00
|
|
|
}
|
|
|
|
|
2014-12-23 18:48:51 +02:00
|
|
|
# Even number threads move up when they have finished a queue, odd numbered threads move down
|
2014-12-22 18:24:32 +02:00
|
|
|
$iQueueIdx += $iDirection;
|
|
|
|
|
2014-12-23 18:48:51 +02:00
|
|
|
# Reset the queue index when it goes over or under the number of queues
|
2014-12-22 18:24:32 +02:00
|
|
|
if ($iQueueIdx < 0)
|
|
|
|
{
|
2014-12-23 22:03:06 +02:00
|
|
|
$iQueueIdx = @{$oyRestoreQueueRef} - 1;
|
2014-12-22 18:24:32 +02:00
|
|
|
}
|
2014-12-23 22:03:06 +02:00
|
|
|
elsif ($iQueueIdx >= @{$oyRestoreQueueRef})
|
2014-12-22 18:24:32 +02:00
|
|
|
{
|
|
|
|
$iQueueIdx = 0;
|
|
|
|
}
|
2015-02-04 03:33:33 +02:00
|
|
|
|
|
|
|
&log(TRACE, "thread waiting for new file from queue: queue ${iQueueIdx}, start queue ${iQueueStartIdx}");
|
2014-12-19 19:49:56 +02:00
|
|
|
}
|
2014-12-22 18:24:32 +02:00
|
|
|
while ($iQueueIdx != $iQueueStartIdx);
|
|
|
|
|
|
|
|
&log(DEBUG, "thread ${iThreadIdx} exiting");
|
2014-12-18 18:42:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
1;
|