1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-11-05 04:30:41 +03:00
Files
pgbackrest/src/postgres/interface.c
David Steele ae7f0af202 Move PostgreSQL version interface test functions to a test harness.
Some version interface test functions were integrated into the core code because they relied on the PostgreSQL versioned interface. Even though they were compiled out for production builds they cluttered the core code and made it harder to determine what was required by core.

Create a PostgreSQL version interface in a test harness to contain these functions. This does require some duplication but the cleaner core code seems a good tradeoff. It is possible for some of this code to be auto-generated but since it is only updated once per year the matter is not pressing.
2021-05-17 07:20:28 -04:00

705 lines
24 KiB
C

/***********************************************************************************************************************************
PostgreSQL Interface
***********************************************************************************************************************************/
#include "build.auto.h"
#include <string.h>
#include "common/debug.h"
#include "common/log.h"
#include "common/memContext.h"
#include "common/regExp.h"
#include "postgres/interface.h"
#include "postgres/interface/version.h"
#include "postgres/version.h"
#include "storage/helper.h"
/***********************************************************************************************************************************
Defines for various Postgres paths and files
***********************************************************************************************************************************/
STRING_EXTERN(PG_FILE_PGVERSION_STR, PG_FILE_PGVERSION);
STRING_EXTERN(PG_FILE_POSTGRESQLAUTOCONF_STR, PG_FILE_POSTGRESQLAUTOCONF);
STRING_EXTERN(PG_FILE_POSTMASTERPID_STR, PG_FILE_POSTMASTERPID);
STRING_EXTERN(PG_FILE_RECOVERYCONF_STR, PG_FILE_RECOVERYCONF);
STRING_EXTERN(PG_FILE_RECOVERYDONE_STR, PG_FILE_RECOVERYDONE);
STRING_EXTERN(PG_FILE_RECOVERYSIGNAL_STR, PG_FILE_RECOVERYSIGNAL);
STRING_EXTERN(PG_FILE_STANDBYSIGNAL_STR, PG_FILE_STANDBYSIGNAL);
STRING_EXTERN(PG_PATH_GLOBAL_STR, PG_PATH_GLOBAL);
STRING_EXTERN(PG_NAME_WAL_STR, PG_NAME_WAL);
STRING_EXTERN(PG_NAME_XLOG_STR, PG_NAME_XLOG);
// Wal path names depending on version
STRING_STATIC(PG_PATH_PGWAL_STR, "pg_wal");
STRING_STATIC(PG_PATH_PGXLOG_STR, "pg_xlog");
// Transaction commit log path names depending on version
STRING_STATIC(PG_PATH_PGCLOG_STR, "pg_clog");
STRING_STATIC(PG_PATH_PGXACT_STR, "pg_xact");
// Lsn name used in functions depnding on version
STRING_STATIC(PG_NAME_LSN_STR, "lsn");
STRING_STATIC(PG_NAME_LOCATION_STR, "location");
/***********************************************************************************************************************************
The control file is 8192 bytes but only the first 512 bytes are used to prevent torn pages even on really old storage with 512-byte
sectors. This is true across all versions of PostgreSQL.
***********************************************************************************************************************************/
#define PG_CONTROL_DATA_SIZE 512
/***********************************************************************************************************************************
PostgreSQL interface definitions
Each supported version of PostgreSQL must have interface files named postgres/interface/vXXX.c/h that implement the functions
specified in the interface structure below. The functions are documented here rather than in the interface files so that a change
in wording does not need to be propagated through N source files.
***********************************************************************************************************************************/
typedef struct PgInterface
{
// Version of PostgreSQL supported by this interface
unsigned int version;
// Does pg_control match this version of PostgreSQL?
bool (*controlIs)(const unsigned char *);
// Convert pg_control to a common data structure
PgControl (*control)(const unsigned char *);
// Get the control version for this version of PostgreSQL
uint32_t (*controlVersion)(void);
// Does the WAL header match this version of PostgreSQL?
bool (*walIs)(const unsigned char *);
// Convert WAL header to a common data structure
PgWal (*wal)(const unsigned char *);
} PgInterface;
static const PgInterface pgInterface[] =
{
{
.version = PG_VERSION_13,
.controlIs = pgInterfaceControlIs130,
.control = pgInterfaceControl130,
.controlVersion = pgInterfaceControlVersion130,
.walIs = pgInterfaceWalIs130,
.wal = pgInterfaceWal130,
},
{
.version = PG_VERSION_12,
.controlIs = pgInterfaceControlIs120,
.control = pgInterfaceControl120,
.controlVersion = pgInterfaceControlVersion120,
.walIs = pgInterfaceWalIs120,
.wal = pgInterfaceWal120,
},
{
.version = PG_VERSION_11,
.controlIs = pgInterfaceControlIs110,
.control = pgInterfaceControl110,
.controlVersion = pgInterfaceControlVersion110,
.walIs = pgInterfaceWalIs110,
.wal = pgInterfaceWal110,
},
{
.version = PG_VERSION_10,
.controlIs = pgInterfaceControlIs100,
.control = pgInterfaceControl100,
.controlVersion = pgInterfaceControlVersion100,
.walIs = pgInterfaceWalIs100,
.wal = pgInterfaceWal100,
},
{
.version = PG_VERSION_96,
.controlIs = pgInterfaceControlIs096,
.control = pgInterfaceControl096,
.controlVersion = pgInterfaceControlVersion096,
.walIs = pgInterfaceWalIs096,
.wal = pgInterfaceWal096,
},
{
.version = PG_VERSION_95,
.controlIs = pgInterfaceControlIs095,
.control = pgInterfaceControl095,
.controlVersion = pgInterfaceControlVersion095,
.walIs = pgInterfaceWalIs095,
.wal = pgInterfaceWal095,
},
{
.version = PG_VERSION_94,
.controlIs = pgInterfaceControlIs094,
.control = pgInterfaceControl094,
.controlVersion = pgInterfaceControlVersion094,
.walIs = pgInterfaceWalIs094,
.wal = pgInterfaceWal094,
},
{
.version = PG_VERSION_93,
.controlIs = pgInterfaceControlIs093,
.control = pgInterfaceControl093,
.controlVersion = pgInterfaceControlVersion093,
.walIs = pgInterfaceWalIs093,
.wal = pgInterfaceWal093,
},
{
.version = PG_VERSION_92,
.controlIs = pgInterfaceControlIs092,
.control = pgInterfaceControl092,
.controlVersion = pgInterfaceControlVersion092,
.walIs = pgInterfaceWalIs092,
.wal = pgInterfaceWal092,
},
{
.version = PG_VERSION_91,
.controlIs = pgInterfaceControlIs091,
.control = pgInterfaceControl091,
.controlVersion = pgInterfaceControlVersion091,
.walIs = pgInterfaceWalIs091,
.wal = pgInterfaceWal091,
},
{
.version = PG_VERSION_90,
.controlIs = pgInterfaceControlIs090,
.control = pgInterfaceControl090,
.controlVersion = pgInterfaceControlVersion090,
.walIs = pgInterfaceWalIs090,
.wal = pgInterfaceWal090,
},
{
.version = PG_VERSION_84,
.controlIs = pgInterfaceControlIs084,
.control = pgInterfaceControl084,
.controlVersion = pgInterfaceControlVersion084,
.walIs = pgInterfaceWalIs084,
.wal = pgInterfaceWal084,
},
{
.version = PG_VERSION_83,
.controlIs = pgInterfaceControlIs083,
.control = pgInterfaceControl083,
.controlVersion = pgInterfaceControlVersion083,
.walIs = pgInterfaceWalIs083,
.wal = pgInterfaceWal083,
},
};
// Total PostgreSQL versions in pgInterface
#define PG_INTERFACE_SIZE (sizeof(pgInterface) / sizeof(PgInterface))
/***********************************************************************************************************************************
These pg_control fields are common to all versions of PostgreSQL, so we can use them to generate error messages when the pg_control
version cannot be found.
***********************************************************************************************************************************/
typedef struct PgControlCommon
{
uint64_t systemId;
uint32_t controlVersion;
uint32_t catalogVersion;
} PgControlCommon;
/***********************************************************************************************************************************
Get the interface for a PostgreSQL version
***********************************************************************************************************************************/
static const PgInterface *
pgInterfaceVersion(unsigned int pgVersion)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, pgVersion);
FUNCTION_TEST_END();
const PgInterface *result = NULL;
for (unsigned int interfaceIdx = 0; interfaceIdx < PG_INTERFACE_SIZE; interfaceIdx++)
{
if (pgInterface[interfaceIdx].version == pgVersion)
{
result = &pgInterface[interfaceIdx];
break;
}
}
// If the version was not found then error
if (result == NULL)
THROW_FMT(AssertError, "invalid " PG_NAME " version %u", pgVersion);
FUNCTION_TEST_RETURN(result);
}
/***********************************************************************************************************************************
Check expected WAL segment size for older PostgreSQL versions
***********************************************************************************************************************************/
static void
pgWalSegmentSizeCheck(unsigned int pgVersion, unsigned int walSegmentSize)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, pgVersion);
FUNCTION_TEST_PARAM(UINT, walSegmentSize);
FUNCTION_TEST_END();
if (pgVersion < PG_VERSION_11 && walSegmentSize != PG_WAL_SEGMENT_SIZE_DEFAULT)
{
THROW_FMT(
FormatError, "wal segment size is %u but must be %u for " PG_NAME " <= " PG_VERSION_10_STR, walSegmentSize,
PG_WAL_SEGMENT_SIZE_DEFAULT);
}
FUNCTION_TEST_RETURN_VOID();
}
/**********************************************************************************************************************************/
PgControl
pgControlFromBuffer(const Buffer *controlFile)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(BUFFER, controlFile);
FUNCTION_LOG_END();
ASSERT(controlFile != NULL);
// Search for the version of PostgreSQL that uses this control file
const PgInterface *interface = NULL;
for (unsigned int interfaceIdx = 0; interfaceIdx < PG_INTERFACE_SIZE; interfaceIdx++)
{
if (pgInterface[interfaceIdx].controlIs(bufPtrConst(controlFile)))
{
interface = &pgInterface[interfaceIdx];
break;
}
}
// If the version was not found then error with the control and catalog version that were found
if (interface == NULL)
{
const PgControlCommon *controlCommon = (const PgControlCommon *)bufPtrConst(controlFile);
THROW_FMT(
VersionNotSupportedError,
"unexpected control version = %u and catalog version = %u\n"
"HINT: is this version of PostgreSQL supported?",
controlCommon->controlVersion, controlCommon->catalogVersion);
}
// Get info from the control file
PgControl result = interface->control(bufPtrConst(controlFile));
result.version = interface->version;
// Check the segment size
pgWalSegmentSizeCheck(result.version, result.walSegmentSize);
// Check the page size
if (result.pageSize != PG_PAGE_SIZE_DEFAULT)
THROW_FMT(FormatError, "page size is %u but must be %u", result.pageSize, PG_PAGE_SIZE_DEFAULT);
FUNCTION_LOG_RETURN(PG_CONTROL, result);
}
PgControl
pgControlFromFile(const Storage *storage)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STORAGE, storage);
FUNCTION_LOG_END();
ASSERT(storage != NULL);
PgControl result = {0};
MEM_CONTEXT_TEMP_BEGIN()
{
// Read control file
Buffer *controlFile = storageGetP(
storageNewReadP(storage, STRDEF(PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL)), .exactSize = PG_CONTROL_DATA_SIZE);
result = pgControlFromBuffer(controlFile);
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(PG_CONTROL, result);
}
/**********************************************************************************************************************************/
uint32_t
pgControlVersion(unsigned int pgVersion)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, pgVersion);
FUNCTION_TEST_END();
FUNCTION_TEST_RETURN(pgInterfaceVersion(pgVersion)->controlVersion());
}
/***********************************************************************************************************************************
These WAL header fields are common to all versions of PostgreSQL, so we can use them to generate error messages when the WAL magic
cannot be found.
***********************************************************************************************************************************/
typedef struct PgWalCommon
{
uint16_t magic;
uint16_t flag;
} PgWalCommon;
#define PG_WAL_LONG_HEADER 0x0002
/**********************************************************************************************************************************/
PgWal
pgWalFromBuffer(const Buffer *walBuffer)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(BUFFER, walBuffer);
FUNCTION_LOG_END();
ASSERT(walBuffer != NULL);
// Check that this is a long format WAL header
if (!(((const PgWalCommon *)bufPtrConst(walBuffer))->flag & PG_WAL_LONG_HEADER))
THROW_FMT(FormatError, "first page header in WAL file is expected to be in long format");
// Search for the version of PostgreSQL that uses this WAL magic
const PgInterface *interface = NULL;
for (unsigned int interfaceIdx = 0; interfaceIdx < PG_INTERFACE_SIZE; interfaceIdx++)
{
if (pgInterface[interfaceIdx].walIs(bufPtrConst(walBuffer)))
{
interface = &pgInterface[interfaceIdx];
break;
}
}
// If the version was not found then error with the magic that was found
if (interface == NULL)
{
THROW_FMT(
VersionNotSupportedError,
"unexpected WAL magic %u\n"
"HINT: is this version of PostgreSQL supported?",
((const PgWalCommon *)bufPtrConst(walBuffer))->magic);
}
// Get info from the control file
PgWal result = interface->wal(bufPtrConst(walBuffer));
result.version = interface->version;
// Check the segment size
pgWalSegmentSizeCheck(result.version, result.size);
FUNCTION_LOG_RETURN(PG_WAL, result);
}
PgWal
pgWalFromFile(const String *walFile, const Storage *storage)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, walFile);
FUNCTION_LOG_END();
ASSERT(walFile != NULL);
PgWal result = {0};
MEM_CONTEXT_TEMP_BEGIN()
{
// Read WAL segment header
Buffer *walBuffer = storageGetP(storageNewReadP(storage, walFile), .exactSize = PG_WAL_HEADER_SIZE);
result = pgWalFromBuffer(walBuffer);
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(PG_WAL, result);
}
/**********************************************************************************************************************************/
String *
pgTablespaceId(unsigned int pgVersion, unsigned int pgCatalogVersion)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, pgVersion);
FUNCTION_TEST_PARAM(UINT, pgCatalogVersion);
FUNCTION_TEST_END();
String *result = NULL;
if (pgVersion >= PG_VERSION_90)
{
MEM_CONTEXT_TEMP_BEGIN()
{
String *pgVersionStr = pgVersionToStr(pgVersion);
MEM_CONTEXT_PRIOR_BEGIN()
{
result = strNewFmt("PG_%s_%u", strZ(pgVersionStr), pgCatalogVersion);
}
MEM_CONTEXT_PRIOR_END();
}
MEM_CONTEXT_TEMP_END();
}
FUNCTION_TEST_RETURN(result);
}
/**********************************************************************************************************************************/
uint64_t
pgLsnFromStr(const String *lsn)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRING, lsn);
FUNCTION_TEST_END();
uint64_t result = 0;
MEM_CONTEXT_TEMP_BEGIN()
{
StringList *lsnPart = strLstNewSplit(lsn, FSLASH_STR);
CHECK(strLstSize(lsnPart) == 2);
result = (cvtZToUInt64Base(strZ(strLstGet(lsnPart, 0)), 16) << 32) + cvtZToUInt64Base(strZ(strLstGet(lsnPart, 1)), 16);
}
MEM_CONTEXT_TEMP_END();
FUNCTION_TEST_RETURN(result);
}
String *
pgLsnToStr(uint64_t lsn)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT64, lsn);
FUNCTION_TEST_END();
FUNCTION_TEST_RETURN(strNewFmt("%x/%x", (unsigned int)(lsn >> 32), (unsigned int)(lsn & 0xFFFFFFFF)));
}
/**********************************************************************************************************************************/
String *
pgLsnToWalSegment(uint32_t timeline, uint64_t lsn, unsigned int walSegmentSize)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, timeline);
FUNCTION_TEST_PARAM(UINT64, lsn);
FUNCTION_TEST_PARAM(UINT, walSegmentSize);
FUNCTION_TEST_END();
FUNCTION_TEST_RETURN(
strNewFmt("%08X%08X%08X", timeline, (unsigned int)(lsn >> 32), (unsigned int)(lsn & 0xFFFFFFFF) / walSegmentSize));
}
uint64_t
pgLsnFromWalSegment(const String *walSegment, unsigned int walSegmentSize)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRING, walSegment);
FUNCTION_TEST_PARAM(UINT, walSegmentSize);
FUNCTION_TEST_END();
ASSERT(walSegment != NULL);
ASSERT(strSize(walSegment) == 24);
ASSERT(walSegmentSize > 0);
FUNCTION_TEST_RETURN(
(cvtZToUInt64Base(strZ(strSubN(walSegment, 8, 8)), 16) << 32) +
(cvtZToUInt64Base(strZ(strSubN(walSegment, 16, 8)), 16) * walSegmentSize));
}
/**********************************************************************************************************************************/
StringList *
pgLsnRangeToWalSegmentList(
unsigned int pgVersion, uint32_t timeline, uint64_t lsnStart, uint64_t lsnStop, unsigned int walSegmentSize)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, pgVersion);
FUNCTION_TEST_PARAM(UINT, timeline);
FUNCTION_TEST_PARAM(UINT64, lsnStart);
FUNCTION_TEST_PARAM(UINT64, lsnStop);
FUNCTION_TEST_PARAM(UINT, walSegmentSize);
FUNCTION_TEST_END();
ASSERT(pgVersion != 0);
ASSERT(timeline != 0);
ASSERT(lsnStart <= lsnStop);
ASSERT(walSegmentSize != 0);
ASSERT(pgVersion > PG_VERSION_92 || walSegmentSize == PG_WAL_SEGMENT_SIZE_DEFAULT);
StringList *result = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
result = strLstNew();
// Skip the FF segment when PostgreSQL <= 9.2 (in this case segment size should always be 16MB)
bool skipFF = pgVersion <= PG_VERSION_92;
// Calculate the start and stop segments
unsigned int startMajor = (unsigned int)(lsnStart >> 32);
unsigned int startMinor = (unsigned int)(lsnStart & 0xFFFFFFFF) / walSegmentSize;
unsigned int stopMajor = (unsigned int)(lsnStop >> 32);
unsigned int stopMinor = (unsigned int)(lsnStop & 0xFFFFFFFF) / walSegmentSize;
unsigned int minorPerMajor = 0xFFFFFFFF / walSegmentSize;
// Create list
strLstAdd(result, strNewFmt("%08X%08X%08X", timeline, startMajor, startMinor));
while (!(startMajor == stopMajor && startMinor == stopMinor))
{
startMinor++;
if ((skipFF && startMinor == 0xFF) || (!skipFF && startMinor > minorPerMajor))
{
startMajor++;
startMinor = 0;
}
strLstAdd(result, strNewFmt("%08X%08X%08X", timeline, startMajor, startMinor));
}
strLstMove(result, memContextPrior());
}
MEM_CONTEXT_TEMP_END();
FUNCTION_TEST_RETURN(result);
}
/**********************************************************************************************************************************/
const String *
pgLsnName(unsigned int pgVersion)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, pgVersion);
FUNCTION_TEST_END();
FUNCTION_TEST_RETURN(pgVersion >= PG_VERSION_WAL_RENAME ? PG_NAME_LSN_STR : PG_NAME_LOCATION_STR);
}
/***********************************************************************************************************************************
Get WAL name (wal/xlog) for a PostgreSQL version
***********************************************************************************************************************************/
const String *
pgWalName(unsigned int pgVersion)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, pgVersion);
FUNCTION_TEST_END();
FUNCTION_TEST_RETURN(pgVersion >= PG_VERSION_WAL_RENAME ? PG_NAME_WAL_STR : PG_NAME_XLOG_STR);
}
/**********************************************************************************************************************************/
const String *
pgWalPath(unsigned int pgVersion)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, pgVersion);
FUNCTION_TEST_END();
FUNCTION_TEST_RETURN(pgVersion >= PG_VERSION_WAL_RENAME ? PG_PATH_PGWAL_STR : PG_PATH_PGXLOG_STR);
}
/**********************************************************************************************************************************/
const String *
pgXactPath(unsigned int pgVersion)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, pgVersion);
FUNCTION_TEST_END();
FUNCTION_TEST_RETURN(pgVersion >= PG_VERSION_WAL_RENAME ? PG_PATH_PGXACT_STR : PG_PATH_PGCLOG_STR);
}
/**********************************************************************************************************************************/
unsigned int
pgVersionFromStr(const String *version)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STRING, version);
FUNCTION_LOG_END();
ASSERT(version != NULL);
unsigned int result = 0;
MEM_CONTEXT_TEMP_BEGIN()
{
// If format is not number.number (9.4) or number only (10) then error
if (!regExpMatchOne(STRDEF("^[0-9]+[.]*[0-9]+$"), version))
THROW_FMT(AssertError, "version %s format is invalid", strZ(version));
// If there is a dot set the major and minor versions, else just the major
int idxStart = strChr(version, '.');
unsigned int major;
unsigned int minor = 0;
if (idxStart != -1)
{
major = cvtZToUInt(strZ(strSubN(version, 0, (size_t)idxStart)));
minor = cvtZToUInt(strZ(strSub(version, (size_t)idxStart + 1)));
}
else
major = cvtZToUInt(strZ(version));
// No check to see if valid/supported PG version is on purpose
result = major * 10000 + minor * 100;
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(UINT, result);
}
String *
pgVersionToStr(unsigned int version)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(UINT, version);
FUNCTION_LOG_END();
String *result = version >= PG_VERSION_10 ?
strNewFmt("%u", version / 10000) : strNewFmt("%u.%u", version / 10000, version % 10000 / 100);
FUNCTION_LOG_RETURN(STRING, result);
}
/**********************************************************************************************************************************/
String *
pgControlToLog(const PgControl *pgControl)
{
return strNewFmt(
"{version: %u, systemId: %" PRIu64 ", walSegmentSize: %u, pageChecksum: %s}", pgControl->version, pgControl->systemId,
pgControl->walSegmentSize, cvtBoolToConstZ(pgControl->pageChecksum));
}
String *
pgWalToLog(const PgWal *pgWal)
{
return strNewFmt("{version: %u, systemId: %" PRIu64 "}", pgWal->version, pgWal->systemId);
}