mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2024-12-14 10:13:05 +02:00
801e2a5a2c
This brings consistency between the C and Perl constants and allows for easier code reuse.
1128 lines
50 KiB
Perl
1128 lines
50 KiB
Perl
####################################################################################################################################
|
|
# BACKUP MODULE
|
|
####################################################################################################################################
|
|
package pgBackRest::Backup::Backup;
|
|
|
|
use strict;
|
|
use warnings FATAL => qw(all);
|
|
use Carp qw(confess);
|
|
use English '-no_match_vars';
|
|
|
|
use Exporter qw(import);
|
|
use File::Basename;
|
|
|
|
use pgBackRest::Archive::Common;
|
|
use pgBackRest::Archive::Get::Get;
|
|
use pgBackRest::Backup::Common;
|
|
use pgBackRest::Backup::File;
|
|
use pgBackRest::Backup::Info;
|
|
use pgBackRest::Common::Cipher;
|
|
use pgBackRest::Common::Exception;
|
|
use pgBackRest::Common::Ini;
|
|
use pgBackRest::Common::Log;
|
|
use pgBackRest::Common::Wait;
|
|
use pgBackRest::Common::String;
|
|
use pgBackRest::Config::Config;
|
|
use pgBackRest::Db;
|
|
use pgBackRest::DbVersion;
|
|
use pgBackRest::Manifest;
|
|
use pgBackRest::Protocol::Local::Process;
|
|
use pgBackRest::Protocol::Helper;
|
|
use pgBackRest::Protocol::Storage::Helper;
|
|
use pgBackRest::Common::Io::Handle;
|
|
use pgBackRest::Storage::Base;
|
|
use pgBackRest::Storage::Filter::Gzip;
|
|
use pgBackRest::Storage::Filter::Sha;
|
|
use pgBackRest::Storage::Helper;
|
|
use pgBackRest::Version;
|
|
|
|
####################################################################################################################################
|
|
# new
|
|
####################################################################################################################################
|
|
sub new
|
|
{
|
|
my $class = shift; # Class name
|
|
|
|
# Create the class hash
|
|
my $self = {};
|
|
bless $self, $class;
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
my ($strOperation) = logDebugParam(__PACKAGE__ . '->new');
|
|
|
|
# Return from function and log return values if any
|
|
return logDebugReturn
|
|
(
|
|
$strOperation,
|
|
{name => 'self', value => $self}
|
|
);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# resumeClean - cleans the directory from a previous failed backup so it can be reused
|
|
####################################################################################################################################
|
|
sub resumeClean
|
|
{
|
|
my $self = shift;
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
my
|
|
(
|
|
$strOperation,
|
|
$oStorageRepo,
|
|
$strBackupLabel,
|
|
$oManifest,
|
|
$oAbortedManifest,
|
|
$bOnline,
|
|
$bDelta,
|
|
$strTimelineCurrent,
|
|
$strTimelineLast,
|
|
) =
|
|
logDebugParam
|
|
(
|
|
__PACKAGE__ . '->resumeClean', \@_,
|
|
{name => 'oStorageRepo'},
|
|
{name => 'strBackupLabel'},
|
|
{name => 'oManifest'},
|
|
{name => 'oAbortedManifest'},
|
|
{name => 'bOnline'},
|
|
{name => 'bDelta'},
|
|
{name => 'strTimelineCurrent', required => false},
|
|
{name => 'strTimelineLast', required => false},
|
|
);
|
|
|
|
&log(DETAIL, 'clean resumed backup path: ' . $oStorageRepo->pathGet(STORAGE_REPO_BACKUP . "/${strBackupLabel}"));
|
|
|
|
# Build manifest for aborted backup path
|
|
my $hFile = $oStorageRepo->manifest(STORAGE_REPO_BACKUP . "/${strBackupLabel}");
|
|
|
|
# Get compress flag
|
|
my $bCompressed = $oAbortedManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS);
|
|
|
|
if (!$bDelta)
|
|
{
|
|
# Check to see if delta checksum should be enabled
|
|
$bDelta = $oAbortedManifest->checkDelta(
|
|
'resumed', $oAbortedManifest->boolTest(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_ONLINE, undef, $bOnline),
|
|
$strTimelineCurrent, $strTimelineLast);
|
|
|
|
# If delta is still false, check the files for anomalies
|
|
if (!$bDelta)
|
|
{
|
|
my @stryFileList = ();
|
|
|
|
foreach my $strName (sort(keys(%{$hFile})))
|
|
{
|
|
# Ignore files that will never be in the manifest but should be preserved
|
|
if ($strName eq FILE_MANIFEST_COPY ||
|
|
$strName eq '.')
|
|
{
|
|
next;
|
|
}
|
|
|
|
if ($hFile->{$strName}{type} eq 'f')
|
|
{
|
|
# If the original backup was compressed then remove the extension before checking the manifest
|
|
my $strFile = $strName;
|
|
|
|
if ($bCompressed)
|
|
{
|
|
$strFile = substr($strFile, 0, length($strFile) - 3);
|
|
}
|
|
|
|
# To be preserved the file must exist in the new manifest and not be a reference to a previous backup and must
|
|
# have a checksum
|
|
if ($oManifest->test(MANIFEST_SECTION_TARGET_FILE, $strFile) &&
|
|
!$oManifest->test(MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_REFERENCE) &&
|
|
$oAbortedManifest->test(MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_CHECKSUM))
|
|
{
|
|
push(@stryFileList, $strFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
# If there are files in the list then check if delta should be enabled
|
|
if (@stryFileList)
|
|
{
|
|
$bDelta = $oManifest->checkDeltaFile(\@stryFileList, $oAbortedManifest, undef);
|
|
}
|
|
}
|
|
}
|
|
|
|
# Find paths and files to delete
|
|
my @stryFile;
|
|
|
|
foreach my $strName (sort(keys(%{$hFile})))
|
|
{
|
|
# Ignore files that will never be in the manifest but should be preserved
|
|
if ($strName eq FILE_MANIFEST_COPY ||
|
|
$strName eq '.')
|
|
{
|
|
next;
|
|
}
|
|
|
|
# Get the file type (all links will be deleted since they are easy to recreate)
|
|
my $cType = $hFile->{$strName}{type};
|
|
|
|
# If a directory check if it exists in the new manifest
|
|
if ($cType eq 'd')
|
|
{
|
|
if ($oManifest->test(MANIFEST_SECTION_TARGET_PATH, $strName))
|
|
{
|
|
next;
|
|
}
|
|
}
|
|
# Else if a file
|
|
elsif ($cType eq 'f')
|
|
{
|
|
# If the original backup was compressed then remove the extension before checking the manifest
|
|
my $strFile = $strName;
|
|
|
|
if ($bCompressed)
|
|
{
|
|
$strFile = substr($strFile, 0, length($strFile) - 3);
|
|
}
|
|
|
|
# To be preserved the file must exist in the new manifest and not be a reference to a previous backup
|
|
if ($oManifest->test(MANIFEST_SECTION_TARGET_FILE, $strFile) &&
|
|
!$oManifest->test(MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_REFERENCE))
|
|
{
|
|
# To be preserved the checksum must be defined
|
|
my $strChecksum = $oAbortedManifest->get(MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_CHECKSUM, false);
|
|
|
|
# If the size and timestamp match OR if the size matches and the delta option is set, then keep the file.
|
|
# In the latter case, if the timestamp had changed then rather than removing and recopying the file, the file
|
|
# will be tested in backupFile to see if the db/repo checksum still matches: if so, it is not necessary to recopy,
|
|
# else it will need to be copied to the new backup.
|
|
if (defined($strChecksum) &&
|
|
$oManifest->get(MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_SIZE) ==
|
|
$oAbortedManifest->get(MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_SIZE) &&
|
|
($bDelta ||
|
|
$oManifest->get(MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_TIMESTAMP) ==
|
|
$oAbortedManifest->get(MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_TIMESTAMP)))
|
|
{
|
|
$oManifest->set(MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_CHECKSUM, $strChecksum);
|
|
|
|
# Also copy page checksum results if they exist
|
|
my $bChecksumPage =
|
|
$oAbortedManifest->get(MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_CHECKSUM_PAGE, false);
|
|
|
|
if (defined($bChecksumPage))
|
|
{
|
|
$oManifest->boolSet(MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_CHECKSUM_PAGE, $bChecksumPage);
|
|
|
|
if (!$bChecksumPage &&
|
|
$oAbortedManifest->test(MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_CHECKSUM_PAGE_ERROR))
|
|
{
|
|
$oManifest->set(
|
|
MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_CHECKSUM_PAGE_ERROR,
|
|
$oAbortedManifest->get(
|
|
MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_CHECKSUM_PAGE_ERROR));
|
|
}
|
|
}
|
|
|
|
next;
|
|
}
|
|
}
|
|
}
|
|
|
|
# If a directory then remove it
|
|
if ($cType eq 'd')
|
|
{
|
|
logDebugMisc($strOperation, "remove path ${strName}");
|
|
$oStorageRepo->remove(STORAGE_REPO_BACKUP . "/${strBackupLabel}/${strName}", {bRecurse => true});
|
|
}
|
|
# Else add the file/link to be deleted later
|
|
else
|
|
{
|
|
logDebugMisc($strOperation, "remove file ${strName}");
|
|
push(@stryFile, STORAGE_REPO_BACKUP . "/${strBackupLabel}/${strName}");
|
|
}
|
|
}
|
|
|
|
# Delete files in batch for more efficiency
|
|
if (@stryFile > 0)
|
|
{
|
|
$oStorageRepo->remove(\@stryFile);
|
|
}
|
|
|
|
# Return from function and log return values if any
|
|
return logDebugReturn
|
|
(
|
|
$strOperation,
|
|
{name => 'bDelta', value => $bDelta, trace => true},
|
|
);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# processManifest
|
|
#
|
|
# Process the file level backup. Uses the information in the manifest to determine which files need to be copied. Directories
|
|
# and tablespace links are only created when needed, except in the case of a full backup or if hardlinks are requested.
|
|
####################################################################################################################################
|
|
sub processManifest
|
|
{
|
|
my $self = shift;
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
my
|
|
(
|
|
$strOperation,
|
|
$strDbMasterPath,
|
|
$strDbCopyPath,
|
|
$strType,
|
|
$strDbVersion,
|
|
$bCompress,
|
|
$bHardLink,
|
|
$oBackupManifest,
|
|
$strBackupLabel,
|
|
$strLsnStart,
|
|
) =
|
|
logDebugParam
|
|
(
|
|
__PACKAGE__ . '->processManifest', \@_,
|
|
{name => 'strDbMasterPath'},
|
|
{name => 'strDbCopyPath'},
|
|
{name => 'strType'},
|
|
{name => 'strDbVersion'},
|
|
{name => 'bCompress'},
|
|
{name => 'bHardLink'},
|
|
{name => 'oBackupManifest'},
|
|
{name => 'strBackupLabel'},
|
|
{name => 'strLsnStart', required => false},
|
|
);
|
|
|
|
# Start backup test point
|
|
&log(TEST, TEST_BACKUP_START);
|
|
|
|
# Get the master protocol for keep-alive
|
|
my $oProtocolMaster =
|
|
!isDbLocal({iRemoteIdx => $self->{iMasterRemoteIdx}}) ?
|
|
protocolGet(CFGOPTVAL_REMOTE_TYPE_DB, $self->{iMasterRemoteIdx}) : undef;
|
|
defined($oProtocolMaster) && $oProtocolMaster->noOp();
|
|
|
|
# Initialize the backup process
|
|
my $oBackupProcess = new pgBackRest::Protocol::Local::Process(CFGOPTVAL_LOCAL_TYPE_DB);
|
|
|
|
if ($self->{iCopyRemoteIdx} != $self->{iMasterRemoteIdx})
|
|
{
|
|
$oBackupProcess->hostAdd($self->{iMasterRemoteIdx}, 1);
|
|
}
|
|
|
|
$oBackupProcess->hostAdd($self->{iCopyRemoteIdx}, cfgOption(CFGOPT_PROCESS_MAX));
|
|
|
|
# Variables used for parallel copy
|
|
my $lFileTotal = 0;
|
|
my $lSizeTotal = 0;
|
|
|
|
# If this is a full backup or hard-linked then create all paths and tablespace links
|
|
if ($bHardLink || $strType eq CFGOPTVAL_BACKUP_TYPE_FULL)
|
|
{
|
|
# Create paths
|
|
foreach my $strPath ($oBackupManifest->keys(MANIFEST_SECTION_TARGET_PATH))
|
|
{
|
|
storageRepo()->pathCreate(STORAGE_REPO_BACKUP . "/${strBackupLabel}/${strPath}", {bIgnoreExists => true});
|
|
}
|
|
|
|
if (storageRepo()->driver()->capability(STORAGE_CAPABILITY_LINK))
|
|
{
|
|
for my $strTarget ($oBackupManifest->keys(MANIFEST_SECTION_BACKUP_TARGET))
|
|
{
|
|
if ($oBackupManifest->isTargetTablespace($strTarget))
|
|
{
|
|
storageRepo()->linkCreate(
|
|
STORAGE_REPO_BACKUP . "/${strBackupLabel}/${strTarget}",
|
|
STORAGE_REPO_BACKUP . "/${strBackupLabel}/" . MANIFEST_TARGET_PGDATA . "/${strTarget}",
|
|
{bRelative => true});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Build the lsn start parameter to pass to the extra function
|
|
my $hStartLsnParam =
|
|
{
|
|
iWalId => defined($strLsnStart) ? hex((split('/', $strLsnStart))[0]) : 0xFFFF,
|
|
iWalOffset => defined($strLsnStart) ? hex((split('/', $strLsnStart))[1]) : 0xFFFF,
|
|
};
|
|
|
|
# Iterate all files in the manifest
|
|
foreach my $strRepoFile (
|
|
sort {sprintf("%016d-%s", $oBackupManifest->numericGet(MANIFEST_SECTION_TARGET_FILE, $b, MANIFEST_SUBKEY_SIZE), $b) cmp
|
|
sprintf("%016d-%s", $oBackupManifest->numericGet(MANIFEST_SECTION_TARGET_FILE, $a, MANIFEST_SUBKEY_SIZE), $a)}
|
|
($oBackupManifest->keys(MANIFEST_SECTION_TARGET_FILE, INI_SORT_NONE)))
|
|
{
|
|
# If the file has a reference it does not need to be copied since it can be retrieved from the referenced backup - unless
|
|
# the option to checksum all files is set. However, if hardlinking is enabled the link will need to be created
|
|
my $strReference = $oBackupManifest->get(MANIFEST_SECTION_TARGET_FILE, $strRepoFile, MANIFEST_SUBKEY_REFERENCE, false);
|
|
|
|
if (defined($strReference))
|
|
{
|
|
# If the delta option to checksum all files is not set or it is set and the file size of the referenced file is zero
|
|
# then skip checking/copying this file
|
|
if (!cfgOption(CFGOPT_DELTA) ||
|
|
$oBackupManifest->numericGet(MANIFEST_SECTION_TARGET_FILE, $strRepoFile, MANIFEST_SUBKEY_SIZE) == 0)
|
|
{
|
|
# This file will not need to be copied
|
|
next;
|
|
}
|
|
}
|
|
|
|
# By default put everything into a single queue
|
|
my $strQueueKey = MANIFEST_TARGET_PGDATA;
|
|
|
|
# If the file belongs in a tablespace then put in a tablespace-specific queue
|
|
if (index($strRepoFile, DB_PATH_PGTBLSPC . '/') == 0)
|
|
{
|
|
$strQueueKey = DB_PATH_PGTBLSPC . '/' . (split('\/', $strRepoFile))[1];
|
|
}
|
|
|
|
# Create the file hash
|
|
my $bIgnoreMissing = true;
|
|
my $strDbFile = $oBackupManifest->dbPathGet($strDbCopyPath, $strRepoFile);
|
|
my $iHostConfigIdx = $self->{iCopyRemoteIdx};
|
|
|
|
# Certain files must be copied from the master
|
|
if ($oBackupManifest->boolGet(MANIFEST_SECTION_TARGET_FILE, $strRepoFile, MANIFEST_SUBKEY_MASTER))
|
|
{
|
|
$strDbFile = $oBackupManifest->dbPathGet($strDbMasterPath, $strRepoFile);
|
|
$iHostConfigIdx = $self->{iMasterRemoteIdx};
|
|
}
|
|
|
|
# Make sure that pg_control is not removed during the backup
|
|
if ($strRepoFile eq MANIFEST_TARGET_PGDATA . '/' . DB_FILE_PGCONTROL)
|
|
{
|
|
$bIgnoreMissing = false;
|
|
}
|
|
|
|
# Increment file total and size
|
|
my $lSize = $oBackupManifest->numericGet(MANIFEST_SECTION_TARGET_FILE, $strRepoFile, MANIFEST_SUBKEY_SIZE);
|
|
|
|
$lFileTotal++;
|
|
$lSizeTotal += $lSize;
|
|
|
|
# Queue for parallel backup
|
|
$oBackupProcess->queueJob(
|
|
$iHostConfigIdx, $strQueueKey, $strRepoFile, OP_BACKUP_FILE,
|
|
[$strDbFile, $strRepoFile, $lSize,
|
|
$oBackupManifest->get(MANIFEST_SECTION_TARGET_FILE, $strRepoFile, MANIFEST_SUBKEY_CHECKSUM, false),
|
|
cfgOption(CFGOPT_CHECKSUM_PAGE) ? isChecksumPage($strRepoFile) : false, $strBackupLabel, $bCompress,
|
|
cfgOption(CFGOPT_COMPRESS_LEVEL), $oBackupManifest->numericGet(MANIFEST_SECTION_TARGET_FILE, $strRepoFile,
|
|
MANIFEST_SUBKEY_TIMESTAMP, false), $bIgnoreMissing,
|
|
cfgOption(CFGOPT_CHECKSUM_PAGE) && isChecksumPage($strRepoFile) ? $hStartLsnParam : undef,
|
|
cfgOption(CFGOPT_DELTA), defined($strReference) ? true : false],
|
|
{rParamSecure => $oBackupManifest->cipherPassSub() ? [$oBackupManifest->cipherPassSub()] : undef});
|
|
|
|
# Size and checksum will be removed and then verified later as a sanity check
|
|
$oBackupManifest->remove(MANIFEST_SECTION_TARGET_FILE, $strRepoFile, MANIFEST_SUBKEY_SIZE);
|
|
$oBackupManifest->remove(MANIFEST_SECTION_TARGET_FILE, $strRepoFile, MANIFEST_SUBKEY_CHECKSUM);
|
|
}
|
|
|
|
# pg_control should always be in the backup (unless this is an offline backup)
|
|
if (!$oBackupManifest->test(MANIFEST_SECTION_TARGET_FILE, MANIFEST_FILE_PGCONTROL) && cfgOption(CFGOPT_ONLINE))
|
|
{
|
|
confess &log(ERROR, DB_FILE_PGCONTROL . " must be present in all online backups\n" .
|
|
'HINT: is something wrong with the clock or filesystem timestamps?', ERROR_FILE_MISSING);
|
|
}
|
|
|
|
# If there are no files to backup then we'll exit with an error unless in test mode. The other way this could happen is if
|
|
# the database is down and backup is called with --no-online twice in a row.
|
|
if ($lFileTotal == 0 && !cfgOption(CFGOPT_TEST))
|
|
{
|
|
confess &log(ERROR, "no files have changed since the last backup - this seems unlikely", ERROR_FILE_MISSING);
|
|
}
|
|
|
|
# Running total of bytes copied
|
|
my $lSizeCurrent = 0;
|
|
|
|
# Determine how often the manifest will be saved
|
|
my $lManifestSaveCurrent = 0;
|
|
my $lManifestSaveSize = int($lSizeTotal / 100);
|
|
|
|
if (cfgOptionSource(CFGOPT_MANIFEST_SAVE_THRESHOLD) ne CFGDEF_SOURCE_DEFAULT ||
|
|
$lManifestSaveSize < cfgOption(CFGOPT_MANIFEST_SAVE_THRESHOLD))
|
|
{
|
|
$lManifestSaveSize = cfgOption(CFGOPT_MANIFEST_SAVE_THRESHOLD);
|
|
}
|
|
|
|
# Run the backup jobs and process results
|
|
while (my $hyJob = $oBackupProcess->process())
|
|
{
|
|
foreach my $hJob (@{$hyJob})
|
|
{
|
|
($lSizeCurrent, $lManifestSaveCurrent) = backupManifestUpdate(
|
|
$oBackupManifest, cfgOption(cfgOptionIdFromIndex(CFGOPT_PG_HOST, $hJob->{iHostConfigIdx}), false),
|
|
$hJob->{iProcessId}, @{$hJob->{rParam}}[0..4], @{$hJob->{rResult}}, $lSizeTotal, $lSizeCurrent, $lManifestSaveSize,
|
|
$lManifestSaveCurrent);
|
|
}
|
|
|
|
# A keep-alive is required here because if there are a large number of resumed files that need to be checksummed
|
|
# then the remote might timeout while waiting for a command.
|
|
protocolKeepAlive();
|
|
}
|
|
|
|
foreach my $strFile ($oBackupManifest->keys(MANIFEST_SECTION_TARGET_FILE))
|
|
{
|
|
# If the file has a reference, then it was not copied since it can be retrieved from the referenced backup. However, if
|
|
# hardlinking is enabled the link will need to be created.
|
|
my $strReference = $oBackupManifest->get(MANIFEST_SECTION_TARGET_FILE, $strFile, MANIFEST_SUBKEY_REFERENCE, false);
|
|
|
|
if ($strReference)
|
|
{
|
|
# If hardlinking is enabled then create a hardlink for files that have not changed since the last backup
|
|
if ($bHardLink)
|
|
{
|
|
&log(DETAIL, "hardlink ${strFile} to ${strReference}");
|
|
|
|
storageRepo()->linkCreate(
|
|
STORAGE_REPO_BACKUP . "/${strReference}/${strFile}" . ($bCompress ? qw{.} . COMPRESS_EXT : ''),
|
|
STORAGE_REPO_BACKUP . "/${strBackupLabel}/${strFile}" . ($bCompress ? qw{.} . COMPRESS_EXT : ''),
|
|
{bHard => true});
|
|
}
|
|
# Else log the reference. With delta, it is possible that references may have been removed if a file needed to be
|
|
# recopied.
|
|
else
|
|
{
|
|
logDebugMisc($strOperation, "reference ${strFile} to ${strReference}");
|
|
}
|
|
}
|
|
}
|
|
|
|
# Validate the manifest
|
|
$oBackupManifest->validate();
|
|
|
|
# Return from function and log return values if any
|
|
return logDebugReturn
|
|
(
|
|
$strOperation,
|
|
{name => 'lSizeTotal', value => $lSizeTotal}
|
|
);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# process
|
|
#
|
|
# Process the database backup.
|
|
####################################################################################################################################
|
|
sub process
|
|
{
|
|
my $self = shift;
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
my ($strOperation) = logDebugParam(__PACKAGE__ . '->process');
|
|
|
|
# Record timestamp start
|
|
my $lTimestampStart = time();
|
|
|
|
# Initialize the local file object
|
|
my $oStorageRepo = storageRepo();
|
|
|
|
# Store local type, compress, and hardlink options since they can be modified by the process
|
|
my $strType = cfgOption(CFGOPT_TYPE);
|
|
my $bCompress = cfgOption(CFGOPT_COMPRESS);
|
|
my $bHardLink = cfgOption(CFGOPT_REPO_HARDLINK);
|
|
|
|
# Load the backup.info
|
|
my $oBackupInfo = new pgBackRest::Backup::Info($oStorageRepo->pathGet(STORAGE_REPO_BACKUP));
|
|
|
|
# Get passphrase to open manifest (undefined if repo not encrypted) and intialize passphrase variable for backup files
|
|
my $strCipherPassManifest = $oBackupInfo->cipherPassSub();
|
|
my $strCipherPassBackupSet;
|
|
|
|
# Initialize database objects
|
|
my $oDbMaster = undef;
|
|
my $oDbStandby = undef;
|
|
|
|
# Get the database objects
|
|
($oDbMaster, $self->{iMasterRemoteIdx}, $oDbStandby, $self->{iCopyRemoteIdx}) = dbObjectGet();
|
|
|
|
# If remote copy was not explicitly set then set it equal to master
|
|
if (!defined($self->{iCopyRemoteIdx}))
|
|
{
|
|
$self->{iCopyRemoteIdx} = $self->{iMasterRemoteIdx};
|
|
}
|
|
|
|
# If backup from standby option is set but a standby was not configured in the config file or on the command line, then turn off
|
|
# CFGOPT_BACKUP_STANDBY & warn that backups will be performed from the master.
|
|
if (!defined($oDbStandby) && cfgOption(CFGOPT_BACKUP_STANDBY))
|
|
{
|
|
cfgOptionSet(CFGOPT_BACKUP_STANDBY, false);
|
|
&log(WARN, 'option backup-standby is enabled but standby is not properly configured - ' .
|
|
'backups will be performed from the master');
|
|
}
|
|
|
|
# Initialize the master file object
|
|
my $oStorageDbMaster = storageDb({iRemoteIdx => $self->{iMasterRemoteIdx}});
|
|
|
|
# Determine the database paths
|
|
my $strDbMasterPath = cfgOption(cfgOptionIdFromIndex(CFGOPT_PG_PATH, $self->{iMasterRemoteIdx}));
|
|
my $strDbCopyPath = cfgOption(cfgOptionIdFromIndex(CFGOPT_PG_PATH, $self->{iCopyRemoteIdx}));
|
|
|
|
# Database info
|
|
my ($strDbVersion, $iControlVersion, $iCatalogVersion, $ullDbSysId) = $oDbMaster->info();
|
|
|
|
my $iDbHistoryId = $oBackupInfo->check($strDbVersion, $iControlVersion, $iCatalogVersion, $ullDbSysId);
|
|
|
|
# Find the previous backup based on the type
|
|
my $oLastManifest;
|
|
my $strBackupLastPath;
|
|
my $strTimelineLast;
|
|
|
|
if ($strType ne CFGOPTVAL_BACKUP_TYPE_FULL)
|
|
{
|
|
$strBackupLastPath = $oBackupInfo->last(
|
|
$strType eq CFGOPTVAL_BACKUP_TYPE_DIFF ? CFGOPTVAL_BACKUP_TYPE_FULL : CFGOPTVAL_BACKUP_TYPE_INCR);
|
|
|
|
# If there is a prior backup and it is for the current database, then use it as base
|
|
if (defined($strBackupLastPath) && $oBackupInfo->confirmDb($strBackupLastPath, $strDbVersion, $ullDbSysId))
|
|
{
|
|
$oLastManifest = new pgBackRest::Manifest(
|
|
$oStorageRepo->pathGet(STORAGE_REPO_BACKUP . "/${strBackupLastPath}/" . FILE_MANIFEST),
|
|
{strCipherPass => $strCipherPassManifest});
|
|
|
|
# If the repo is encrypted then use the passphrase in this manifest for the backup set
|
|
$strCipherPassBackupSet = $oLastManifest->cipherPassSub();
|
|
|
|
# Get archive segment timeline for determining if a timeline switch has occurred. Only defined for prior online backup.
|
|
if ($oLastManifest->test(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_STOP))
|
|
{
|
|
$strTimelineLast = substr($oLastManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_STOP), 0, 8);
|
|
}
|
|
|
|
&log(INFO, 'last backup label = ' . $oLastManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LABEL) .
|
|
', version = ' . $oLastManifest->get(INI_SECTION_BACKREST, INI_KEY_VERSION));
|
|
|
|
# If this is incr or diff warn if certain options have changed
|
|
my $strKey;
|
|
|
|
# Warn if compress option changed
|
|
if (!$oLastManifest->boolTest(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS, undef, $bCompress))
|
|
{
|
|
&log(WARN, "${strType} backup cannot alter compress option to '" . boolFormat($bCompress) .
|
|
"', reset to value in ${strBackupLastPath}");
|
|
$bCompress = $oLastManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS);
|
|
}
|
|
|
|
# Warn if hardlink option changed
|
|
if (!$oLastManifest->boolTest(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_HARDLINK, undef, $bHardLink))
|
|
{
|
|
&log(WARN, "${strType} backup cannot alter hardlink option to '" . boolFormat($bHardLink) .
|
|
"', reset to value in ${strBackupLastPath}");
|
|
$bHardLink = $oLastManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_HARDLINK);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
&log(WARN, "no prior backup exists, ${strType} backup has been changed to full");
|
|
$strType = CFGOPTVAL_BACKUP_TYPE_FULL;
|
|
$strBackupLastPath = undef;
|
|
}
|
|
}
|
|
|
|
# Search cluster directory for an aborted backup
|
|
my $strBackupLabel;
|
|
my $oAbortedManifest;
|
|
my $strBackupPath;
|
|
my $strTimelineAborted;
|
|
|
|
foreach my $strAbortedBackup ($oStorageRepo->list(
|
|
STORAGE_REPO_BACKUP, {strExpression => backupRegExpGet(true, true, true), strSortOrder => 'reverse'}))
|
|
{
|
|
# Aborted backups have a copy of the manifest but no main
|
|
if ($oStorageRepo->exists(STORAGE_REPO_BACKUP . "/${strAbortedBackup}/" . FILE_MANIFEST_COPY) &&
|
|
!$oStorageRepo->exists(STORAGE_REPO_BACKUP . "/${strAbortedBackup}/" . FILE_MANIFEST))
|
|
{
|
|
my $bUsable;
|
|
my $strReason = "resume is disabled";
|
|
$strBackupPath = $oStorageRepo->pathGet(STORAGE_REPO_BACKUP . "/${strAbortedBackup}");
|
|
|
|
# Attempt to read the manifest file in the aborted backup to see if it can be used. If any error at all occurs then the
|
|
# backup will be considered unusable and a resume will not be attempted.
|
|
if (cfgOption(CFGOPT_RESUME))
|
|
{
|
|
$strReason = "unable to read ${strBackupPath}/" . FILE_MANIFEST;
|
|
|
|
eval
|
|
{
|
|
# Load the aborted manifest
|
|
$oAbortedManifest = new pgBackRest::Manifest("${strBackupPath}/" . FILE_MANIFEST,
|
|
{strCipherPass => $strCipherPassManifest});
|
|
|
|
# Key and values that do not match
|
|
my $strKey;
|
|
my $strValueNew;
|
|
my $strValueAborted;
|
|
|
|
# Check version
|
|
if ($oAbortedManifest->get(INI_SECTION_BACKREST, INI_KEY_VERSION) ne PROJECT_VERSION)
|
|
{
|
|
$strKey = INI_KEY_VERSION;
|
|
$strValueNew = PROJECT_VERSION;
|
|
$strValueAborted = $oAbortedManifest->get(INI_SECTION_BACKREST, INI_KEY_VERSION);
|
|
}
|
|
# Check format
|
|
elsif ($oAbortedManifest->get(INI_SECTION_BACKREST, INI_KEY_FORMAT) ne REPOSITORY_FORMAT)
|
|
{
|
|
$strKey = INI_KEY_FORMAT;
|
|
$strValueNew = REPOSITORY_FORMAT;
|
|
$strValueAborted = $oAbortedManifest->get(INI_SECTION_BACKREST, INI_KEY_FORMAT);
|
|
}
|
|
# Check backup type
|
|
elsif ($oAbortedManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TYPE) ne $strType)
|
|
{
|
|
$strKey = MANIFEST_KEY_TYPE;
|
|
$strValueNew = $strType;
|
|
$strValueAborted = $oAbortedManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TYPE);
|
|
}
|
|
# Check prior label
|
|
elsif ($oAbortedManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_PRIOR, undef, false, '<undef>') ne
|
|
(defined($strBackupLastPath) ? $strBackupLastPath : '<undef>'))
|
|
{
|
|
$strKey = MANIFEST_KEY_PRIOR;
|
|
$strValueNew = defined($strBackupLastPath) ? $strBackupLastPath : '<undef>';
|
|
$strValueAborted =
|
|
$oAbortedManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_PRIOR, undef, false, '<undef>');
|
|
}
|
|
# Check compression
|
|
elsif ($oAbortedManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS) !=
|
|
cfgOption(CFGOPT_COMPRESS))
|
|
{
|
|
$strKey = MANIFEST_KEY_COMPRESS;
|
|
$strValueNew = cfgOption(CFGOPT_COMPRESS);
|
|
$strValueAborted = $oAbortedManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS);
|
|
}
|
|
# Check hardlink
|
|
elsif ($oAbortedManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_HARDLINK) !=
|
|
cfgOption(CFGOPT_REPO_HARDLINK))
|
|
{
|
|
$strKey = MANIFEST_KEY_HARDLINK;
|
|
$strValueNew = cfgOption(CFGOPT_REPO_HARDLINK);
|
|
$strValueAborted = $oAbortedManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_HARDLINK);
|
|
}
|
|
|
|
# If key is defined then something didn't match
|
|
if (defined($strKey))
|
|
{
|
|
$strReason = "new ${strKey} '${strValueNew}' does not match aborted ${strKey} '${strValueAborted}'";
|
|
}
|
|
# Else the backup can be resumed
|
|
else
|
|
{
|
|
$bUsable = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
or do
|
|
{
|
|
$bUsable = false;
|
|
}
|
|
}
|
|
|
|
# If the backup is usable then set the backup label
|
|
if ($bUsable)
|
|
{
|
|
$strBackupLabel = $strAbortedBackup;
|
|
|
|
# If the repo is encrypted, set the backup set passphrase from this manifest
|
|
if (defined($strCipherPassManifest))
|
|
{
|
|
$strCipherPassBackupSet = $oAbortedManifest->cipherPassSub();
|
|
}
|
|
|
|
# Get the archive segment timeline for determining if a timeline switch has occurred. Only defined for prior online
|
|
# backup.
|
|
if ($oAbortedManifest->test(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_STOP))
|
|
{
|
|
$strTimelineAborted = substr($oAbortedManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_STOP), 0, 8);
|
|
}
|
|
elsif ($oAbortedManifest->test(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_START))
|
|
{
|
|
$strTimelineAborted = substr($oAbortedManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_START), 0, 8);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
&log(WARN, "aborted backup ${strAbortedBackup} cannot be resumed: ${strReason}");
|
|
&log(TEST, TEST_BACKUP_NORESUME);
|
|
|
|
$oStorageRepo->remove(STORAGE_REPO_BACKUP . "/${strAbortedBackup}", {bRecurse => true});
|
|
undef($oAbortedManifest);
|
|
}
|
|
|
|
last;
|
|
}
|
|
}
|
|
|
|
# Generate a passphrase for the backup set if the repo is encrypted
|
|
if (defined($strCipherPassManifest) && !defined($strCipherPassBackupSet) && $strType eq CFGOPTVAL_BACKUP_TYPE_FULL)
|
|
{
|
|
$strCipherPassBackupSet = cipherPassGen();
|
|
}
|
|
|
|
# If backup label is not defined then create the label and path.
|
|
if (!defined($strBackupLabel))
|
|
{
|
|
$strBackupLabel = backupLabel($oStorageRepo, $strType, $strBackupLastPath, $lTimestampStart);
|
|
$strBackupPath = $oStorageRepo->pathGet(STORAGE_REPO_BACKUP . "/${strBackupLabel}");
|
|
}
|
|
|
|
# Declare the backup manifest. Since the manifest could be an aborted backup, don't load it from the file here.
|
|
# Instead just instantiate it. Pass the passphrases to open the manifest and one to encrypt the backup files if the repo is
|
|
# encrypted (undefined if not).
|
|
my $oBackupManifest = new pgBackRest::Manifest("$strBackupPath/" . FILE_MANIFEST,
|
|
{bLoad => false, strDbVersion => $strDbVersion, iDbCatalogVersion => $iCatalogVersion,
|
|
strCipherPass => defined($strCipherPassManifest) ? $strCipherPassManifest : undef,
|
|
strCipherPassSub => defined($strCipherPassManifest) ? $strCipherPassBackupSet : undef});
|
|
|
|
# Backup settings
|
|
$oBackupManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TYPE, undef, $strType);
|
|
$oBackupManifest->numericSet(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TIMESTAMP_START, undef, $lTimestampStart);
|
|
$oBackupManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_BACKUP_STANDBY, undef, cfgOption(CFGOPT_BACKUP_STANDBY));
|
|
$oBackupManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS, undef, $bCompress);
|
|
$oBackupManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_HARDLINK, undef, $bHardLink);
|
|
$oBackupManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_ONLINE, undef, cfgOption(CFGOPT_ONLINE));
|
|
$oBackupManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_ARCHIVE_COPY, undef,
|
|
!cfgOption(CFGOPT_ONLINE) ||
|
|
(cfgOption(CFGOPT_ARCHIVE_CHECK) && cfgOption(CFGOPT_ARCHIVE_COPY)));
|
|
$oBackupManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_ARCHIVE_CHECK, undef,
|
|
!cfgOption(CFGOPT_ONLINE) || cfgOption(CFGOPT_ARCHIVE_CHECK));
|
|
|
|
# Database settings
|
|
$oBackupManifest->numericSet(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_ID, undef, $iDbHistoryId);
|
|
$oBackupManifest->numericSet(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_CONTROL, undef, $iControlVersion);
|
|
$oBackupManifest->numericSet(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_SYSTEM_ID, undef, $ullDbSysId);
|
|
|
|
# Backup from standby can only be used on PostgreSQL >= 9.1
|
|
if (cfgOption(CFGOPT_ONLINE) && cfgOption(CFGOPT_BACKUP_STANDBY) && $strDbVersion < PG_VERSION_BACKUP_STANDBY)
|
|
{
|
|
confess &log(ERROR,
|
|
'option \'' . cfgOptionName(CFGOPT_BACKUP_STANDBY) . '\' not valid for PostgreSQL < ' . PG_VERSION_BACKUP_STANDBY,
|
|
ERROR_CONFIG);
|
|
}
|
|
|
|
# Start backup (unless --no-online is set)
|
|
my $strArchiveStart = undef;
|
|
my $strLsnStart = undef;
|
|
my $iWalSegmentSize = undef;
|
|
my $hTablespaceMap = undef;
|
|
my $hDatabaseMap = undef;
|
|
my $strTimelineCurrent = undef;
|
|
|
|
# If this is an offline backup
|
|
if (!cfgOption(CFGOPT_ONLINE))
|
|
{
|
|
# If checksum-page is not explictly enabled then disable it. Even if the version is high enough to have checksums we can't
|
|
# know if they are enabled without asking the database. When pg_control can be reliably parsed then this decision could be
|
|
# based on that.
|
|
if (!cfgOptionTest(CFGOPT_CHECKSUM_PAGE))
|
|
{
|
|
cfgOptionSet(CFGOPT_CHECKSUM_PAGE, false);
|
|
}
|
|
|
|
# Check if Postgres is running and if so only continue when forced
|
|
if ($oStorageDbMaster->exists($strDbMasterPath . '/' . DB_FILE_POSTMASTERPID))
|
|
{
|
|
if (cfgOption(CFGOPT_FORCE))
|
|
{
|
|
&log(WARN, '--no-online passed and ' . DB_FILE_POSTMASTERPID . ' exists but --force was passed so backup will ' .
|
|
'continue though it looks like the postmaster is running and the backup will probably not be ' .
|
|
'consistent');
|
|
}
|
|
else
|
|
{
|
|
confess &log(ERROR, '--no-online passed but ' . DB_FILE_POSTMASTERPID . ' exists - looks like the postmaster is ' .
|
|
'running. Shutdown the postmaster and try again, or use --force.', ERROR_POSTMASTER_RUNNING);
|
|
}
|
|
}
|
|
}
|
|
# Else start the backup normally
|
|
else
|
|
{
|
|
# Start the backup
|
|
($strArchiveStart, $strLsnStart, $iWalSegmentSize) =
|
|
$oDbMaster->backupStart(
|
|
PROJECT_NAME . ' backup started at ' . timestampFormat(undef, $lTimestampStart), cfgOption(CFGOPT_START_FAST));
|
|
|
|
# Record the archive start location
|
|
$oBackupManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_START, undef, $strArchiveStart);
|
|
$oBackupManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LSN_START, undef, $strLsnStart);
|
|
&log(INFO, "backup start archive = ${strArchiveStart}, lsn = ${strLsnStart}");
|
|
|
|
# Get the timeline from the archive
|
|
$strTimelineCurrent = substr($strArchiveStart, 0, 8);
|
|
|
|
# Get tablespace map
|
|
$hTablespaceMap = $oDbMaster->tablespaceMapGet();
|
|
|
|
# Get database map
|
|
$hDatabaseMap = $oDbMaster->databaseMapGet();
|
|
|
|
# Wait for replay on the standby to catch up
|
|
if (cfgOption(CFGOPT_BACKUP_STANDBY))
|
|
{
|
|
my ($strStandbyDbVersion, $iStandbyControlVersion, $iStandbyCatalogVersion, $ullStandbyDbSysId) = $oDbStandby->info();
|
|
$oBackupInfo->check($strStandbyDbVersion, $iStandbyControlVersion, $iStandbyCatalogVersion, $ullStandbyDbSysId);
|
|
|
|
$oDbStandby->configValidate();
|
|
|
|
&log(INFO, "wait for replay on the standby to reach ${strLsnStart}");
|
|
|
|
my ($strReplayedLSN, $strCheckpointLSN) = $oDbStandby->replayWait($strLsnStart);
|
|
|
|
&log(
|
|
INFO,
|
|
"replay on the standby reached ${strReplayedLSN}" .
|
|
(defined($strCheckpointLSN) ? ", checkpoint ${strCheckpointLSN}" : ''));
|
|
|
|
# The standby db object won't be used anymore so undef it to catch any subsequent references
|
|
undef($oDbStandby);
|
|
protocolDestroy(CFGOPTVAL_REMOTE_TYPE_DB, $self->{iCopyRemoteIdx}, true);
|
|
}
|
|
}
|
|
|
|
# Don't allow the checksum-page option to change in a diff or incr backup. This could be confusing as only certain files would
|
|
# be checksummed and the list could be incomplete during reporting.
|
|
if ($strType ne CFGOPTVAL_BACKUP_TYPE_FULL && defined($strBackupLastPath))
|
|
{
|
|
# If not defined this backup was done in a version prior to page checksums being introduced. Just set checksum-page to
|
|
# false and move on without a warning. Page checksums will start on the next full backup.
|
|
if (!$oLastManifest->test(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_CHECKSUM_PAGE))
|
|
{
|
|
cfgOptionSet(CFGOPT_CHECKSUM_PAGE, false);
|
|
}
|
|
else
|
|
{
|
|
my $bChecksumPageLast =
|
|
$oLastManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_CHECKSUM_PAGE);
|
|
|
|
if ($bChecksumPageLast != cfgOption(CFGOPT_CHECKSUM_PAGE))
|
|
{
|
|
&log(WARN,
|
|
"${strType} backup cannot alter '" . cfgOptionName(CFGOPT_CHECKSUM_PAGE) . "' option to '" .
|
|
boolFormat(cfgOption(CFGOPT_CHECKSUM_PAGE)) . "', reset to '" . boolFormat($bChecksumPageLast) .
|
|
"' from ${strBackupLastPath}");
|
|
cfgOptionSet(CFGOPT_CHECKSUM_PAGE, $bChecksumPageLast);
|
|
}
|
|
}
|
|
}
|
|
|
|
# Record checksum-page option in the manifest
|
|
$oBackupManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_CHECKSUM_PAGE, undef, cfgOption(CFGOPT_CHECKSUM_PAGE));
|
|
|
|
# Build the manifest. The delta option may have changed from false to true during the manifest build so set it to the result.
|
|
cfgOptionSet(CFGOPT_DELTA, $oBackupManifest->build(
|
|
$oStorageDbMaster, $strDbMasterPath, $oLastManifest, cfgOption(CFGOPT_ONLINE), cfgOption(CFGOPT_DELTA), $hTablespaceMap,
|
|
$hDatabaseMap, cfgOption(CFGOPT_EXCLUDE, false), $strTimelineCurrent, $strTimelineLast));
|
|
|
|
&log(TEST, TEST_MANIFEST_BUILD);
|
|
|
|
# If resuming from an aborted backup
|
|
if (defined($oAbortedManifest))
|
|
{
|
|
&log(WARN, "aborted backup ${strBackupLabel} of same type exists, will be cleaned to remove invalid files and resumed");
|
|
&log(TEST, TEST_BACKUP_RESUME);
|
|
|
|
# Clean the backup path before resuming. The delta option may have changed from false to true during the reseume clean
|
|
# so set it to the result.
|
|
cfgOptionSet(CFGOPT_DELTA, $self->resumeClean($oStorageRepo, $strBackupLabel, $oBackupManifest, $oAbortedManifest,
|
|
cfgOption(CFGOPT_ONLINE), cfgOption(CFGOPT_DELTA), $strTimelineCurrent, $strTimelineAborted));
|
|
}
|
|
# Else create the backup path
|
|
else
|
|
{
|
|
logDebugMisc($strOperation, "create backup path ${strBackupPath}");
|
|
$oStorageRepo->pathCreate(STORAGE_REPO_BACKUP . "/${strBackupLabel}");
|
|
}
|
|
|
|
# Set the delta option in the manifest
|
|
$oBackupManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_DELTA, undef, cfgOption(CFGOPT_DELTA));
|
|
|
|
# Save the backup manifest
|
|
$oBackupManifest->saveCopy();
|
|
|
|
# Perform the backup
|
|
my $lBackupSizeTotal =
|
|
$self->processManifest(
|
|
$strDbMasterPath, $strDbCopyPath, $strType, $strDbVersion, $bCompress, $bHardLink, $oBackupManifest, $strBackupLabel,
|
|
$strLsnStart);
|
|
&log(INFO, "${strType} backup size = " . fileSizeFormat($lBackupSizeTotal));
|
|
|
|
# Master file object no longer needed
|
|
undef($oStorageDbMaster);
|
|
|
|
# Stop backup (unless --no-online is set)
|
|
my $strArchiveStop = undef;
|
|
my $strLsnStop = undef;
|
|
|
|
if (cfgOption(CFGOPT_ONLINE))
|
|
{
|
|
($strArchiveStop, $strLsnStop, my $strTimestampDbStop, my $oFileHash) = $oDbMaster->backupStop();
|
|
|
|
$oBackupManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_STOP, undef, $strArchiveStop);
|
|
$oBackupManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LSN_STOP, undef, $strLsnStop);
|
|
&log(INFO, "backup stop archive = ${strArchiveStop}, lsn = ${strLsnStop}");
|
|
|
|
# Write out files returned from stop backup
|
|
foreach my $strFile (sort(keys(%{$oFileHash})))
|
|
{
|
|
# Only save the file if it has content
|
|
if (defined($oFileHash->{$strFile}))
|
|
{
|
|
my $rhyFilter = [{strClass => STORAGE_FILTER_SHA}];
|
|
|
|
# Add compression filter
|
|
if ($bCompress)
|
|
{
|
|
push(@{$rhyFilter}, {strClass => STORAGE_FILTER_GZIP});
|
|
}
|
|
|
|
# If the backups are encrypted, then the passphrase for the backup set from the manifest file is required to access
|
|
# the file in the repo
|
|
my $oDestinationFileIo = $oStorageRepo->openWrite(
|
|
STORAGE_REPO_BACKUP . "/${strBackupLabel}/${strFile}" . ($bCompress ? qw{.} . COMPRESS_EXT : ''),
|
|
{rhyFilter => $rhyFilter,
|
|
strCipherPass => defined($strCipherPassBackupSet) ? $strCipherPassBackupSet : undef});
|
|
|
|
# Write content out to a file
|
|
$oStorageRepo->put($oDestinationFileIo, $oFileHash->{$strFile});
|
|
|
|
# Add file to manifest
|
|
$oBackupManifest->fileAdd(
|
|
$strFile, time(), length($oFileHash->{$strFile}), $oDestinationFileIo->result(STORAGE_FILTER_SHA), true);
|
|
|
|
&log(DETAIL, "wrote '${strFile}' file returned from pg_stop_backup()");
|
|
}
|
|
}
|
|
}
|
|
|
|
# Remotes no longer needed (destroy them here so they don't timeout)
|
|
&log(TEST, TEST_BACKUP_STOP);
|
|
|
|
undef($oDbMaster);
|
|
protocolDestroy(undef, undef, true);
|
|
|
|
# If archive logs are required to complete the backup, then check them. This is the default, but can be overridden if the
|
|
# archive logs are going to a different server. Be careful of this option because there is no way to verify that the backup
|
|
# will be consistent - at least not here.
|
|
if (cfgOption(CFGOPT_ONLINE) && cfgOption(CFGOPT_ARCHIVE_CHECK))
|
|
{
|
|
# Save the backup manifest before getting archive logs in case of failure
|
|
$oBackupManifest->saveCopy();
|
|
|
|
# Create the modification time for the archive logs
|
|
my $lModificationTime = time();
|
|
|
|
# After the backup has been stopped, need to make a copy of the archive logs to make the db consistent
|
|
logDebugMisc($strOperation, "retrieve archive logs ${strArchiveStart}:${strArchiveStop}");
|
|
|
|
my $oArchiveInfo = new pgBackRest::Archive::Info(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE), true);
|
|
my $strArchiveId = $oArchiveInfo->archiveId();
|
|
my @stryArchive = lsnFileRange($strLsnStart, $strLsnStop, $strDbVersion, $iWalSegmentSize);
|
|
|
|
foreach my $strArchive (@stryArchive)
|
|
{
|
|
my $strArchiveFile = walSegmentFind(
|
|
$oStorageRepo, $strArchiveId, substr($strArchiveStop, 0, 8) . $strArchive, cfgOption(CFGOPT_ARCHIVE_TIMEOUT));
|
|
|
|
$strArchive = substr($strArchiveFile, 0, 24);
|
|
|
|
if (cfgOption(CFGOPT_ARCHIVE_COPY))
|
|
{
|
|
logDebugMisc($strOperation, "archive: ${strArchive} (${strArchiveFile})");
|
|
|
|
# Copy the log file from the archive repo to the backup
|
|
my $bArchiveCompressed = $strArchiveFile =~ ('^.*\.' . COMPRESS_EXT . '\$');
|
|
|
|
$oStorageRepo->copy(
|
|
$oStorageRepo->openRead(STORAGE_REPO_ARCHIVE . "/${strArchiveId}/${strArchiveFile}",
|
|
{strCipherPass => $oArchiveInfo->cipherPassSub()}),
|
|
$oStorageRepo->openWrite(STORAGE_REPO_BACKUP . "/${strBackupLabel}/" . MANIFEST_TARGET_PGDATA . qw{/} .
|
|
$oBackupManifest->walPath() . "/${strArchive}" . ($bCompress ? qw{.} . COMPRESS_EXT : ''),
|
|
{bPathCreate => true, strCipherPass => $strCipherPassBackupSet})
|
|
);
|
|
|
|
# Add the archive file to the manifest so it can be part of the restore and checked in validation
|
|
my $strPathLog = MANIFEST_TARGET_PGDATA . qw{/} . $oBackupManifest->walPath();
|
|
my $strFileLog = "${strPathLog}/${strArchive}";
|
|
|
|
# Add file to manifest
|
|
$oBackupManifest->fileAdd(
|
|
$strFileLog, $lModificationTime, PG_WAL_SEGMENT_SIZE, substr($strArchiveFile, 25, 40), true);
|
|
}
|
|
}
|
|
}
|
|
|
|
# Record timestamp stop in the config
|
|
my $lTimestampStop = time();
|
|
$oBackupManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TIMESTAMP_STOP, undef, $lTimestampStop + 0);
|
|
$oBackupManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LABEL, undef, $strBackupLabel);
|
|
|
|
# Sync backup path if supported
|
|
if ($oStorageRepo->driver()->capability(STORAGE_CAPABILITY_PATH_SYNC))
|
|
{
|
|
# Sync all paths in the backup
|
|
$oStorageRepo->pathSync(STORAGE_REPO_BACKUP . "/${strBackupLabel}");
|
|
|
|
foreach my $strPath ($oBackupManifest->keys(MANIFEST_SECTION_TARGET_PATH))
|
|
{
|
|
my $strPathSync = $oStorageRepo->pathGet(STORAGE_REPO_BACKUP . "/${strBackupLabel}/$strPath");
|
|
|
|
# Not all paths are created for diff/incr backups, so only sync if this is a full backup or the path exists
|
|
if ($strType eq CFGOPTVAL_BACKUP_TYPE_FULL || $oStorageRepo->pathExists($strPathSync))
|
|
{
|
|
$oStorageRepo->pathSync($strPathSync);
|
|
}
|
|
}
|
|
}
|
|
|
|
# Final save of the backup manifest
|
|
$oBackupManifest->save();
|
|
|
|
&log(INFO, "new backup label = ${strBackupLabel}");
|
|
|
|
# Copy a compressed version of the manifest to history. If the repo is encrypted then the passphrase to open the manifest is
|
|
# required.
|
|
my $strHistoryPath = $oStorageRepo->pathGet(
|
|
STORAGE_REPO_BACKUP . qw{/} . PATH_BACKUP_HISTORY . qw{/} . substr($strBackupLabel, 0, 4));
|
|
|
|
$oStorageRepo->copy(
|
|
$oStorageRepo->openRead(STORAGE_REPO_BACKUP . "/${strBackupLabel}/" . FILE_MANIFEST,
|
|
{'strCipherPass' => $strCipherPassManifest}),
|
|
$oStorageRepo->openWrite(
|
|
"${strHistoryPath}/${strBackupLabel}.manifest." . COMPRESS_EXT,
|
|
{rhyFilter => [{strClass => STORAGE_FILTER_GZIP}],
|
|
bPathCreate => true, bAtomic => true,
|
|
strCipherPass => defined($strCipherPassManifest) ? $strCipherPassManifest : undef}));
|
|
|
|
# Sync history path if supported
|
|
if ($oStorageRepo->driver()->capability(STORAGE_CAPABILITY_PATH_SYNC))
|
|
{
|
|
$oStorageRepo->pathSync(STORAGE_REPO_BACKUP . qw{/} . PATH_BACKUP_HISTORY);
|
|
$oStorageRepo->pathSync($strHistoryPath);
|
|
}
|
|
|
|
# Create a link to the most recent backup
|
|
$oStorageRepo->remove(STORAGE_REPO_BACKUP . qw(/) . LINK_LATEST);
|
|
|
|
if (storageRepo()->driver()->capability(STORAGE_CAPABILITY_LINK))
|
|
{
|
|
$oStorageRepo->linkCreate(
|
|
STORAGE_REPO_BACKUP . "/${strBackupLabel}", STORAGE_REPO_BACKUP . qw{/} . LINK_LATEST, {bRelative => true});
|
|
}
|
|
|
|
# Save backup info
|
|
$oBackupInfo->add($oBackupManifest);
|
|
|
|
# Sync backup root path if supported
|
|
if ($oStorageRepo->driver()->capability(STORAGE_CAPABILITY_PATH_SYNC))
|
|
{
|
|
$oStorageRepo->pathSync(STORAGE_REPO_BACKUP);
|
|
}
|
|
|
|
# Return from function and log return values if any
|
|
return logDebugReturn($strOperation);
|
|
}
|
|
|
|
1;
|