forked from huawei/openGauss-server
666 lines
17 KiB
C++
666 lines
17 KiB
C++
/*-------------------------------------------------------------------------
|
|
*
|
|
* util.c: log messages to log file or stderr, and misc code.
|
|
*
|
|
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
|
* Portions Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
|
* Portions Copyright (c) 2015-2019, Postgres Professional
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "pg_probackup.h"
|
|
|
|
#include "catalog/pg_control.h"
|
|
|
|
#include <signal.h>
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/stat.h>
|
|
#include "common/fe_memutils.h"
|
|
|
|
static const char *statusName[] =
|
|
{
|
|
"UNKNOWN",
|
|
"OK",
|
|
"ERROR",
|
|
"RUNNING",
|
|
"MERGING",
|
|
"MERGED",
|
|
"DELETING",
|
|
"DELETED",
|
|
"DONE",
|
|
"ORPHAN",
|
|
"CORRUPT"
|
|
};
|
|
|
|
uint32 NUM_65536 = 65536;
|
|
uint32 NUM_10000 = 10000;
|
|
|
|
const char *
|
|
base36enc(long unsigned int value)
|
|
{
|
|
const char base36[36 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
/* log(2**64) / log(36) = 12.38 => max 13 char + '\0' */
|
|
static char buffer[14];
|
|
unsigned int offset = sizeof(buffer);
|
|
|
|
buffer[--offset] = '\0';
|
|
do {
|
|
buffer[--offset] = base36[value % 36];
|
|
} while (value /= 36);
|
|
|
|
return &buffer[offset];
|
|
}
|
|
|
|
/*
|
|
* Same as base36enc(), but the results must be released by the user.
|
|
*/
|
|
char *
|
|
base36enc_dup(long unsigned int value)
|
|
{
|
|
const char base36[36 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
/* log(2**64) / log(36) = 12.38 => max 13 char + '\0' */
|
|
char buffer[14];
|
|
unsigned int offset = sizeof(buffer);
|
|
|
|
buffer[--offset] = '\0';
|
|
do {
|
|
buffer[--offset] = base36[value % 36];
|
|
} while (value /= 36);
|
|
|
|
return strdup(&buffer[offset]);
|
|
}
|
|
|
|
long unsigned int
|
|
base36dec(const char *text)
|
|
{
|
|
return strtoul(text, NULL, 36);
|
|
}
|
|
|
|
static void
|
|
checkControlFile(ControlFileData *ControlFile)
|
|
{
|
|
pg_crc32c crc;
|
|
|
|
/* Calculate CRC */
|
|
INIT_CRC32C(crc);
|
|
COMP_CRC32C(crc, (char *) ControlFile, offsetof(ControlFileData, crc));
|
|
FIN_CRC32C(crc);
|
|
|
|
/* Then compare it */
|
|
if (!EQ_CRC32C(crc, ControlFile->crc))
|
|
elog(ERROR, "Calculated CRC checksum does not match value stored in file.\n"
|
|
"Either the file is corrupt, or it has a different layout than this program\n"
|
|
"is expecting. The results below are untrustworthy.");
|
|
|
|
if ((ControlFile->pg_control_version % NUM_65536 == 0 || ControlFile->pg_control_version % NUM_65536 > NUM_10000) &&
|
|
ControlFile->pg_control_version / NUM_65536 != 0)
|
|
elog(ERROR, "possible byte ordering mismatch\n"
|
|
"The byte ordering used to store the pg_control file might not match the one\n"
|
|
"used by this program. In that case the results below would be incorrect, and\n"
|
|
"the PostgreSQL installation would be incompatible with this data directory.");
|
|
}
|
|
|
|
/*
|
|
* Verify control file contents in the buffer src, and copy it to *ControlFile.
|
|
*/
|
|
static void
|
|
digestControlFile(ControlFileData *ControlFile, char *src, size_t size)
|
|
{
|
|
if (size != PG_CONTROL_SIZE)
|
|
elog(ERROR, "unexpected control file size %d, expected %d",
|
|
(int) size, PG_CONTROL_SIZE);
|
|
|
|
errno_t rc = memcpy_s(ControlFile, sizeof(ControlFileData), src, sizeof(ControlFileData));
|
|
securec_check_c(rc, "\0", "\0");
|
|
|
|
/* Additional checks on control file */
|
|
checkControlFile(ControlFile);
|
|
}
|
|
|
|
/*
|
|
* Write ControlFile to pg_control
|
|
*/
|
|
static void
|
|
writeControlFile(ControlFileData *ControlFile, const char *path, fio_location location)
|
|
{
|
|
int fd;
|
|
char *buffer = NULL;
|
|
|
|
buffer = (char *)pg_malloc(PG_CONTROL_SIZE);
|
|
errno_t rc = memcpy_s(buffer, sizeof(ControlFileData), ControlFile, sizeof(ControlFileData));
|
|
securec_check_c(rc, "\0", "\0");
|
|
/* Write pg_control */
|
|
fd = fio_open(path,
|
|
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY, location);
|
|
|
|
if (fd < 0)
|
|
elog(ERROR, "Failed to open file: %s", path);
|
|
|
|
if (fio_write(fd, buffer, PG_CONTROL_SIZE) != PG_CONTROL_SIZE)
|
|
elog(ERROR, "Failed to overwrite file: %s", path);
|
|
|
|
if (fio_flush(fd) != 0)
|
|
elog(ERROR, "Failed to sync file: %s", path);
|
|
|
|
fio_close(fd);
|
|
pg_free(buffer);
|
|
}
|
|
|
|
/*
|
|
* Utility shared by backup and restore to fetch the current timeline
|
|
* used by a node.
|
|
*/
|
|
TimeLineID
|
|
get_current_timeline(PGconn *conn)
|
|
{
|
|
|
|
PGresult *res;
|
|
TimeLineID tli = 0;
|
|
char *val;
|
|
|
|
res = pgut_execute_extended(conn,
|
|
"SELECT timeline_id FROM pg_control_checkpoint()", 0, NULL, true, true);
|
|
|
|
if (PQresultStatus(res) == PGRES_TUPLES_OK)
|
|
val = PQgetvalue(res, 0, 0);
|
|
else
|
|
return get_current_timeline_from_control(false);
|
|
|
|
if (!parse_uint32(val, &tli, 0))
|
|
{
|
|
PQclear(res);
|
|
elog(WARNING, "Invalid value of timeline_id %s", val);
|
|
|
|
/* TODO 3.0 remove it and just error out */
|
|
return get_current_timeline_from_control(false);
|
|
}
|
|
|
|
return tli;
|
|
}
|
|
|
|
/* Get timeline from pg_control file */
|
|
TimeLineID
|
|
get_current_timeline_from_control(bool safe)
|
|
{
|
|
ControlFileData ControlFile;
|
|
char *buffer;
|
|
size_t size;
|
|
|
|
/* First fetch file... */
|
|
buffer = slurpFile(instance_config.pgdata, XLOG_CONTROL_FILE, &size,
|
|
safe, FIO_DB_HOST);
|
|
if (safe && buffer == NULL)
|
|
return 0;
|
|
|
|
digestControlFile(&ControlFile, buffer, size);
|
|
pg_free(buffer);
|
|
|
|
return ControlFile.checkPointCopy.ThisTimeLineID;
|
|
}
|
|
|
|
/*
|
|
* Get last check point record ptr from pg_tonrol.
|
|
*/
|
|
XLogRecPtr
|
|
get_checkpoint_location(PGconn *conn)
|
|
{
|
|
#if PG_VERSION_NUM >= 90600
|
|
PGresult *res;
|
|
uint32 lsn_hi;
|
|
uint32 lsn_lo;
|
|
XLogRecPtr lsn;
|
|
|
|
#if PG_VERSION_NUM >= 100000
|
|
res = pgut_execute(conn,
|
|
"SELECT checkpoint_lsn FROM pg_catalog.pg_control_checkpoint()",
|
|
0, NULL);
|
|
#else
|
|
res = pgut_execute(conn,
|
|
"SELECT checkpoint_location FROM pg_catalog.pg_control_checkpoint()",
|
|
0, NULL);
|
|
#endif
|
|
XLogDataFromLSN(PQgetvalue(res, 0, 0), &lsn_hi, &lsn_lo);
|
|
PQclear(res);
|
|
/* Calculate LSN */
|
|
lsn = ((uint64) lsn_hi) << 32 | lsn_lo;
|
|
|
|
return lsn;
|
|
#else
|
|
char *buffer;
|
|
size_t size;
|
|
ControlFileData ControlFile;
|
|
|
|
buffer = slurpFile(instance_config.pgdata, XLOG_CONTROL_FILE, &size, false, FIO_DB_HOST);
|
|
digestControlFile(&ControlFile, buffer, size);
|
|
pg_free(buffer);
|
|
|
|
return ControlFile.checkPoint;
|
|
#endif
|
|
}
|
|
|
|
uint64
|
|
get_system_identifier(const char *pgdata_path)
|
|
{
|
|
ControlFileData ControlFile;
|
|
char *buffer;
|
|
size_t size;
|
|
|
|
/* First fetch file... */
|
|
buffer = slurpFile(pgdata_path, XLOG_CONTROL_FILE, &size, false, FIO_DB_HOST);
|
|
if (buffer == NULL)
|
|
return 0;
|
|
digestControlFile(&ControlFile, buffer, size);
|
|
pg_free(buffer);
|
|
|
|
return ControlFile.system_identifier;
|
|
}
|
|
|
|
uint64
|
|
get_remote_system_identifier(PGconn *conn)
|
|
{
|
|
#if PG_VERSION_NUM >= 90600
|
|
PGresult *res;
|
|
uint64 system_id_conn;
|
|
char *val;
|
|
|
|
res = pgut_execute(conn,
|
|
"SELECT system_identifier FROM pg_catalog.pg_control_system()",
|
|
0, NULL);
|
|
val = PQgetvalue(res, 0, 0);
|
|
if (!parse_uint64(val, &system_id_conn, 0))
|
|
{
|
|
PQclear(res);
|
|
elog(ERROR, "%s is not system_identifier", val);
|
|
}
|
|
PQclear(res);
|
|
|
|
return system_id_conn;
|
|
#else
|
|
char *buffer;
|
|
size_t size;
|
|
ControlFileData ControlFile;
|
|
|
|
buffer = slurpFile(instance_config.pgdata, XLOG_CONTROL_FILE, &size, false, FIO_DB_HOST);
|
|
digestControlFile(&ControlFile, buffer, size);
|
|
pg_free(buffer);
|
|
|
|
return ControlFile.system_identifier;
|
|
#endif
|
|
}
|
|
|
|
uint32
|
|
get_xlog_seg_size(char *pgdata_path)
|
|
{
|
|
#if PG_VERSION_NUM >= 110000
|
|
ControlFileData ControlFile;
|
|
char *buffer;
|
|
size_t size;
|
|
|
|
/* First fetch file... */
|
|
buffer = slurpFile(pgdata_path, XLOG_CONTROL_FILE, &size, false, FIO_DB_HOST);
|
|
digestControlFile(&ControlFile, buffer, size);
|
|
pg_free(buffer);
|
|
|
|
return ControlFile.xlog_seg_size;
|
|
#else
|
|
return (uint32) XLOG_SEG_SIZE;
|
|
#endif
|
|
}
|
|
|
|
uint32
|
|
get_data_checksum_version(bool safe)
|
|
{
|
|
ControlFileData ControlFile;
|
|
char *buffer;
|
|
size_t size;
|
|
|
|
/* First fetch file... */
|
|
buffer = slurpFile(instance_config.pgdata, XLOG_CONTROL_FILE, &size,
|
|
safe, FIO_DB_HOST);
|
|
if (buffer == NULL)
|
|
return 0;
|
|
digestControlFile(&ControlFile, buffer, size);
|
|
pg_free(buffer);
|
|
|
|
return 0; // ControlFile.data_checksum_version;
|
|
}
|
|
|
|
pg_crc32c
|
|
get_pgcontrol_checksum(const char *pgdata_path)
|
|
{
|
|
ControlFileData ControlFile;
|
|
char *buffer;
|
|
size_t size;
|
|
|
|
/* First fetch file... */
|
|
buffer = slurpFile(pgdata_path, XLOG_CONTROL_FILE, &size, false, FIO_BACKUP_HOST);
|
|
|
|
digestControlFile(&ControlFile, buffer, size);
|
|
pg_free(buffer);
|
|
|
|
return ControlFile.crc;
|
|
}
|
|
|
|
void
|
|
get_redo(const char *pgdata_path, RedoParams *redo)
|
|
{
|
|
ControlFileData ControlFile;
|
|
char *buffer;
|
|
size_t size;
|
|
|
|
/* First fetch file... */
|
|
buffer = slurpFile(pgdata_path, XLOG_CONTROL_FILE, &size, false, FIO_DB_HOST);
|
|
|
|
digestControlFile(&ControlFile, buffer, size);
|
|
pg_free(buffer);
|
|
|
|
redo->lsn = ControlFile.checkPointCopy.redo;
|
|
redo->tli = ControlFile.checkPointCopy.ThisTimeLineID;
|
|
|
|
if (ControlFile.minRecoveryPoint > 0 &&
|
|
ControlFile.minRecoveryPoint < redo->lsn)
|
|
{
|
|
redo->lsn = ControlFile.minRecoveryPoint;
|
|
}
|
|
|
|
if (ControlFile.backupStartPoint > 0 &&
|
|
ControlFile.backupStartPoint < redo->lsn)
|
|
{
|
|
redo->lsn = ControlFile.backupStartPoint;
|
|
redo->tli = ControlFile.checkPointCopy.ThisTimeLineID;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Rewrite minRecoveryPoint of pg_control in backup directory. minRecoveryPoint
|
|
* 'as-is' is not to be trusted.
|
|
*/
|
|
void
|
|
set_min_recovery_point(pgFile *file, const char *backup_path,
|
|
XLogRecPtr stop_backup_lsn)
|
|
{
|
|
ControlFileData ControlFile;
|
|
char *buffer;
|
|
size_t size;
|
|
char fullpath[MAXPGPATH];
|
|
|
|
/* First fetch file content */
|
|
buffer = slurpFile(instance_config.pgdata, XLOG_CONTROL_FILE, &size, false, FIO_DB_HOST);
|
|
digestControlFile(&ControlFile, buffer, size);
|
|
|
|
elog(LOG, "Current minRecPoint %X/%X",
|
|
(uint32) (ControlFile.minRecoveryPoint >> 32),
|
|
(uint32) ControlFile.minRecoveryPoint);
|
|
|
|
elog(LOG, "Setting minRecPoint to %X/%X",
|
|
(uint32) (stop_backup_lsn >> 32),
|
|
(uint32) stop_backup_lsn);
|
|
|
|
ControlFile.minRecoveryPoint = stop_backup_lsn;
|
|
|
|
/* Update checksum in pg_control header */
|
|
INIT_CRC32C(ControlFile.crc);
|
|
COMP_CRC32C(ControlFile.crc, (char *) &ControlFile,
|
|
offsetof(ControlFileData, crc));
|
|
FIN_CRC32C(ControlFile.crc);
|
|
|
|
/* overwrite pg_control */
|
|
errno_t rc = snprintf_s(fullpath, sizeof(fullpath), sizeof(fullpath) - 1,
|
|
"%s/%s", backup_path, XLOG_CONTROL_FILE);
|
|
securec_check_ss_c(rc, "\0", "\0");
|
|
writeControlFile(&ControlFile, fullpath, FIO_LOCAL_HOST);
|
|
|
|
/* Update pg_control checksum in backup_list */
|
|
file->crc = ControlFile.crc;
|
|
|
|
pg_free(buffer);
|
|
}
|
|
|
|
/*
|
|
* Copy pg_control file to backup. We do not apply compression to this file.
|
|
*/
|
|
void
|
|
copy_pgcontrol_file(const char *from_fullpath, fio_location from_location,
|
|
const char *to_fullpath, fio_location to_location, pgFile *file)
|
|
{
|
|
ControlFileData ControlFile;
|
|
char *buffer;
|
|
size_t size;
|
|
|
|
buffer = slurpFile(from_fullpath, "", &size, false, from_location);
|
|
|
|
digestControlFile(&ControlFile, buffer, size);
|
|
|
|
file->crc = ControlFile.crc;
|
|
file->read_size = size;
|
|
file->write_size = size;
|
|
file->uncompressed_size = size;
|
|
|
|
writeControlFile(&ControlFile, to_fullpath, to_location);
|
|
|
|
pg_free(buffer);
|
|
}
|
|
|
|
/*
|
|
* Parse string representation of the server version.
|
|
*/
|
|
uint32
|
|
parse_server_version(const char *server_version_str)
|
|
{
|
|
int nfields;
|
|
uint32 result = 0;
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
|
|
nfields = sscanf_s(server_version_str, "%d.%d", &major_version, &minor_version);
|
|
if (nfields == 2)
|
|
{
|
|
/* Server version lower than 10 */
|
|
if (major_version > 10)
|
|
elog(ERROR, "Server version format doesn't match major version %d", major_version);
|
|
result = major_version * 10000 + minor_version * 100;
|
|
}
|
|
else if (nfields == 1)
|
|
{
|
|
if (major_version < 10)
|
|
elog(ERROR, "Server version format doesn't match major version %d", major_version);
|
|
result = major_version * 10000;
|
|
}
|
|
else
|
|
elog(ERROR, "Unknown server version format %s", server_version_str);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Parse string representation of the program version.
|
|
*/
|
|
uint32
|
|
parse_program_version(const char *program_version)
|
|
{
|
|
int nfields;
|
|
int major = 0,
|
|
minor = 0,
|
|
micro = 0;
|
|
uint32 result = 0;
|
|
|
|
if (program_version == NULL || program_version[0] == '\0')
|
|
return 0;
|
|
|
|
nfields = sscanf_s(program_version, "%d.%d.%d", &major, &minor, µ);
|
|
if (nfields == 3)
|
|
result = major * 10000 + minor * 100 + micro;
|
|
else
|
|
elog(ERROR, "Unknown program version format %s", program_version);
|
|
|
|
return result;
|
|
}
|
|
|
|
const char *
|
|
status2str(BackupStatus status)
|
|
{
|
|
if (status < BACKUP_STATUS_INVALID || BACKUP_STATUS_CORRUPT < status)
|
|
return "UNKNOWN";
|
|
|
|
return statusName[status];
|
|
}
|
|
|
|
BackupStatus
|
|
str2status(const char *status)
|
|
{
|
|
for (int i = 0; i <= BACKUP_STATUS_CORRUPT; i++)
|
|
{
|
|
if (pg_strcasecmp(status, statusName[i]) == 0) {
|
|
return (BackupStatus)i;
|
|
}
|
|
}
|
|
return BACKUP_STATUS_INVALID;
|
|
}
|
|
|
|
bool
|
|
datapagemap_is_set(datapagemap_t *map, BlockNumber blkno)
|
|
{
|
|
int offset;
|
|
int bitno;
|
|
|
|
offset = blkno / 8;
|
|
bitno = blkno % 8;
|
|
|
|
/* enlarge or create bitmap if needed */
|
|
if (map->bitmapsize <= offset)
|
|
{
|
|
int oldsize = map->bitmapsize;
|
|
int newsize;
|
|
|
|
/*
|
|
* The minimum to hold the new bit is offset + 1. But add some
|
|
* headroom, so that we don't need to repeatedly enlarge the bitmap in
|
|
* the common case that blocks are modified in order, from beginning
|
|
* of a relation to the end.
|
|
*/
|
|
newsize = offset + 1;
|
|
newsize += 10;
|
|
|
|
map->bitmap = (unsigned char *)pg_realloc(map->bitmap, newsize);
|
|
|
|
/* zero out the newly allocated region */
|
|
errno_t rc = memset_s(&map->bitmap[oldsize], newsize - oldsize, 0, newsize - oldsize);
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
map->bitmapsize = newsize;
|
|
}
|
|
|
|
|
|
|
|
/* check the bit */
|
|
return map->bitmap[offset] & (1 << bitno);
|
|
}
|
|
|
|
/*
|
|
* A debugging aid. Prints out the contents of the page map.
|
|
*/
|
|
void
|
|
datapagemap_print_debug(datapagemap_t *map)
|
|
{
|
|
datapagemap_iterator_t *iter;
|
|
BlockNumber blocknum;
|
|
|
|
iter = datapagemap_iterate(map);
|
|
while (datapagemap_next(iter, &blocknum))
|
|
elog(INFO, " block %u", blocknum);
|
|
|
|
pg_free(iter);
|
|
}
|
|
|
|
/*
|
|
* Return pid of postmaster process running in given pgdata.
|
|
* Return 0 if there is none.
|
|
* Return 1 if postmaster.pid is mangled.
|
|
*/
|
|
pid_t
|
|
check_postmaster(const char *pgdata)
|
|
{
|
|
FILE *fp;
|
|
pid_t pid;
|
|
char pid_file[MAXPGPATH];
|
|
|
|
errno_t rc = snprintf_s(pid_file, MAXPGPATH, MAXPGPATH - 1,
|
|
"%s/postmaster.pid", pgdata);
|
|
securec_check_ss_c(rc, "\0", "\0");
|
|
canonicalize_path(pid_file);
|
|
fp = fopen(pid_file, "r");
|
|
if (fp == NULL)
|
|
{
|
|
/* No pid file, acceptable*/
|
|
if (errno == ENOENT)
|
|
return 0;
|
|
else
|
|
elog(ERROR, "Cannot open file \"%s\": %s",
|
|
pid_file, strerror(errno));
|
|
}
|
|
|
|
if (fscanf_s(fp, "%i", &pid) != 1)
|
|
{
|
|
/* something is wrong with the file content */
|
|
pid = 1;
|
|
}
|
|
|
|
if (pid > 1)
|
|
{
|
|
if (kill(pid, 0) != 0)
|
|
{
|
|
/* process no longer exists */
|
|
if (errno == ESRCH)
|
|
pid = 0;
|
|
else
|
|
elog(ERROR, "Failed to send signal 0 to a process %d: %s",
|
|
pid, strerror(errno));
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
return pid;
|
|
}
|
|
|
|
/*
|
|
* Replace the actual password with *'s.
|
|
*/
|
|
void replace_password(int argc, char** argv, const char* optionName)
|
|
{
|
|
int count = 0;
|
|
char* pchPass = NULL;
|
|
char* pchTemp = NULL;
|
|
|
|
// Check if password option is specified in command line
|
|
for (count = 0; count < argc; count++) {
|
|
// Password can be specified by optionName
|
|
if (strncmp(optionName, argv[count], strlen(optionName)) == 0) {
|
|
pchTemp = strchr(argv[count], '=');
|
|
if (pchTemp != NULL) {
|
|
pchPass = pchTemp + 1;
|
|
} else if ((NULL != strstr(argv[count], optionName)) && (strlen(argv[count]) > strlen(optionName))) {
|
|
pchPass = argv[count] + strlen(optionName);
|
|
} else {
|
|
pchPass = argv[(int)(count + 1)];
|
|
}
|
|
|
|
// Replace first char of password with * and rest clear it
|
|
if (strlen(pchPass) > 0) {
|
|
*pchPass = '*';
|
|
pchPass = pchPass + 1;
|
|
while ('\0' != *pchPass) {
|
|
*pchPass = '\0';
|
|
pchPass++;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|