You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-11-05 04:30:41 +03:00
This macro block encapsulates the common pattern of switching to the prior (formerly called old) mem context to return results from a function. Also rename MEM_CONTEXT_OLD() to memContextPrior(). This violates our convention of macros being in all caps but memContextPrior() will become a function very soon so this will reduce churn.
480 lines
19 KiB
C
480 lines
19 KiB
C
/***********************************************************************************************************************************
|
|
Archive Common
|
|
***********************************************************************************************************************************/
|
|
#include "build.auto.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "command/archive/common.h"
|
|
#include "common/debug.h"
|
|
#include "common/log.h"
|
|
#include "common/memContext.h"
|
|
#include "common/regExp.h"
|
|
#include "common/wait.h"
|
|
#include "config/config.h"
|
|
#include "postgres/version.h"
|
|
#include "storage/helper.h"
|
|
#include "storage/helper.h"
|
|
|
|
/***********************************************************************************************************************************
|
|
WAL segment constants
|
|
***********************************************************************************************************************************/
|
|
STRING_EXTERN(WAL_SEGMENT_REGEXP_STR, WAL_SEGMENT_REGEXP);
|
|
STRING_EXTERN(WAL_SEGMENT_PARTIAL_REGEXP_STR, WAL_SEGMENT_PARTIAL_REGEXP);
|
|
STRING_EXTERN(WAL_SEGMENT_DIR_REGEXP_STR, WAL_SEGMENT_DIR_REGEXP);
|
|
STRING_EXTERN(WAL_SEGMENT_FILE_REGEXP_STR, WAL_SEGMENT_FILE_REGEXP);
|
|
|
|
/***********************************************************************************************************************************
|
|
Global error file constant
|
|
***********************************************************************************************************************************/
|
|
#define STATUS_FILE_GLOBAL "global"
|
|
STRING_STATIC(STATUS_FILE_GLOBAL_STR, STATUS_FILE_GLOBAL);
|
|
|
|
STRING_STATIC(STATUS_FILE_GLOBAL_ERROR_STR, STATUS_FILE_GLOBAL STATUS_EXT_ERROR);
|
|
|
|
/***********************************************************************************************************************************
|
|
Get the correct spool queue based on the archive mode
|
|
***********************************************************************************************************************************/
|
|
static const String *
|
|
archiveAsyncSpoolQueue(ArchiveMode archiveMode)
|
|
{
|
|
FUNCTION_TEST_BEGIN();
|
|
FUNCTION_TEST_PARAM(ENUM, archiveMode);
|
|
FUNCTION_TEST_END();
|
|
|
|
FUNCTION_TEST_RETURN((archiveMode == archiveModeGet ? STORAGE_SPOOL_ARCHIVE_IN_STR : STORAGE_SPOOL_ARCHIVE_OUT_STR));
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Check for ok/error status files in the spool in/out directory
|
|
***********************************************************************************************************************************/
|
|
bool
|
|
archiveAsyncStatus(ArchiveMode archiveMode, const String *walSegment, bool confessOnError)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
|
FUNCTION_LOG_PARAM(ENUM, archiveMode);
|
|
FUNCTION_LOG_PARAM(STRING, walSegment);
|
|
FUNCTION_LOG_PARAM(BOOL, confessOnError);
|
|
FUNCTION_LOG_END();
|
|
|
|
bool result = false;
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
{
|
|
const String *errorFile = NULL;
|
|
bool errorFileExists = false;
|
|
|
|
const String *spoolQueue = archiveAsyncSpoolQueue(archiveMode);
|
|
|
|
String *okFile = strNewFmt("%s" STATUS_EXT_OK, strPtr(walSegment));
|
|
bool okFileExists = storageExistsP(storageSpool(), strNewFmt("%s/%s", strPtr(spoolQueue), strPtr(okFile)));
|
|
|
|
// If the ok file does not exist then check to see if a file-specific or global error exists
|
|
if (!okFileExists)
|
|
{
|
|
// Check for a file-specific error first
|
|
errorFile = strNewFmt("%s" STATUS_EXT_ERROR, strPtr(walSegment));
|
|
errorFileExists = storageExistsP(storageSpool(), strNewFmt("%s/%s", strPtr(spoolQueue), strPtr(errorFile)));
|
|
|
|
// If that doesn't exist then check for a global error
|
|
if (!errorFileExists)
|
|
{
|
|
errorFile = STATUS_FILE_GLOBAL_ERROR_STR;
|
|
errorFileExists = storageExistsP(storageSpool(), strNewFmt("%s/%s", strPtr(spoolQueue), strPtr(errorFile)));
|
|
}
|
|
}
|
|
|
|
// If either of them exists then check what happened and report back
|
|
if (okFileExists || errorFileExists)
|
|
{
|
|
// Get the status file content
|
|
const String *statusFile = okFileExists ? okFile: errorFile;
|
|
|
|
String *content = strNewBuf(
|
|
storageGetP(storageNewReadP(storageSpool(), strNewFmt("%s/%s", strPtr(spoolQueue), strPtr(statusFile)))));
|
|
|
|
// Get the code and message if the file has content
|
|
int code = 0;
|
|
const String *message = NULL;
|
|
|
|
if (strSize(content) != 0)
|
|
{
|
|
// Find the line feed after the error code -- should be the first one
|
|
const char *linefeedPtr = strchr(strPtr(content), '\n');
|
|
|
|
// Error if linefeed not found
|
|
if (linefeedPtr == NULL)
|
|
THROW_FMT(FormatError, "%s content must have at least two lines", strPtr(statusFile));
|
|
|
|
// Error if message is zero-length
|
|
if (strlen(linefeedPtr + 1) == 0)
|
|
THROW_FMT(FormatError, "%s message must be > 0", strPtr(statusFile));
|
|
|
|
// Get contents
|
|
code = varIntForce(VARSTR(strNewN(strPtr(content), (size_t)(linefeedPtr - strPtr(content)))));
|
|
message = strTrim(strNew(linefeedPtr + 1));
|
|
}
|
|
|
|
// Process OK files
|
|
if (okFileExists)
|
|
{
|
|
// If there is content in the status file it is a warning
|
|
if (strSize(content) != 0)
|
|
{
|
|
// If error code is not success, then this was a renamed error file
|
|
if (code != 0)
|
|
{
|
|
message = strNewFmt(
|
|
"WAL segment '%s' was not pushed due to error [%d] and was manually skipped: %s", strPtr(walSegment),
|
|
code, strPtr(message));
|
|
}
|
|
|
|
LOG_WARN(strPtr(message));
|
|
}
|
|
|
|
result = true;
|
|
}
|
|
else if (confessOnError)
|
|
{
|
|
// Error status files must have content
|
|
if (strSize(content) == 0)
|
|
THROW_FMT(AssertError, "status file '%s' has no content", strPtr(statusFile));
|
|
|
|
// Throw error using the code passed in the file
|
|
THROW_CODE(code, strPtr(message));
|
|
}
|
|
}
|
|
}
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
FUNCTION_LOG_RETURN(BOOL, result);
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Write an error status file
|
|
***********************************************************************************************************************************/
|
|
void
|
|
archiveAsyncStatusErrorWrite(ArchiveMode archiveMode, const String *walSegment, int code, const String *message)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
|
FUNCTION_LOG_PARAM(ENUM, archiveMode);
|
|
FUNCTION_LOG_PARAM(STRING, walSegment);
|
|
FUNCTION_LOG_PARAM(INT, code);
|
|
FUNCTION_LOG_PARAM(STRING, message);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(code != 0);
|
|
ASSERT(message != NULL);
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
{
|
|
const String *errorFile = walSegment == NULL ? STATUS_FILE_GLOBAL_STR : walSegment;
|
|
|
|
storagePutP(
|
|
storageNewWriteP(
|
|
storageSpoolWrite(),
|
|
strNewFmt("%s/%s" STATUS_EXT_ERROR, strPtr(archiveAsyncSpoolQueue(archiveMode)), strPtr(errorFile))),
|
|
BUFSTR(strNewFmt("%d\n%s", code, strPtr(message))));
|
|
}
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Write an ok status file
|
|
***********************************************************************************************************************************/
|
|
void
|
|
archiveAsyncStatusOkWrite(ArchiveMode archiveMode, const String *walSegment, const String *warning)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
|
FUNCTION_LOG_PARAM(ENUM, archiveMode);
|
|
FUNCTION_LOG_PARAM(STRING, walSegment);
|
|
FUNCTION_LOG_PARAM(STRING, warning);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(walSegment != NULL);
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
{
|
|
// Write file
|
|
storagePutP(
|
|
storageNewWriteP(
|
|
storageSpoolWrite(),
|
|
strNewFmt("%s/%s" STATUS_EXT_OK, strPtr(archiveAsyncSpoolQueue(archiveMode)), strPtr(walSegment))),
|
|
warning == NULL ? NULL : BUFSTR(strNewFmt("0\n%s", strPtr(warning))));
|
|
}
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Is the segment partial?
|
|
***********************************************************************************************************************************/
|
|
bool
|
|
walIsPartial(const String *walSegment)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
FUNCTION_LOG_PARAM(STRING, walSegment);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(walSegment != NULL);
|
|
ASSERT(walIsSegment(walSegment));
|
|
|
|
FUNCTION_LOG_RETURN(BOOL, strEndsWithZ(walSegment, WAL_SEGMENT_PARTIAL_EXT));
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Generates the location of the wal directory using a relative wal path and the supplied pg path
|
|
***********************************************************************************************************************************/
|
|
String *
|
|
walPath(const String *walFile, const String *pgPath, const String *command)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
|
FUNCTION_LOG_PARAM(STRING, walFile);
|
|
FUNCTION_LOG_PARAM(STRING, pgPath);
|
|
FUNCTION_LOG_PARAM(STRING, command);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(walFile != NULL);
|
|
ASSERT(command != NULL);
|
|
|
|
String *result = NULL;
|
|
|
|
if (!strBeginsWithZ(walFile, "/"))
|
|
{
|
|
// Error if walFile has a relative path and pgPath is not set
|
|
if (pgPath == NULL)
|
|
{
|
|
THROW_FMT(
|
|
OptionRequiredError,
|
|
"option '" CFGOPT_PG1_PATH "' must be specified when relative wal paths are used\n"
|
|
"HINT: is %%f passed to %s instead of %%p?\n"
|
|
"HINT: PostgreSQL may pass relative paths even with %%p depending on the environment.",
|
|
strPtr(command));
|
|
}
|
|
|
|
// Get the working directory
|
|
char currentWorkDir[4096];
|
|
THROW_ON_SYS_ERROR(getcwd(currentWorkDir, sizeof(currentWorkDir)) == NULL, FormatError, "unable to get cwd");
|
|
|
|
// Check if the working directory is the same as pgPath
|
|
if (!strEqZ(pgPath, currentWorkDir))
|
|
{
|
|
// If not we'll change the working directory to pgPath and see if that equals the working directory we got called with
|
|
THROW_ON_SYS_ERROR_FMT(chdir(strPtr(pgPath)) != 0, PathMissingError, "unable to chdir() to '%s'", strPtr(pgPath));
|
|
|
|
// Get the new working directory
|
|
char newWorkDir[4096];
|
|
THROW_ON_SYS_ERROR(getcwd(newWorkDir, sizeof(newWorkDir)) == NULL, FormatError, "unable to get cwd");
|
|
|
|
// Error if the new working directory is not equal to the original current working directory. This means that
|
|
// PostgreSQL and pgBackrest have a different idea about where the PostgreSQL data directory is located.
|
|
if (strcmp(currentWorkDir, newWorkDir) != 0)
|
|
THROW_FMT(AssertError, "working path '%s' is not the same path as '%s'", currentWorkDir, strPtr(pgPath));
|
|
}
|
|
|
|
result = strNewFmt("%s/%s", strPtr(pgPath), strPtr(walFile));
|
|
}
|
|
else
|
|
result = strDup(walFile);
|
|
|
|
FUNCTION_LOG_RETURN(STRING, result);
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Is the file a segment or some other file (e.g. .history, .backup, etc)
|
|
***********************************************************************************************************************************/
|
|
bool
|
|
walIsSegment(const String *walSegment)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
FUNCTION_LOG_PARAM(STRING, walSegment);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(walSegment != NULL);
|
|
|
|
// Create the regular expression to identify WAL segments if it does not already exist
|
|
static RegExp *regExpSegment = NULL;
|
|
|
|
if (regExpSegment == NULL)
|
|
{
|
|
MEM_CONTEXT_BEGIN(memContextTop())
|
|
{
|
|
regExpSegment = regExpNew(WAL_SEGMENT_PARTIAL_REGEXP_STR);
|
|
}
|
|
MEM_CONTEXT_END();
|
|
}
|
|
|
|
FUNCTION_LOG_RETURN(BOOL, regExpMatch(regExpSegment, walSegment));
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Find a WAL segment in the repository
|
|
|
|
The file name can have several things appended such as a hash, compression extension, and partial extension so it is possible to
|
|
have multiple files that match the segment, though more than one match is not a good thing.
|
|
***********************************************************************************************************************************/
|
|
String *
|
|
walSegmentFind(const Storage *storage, const String *archiveId, const String *walSegment, TimeMSec timeout)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
|
FUNCTION_LOG_PARAM(STORAGE, storage);
|
|
FUNCTION_LOG_PARAM(STRING, archiveId);
|
|
FUNCTION_LOG_PARAM(STRING, walSegment);
|
|
FUNCTION_LOG_PARAM(TIME_MSEC, timeout);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(storage != NULL);
|
|
ASSERT(archiveId != NULL);
|
|
ASSERT(walSegment != NULL);
|
|
ASSERT(walIsSegment(walSegment));
|
|
|
|
String *result = NULL;
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
{
|
|
Wait *wait = timeout > 0 ? waitNew(timeout) : NULL;
|
|
|
|
do
|
|
{
|
|
// Get a list of all WAL segments that match
|
|
StringList *list = storageListP(
|
|
storage, strNewFmt(STORAGE_REPO_ARCHIVE "/%s/%s", strPtr(archiveId), strPtr(strSubN(walSegment, 0, 16))),
|
|
.expression = strNewFmt("^%s%s-[0-f]{40}(\\.gz){0,1}$", strPtr(strSubN(walSegment, 0, 24)),
|
|
walIsPartial(walSegment) ? WAL_SEGMENT_PARTIAL_EXT : ""), .nullOnMissing = true);
|
|
|
|
// If there are results
|
|
if (list != NULL && strLstSize(list) > 0)
|
|
{
|
|
// Error if there is more than one match
|
|
if (strLstSize(list) > 1)
|
|
{
|
|
THROW_FMT(
|
|
ArchiveDuplicateError,
|
|
"duplicates found in archive for WAL segment %s: %s\n"
|
|
"HINT: are multiple primaries archiving to this stanza?",
|
|
strPtr(walSegment), strPtr(strLstJoin(strLstSort(list, sortOrderAsc), ", ")));
|
|
}
|
|
|
|
// Copy file name of WAL segment found into the prior context
|
|
MEM_CONTEXT_PRIOR_BEGIN()
|
|
{
|
|
result = strDup(strLstGet(list, 0));
|
|
}
|
|
MEM_CONTEXT_PRIOR_END();
|
|
}
|
|
}
|
|
while (result == NULL && wait != NULL && waitMore(wait));
|
|
}
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
if (result == NULL && timeout != 0)
|
|
{
|
|
THROW_FMT(
|
|
ArchiveTimeoutError,
|
|
"WAL segment %s was not archived before the %" PRIu64 "ms timeout\n"
|
|
"HINT: check the archive_command to ensure that all options are correct (especially --stanza).\n"
|
|
"HINT: check the PostgreSQL server log for errors.",
|
|
strPtr(walSegment), timeout);
|
|
}
|
|
|
|
FUNCTION_LOG_RETURN(STRING, result);
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Get the next WAL segment given a WAL segment and WAL segment size
|
|
***********************************************************************************************************************************/
|
|
String *
|
|
walSegmentNext(const String *walSegment, size_t walSegmentSize, unsigned int pgVersion)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
FUNCTION_LOG_PARAM(STRING, walSegment);
|
|
FUNCTION_LOG_PARAM(SIZE, walSegmentSize);
|
|
FUNCTION_LOG_PARAM(UINT, pgVersion);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(walSegment != NULL);
|
|
ASSERT(strSize(walSegment) == 24);
|
|
ASSERT(UINT32_MAX % walSegmentSize == walSegmentSize - 1);
|
|
ASSERT(pgVersion >= PG_VERSION_11 || walSegmentSize == 16 * 1024 * 1024);
|
|
|
|
// Extract WAL parts
|
|
uint32_t timeline = 0;
|
|
uint32_t major = 0;
|
|
uint32_t minor = 0;
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
{
|
|
timeline = (uint32_t)strtol(strPtr(strSubN(walSegment, 0, 8)), NULL, 16);
|
|
major = (uint32_t)strtol(strPtr(strSubN(walSegment, 8, 8)), NULL, 16);
|
|
minor = (uint32_t)strtol(strPtr(strSubN(walSegment, 16, 8)), NULL, 16);
|
|
|
|
// Increment minor and adjust major dir on overflow
|
|
minor++;
|
|
|
|
if (minor > UINT32_MAX / walSegmentSize)
|
|
{
|
|
major++;
|
|
minor = 0;
|
|
}
|
|
|
|
// Special hack for PostgreSQL < 9.3 which skipped minor FF
|
|
if (minor == 0xFF && pgVersion < PG_VERSION_93)
|
|
{
|
|
major++;
|
|
minor = 0;
|
|
}
|
|
}
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
FUNCTION_LOG_RETURN(STRING, strNewFmt("%08X%08X%08X", timeline, major, minor));
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Build a list of WAL segments based on a beginning WAL and number of WAL in the range (inclusive)
|
|
***********************************************************************************************************************************/
|
|
StringList *
|
|
walSegmentRange(const String *walSegmentBegin, size_t walSegmentSize, unsigned int pgVersion, unsigned int range)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
|
FUNCTION_LOG_PARAM(STRING, walSegmentBegin);
|
|
FUNCTION_LOG_PARAM(SIZE, walSegmentSize);
|
|
FUNCTION_LOG_PARAM(UINT, pgVersion);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(range > 0);
|
|
|
|
StringList *result = NULL;
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
{
|
|
result = strLstNew();
|
|
strLstAdd(result, walSegmentBegin);
|
|
|
|
if (range > 1)
|
|
{
|
|
String *current = strDup(walSegmentBegin);
|
|
|
|
for (unsigned int rangeIdx = 0; rangeIdx < range - 1; rangeIdx++)
|
|
{
|
|
String *next = walSegmentNext(current, walSegmentSize, pgVersion);
|
|
|
|
strLstAdd(result, next);
|
|
|
|
strFree(current);
|
|
current = next;
|
|
}
|
|
}
|
|
|
|
strLstMove(result, memContextPrior());
|
|
}
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
FUNCTION_LOG_RETURN(STRING_LIST, result);
|
|
}
|