forked from huawei/openGauss-server
1839 lines
66 KiB
C++
1839 lines
66 KiB
C++
/* -------------------------------------------------------------------------
|
|
*
|
|
* basebackup.cpp
|
|
* code for taking a base backup and streaming it to a standby
|
|
*
|
|
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
|
* Portions Copyright (c) 2010-2012, PostgreSQL Global Development Group
|
|
*
|
|
* IDENTIFICATION
|
|
* src/gausskernel/storage/replication/basebackup.cpp
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "access/xlog_internal.h" /* for pg_start/stop_backup */
|
|
#include "access/cbmparsexlog.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "lib/stringinfo.h"
|
|
#include "libpq/libpq.h"
|
|
#include "libpq/pqformat.h"
|
|
#include "nodes/pg_list.h"
|
|
#include "replication/basebackup.h"
|
|
#include "replication/walsender.h"
|
|
#include "replication/walsender_private.h"
|
|
#include "replication/slot.h"
|
|
#include "access/xlog.h"
|
|
#include "storage/fd.h"
|
|
#include "storage/ipc.h"
|
|
#include "storage/pmsignal.h"
|
|
#include "storage/checksum.h"
|
|
#ifdef ENABLE_MOT
|
|
#include "storage/mot/mot_fdw.h"
|
|
#endif
|
|
#include "utils/builtins.h"
|
|
#include "utils/elog.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/ps_status.h"
|
|
#include "utils/timestamp.h"
|
|
#include "postmaster/syslogger.h"
|
|
#include "pgxc/pgxc.h"
|
|
|
|
/* t_thrd.proc_cxt.DataDir */
|
|
#include "miscadmin.h"
|
|
|
|
typedef struct {
|
|
const char *label;
|
|
bool progress;
|
|
bool fastcheckpoint;
|
|
bool nowait;
|
|
bool includewal;
|
|
bool sendtblspcmapfile;
|
|
bool isBuildFromStandby;
|
|
} basebackup_options;
|
|
|
|
#define BUILD_PATH_LEN 2560 /* (MAXPGPATH*2 + 512) */
|
|
const int FILE_NAME_MAX_LEN = 1024;
|
|
const int MATCH_ONE = 1;
|
|
const int MATCH_TWO = 2;
|
|
const int MATCH_THREE = 3;
|
|
const int MATCH_FOUR = 4;
|
|
const int MATCH_FIVE = 5;
|
|
const int MATCH_SIX = 6;
|
|
const int MATCH_SEVEN = 7;
|
|
|
|
/*
|
|
* Size of each block sent into the tar stream for larger files.
|
|
*/
|
|
#define TAR_SEND_SIZE (32 * 1024) /* data send unit 32KB */
|
|
#define EREPORT_WAL_NOT_FOUND(segno) \
|
|
do { \
|
|
char walErrorName[MAXFNAMELEN]; \
|
|
XLogFileName(walErrorName, t_thrd.xlog_cxt.ThisTimeLineID, segno); \
|
|
ereport(ERROR, (errmsg("could not find WAL file \"%s\"", walErrorName))); \
|
|
} while (0)
|
|
|
|
XLogRecPtr XlogCopyStartPtr = InvalidXLogRecPtr;
|
|
|
|
#ifdef ENABLE_MOT
|
|
static int64 sendDir(
|
|
const char *path, int basepathlen, bool sizeonly, List *tablespaces, bool sendtblspclinks, bool skipmot = true);
|
|
#else
|
|
static int64 sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces, bool sendtblspclinks);
|
|
#endif
|
|
int64 sendTablespace(const char *path, bool sizeonly);
|
|
static bool sendFile(char *readfilename, char *tarfilename, struct stat *statbuf, bool missing_ok);
|
|
static void sendFileWithContent(const char *filename, const char *content);
|
|
static void _tarWriteHeader(const char *filename, const char *linktarget, struct stat *statbuf);
|
|
static void send_int8_string(StringInfoData *buf, int64 intval);
|
|
static void SendBackupHeader(List *tablespaces);
|
|
#ifdef ENABLE_MOT
|
|
static void SendMotCheckpointHeader(const char* path);
|
|
#endif
|
|
static void base_backup_cleanup(int code, Datum arg);
|
|
static void perform_base_backup(basebackup_options *opt, DIR *tblspcdir);
|
|
static void parse_basebackup_options(List *options, basebackup_options *opt);
|
|
static int CompareWalFileNames(const void* a, const void* b);
|
|
static void SendXlogRecPtrResult(XLogRecPtr ptr);
|
|
static void send_xlog_location();
|
|
static void send_xlog_header(const char *linkpath);
|
|
static void save_xlogloc(const char *xloglocation);
|
|
static void SendTableSpaceForBackup(basebackup_options* opt, List* tablespaces, char* labelfile, char* tblspc_map_file);
|
|
|
|
/*
|
|
* save xlog location
|
|
*/
|
|
static void save_xlogloc(const char *xloglocation)
|
|
{
|
|
errno_t rc = 0;
|
|
|
|
if (0 == strncmp(xloglocation, t_thrd.proc_cxt.DataDir, strlen(t_thrd.proc_cxt.DataDir))) {
|
|
rc = strncpy_s(t_thrd.basebackup_cxt.g_xlog_location, MAXPGPATH,
|
|
xloglocation + strlen(t_thrd.proc_cxt.DataDir) + 1, MAXPGPATH - 1);
|
|
securec_check(rc, "", "");
|
|
t_thrd.basebackup_cxt.g_xlog_location[MAXPGPATH - 1] = '\0';
|
|
}
|
|
}
|
|
|
|
/*
|
|
* send xlog location header
|
|
*/
|
|
static void send_xlog_header(const char *linkpath)
|
|
{
|
|
StringInfoData buf;
|
|
char pg_xlog[] = "data/pg_xlog";
|
|
|
|
/* Construct and send the directory information */
|
|
pq_beginmessage(&buf, 'T'); /* RowDescription */
|
|
pq_sendint16(&buf, 1); /* 1 fields */
|
|
|
|
/* Second field - xloglink */
|
|
pq_sendstring(&buf, "xloglink");
|
|
pq_sendint32(&buf, 0);
|
|
pq_sendint16(&buf, 0);
|
|
pq_sendint32(&buf, TEXTOID);
|
|
pq_sendint16(&buf, UINT16_MAX);
|
|
pq_sendint32(&buf, 0);
|
|
pq_sendint16(&buf, 0);
|
|
pq_endmessage_noblock(&buf);
|
|
|
|
/* Data row */
|
|
pq_beginmessage(&buf, 'D');
|
|
pq_sendint16(&buf, 1); /* number of columns */
|
|
if (linkpath == NULL) {
|
|
/* default data ,here we just send a fixed str */
|
|
pq_sendint32(&buf, strlen(pg_xlog));
|
|
pq_sendbytes(&buf, pg_xlog, strlen(pg_xlog));
|
|
} else {
|
|
pq_sendint32(&buf, strlen(linkpath));
|
|
pq_sendbytes(&buf, linkpath, strlen(linkpath));
|
|
}
|
|
pq_endmessage_noblock(&buf);
|
|
|
|
/* Send a CommandComplete message */
|
|
pq_puttextmessage_noblock('C', "SELECT");
|
|
}
|
|
|
|
/*
|
|
* if xlog location is a link ,send it to standby
|
|
*/
|
|
static void send_xlog_location()
|
|
{
|
|
char fullpath[MAXPGPATH] = {0};
|
|
struct stat statbuf;
|
|
int rc = 0;
|
|
|
|
rc = snprintf_s(fullpath, sizeof(fullpath), sizeof(fullpath) - 1, "%s/pg_xlog", t_thrd.proc_cxt.DataDir);
|
|
securec_check_ss(rc, "", "");
|
|
|
|
if (lstat(fullpath, &statbuf) != 0) {
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat control file \"%s\": %m", fullpath)));
|
|
}
|
|
|
|
#ifndef WIN32
|
|
if (S_ISLNK(statbuf.st_mode)) {
|
|
#else
|
|
if (pgwin32_is_junction(fullpath)) {
|
|
#endif
|
|
#if defined(HAVE_READLINK) || defined(WIN32)
|
|
char linkpath[MAXPGPATH] = {0};
|
|
int rllen;
|
|
|
|
rllen = readlink(fullpath, linkpath, sizeof(linkpath));
|
|
if (rllen < 0)
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not read symbolic link \"%s\": %m", fullpath)));
|
|
if (rllen >= (int)sizeof(linkpath))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NAME_TOO_LONG), errmsg("symbolic link \"%s\" target is too long", fullpath)));
|
|
linkpath[MAXPGPATH - 1] = '\0';
|
|
|
|
/* save xlog location to varible */
|
|
save_xlogloc(linkpath);
|
|
|
|
send_xlog_header(linkpath);
|
|
|
|
#else
|
|
|
|
/*
|
|
* If the platform does not have symbolic links, it should not be
|
|
* possible to have tablespaces - clearly somebody else created
|
|
* them. Warn about it and ignore.
|
|
*/
|
|
ereport(WARNING,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("tablespaces are not supported on this platform")));
|
|
continue;
|
|
#endif /* HAVE_READLINK */
|
|
} else if (S_ISDIR(statbuf.st_mode)) {
|
|
statbuf.st_mode = S_IFDIR | S_IRWXU;
|
|
send_xlog_header(NULL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called when ERROR or FATAL happens in perform_base_backup() after
|
|
* we have started the backup - make sure we end it!
|
|
*/
|
|
static void base_backup_cleanup(int code, Datum arg)
|
|
{
|
|
do_pg_abort_backup();
|
|
}
|
|
|
|
/*
|
|
* Actually do a base backup for the specified tablespaces.
|
|
*
|
|
* This is split out mainly to avoid complaints about "variable might be
|
|
* clobbered by longjmp" from stupider versions of gcc.
|
|
*/
|
|
static void perform_base_backup(basebackup_options *opt, DIR *tblspcdir)
|
|
{
|
|
XLogRecPtr startptr;
|
|
XLogRecPtr endptr;
|
|
char *labelfile = NULL;
|
|
char* tblspc_map_file = NULL;
|
|
List* tablespaces = NIL;
|
|
|
|
if (opt->isBuildFromStandby) {
|
|
startptr = StandbyDoStartBackup(opt->label, &labelfile, &tblspc_map_file, &tablespaces,
|
|
tblspcdir, opt->progress);
|
|
} else {
|
|
startptr =
|
|
do_pg_start_backup(opt->label, opt->fastcheckpoint, &labelfile, tblspcdir, &tblspc_map_file, &tablespaces,
|
|
opt->progress, opt->sendtblspcmapfile);
|
|
}
|
|
|
|
/* Get the slot minimum LSN */
|
|
ReplicationSlotsComputeRequiredXmin(false);
|
|
ReplicationSlotsComputeRequiredLSN(NULL);
|
|
|
|
XLogRecPtr minlsn = XLogGetReplicationSlotMinimumLSNByOther();
|
|
if (!XLByteEQ(minlsn, InvalidXLogRecPtr) && (minlsn < startptr)) {
|
|
/* If xlog file has been recycled, don't use this minlsn */
|
|
if (XlogFileIsExisted(t_thrd.proc_cxt.DataDir, minlsn, DEFAULT_TIMELINE_ID) == true) {
|
|
startptr = minlsn;
|
|
}
|
|
}
|
|
LWLockAcquire(FullBuildXlogCopyStartPtrLock, LW_EXCLUSIVE);
|
|
XlogCopyStartPtr = startptr;
|
|
LWLockRelease(FullBuildXlogCopyStartPtrLock);
|
|
ereport(
|
|
INFO,
|
|
(errmsg("The starting position of the xlog copy of the full build is: %X/%X. The slot minimum LSN is: %X/%X.",
|
|
(uint32)(startptr >> 32), (uint32)startptr, (uint32)(minlsn >> 32), (uint32)minlsn)));
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
cbm_rotate_file(startptr);
|
|
#endif
|
|
SendXlogRecPtrResult(startptr);
|
|
|
|
PG_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum)0);
|
|
SendTableSpaceForBackup(opt, tablespaces, labelfile, tblspc_map_file);
|
|
PG_END_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum)0);
|
|
|
|
if (opt->isBuildFromStandby) {
|
|
endptr = StandbyDoStopBackup(labelfile);
|
|
} else {
|
|
endptr = do_pg_stop_backup(labelfile, !opt->nowait);
|
|
}
|
|
|
|
if (opt->includewal) {
|
|
/*
|
|
* We've left the last tar file "open", so we can now append the
|
|
* required WAL files to it.
|
|
*/
|
|
char pathbuf[MAXPGPATH];
|
|
XLogSegNo segno;
|
|
XLogSegNo startsegno;
|
|
XLogSegNo endsegno;
|
|
struct stat statbuf;
|
|
List* historyFileList = NIL;
|
|
List* walFileList = NIL;
|
|
char** walFiles;
|
|
int nWalFiles;
|
|
char firstoff[MAXFNAMELEN];
|
|
char lastoff[MAXFNAMELEN];
|
|
struct dirent* de;
|
|
int i;
|
|
ListCell* lc;
|
|
TimeLineID tli;
|
|
|
|
/*
|
|
* I'd rather not worry about timelines here, so scan pg_xlog and
|
|
* include all WAL files in the range between 'startptr' and 'endptr',
|
|
* regardless of the timeline the file is stamped with. If there are
|
|
* some spurious WAL files belonging to timelines that don't belong in
|
|
* this server's history, they will be included too. Normally there
|
|
* shouldn't be such files, but if there are, there's little harm in
|
|
* including them.
|
|
*/
|
|
XLByteToSeg(startptr, startsegno);
|
|
XLogFileName(firstoff, t_thrd.xlog_cxt.ThisTimeLineID, startsegno);
|
|
XLByteToPrevSeg(endptr, endsegno);
|
|
XLogFileName(lastoff, t_thrd.xlog_cxt.ThisTimeLineID, endsegno);
|
|
|
|
DIR* dir = AllocateDir("pg_xlog");
|
|
if (!dir) {
|
|
ereport(ERROR, (errmsg("could not open directory \"%s\": %m", "pg_xlog")));
|
|
}
|
|
while ((de = ReadDir(dir, "pg_xlog")) != NULL) {
|
|
/* Does it look like a WAL segment, and is it in the range? */
|
|
if (strlen(de->d_name) == 24 && strspn(de->d_name, "0123456789ABCDEF") == 24 &&
|
|
strcmp(de->d_name + 8, firstoff + 8) >= 0 && strcmp(de->d_name + 8, lastoff + 8) <= 0) {
|
|
walFileList = lappend(walFileList, pstrdup(de->d_name));
|
|
} else if (strlen(de->d_name) == 8 + strlen(".history") && strspn(de->d_name, "0123456789ABCDEF") == 8 &&
|
|
strcmp(de->d_name + 8, ".history") == 0) {
|
|
/* Does it look like a timeline history file? */
|
|
historyFileList = lappend(historyFileList, pstrdup(de->d_name));
|
|
}
|
|
}
|
|
FreeDir(dir);
|
|
|
|
/*
|
|
* Before we go any further, check that none of the WAL segments we
|
|
* need were removed.
|
|
*/
|
|
CheckXLogRemoved(startsegno, t_thrd.xlog_cxt.ThisTimeLineID);
|
|
|
|
/*
|
|
* Put the WAL filenames into an array, and sort. We send the files in
|
|
* order from oldest to newest, to reduce the chance that a file is
|
|
* recycled before we get a chance to send it over.
|
|
*/
|
|
nWalFiles = list_length(walFileList);
|
|
/*
|
|
* There must be at least one xlog file in the pg_xlog directory,
|
|
* since we are doing backup-including-xlog.
|
|
*/
|
|
if (nWalFiles < 1) {
|
|
ereport(ERROR, (errmsg("could not find any WAL files")));
|
|
}
|
|
|
|
walFiles = (char**)palloc0(nWalFiles * sizeof(char*));
|
|
i = 0;
|
|
foreach (lc, walFileList) {
|
|
walFiles[i++] = (char*)lfirst(lc);
|
|
}
|
|
qsort(walFiles, nWalFiles, sizeof(char*), CompareWalFileNames);
|
|
|
|
/*
|
|
* Sanity check: the first and last segment should cover startptr and
|
|
* endptr, with no gaps in between.
|
|
*/
|
|
XLogFromFileName(walFiles[0], &tli, &segno);
|
|
if (segno != startsegno) {
|
|
EREPORT_WAL_NOT_FOUND(startsegno);
|
|
}
|
|
for (i = 0; i < nWalFiles; i++) {
|
|
XLogSegNo currsegno = segno;
|
|
XLogSegNo nextsegno = segno + 1;
|
|
|
|
XLogFromFileName(walFiles[i], &tli, &segno);
|
|
if (!(nextsegno == segno || currsegno == segno)) {
|
|
EREPORT_WAL_NOT_FOUND(nextsegno);
|
|
}
|
|
}
|
|
if (segno != endsegno) {
|
|
EREPORT_WAL_NOT_FOUND(endsegno);
|
|
}
|
|
|
|
/* Ok, we have everything we need. Send the WAL files. */
|
|
for (i = 0; i < nWalFiles; i++) {
|
|
FILE* fp;
|
|
char buf[TAR_SEND_SIZE];
|
|
size_t cnt;
|
|
pgoff_t len = 0;
|
|
|
|
int rt = snprintf_s(pathbuf, MAXPGPATH,MAXPGPATH -1, XLOGDIR "/%s", walFiles[i]);
|
|
securec_check_ss_c(rt, "\0", "\0");
|
|
XLogFromFileName(walFiles[i], &tli, &segno);
|
|
|
|
fp = AllocateFile(pathbuf, "rb");
|
|
if (fp == NULL) {
|
|
int save_errno = errno;
|
|
|
|
/*
|
|
* Most likely reason for this is that the file was already
|
|
* removed by a checkpoint, so check for that to get a better
|
|
* error message.
|
|
*/
|
|
CheckXLogRemoved(segno, tli);
|
|
|
|
errno = save_errno;
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\": %m", pathbuf)));
|
|
}
|
|
|
|
if (fstat(fileno(fp), &statbuf) != 0) {
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", pathbuf)));
|
|
}
|
|
if (statbuf.st_size != XLogSegSize) {
|
|
CheckXLogRemoved(segno, tli);
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("unexpected WAL file size \"%s\"", walFiles[i])));
|
|
}
|
|
|
|
/* send the WAL file itself */
|
|
_tarWriteHeader(pathbuf, NULL, &statbuf);
|
|
|
|
while ((cnt = fread(buf, 1, Min((uint32)sizeof(buf), XLogSegSize - len), fp)) > 0) {
|
|
CheckXLogRemoved(segno, tli);
|
|
/* Send the chunk as a CopyData message */
|
|
if (pq_putmessage('d', buf, cnt)) {
|
|
ereport(ERROR, (errmsg("base backup could not send data, aborting backup")));
|
|
}
|
|
|
|
len += cnt;
|
|
|
|
if (len == XLogSegSize)
|
|
break;
|
|
}
|
|
|
|
if (len != XLogSegSize) {
|
|
CheckXLogRemoved(segno, tli);
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("unexpected WAL file size \"%s\"", walFiles[i])));
|
|
}
|
|
|
|
/* XLogSegSize is a multiple of 512, so no need for padding */
|
|
FreeFile(fp);
|
|
|
|
/*
|
|
* Mark file as archived, otherwise files can get archived again
|
|
* after promotion of a new node. This is in line with
|
|
* walreceiver.c always doing a XLogArchiveForceDone() after a
|
|
* complete segment.
|
|
*/
|
|
StatusFilePath(pathbuf, walFiles[i], ".done");
|
|
sendFileWithContent(pathbuf, "");
|
|
}
|
|
|
|
/*
|
|
* Send timeline history files too. Only the latest timeline history
|
|
* file is required for recovery, and even that only if there happens
|
|
* to be a timeline switch in the first WAL segment that contains the
|
|
* checkpoint record, or if we're taking a base backup from a standby
|
|
* server and the target timeline changes while the backup is taken.
|
|
* But they are small and highly useful for debugging purposes, so
|
|
* better include them all, always.
|
|
*/
|
|
foreach (lc, historyFileList) {
|
|
char* fname = (char*)lfirst(lc);
|
|
|
|
int rt = snprintf_s(pathbuf, MAXPGPATH, MAXPGPATH-1, XLOGDIR "/%s", fname);
|
|
securec_check_ss_c(rt, "\0", "\0");
|
|
if (lstat(pathbuf, &statbuf) != 0)
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", pathbuf)));
|
|
|
|
sendFile(pathbuf, pathbuf, &statbuf, false);
|
|
|
|
/* unconditionally mark file as archived */
|
|
StatusFilePath(pathbuf, fname, ".done");
|
|
sendFileWithContent(pathbuf, "");
|
|
}
|
|
|
|
/* Send CopyDone message for the last tar file */
|
|
pq_putemptymessage('c');
|
|
}
|
|
SendXlogRecPtrResult(endptr);
|
|
LWLockAcquire(FullBuildXlogCopyStartPtrLock, LW_EXCLUSIVE);
|
|
XlogCopyStartPtr = InvalidXLogRecPtr;
|
|
LWLockRelease(FullBuildXlogCopyStartPtrLock);
|
|
}
|
|
|
|
/*
|
|
* list_sort comparison function, to compare log/seg portion of WAL segment
|
|
* filenames, ignoring the timeline portion.
|
|
*/
|
|
static int CompareWalFileNames(const void* a, const void* b)
|
|
{
|
|
char* fna = *((char**)a);
|
|
char* fnb = *((char**)b);
|
|
|
|
return strcmp(fna + 8, fnb + 8);
|
|
}
|
|
|
|
#ifdef ENABLE_MOT
|
|
/*
|
|
* Called when ERROR or FATAL happens in PerformMotCheckpointFetch() after
|
|
* we have started the operation - make sure we end it!
|
|
*/
|
|
static void mot_checkpoint_fetch_cleanup(int code, Datum arg)
|
|
{
|
|
MOTCheckpointFetchUnlock();
|
|
}
|
|
|
|
/*
|
|
* Sends the current checkpoint to the client.
|
|
*/
|
|
void PerformMotCheckpointFetch()
|
|
{
|
|
char fullChkptDir[MAXPGPATH] = {0};
|
|
char ctrlFilePath[MAXPGPATH] = {0};
|
|
size_t basePathLen = 0;
|
|
|
|
MOTCheckpointFetchLock();
|
|
PG_ENSURE_ERROR_CLEANUP(mot_checkpoint_fetch_cleanup, (Datum)0);
|
|
{
|
|
if (MOTCheckpointExists(ctrlFilePath, MAXPGPATH, fullChkptDir, MAXPGPATH, basePathLen) == false) {
|
|
/* no checkpoint exists */
|
|
break;
|
|
}
|
|
|
|
/* send mot header */
|
|
SendMotCheckpointHeader(fullChkptDir);
|
|
StringInfoData buf;
|
|
|
|
/* send mot.ctrl file */
|
|
pq_beginmessage(&buf, 'H');
|
|
pq_sendbyte(&buf, 0); /* overall format */
|
|
pq_sendint16(&buf, 0); /* natts */
|
|
pq_endmessage_noblock(&buf);
|
|
|
|
struct stat statbuf;
|
|
if (lstat(ctrlFilePath, &statbuf) != 0) {
|
|
ereport(
|
|
ERROR, (errcode_for_file_access(), errmsg("could not stat mot ctrl file \"%s\": %m", ctrlFilePath)));
|
|
}
|
|
|
|
/* send the MOT control file */
|
|
sendFile(ctrlFilePath, "mot.ctrl", &statbuf, false);
|
|
|
|
/* send the checkpoint dir */
|
|
sendDir(fullChkptDir, (int)basePathLen, false, NIL, false);
|
|
|
|
/* CopyDone */
|
|
pq_putemptymessage_noblock('c');
|
|
}
|
|
PG_END_ENSURE_ERROR_CLEANUP(mot_checkpoint_fetch_cleanup, (Datum)0);
|
|
mot_checkpoint_fetch_cleanup(0, (Datum)0);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Parse the base backup options passed down by the parser
|
|
*/
|
|
static void parse_basebackup_options(List *options, basebackup_options *opt)
|
|
{
|
|
ListCell *lopt = NULL;
|
|
bool o_label = false;
|
|
bool o_progress = false;
|
|
bool o_fast = false;
|
|
bool o_nowait = false;
|
|
bool o_wal = false;
|
|
bool o_buildstandby = false;
|
|
bool o_tablespace_map = false;
|
|
errno_t rc = memset_s(opt, sizeof(*opt), 0, sizeof(*opt));
|
|
securec_check(rc, "", "");
|
|
foreach (lopt, options) {
|
|
DefElem *defel = (DefElem *)lfirst(lopt);
|
|
if (strcmp(defel->defname, "label") == 0) {
|
|
if (o_label)
|
|
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("duplicate option \"%s\"", defel->defname)));
|
|
opt->label = strVal(defel->arg);
|
|
o_label = true;
|
|
} else if (strcmp(defel->defname, "progress") == 0) {
|
|
if (o_progress)
|
|
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("duplicate option \"%s\"", defel->defname)));
|
|
opt->progress = true;
|
|
o_progress = true;
|
|
} else if (strcmp(defel->defname, "fast") == 0) {
|
|
if (o_fast)
|
|
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("duplicate option \"%s\"", defel->defname)));
|
|
opt->fastcheckpoint = true;
|
|
o_fast = true;
|
|
} else if (strcmp(defel->defname, "nowait") == 0) {
|
|
if (o_nowait)
|
|
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("duplicate option \"%s\"", defel->defname)));
|
|
opt->nowait = true;
|
|
o_nowait = true;
|
|
} else if (strcmp(defel->defname, "wal") == 0) {
|
|
if (o_wal)
|
|
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("duplicate option \"%s\"", defel->defname)));
|
|
opt->includewal = true;
|
|
o_wal = true;
|
|
} else if (strcmp(defel->defname, "buildstandby") == 0) {
|
|
if (o_buildstandby) {
|
|
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("duplicate option \"%s\"", defel->defname)));
|
|
}
|
|
opt->isBuildFromStandby = true;
|
|
o_buildstandby = true;
|
|
} else if (strcmp(defel->defname, "tablespace_map") == 0) {
|
|
if (o_tablespace_map) {
|
|
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("duplicate option \"%s\"", defel->defname)));
|
|
}
|
|
opt->sendtblspcmapfile = true;
|
|
o_tablespace_map = true;
|
|
} else
|
|
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("option \"%s\" not recognized", defel->defname)));
|
|
}
|
|
if (opt->label == NULL)
|
|
opt->label = "base backup";
|
|
}
|
|
|
|
/*
|
|
* SendBaseBackup() - send a complete base backup.
|
|
*
|
|
* The function will put the system into backup mode like pg_start_backup()
|
|
* does, so that the backup is consistent even though we read directly from
|
|
* the filesystem, bypassing the buffer cache.
|
|
*/
|
|
void SendBaseBackup(BaseBackupCmd *cmd)
|
|
{
|
|
DIR *dir = NULL;
|
|
MemoryContext backup_context;
|
|
MemoryContext old_context;
|
|
basebackup_options opt;
|
|
|
|
parse_basebackup_options(cmd->options, &opt);
|
|
|
|
backup_context = AllocSetContextCreate(CurrentMemoryContext, "Streaming base backup context",
|
|
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE,
|
|
ALLOCSET_DEFAULT_MAXSIZE);
|
|
old_context = MemoryContextSwitchTo(backup_context);
|
|
|
|
WalSndSetState(WALSNDSTATE_BACKUP);
|
|
|
|
if (u_sess->attr.attr_common.update_process_title) {
|
|
char activitymsg[50];
|
|
int rc = 0;
|
|
|
|
rc = snprintf_s(activitymsg, sizeof(activitymsg), sizeof(activitymsg) - 1, "sending backup \"%s\"", opt.label);
|
|
securec_check_ss(rc, "", "");
|
|
|
|
set_ps_display(activitymsg, false);
|
|
}
|
|
|
|
/* Make sure we can open the directory with tablespaces in it */
|
|
dir = AllocateDir("pg_tblspc");
|
|
if (dir == NULL) {
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not open directory \"%s\": %m", "pg_tblspc")));
|
|
return;
|
|
}
|
|
|
|
/* read xlog location ,if xlog is a link ,send the link to client */
|
|
send_xlog_location();
|
|
|
|
perform_base_backup(&opt, dir);
|
|
|
|
FreeDir(dir);
|
|
|
|
MemoryContextSwitchTo(old_context);
|
|
MemoryContextDelete(backup_context);
|
|
}
|
|
|
|
static void send_int8_string(StringInfoData *buf, int64 intval)
|
|
{
|
|
char is[32];
|
|
int rc = 0;
|
|
|
|
rc = snprintf_s(is, sizeof(is), sizeof(is) - 1, INT64_FORMAT, intval);
|
|
securec_check_ss(rc, "", "");
|
|
|
|
pq_sendint32(buf, strlen(is));
|
|
pq_sendbytes(buf, is, strlen(is));
|
|
}
|
|
|
|
static void SendBackupHeader(List *tablespaces)
|
|
{
|
|
StringInfoData buf;
|
|
ListCell *lc = NULL;
|
|
|
|
/* Construct and send the directory information */
|
|
pq_beginmessage(&buf, 'T'); /* RowDescription */
|
|
pq_sendint16(&buf, 4); /* 4 fields */
|
|
|
|
/* First field - spcoid */
|
|
pq_sendstring(&buf, "spcoid");
|
|
pq_sendint32(&buf, 0); /* table oid */
|
|
pq_sendint16(&buf, 0); /* attnum */
|
|
pq_sendint32(&buf, OIDOID); /* type oid */
|
|
pq_sendint16(&buf, 4); /* typlen */
|
|
pq_sendint32(&buf, 0); /* typmod */
|
|
pq_sendint16(&buf, 0); /* format code */
|
|
|
|
/* Second field - spcpath */
|
|
pq_sendstring(&buf, "spclocation");
|
|
pq_sendint32(&buf, 0);
|
|
pq_sendint16(&buf, 0);
|
|
pq_sendint32(&buf, TEXTOID);
|
|
pq_sendint16(&buf, UINT16_MAX);
|
|
pq_sendint32(&buf, 0);
|
|
pq_sendint16(&buf, 0);
|
|
|
|
/* Third field - size */
|
|
pq_sendstring(&buf, "size");
|
|
pq_sendint32(&buf, 0);
|
|
pq_sendint16(&buf, 0);
|
|
pq_sendint32(&buf, INT8OID);
|
|
pq_sendint16(&buf, 8);
|
|
pq_sendint32(&buf, 0);
|
|
pq_sendint16(&buf, 0);
|
|
|
|
/* Third field - size */
|
|
pq_sendstring(&buf, "relative");
|
|
pq_sendint32(&buf, 0);
|
|
pq_sendint16(&buf, 0);
|
|
pq_sendint32(&buf, INT8OID);
|
|
pq_sendint16(&buf, 8);
|
|
pq_sendint32(&buf, 0);
|
|
pq_sendint16(&buf, 0);
|
|
|
|
pq_endmessage_noblock(&buf);
|
|
|
|
foreach (lc, tablespaces) {
|
|
tablespaceinfo *ti = (tablespaceinfo *)lfirst(lc);
|
|
|
|
/* Send one datarow message */
|
|
pq_beginmessage(&buf, 'D');
|
|
pq_sendint16(&buf, 4); /* number of columns */
|
|
if (ti->path == NULL) {
|
|
/* Length = -1 ==> NULL */
|
|
pq_sendint32(&buf, UINT32_MAX);
|
|
pq_sendint32(&buf, UINT32_MAX);
|
|
} else {
|
|
pq_sendint32(&buf, strlen(ti->oid)); /* length */
|
|
pq_sendbytes(&buf, ti->oid, strlen(ti->oid));
|
|
|
|
char *path = ti->path;
|
|
if (ti->relativePath) {
|
|
path = ti->relativePath;
|
|
Assert(strlen(path) != strlen(ti->path));
|
|
}
|
|
pq_sendint32(&buf, strlen(path)); /* length */
|
|
pq_sendbytes(&buf, path, strlen(path));
|
|
}
|
|
if (ti->size >= 0)
|
|
send_int8_string(&buf, ti->size / 1024);
|
|
else
|
|
pq_sendint32(&buf, UINT32_MAX); /* NULL */
|
|
|
|
/* send last column: relativePath flag */
|
|
if (ti->relativePath)
|
|
send_int8_string(&buf, 1);
|
|
else
|
|
pq_sendint32(&buf, UINT32_MAX); /* NULL */
|
|
|
|
pq_endmessage_noblock(&buf);
|
|
}
|
|
|
|
/* Send a CommandComplete message */
|
|
pq_puttextmessage_noblock('C', "SELECT");
|
|
}
|
|
|
|
#ifdef ENABLE_MOT
|
|
static void SendMotCheckpointHeader(const char* path)
|
|
{
|
|
StringInfoData buf;
|
|
|
|
/* Construct and send the directory information */
|
|
pq_beginmessage(&buf, 'T'); /* RowDescription */
|
|
pq_sendint16(&buf, 1); /* nfields */
|
|
|
|
/* First field - checkpoint path */
|
|
pq_sendstring(&buf, "chkptloc");
|
|
pq_sendint32(&buf, 0);
|
|
pq_sendint16(&buf, 0);
|
|
pq_sendint32(&buf, TEXTOID);
|
|
pq_sendint16(&buf, UINT16_MAX);
|
|
pq_sendint32(&buf, 0);
|
|
pq_sendint16(&buf, 0);
|
|
pq_endmessage_noblock(&buf);
|
|
|
|
if (path != NULL) {
|
|
/* Send one datarow message */
|
|
pq_beginmessage(&buf, 'D');
|
|
pq_sendint16(&buf, 1); /* number of columns */
|
|
pq_sendint32(&buf, strlen(path)); /* length */
|
|
pq_sendbytes(&buf, path, strlen(path));
|
|
pq_endmessage_noblock(&buf);
|
|
}
|
|
|
|
/* Send a CommandComplete message */
|
|
pq_puttextmessage_noblock('C', "SELECT");
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Send a single resultset containing just a single
|
|
* XlogRecPtr record (in text format)
|
|
*/
|
|
static void SendXlogRecPtrResult(XLogRecPtr ptr)
|
|
{
|
|
StringInfoData buf;
|
|
char str[MAXFNAMELEN];
|
|
int nRet = 0;
|
|
|
|
nRet = snprintf_s(str, MAXFNAMELEN, MAXFNAMELEN - 1, "%X/%X", (uint32)(ptr >> 32), (uint32)ptr);
|
|
securec_check_ss(nRet, "", "");
|
|
|
|
pq_beginmessage(&buf, 'T'); /* RowDescription */
|
|
pq_sendint16(&buf, 1); /* 1 field */
|
|
|
|
/* Field header */
|
|
pq_sendstring(&buf, "recptr");
|
|
pq_sendint32(&buf, 0); /* table oid */
|
|
pq_sendint16(&buf, 0); /* attnum */
|
|
pq_sendint32(&buf, TEXTOID); /* type oid */
|
|
pq_sendint16(&buf, UINT16_MAX);
|
|
pq_sendint32(&buf, 0);
|
|
pq_sendint16(&buf, 0);
|
|
pq_endmessage_noblock(&buf);
|
|
|
|
/* Data row */
|
|
pq_beginmessage(&buf, 'D');
|
|
pq_sendint16(&buf, 1); /* number of columns */
|
|
pq_sendint32(&buf, strlen(str)); /* length */
|
|
pq_sendbytes(&buf, str, strlen(str));
|
|
pq_endmessage_noblock(&buf);
|
|
|
|
/* Send a CommandComplete message */
|
|
pq_puttextmessage_noblock('C', "SELECT");
|
|
}
|
|
|
|
/*
|
|
* Inject a file with given name and content in the output tar stream.
|
|
*/
|
|
static void sendFileWithContent(const char *filename, const char *content)
|
|
{
|
|
struct stat statbuf;
|
|
int pad, len;
|
|
|
|
len = strlen(content);
|
|
|
|
/*
|
|
* Construct a stat struct for the backup_label file we're injecting in
|
|
* the tar.
|
|
*/
|
|
/* Windows doesn't have the concept of uid and gid */
|
|
#ifdef WIN32
|
|
statbuf.st_uid = 0;
|
|
statbuf.st_gid = 0;
|
|
#else
|
|
statbuf.st_uid = geteuid();
|
|
statbuf.st_gid = getegid();
|
|
#endif
|
|
statbuf.st_mtime = time(NULL);
|
|
statbuf.st_mode = S_IRUSR | S_IWUSR;
|
|
statbuf.st_size = len;
|
|
|
|
_tarWriteHeader(filename, NULL, &statbuf);
|
|
/* Send the contents as a CopyData message */
|
|
(void)pq_putmessage_noblock('d', content, len);
|
|
|
|
/* Pad to 512 byte boundary, per tar format requirements */
|
|
pad = ((len + 511) & ~511) - len;
|
|
if (pad > 0) {
|
|
char buf[512];
|
|
errno_t rc = 0;
|
|
|
|
rc = memset_s(buf, sizeof(buf), 0, pad);
|
|
securec_check(rc, "", "");
|
|
(void)pq_putmessage_noblock('d', buf, pad);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Include the tablespace directory pointed to by 'path' in the output tar
|
|
* stream. If 'sizeonly' is true, we just calculate a total length and return
|
|
* it, without actually sending anything.
|
|
*
|
|
* Only used to send auxiliary tablespaces, not GAUSSDATA.
|
|
*/
|
|
int64 sendTablespace(const char *path, bool sizeonly)
|
|
{
|
|
int64 size = 0;
|
|
char pathbuf[MAXPGPATH] = {0};
|
|
char relativedirname[MAXPGPATH] = {0};
|
|
struct stat statbuf;
|
|
int rc = 0;
|
|
|
|
/*
|
|
* 'path' points to the tablespace location, but we only want to include
|
|
* the version directory in it that belongs to us.
|
|
*/
|
|
rc = snprintf_s(relativedirname, sizeof(relativedirname), sizeof(relativedirname) - 1, "%s_%s",
|
|
TABLESPACE_VERSION_DIRECTORY, g_instance.attr.attr_common.PGXCNodeName);
|
|
securec_check_ss(rc, "", "");
|
|
|
|
rc = snprintf_s(pathbuf, sizeof(pathbuf), sizeof(pathbuf) - 1, "%s/%s", path, relativedirname);
|
|
securec_check_ss(rc, "", "");
|
|
|
|
/*
|
|
* Store a directory entry in the tar file so we get the permissions
|
|
* right.
|
|
*/
|
|
if (lstat(pathbuf, &statbuf) != 0) {
|
|
if (errno != ENOENT)
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file or directory \"%s\": %m", pathbuf)));
|
|
|
|
/* If the tablespace went away while scanning, it's no error. */
|
|
return 0;
|
|
}
|
|
if (!sizeonly)
|
|
_tarWriteHeader(relativedirname, NULL, &statbuf);
|
|
size = BUILD_PATH_LEN; /* Size of the header just added */
|
|
|
|
/* Send all the files in the tablespace version directory */
|
|
size += sendDir(pathbuf, strlen(path), sizeonly, NIL, true);
|
|
|
|
return size;
|
|
}
|
|
|
|
bool IsSkipDir(const char * dirName)
|
|
{
|
|
if (strcmp(dirName, ".") == 0 || strcmp(dirName, "..") == 0)
|
|
return true;
|
|
if (strncmp(dirName, t_thrd.basebackup_cxt.g_xlog_location, strlen(dirName)) == 0)
|
|
return true;
|
|
if (strcmp(dirName, u_sess->attr.attr_common.Log_directory) == 0)
|
|
return true;
|
|
/* Skip temporary files */
|
|
if (strncmp(dirName, PG_TEMP_FILE_PREFIX, strlen(PG_TEMP_FILE_PREFIX)) == 0)
|
|
return true;
|
|
|
|
/*
|
|
* If there's a backup_label file, it belongs to a backup started by
|
|
* the user with pg_start_backup(). It is *not* correct for this
|
|
* backup, our backup_label is injected into the tar separately.
|
|
*/
|
|
if (strcmp(dirName, BACKUP_LABEL_FILE) == 0)
|
|
return true;
|
|
if (strcmp(dirName, DISABLE_CONN_FILE) == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsSkipPath(const char * pathName)
|
|
{
|
|
/* Skip pg_control here to back up it last */
|
|
if (strcmp(pathName, "./global/pg_control") == 0)
|
|
return true;
|
|
if (strcmp(pathName, "./global/pg_dw") == 0)
|
|
return true;
|
|
if (strcmp(pathName, "./global/pg_dw.build") == 0)
|
|
return true;
|
|
if (strcmp(pathName, "./global/config_exec_params") == 0)
|
|
return true;
|
|
|
|
if (strcmp(pathName, "./gaussdb.state") == 0 || strcmp(pathName, "./gs_build.pid") == 0)
|
|
return true;
|
|
|
|
if (strcmp(pathName, "./disc_readonly_test") == 0)
|
|
return true;
|
|
|
|
if (strstr(pathName, "./pg_rewind_bak") != NULL || strstr(pathName, "./pg_rewind_filemap") != NULL)
|
|
return true;
|
|
|
|
/*
|
|
* 1 major version upgrade mode, following path contains
|
|
* old database backup, need to skip
|
|
* 2 pg_location is for relative tablespace, we need skip it
|
|
*/
|
|
if (strcmp(pathName, "./full_upgrade_bak") == 0 || strcmp(pathName, "./pg_location") == 0)
|
|
return true;
|
|
|
|
if (strcmp(pathName, "./delay_xlog_recycle") == 0 || strcmp(pathName, "./delay_ddl_recycle") == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Include all files from the given directory in the output tar stream. If
|
|
* 'sizeonly' is true, we just calculate a total length and return it, without
|
|
* actually sending anything.
|
|
*
|
|
* Omit any directory in the tablespaces list, to avoid backing up
|
|
* tablespaces twice when they were created inside PGDATA.
|
|
*/
|
|
#ifdef ENABLE_MOT
|
|
static int64 sendDir(
|
|
const char *path, int basepathlen, bool sizeonly, List *tablespaces, bool sendtblspclinks, bool skipmot)
|
|
#else
|
|
static int64 sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces, bool sendtblspclinks)
|
|
#endif
|
|
{
|
|
struct dirent *de = NULL;
|
|
char pathbuf[MAXPGPATH];
|
|
struct stat statbuf;
|
|
int64 size = 0;
|
|
int rc = 0;
|
|
|
|
DIR *dir = AllocateDir(path);
|
|
while ((de = ReadDir(dir, path)) != NULL) {
|
|
/* Skip special stuff */
|
|
if (IsSkipDir(de->d_name)) {
|
|
continue;
|
|
}
|
|
|
|
|
|
/*
|
|
* Check if the postmaster has signaled us to exit, and abort with an
|
|
* error in that case. The error handler further up will call
|
|
* do_pg_abort_backup() for us.
|
|
*/
|
|
if (!PostmasterIsAlive()) {
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("Postmaster exited, aborting active base backup")));
|
|
}
|
|
|
|
if (t_thrd.walsender_cxt.walsender_shutdown_requested || t_thrd.walsender_cxt.walsender_ready_to_stop)
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("shutdown requested, aborting active base backup")));
|
|
|
|
if (t_thrd.postmaster_cxt.HaShmData &&
|
|
(t_thrd.walsender_cxt.server_run_mode != t_thrd.postmaster_cxt.HaShmData->current_mode))
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("server run mode changed, aborting active base backup")));
|
|
|
|
rc = snprintf_s(pathbuf, MAXPGPATH, MAXPGPATH - 1, "%s/%s", path, de->d_name);
|
|
securec_check_ss(rc, "", "");
|
|
|
|
/* Skip postmaster.pid and postmaster.opts in the data directory */
|
|
if (strcmp(pathbuf, "./postmaster.pid") == 0 || strcmp(pathbuf, "./postmaster.opts") == 0)
|
|
continue;
|
|
|
|
/* For gs_basebackup, we should not skip these files */
|
|
if (strcmp(u_sess->attr.attr_common.application_name, "gs_basebackup") != 0) {
|
|
/* For gs_backup, we should not skip these files */
|
|
if (strcmp(pathbuf, "./pg_ctl.lock") == 0 || strcmp(pathbuf, "./postgresql.conf.lock") == 0 ||
|
|
strcmp(pathbuf, "./postgresql.conf.bak") == 0 || strcmp(pathbuf, "./postgresql.conf") == 0 ||
|
|
strcmp(pathbuf, "./postgresql.conf.bak.old") == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(de->d_name, g_instance.attr.attr_security.ssl_cert_file) == 0 ||
|
|
strcmp(de->d_name, g_instance.attr.attr_security.ssl_key_file) == 0 ||
|
|
strcmp(de->d_name, g_instance.attr.attr_security.ssl_ca_file) == 0 ||
|
|
strcmp(de->d_name, g_instance.attr.attr_security.ssl_crl_file) == 0 ||
|
|
strcmp(de->d_name, ssl_cipher_file) == 0 || strcmp(de->d_name, ssl_rand_file) == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(pathbuf, "./client.crt") == 0 || strcmp(pathbuf, "./client.key") == 0) {
|
|
continue;
|
|
}
|
|
}
|
|
if (IS_PGXC_COORDINATOR && strcmp(pathbuf, "./pg_hba.conf") == 0)
|
|
continue;
|
|
/* Skip cn_drop_backup */
|
|
if (IS_PGXC_COORDINATOR && strcmp(pathbuf, "./cn_drop_backup") == 0)
|
|
continue;
|
|
|
|
/* Skip pg_control here to back up it last */
|
|
if (IsSkipPath(pathbuf)) {
|
|
continue;
|
|
}
|
|
|
|
#ifdef ENABLE_MOT
|
|
/* skip mot files */
|
|
if (skipmot && (strcmp(pathbuf, "./mot.ctrl") == 0 || strncmp(pathbuf, "./chkpt_", strlen("./chkpt_")) == 0)) {
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
if (lstat(pathbuf, &statbuf) != 0) {
|
|
if (errno != ENOENT)
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(), errmsg("could not stat file or directory \"%s\": %m", pathbuf)));
|
|
|
|
/* If the file went away while scanning, it's no error. */
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Skip pg_errorinfo, not useful to copy. But include
|
|
* it as an empty directory anyway, so we get permissions right.
|
|
*/
|
|
if (strcmp(de->d_name, "pg_errorinfo") == 0) {
|
|
if (!sizeonly)
|
|
_tarWriteHeader(pathbuf + basepathlen + 1, NULL, &statbuf);
|
|
size += BUILD_PATH_LEN; /* Size of the header just added */
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Skip physical slot file, not useful to copy, only include logical slot file.
|
|
*/
|
|
if (strcmp(path, "./pg_replslot") == 0) {
|
|
bool isphysicalslot = false;
|
|
LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
|
|
for (int i = 0; i < g_instance.attr.attr_storage.max_replication_slots; i++) {
|
|
ReplicationSlot *s = &t_thrd.slot_cxt.ReplicationSlotCtl->replication_slots[i];
|
|
if (s->in_use && s->data.database == InvalidOid && strcmp(de->d_name, NameStr(s->data.name)) == 0 &&
|
|
GET_SLOT_PERSISTENCY(s->data) != RS_BACKUP && GET_SLOT_EXTRA_DATA_LENGTH(s->data) == 0) {
|
|
isphysicalslot = true;
|
|
break;
|
|
}
|
|
}
|
|
LWLockRelease(ReplicationSlotControlLock);
|
|
|
|
if (isphysicalslot)
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* We can skip pg_xlog, the WAL segments need to be fetched from the
|
|
* WAL archive anyway. But include it as an empty directory anyway, so
|
|
* we get permissions right.
|
|
*/
|
|
if (strcmp(pathbuf, "./pg_xlog") == 0) {
|
|
if (!sizeonly) {
|
|
/* If pg_xlog is a symlink, write it as a directory anyway */
|
|
#ifndef WIN32
|
|
if (S_ISLNK(statbuf.st_mode)) {
|
|
#else
|
|
if (pgwin32_is_junction(pathbuf)) {
|
|
#endif
|
|
#if defined(HAVE_READLINK) || defined(WIN32)
|
|
char linkpath[MAXPGPATH] = {0};
|
|
|
|
int rllen = readlink(pathbuf, linkpath, sizeof(linkpath));
|
|
if (rllen < 0)
|
|
ereport(ERROR, (errcode_for_file_access(),
|
|
errmsg("could not read symbolic link \"%s\": %m", pathbuf)));
|
|
if (rllen >= (int)sizeof(linkpath))
|
|
ereport(ERROR, (errcode(ERRCODE_NAME_TOO_LONG),
|
|
errmsg("symbolic link \"%s\" target is too long", pathbuf)));
|
|
linkpath[MAXPGPATH - 1] = '\0';
|
|
|
|
if (!sizeonly)
|
|
_tarWriteHeader(pathbuf + basepathlen + 1, linkpath, &statbuf);
|
|
#else
|
|
|
|
/*
|
|
* If the platform does not have symbolic links, it should not be
|
|
* possible to have tablespaces - clearly somebody else created
|
|
* them. Warn about it and ignore.
|
|
*/
|
|
ereport(WARNING, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("tablespaces are not supported on this platform")));
|
|
continue;
|
|
#endif /* HAVE_READLINK */
|
|
} else if (S_ISDIR(statbuf.st_mode)) {
|
|
statbuf.st_mode = S_IFDIR | S_IRWXU;
|
|
_tarWriteHeader(pathbuf + basepathlen + 1, NULL, &statbuf);
|
|
}
|
|
}
|
|
size += BUILD_PATH_LEN; /* Size of the header just added */
|
|
if (!sizeonly) {
|
|
#ifndef WIN32
|
|
if (S_ISLNK(statbuf.st_mode)) {
|
|
#else
|
|
if (pgwin32_is_junction(pathbuf)) {
|
|
#endif
|
|
#if defined(HAVE_READLINK) || defined(WIN32)
|
|
char linkpath[MAXPGPATH] = {0};
|
|
|
|
int rllen = readlink(pathbuf, linkpath, sizeof(linkpath));
|
|
if (rllen < 0)
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(), errmsg("could not read symbolic link \"%s\": %m", pathbuf)));
|
|
if (rllen >= (int)sizeof(linkpath))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NAME_TOO_LONG),
|
|
errmsg("symbolic link \"%s\" target is too long", pathbuf)));
|
|
linkpath[MAXPGPATH - 1] = '\0';
|
|
_tarWriteHeader(pathbuf + basepathlen + 1, linkpath, &statbuf);
|
|
#else
|
|
/*
|
|
* If the platform does not have symbolic links, it should not be
|
|
* possible to have tablespaces - clearly somebody else created
|
|
* them. Warn about it and ignore.
|
|
*/
|
|
ereport(WARNING, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("tablespaces are not supported on this platform")));
|
|
continue;
|
|
#endif /* HAVE_READLINK */
|
|
|
|
} else if (S_ISDIR(statbuf.st_mode)) {
|
|
/*
|
|
* Also send archive_status directory (by hackishly reusing
|
|
* statbuf from above ...).
|
|
*/
|
|
statbuf.st_mode = S_IFDIR | S_IRWXU;
|
|
_tarWriteHeader("pg_xlog/archive_status", NULL, &statbuf);
|
|
}
|
|
}
|
|
size += BUILD_PATH_LEN; /* Size of the header just added */
|
|
continue; /* don't recurse into pg_xlog */
|
|
}
|
|
|
|
/* Allow symbolic links in pg_tblspc only */
|
|
if (strcmp(path, "./pg_tblspc") == 0 &&
|
|
#ifndef WIN32
|
|
S_ISLNK(statbuf.st_mode)
|
|
#else
|
|
pgwin32_is_junction(pathbuf)
|
|
#endif
|
|
) {
|
|
#if defined(HAVE_READLINK) || defined(WIN32)
|
|
char linkpath[MAXPGPATH];
|
|
|
|
int rllen = readlink(pathbuf, linkpath, sizeof(linkpath));
|
|
if (rllen < 0)
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not read symbolic link \"%s\": %m", pathbuf)));
|
|
if (rllen >= (int)sizeof(linkpath))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NAME_TOO_LONG), errmsg("symbolic link \"%s\" target is too long", pathbuf)));
|
|
linkpath[rllen] = '\0';
|
|
if (!sizeonly)
|
|
_tarWriteHeader(pathbuf + basepathlen + 1, linkpath, &statbuf);
|
|
size += BUILD_PATH_LEN; /* Size of the header just added */
|
|
#else
|
|
|
|
/*
|
|
* If the platform does not have symbolic links, it should not be
|
|
* possible to have tablespaces - clearly somebody else created
|
|
* them. Warn about it and ignore.
|
|
*/
|
|
ereport(WARNING,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("tablespaces are not supported on this platform")));
|
|
continue;
|
|
#endif /* HAVE_READLINK */
|
|
} else if (S_ISDIR(statbuf.st_mode)) {
|
|
bool skip_this_dir = false;
|
|
ListCell *lc = NULL;
|
|
|
|
/*
|
|
* Store a directory entry in the tar file so we can get the
|
|
* permissions right.
|
|
*/
|
|
if (!sizeonly)
|
|
_tarWriteHeader(pathbuf + basepathlen + 1, NULL, &statbuf);
|
|
size += BUILD_PATH_LEN; /* Size of the header just added */
|
|
|
|
/*
|
|
* Call ourselves recursively for a directory, unless it happens
|
|
* to be a separate tablespace located within PGDATA.
|
|
*/
|
|
foreach (lc, tablespaces) {
|
|
tablespaceinfo *ti = (tablespaceinfo *)lfirst(lc);
|
|
|
|
/*
|
|
* ti->rpath is the tablespace relative path within PGDATA, or
|
|
* NULL if the tablespace has been properly located somewhere
|
|
* else.
|
|
*
|
|
* Skip past the leading "./" in pathbuf when comparing.
|
|
*/
|
|
if (ti->relativePath && strcmp(ti->relativePath, pathbuf + 2) == 0) {
|
|
skip_this_dir = true;
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
* skip sending directories inside pg_tblspc, if not required.
|
|
*/
|
|
if (strcmp(pathbuf, "./pg_tblspc") == 0 && !sendtblspclinks)
|
|
skip_this_dir = true;
|
|
if (!skip_this_dir)
|
|
size += sendDir(pathbuf, basepathlen, sizeonly, tablespaces, sendtblspclinks);
|
|
} else if (S_ISREG(statbuf.st_mode)) {
|
|
bool sent = false;
|
|
|
|
if (!sizeonly)
|
|
sent = sendFile(pathbuf, pathbuf + basepathlen + 1, &statbuf, true);
|
|
|
|
if (sent || sizeonly) {
|
|
/* Add size, rounded up to 512byte block */
|
|
size = size + ((statbuf.st_size + 511) & ~511) + BUILD_PATH_LEN;
|
|
}
|
|
} else
|
|
ereport(WARNING, (errmsg("skipping special file \"%s\"", pathbuf)));
|
|
}
|
|
FreeDir(dir);
|
|
return size;
|
|
}
|
|
|
|
/*
|
|
* Functions for handling tar file format
|
|
*
|
|
* Copied from pg_dump, but modified to work with libpq for sending
|
|
*
|
|
* Utility routine to print possibly larger than 32 bit integers in a
|
|
* portable fashion. Filled with zeros.
|
|
*/
|
|
static void print_val(char *s, uint64 val, unsigned int base, size_t len)
|
|
{
|
|
int i;
|
|
|
|
for (i = len; i > 0; i--) {
|
|
int digit = val % base;
|
|
|
|
s[i - 1] = '0' + digit;
|
|
val = val / base;
|
|
}
|
|
}
|
|
|
|
bool check_base_path(const char *fname, int *segNo)
|
|
{
|
|
RelFileNode rnode;
|
|
int columnid = 0;
|
|
int nmatch;
|
|
|
|
rnode.spcNode = InvalidOid;
|
|
rnode.dbNode = InvalidOid;
|
|
rnode.relNode = InvalidOid;
|
|
rnode.bucketNode = InvalidBktId;
|
|
|
|
/* Column Store Table File Name Format Checking */
|
|
nmatch = sscanf_s(fname, "base/%u/%u_C%d.%d", &rnode.dbNode, &rnode.relNode, &columnid, segNo);
|
|
if (nmatch == MATCH_FOUR) {
|
|
return false;
|
|
}
|
|
|
|
/* Hashbucket Table File Name Format Checking */
|
|
nmatch = sscanf_s(fname, "base/%u/%u/%u_b%d.%d", &rnode.dbNode, &rnode.relNode, &rnode.relNode, &rnode.bucketNode,
|
|
segNo);
|
|
if (nmatch == MATCH_FOUR || nmatch == MATCH_FIVE) {
|
|
return true;
|
|
}
|
|
nmatch = sscanf_s(fname, "base/%u/%u/%u_b%d_vm.%d", &rnode.dbNode, &rnode.relNode, &rnode.relNode,
|
|
&rnode.bucketNode, segNo);
|
|
if (nmatch == MATCH_FOUR || nmatch == MATCH_FIVE) {
|
|
return true;
|
|
}
|
|
nmatch = sscanf_s(fname, "base/%u/%u/%u_b%d_fsm.%d", &rnode.dbNode, &rnode.relNode, &rnode.relNode,
|
|
&rnode.bucketNode, segNo);
|
|
if (nmatch == MATCH_FOUR || nmatch == MATCH_FIVE) {
|
|
return true;
|
|
}
|
|
|
|
/* Normal Table File Name Format Checking */
|
|
nmatch = sscanf_s(fname, "base/%u/%u.%d", &rnode.dbNode, &rnode.relNode, segNo);
|
|
if (nmatch == MATCH_TWO || nmatch == MATCH_THREE) {
|
|
return true;
|
|
}
|
|
|
|
nmatch = sscanf_s(fname, "base/%u/%u_vm.%d", &rnode.dbNode, &rnode.relNode, segNo);
|
|
if (nmatch == MATCH_TWO || nmatch == MATCH_THREE) {
|
|
return true;
|
|
}
|
|
nmatch = sscanf_s(fname, "base/%u/%u_fsm.%d", &rnode.dbNode, &rnode.relNode, segNo);
|
|
if (nmatch == MATCH_TWO || nmatch == MATCH_THREE) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool check_rel_tblspac_path(const char *fname, int *segNo)
|
|
{
|
|
char buf[FILE_NAME_MAX_LEN] = {0};
|
|
RelFileNode rnode;
|
|
int columnid = 0;
|
|
int nmatch;
|
|
|
|
rnode.spcNode = InvalidOid;
|
|
rnode.dbNode = InvalidOid;
|
|
rnode.relNode = InvalidOid;
|
|
rnode.bucketNode = InvalidBktId;
|
|
|
|
nmatch = sscanf_s(fname, "pg_tblspc/%u/%[^/]/%u/%u_C%d.%d", &rnode.spcNode, buf, sizeof(buf), &rnode.dbNode,
|
|
&rnode.relNode, &columnid, segNo);
|
|
if (nmatch == MATCH_SIX) {
|
|
return false;
|
|
}
|
|
|
|
/* Hashbucket File Name Format Checking */
|
|
nmatch = sscanf_s(fname, "pg_tblspc/%u/%u/%[^/]/%u/%u_b%d.%d", &rnode.spcNode, buf, sizeof(buf), &rnode.dbNode,
|
|
&rnode.relNode, &rnode.relNode, &rnode.bucketNode, segNo);
|
|
if (nmatch == MATCH_SIX || nmatch == MATCH_SEVEN) {
|
|
return true;
|
|
}
|
|
|
|
nmatch = sscanf_s(fname, "pg_tblspc/%u/%[^/]/%u/%u/%u_b%d_fsm.%d", &rnode.spcNode, buf, sizeof(buf), &rnode.dbNode,
|
|
&rnode.relNode, &rnode.relNode, &rnode.bucketNode, segNo);
|
|
if (nmatch == MATCH_SIX || nmatch == MATCH_SEVEN) {
|
|
return true;
|
|
}
|
|
|
|
nmatch = sscanf_s(fname, "pg_tblspc/%u/%[^/]/%u/%u/%u_b%d_vm.%d", &rnode.spcNode, buf, sizeof(buf), &rnode.dbNode,
|
|
&rnode.relNode, &rnode.relNode, &rnode.bucketNode, segNo);
|
|
if (nmatch == MATCH_SIX || nmatch == MATCH_SEVEN) {
|
|
return true;
|
|
}
|
|
|
|
/* Normal Table File Name Format Checking */
|
|
nmatch = sscanf_s(fname, "pg_tblspc/%u/%[^/]/%u/%u.%d", &rnode.spcNode, buf, sizeof(buf), &rnode.dbNode,
|
|
&rnode.relNode, segNo);
|
|
if (nmatch == MATCH_FOUR || nmatch == MATCH_FIVE) {
|
|
return true;
|
|
}
|
|
|
|
nmatch = sscanf_s(fname, "pg_tblspc/%u/%[^/]/%u/%u_fsm.%d", &rnode.spcNode, buf, sizeof(buf), &rnode.dbNode,
|
|
&rnode.relNode, segNo);
|
|
if (nmatch == MATCH_FOUR || nmatch == MATCH_FIVE) {
|
|
return true;
|
|
}
|
|
|
|
nmatch = sscanf_s(fname, "pg_tblspc/%u/%[^/]/%u/%u_vm.%d", &rnode.spcNode, buf, sizeof(buf), &rnode.dbNode,
|
|
&rnode.relNode, segNo);
|
|
if (nmatch == MATCH_FOUR || nmatch == MATCH_FIVE) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool check_abs_tblspac_path(const char *fname, int *segNo)
|
|
{
|
|
char buf[FILE_NAME_MAX_LEN] = {0};
|
|
RelFileNode rnode;
|
|
int columnid = 0;
|
|
int nmatch;
|
|
|
|
rnode.spcNode = InvalidOid;
|
|
rnode.dbNode = InvalidOid;
|
|
rnode.relNode = InvalidOid;
|
|
rnode.bucketNode = InvalidBktId;
|
|
|
|
nmatch = sscanf_s(fname, "PG_9.2_201611171_%[^/]/%u/%u_C%d.%d", buf, sizeof(buf), &rnode.dbNode, &rnode.relNode,
|
|
&columnid, segNo);
|
|
if (nmatch == MATCH_FIVE) {
|
|
return false;
|
|
}
|
|
|
|
/* Hashbucket Table File Name Format Checking */
|
|
nmatch = sscanf_s(fname, "PG_9.2_201611171_%[^/]/%u/%u/%u_b%d.%d", buf, sizeof(buf), &rnode.dbNode, &rnode.relNode,
|
|
&rnode.relNode, &rnode.bucketNode, segNo);
|
|
if (nmatch == MATCH_FIVE || nmatch == MATCH_SIX) {
|
|
return true;
|
|
}
|
|
|
|
nmatch = sscanf_s(fname, "PG_9.2_201611171_%[^/]/%u/%u/%u_b%d_fsm.%d", buf, sizeof(buf), &rnode.dbNode,
|
|
&rnode.relNode, &rnode.relNode, &rnode.bucketNode, segNo);
|
|
if (nmatch == MATCH_FIVE || nmatch == MATCH_SIX) {
|
|
return true;
|
|
}
|
|
|
|
nmatch = sscanf_s(fname, "PG_9.2_201611171_%[^/]/%u/%u/%u_b%d_vm.%d", buf, sizeof(buf), &rnode.dbNode,
|
|
&rnode.relNode, &rnode.relNode, &rnode.bucketNode, segNo);
|
|
if (nmatch == MATCH_FIVE || nmatch == MATCH_SIX) {
|
|
return true;
|
|
}
|
|
|
|
/* Normal Table File Name Format Checking */
|
|
nmatch = sscanf_s(fname, "PG_9.2_201611171_%[^/]/%u/%u.%d", buf, sizeof(buf), &rnode.dbNode, &rnode.relNode, segNo);
|
|
if (nmatch == MATCH_FOUR || nmatch == MATCH_FIVE) {
|
|
return true;
|
|
}
|
|
|
|
nmatch = sscanf_s(fname, "PG_9.2_201611171_%[^/]/%u/%u_fsm.%d", buf, sizeof(buf), &rnode.dbNode, &rnode.relNode,
|
|
segNo);
|
|
if (nmatch == MATCH_FOUR || nmatch == MATCH_FIVE) {
|
|
return true;
|
|
}
|
|
|
|
nmatch = sscanf_s(fname, "PG_9.2_201611171_%[^/]/%u/%u_vm.%d", buf, sizeof(buf), &rnode.dbNode, &rnode.relNode,
|
|
segNo);
|
|
if (nmatch == MATCH_FOUR || nmatch == MATCH_FIVE) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool is_row_data_file(const char *path, int *segNo)
|
|
{
|
|
char *fname = NULL;
|
|
RelFileNode rnode;
|
|
int nmatch = 0;
|
|
|
|
rnode.spcNode = InvalidOid;
|
|
rnode.dbNode = InvalidOid;
|
|
rnode.relNode = InvalidOid;
|
|
rnode.bucketNode = InvalidBktId;
|
|
|
|
struct stat path_st;
|
|
if (stat(path, &path_st) < 0) {
|
|
if (errno != ENOENT) {
|
|
ereport(ERROR, (errmsg("Cannot stat file %s when judge it's a rowdatafile for roach. OS error: %s", path,
|
|
strerror(errno))));
|
|
} else {
|
|
ereport(INFO, (errmsg("File %s not exists when judge it's a rowdatafile for roach. OS error: %s", path,
|
|
strerror(errno))));
|
|
}
|
|
return false;
|
|
} else if (S_ISDIR(path_st.st_mode)) {
|
|
return false;
|
|
}
|
|
|
|
if (strstr(path, "global/") == NULL && strstr(path, "base/") == NULL && strstr(path, "pg_tblspc/") == NULL &&
|
|
strstr(path, "PG_9.2_201611171") == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if ((fname = strstr((char *)path, "global/")) != NULL) {
|
|
nmatch = sscanf_s(fname, "global/%u.%d", &rnode.relNode, segNo);
|
|
if (nmatch == MATCH_ONE || nmatch == MATCH_TWO) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else if ((fname = strstr((char *)path, "base/")) != NULL) {
|
|
return check_base_path(fname, segNo);
|
|
} else if ((fname = strstr((char *)path, "pg_tblspc/")) != NULL) {
|
|
return check_rel_tblspac_path(fname, segNo);
|
|
} else if ((fname = strstr((char *)path, "PG_9.2_201611171")) != NULL) {
|
|
return check_abs_tblspac_path(fname, segNo);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static void SendTableSpaceForBackup(basebackup_options* opt, List* tablespaces, char* labelfile, char* tblspc_map_file)
|
|
{
|
|
ListCell *lc = NULL;
|
|
/* Add a node for the base directory at the end */
|
|
tablespaceinfo *ti = (tablespaceinfo *)palloc0(sizeof(tablespaceinfo));
|
|
ti->size = opt->progress ? sendDir(".", 1, true, tablespaces, true) : -1;
|
|
tablespaces = (List *)lappend(tablespaces, ti);
|
|
|
|
/* Send tablespace header */
|
|
SendBackupHeader(tablespaces);
|
|
|
|
/* Send off our tablespaces one by one */
|
|
foreach (lc, tablespaces) {
|
|
tablespaceinfo *iterti = (tablespaceinfo *)lfirst(lc);
|
|
StringInfoData buf;
|
|
|
|
/* Send CopyOutResponse message */
|
|
pq_beginmessage(&buf, 'H');
|
|
pq_sendbyte(&buf, 0); /* overall format */
|
|
pq_sendint16(&buf, 0); /* natts */
|
|
pq_endmessage_noblock(&buf);
|
|
|
|
/* In the main tar, include the backup_label first. */
|
|
if (iterti->path == NULL)
|
|
sendFileWithContent(BACKUP_LABEL_FILE, labelfile);
|
|
|
|
/*
|
|
* if the tblspc created in datadir , the files under tblspc do not send,
|
|
* and send them as normal under datadir,
|
|
* so we just send these tblspcs only once.
|
|
*/
|
|
if (iterti->path != NULL) {
|
|
/* Skip the tablespace if it's created in GAUSSDATA */
|
|
sendTablespace(iterti->path, false);
|
|
} else {
|
|
/* Then the tablespace_map file, if required... */
|
|
if (tblspc_map_file && opt->sendtblspcmapfile) {
|
|
sendFileWithContent(TABLESPACE_MAP, tblspc_map_file);
|
|
sendDir(".", 1, false, tablespaces, false);
|
|
} else
|
|
sendDir(".", 1, false, tablespaces, true);
|
|
}
|
|
|
|
/* In the main tar, include pg_control last. */
|
|
if (iterti->path == NULL) {
|
|
struct stat statbuf;
|
|
TimeLineID primay_tli = 0;
|
|
char path[MAXPGPATH] = {0};
|
|
|
|
if (lstat(XLOG_CONTROL_FILE, &statbuf) != 0) {
|
|
LWLockAcquire(FullBuildXlogCopyStartPtrLock, LW_EXCLUSIVE);
|
|
XlogCopyStartPtr = InvalidXLogRecPtr;
|
|
LWLockRelease(FullBuildXlogCopyStartPtrLock);
|
|
ereport(ERROR, (errcode_for_file_access(),
|
|
errmsg("could not stat control file \"%s\": %m", XLOG_CONTROL_FILE)));
|
|
}
|
|
|
|
sendFile(XLOG_CONTROL_FILE, XLOG_CONTROL_FILE, &statbuf, false);
|
|
/* In the main tar, include the last timeline history file at last. */
|
|
primay_tli = t_thrd.xlog_cxt.ThisTimeLineID;
|
|
while (primay_tli > 1) {
|
|
TLHistoryFilePath(path, primay_tli);
|
|
if (lstat(path, &statbuf) == 0)
|
|
sendFile(path, path, &statbuf, false);
|
|
primay_tli--;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we're including WAL, and this is the main data directory we
|
|
* don't terminate the tar stream here. Instead, we will append
|
|
* the xlog files below and terminate it then. This is safe since
|
|
* the main data directory is always sent *last*.
|
|
*/
|
|
if (opt->includewal && iterti->path == NULL) {
|
|
Assert(lnext(lc) == NULL);
|
|
} else
|
|
pq_putemptymessage_noblock('c'); /* CopyDone */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Given the member, write the TAR header & send the file.
|
|
*
|
|
* If 'missing_ok' is true, will not throw an error if the file is not found.
|
|
*
|
|
* Returns true if the file was successfully sent, false if 'missing_ok',
|
|
* and the file did not exist.
|
|
*/
|
|
static bool sendFile(char *readfilename, char *tarfilename, struct stat *statbuf, bool missing_ok)
|
|
{
|
|
FILE *fp = NULL;
|
|
size_t cnt;
|
|
pgoff_t len = 0;
|
|
size_t pad;
|
|
errno_t rc = 0;
|
|
int check_loc = 0;
|
|
BlockNumber blkno = 0;
|
|
uint16 checksum = 0;
|
|
bool isNeedCheck = false;
|
|
int segNo = 0;
|
|
const int MAX_RETRY_LIMIT = 60;
|
|
int retryCnt = 0;
|
|
|
|
if (t_thrd.basebackup_cxt.buf_block == NULL) {
|
|
MemoryContext oldcxt = NULL;
|
|
|
|
oldcxt = MemoryContextSwitchTo(THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE));
|
|
t_thrd.basebackup_cxt.buf_block = (char *)palloc0(TAR_SEND_SIZE);
|
|
MemoryContextSwitchTo(oldcxt);
|
|
}
|
|
|
|
/*
|
|
* repalloc to `MaxBuildAllocSize' in one time, to avoid many small step repalloc in `pq_putmessage_noblock'
|
|
* and low performance.
|
|
*/
|
|
if (INT2SIZET(t_thrd.libpq_cxt.PqSendBufferSize) < MaxBuildAllocSize) {
|
|
t_thrd.libpq_cxt.PqSendBuffer = (char *)repalloc(t_thrd.libpq_cxt.PqSendBuffer, MaxBuildAllocSize);
|
|
t_thrd.libpq_cxt.PqSendBufferSize = MaxBuildAllocSize;
|
|
}
|
|
|
|
/*
|
|
* Some compilers will throw a warning knowing this test can never be true
|
|
* because pgoff_t can't exceed the compared maximum on their platform.
|
|
*/
|
|
if (statbuf->st_size > MAX_TAR_MEMBER_FILELEN) {
|
|
ereport(WARNING, (errcode(ERRCODE_NAME_TOO_LONG),
|
|
errmsg("archive member \"%s\" too large for tar format", tarfilename)));
|
|
return false;
|
|
}
|
|
|
|
fp = AllocateFile(readfilename, "rb");
|
|
if (fp == NULL) {
|
|
if (errno == ENOENT && missing_ok)
|
|
return false;
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\": %m", readfilename)));
|
|
}
|
|
|
|
isNeedCheck = is_row_data_file(readfilename, &segNo);
|
|
ereport(DEBUG1, (errmsg("sendFile, filename is %s, isNeedCheck is %d", readfilename, isNeedCheck)));
|
|
|
|
/* make sure data file size is integer multiple of BLCKSZ and change statbuf if needed */
|
|
if (isNeedCheck) {
|
|
statbuf->st_size = statbuf->st_size - (statbuf->st_size % BLCKSZ);
|
|
}
|
|
|
|
/* send the pkg header containing msg like file size */
|
|
_tarWriteHeader(tarfilename, NULL, statbuf);
|
|
|
|
while ((cnt = fread(t_thrd.basebackup_cxt.buf_block, 1, Min(TAR_SEND_SIZE, statbuf->st_size - len), fp)) > 0) {
|
|
if (t_thrd.walsender_cxt.walsender_ready_to_stop)
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("base backup receive stop message, aborting backup")));
|
|
recheck:
|
|
if (cnt != (size_t)Min(TAR_SEND_SIZE, statbuf->st_size - len)) {
|
|
if (ferror(fp)) {
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not read file \"%s\": %m", readfilename)));
|
|
}
|
|
}
|
|
if (g_instance.attr.attr_storage.enableIncrementalCheckpoint && isNeedCheck) {
|
|
/* len and cnt must be integer multiple of BLCKSZ. */
|
|
if (len % BLCKSZ != 0 || cnt % BLCKSZ != 0) {
|
|
ereport(
|
|
ERROR,
|
|
(errcode_for_file_access(),
|
|
errmsg("base backup file length cannot be divisibed by 8k: file %s, len %ld, cnt %ld, aborting "
|
|
"backup",
|
|
readfilename, len, cnt)));
|
|
}
|
|
for (check_loc = 0; (unsigned int)(check_loc) < cnt; check_loc += BLCKSZ) {
|
|
blkno = len / BLCKSZ + check_loc / BLCKSZ + (segNo * ((BlockNumber)RELSEG_SIZE));
|
|
PageHeader phdr = PageHeader(t_thrd.basebackup_cxt.buf_block + check_loc);
|
|
if (PageIsNew(phdr)) {
|
|
continue;
|
|
}
|
|
checksum = pg_checksum_page(t_thrd.basebackup_cxt.buf_block + check_loc, blkno);
|
|
|
|
if (phdr->pd_checksum != checksum) {
|
|
if (fseeko(fp, (off_t)len, SEEK_SET) != 0) {
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(), errmsg("could not seek in file \"%s\": %m", readfilename)));
|
|
}
|
|
cnt = fread(t_thrd.basebackup_cxt.buf_block, 1, Min(TAR_SEND_SIZE, statbuf->st_size - len), fp);
|
|
if (cnt > 0 && retryCnt < MAX_RETRY_LIMIT) {
|
|
retryCnt++;
|
|
pg_usleep(1000000);
|
|
goto recheck;
|
|
} else if (cnt > 0 && retryCnt == MAX_RETRY_LIMIT) {
|
|
ereport(ERROR, (errcode_for_file_access(),
|
|
errmsg("base backup cheksum failed in file \"%s\"(computed: %d, recorded: %d), "
|
|
"aborting backup",
|
|
readfilename, checksum, phdr->pd_checksum)));
|
|
} else {
|
|
retryCnt = 0;
|
|
break;
|
|
}
|
|
}
|
|
retryCnt = 0;
|
|
}
|
|
}
|
|
|
|
/* Send the chunk as a CopyData message */
|
|
if (pq_putmessage_noblock('d', t_thrd.basebackup_cxt.buf_block, cnt))
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("base backup could not send data, aborting backup")));
|
|
|
|
len += cnt;
|
|
|
|
if (len >= statbuf->st_size) {
|
|
/*
|
|
* Reached end of file. The file could be longer, if it was
|
|
* extended while we were sending it, but for a base backup we can
|
|
* ignore such extended data. It will be restored from WAL.
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If the file was truncated while we were sending it, pad it with zeros */
|
|
if (len < statbuf->st_size) {
|
|
rc = memset_s(t_thrd.basebackup_cxt.buf_block, TAR_SEND_SIZE, 0, TAR_SEND_SIZE);
|
|
securec_check(rc, "", "");
|
|
while (len < statbuf->st_size) {
|
|
cnt = Min(TAR_SEND_SIZE, statbuf->st_size - len);
|
|
(void)pq_putmessage_noblock('d', t_thrd.basebackup_cxt.buf_block, cnt);
|
|
len += cnt;
|
|
}
|
|
}
|
|
|
|
/* Pad to 512 byte boundary, per tar format requirements */
|
|
pad = ((len + 511) & ~511) - len;
|
|
if (pad > 0) {
|
|
rc = memset_s(t_thrd.basebackup_cxt.buf_block, pad, 0, pad);
|
|
securec_check(rc, "", "");
|
|
(void)pq_putmessage_noblock('d', t_thrd.basebackup_cxt.buf_block, pad);
|
|
}
|
|
|
|
(void)FreeFile(fp);
|
|
return true;
|
|
}
|
|
|
|
static void _tarWriteHeader(const char *filename, const char *linktarget, struct stat *statbuf)
|
|
{
|
|
char h[BUILD_PATH_LEN];
|
|
errno_t rc = EOK;
|
|
int nRet = 0;
|
|
/*
|
|
* Note: most of the fields in a tar header are not supposed to be
|
|
* null-terminated. We use sprintf, which will write a null after the
|
|
* required bytes; that null goes into the first byte of the next field.
|
|
* This is okay as long as we fill the fields in order.
|
|
*/
|
|
rc = memset_s(h, sizeof(h), 0, sizeof(h));
|
|
securec_check(rc, "", "");
|
|
|
|
/* Name 1024 */
|
|
nRet = sprintf_s(&h[0], BUILD_PATH_LEN, "%.1023s", filename);
|
|
securec_check_ss(nRet, "", "");
|
|
|
|
if (linktarget != NULL || S_ISDIR(statbuf->st_mode)) {
|
|
/*
|
|
* We only support symbolic links to directories, and this is
|
|
* indicated in the tar format by adding a slash at the end of the
|
|
* name, the same as for regular directories.
|
|
*/
|
|
h[strlen(filename)] = '/';
|
|
h[strlen(filename) + 1] = '\0';
|
|
}
|
|
|
|
/* Mode 8 */
|
|
nRet = sprintf_s(&h[1024], BUILD_PATH_LEN - 1024, "%07o ", statbuf->st_mode);
|
|
securec_check_ss(nRet, "", "");
|
|
|
|
/* File size 12 - 11 digits, 1 space, no NUL */
|
|
if (linktarget != NULL || S_ISDIR(statbuf->st_mode))
|
|
/* Symbolic link or directory has size zero */
|
|
print_val(&h[1048], 0, 8, 11);
|
|
else
|
|
print_val(&h[1048], statbuf->st_size, 8, 11);
|
|
|
|
if (linktarget != NULL) {
|
|
/* Type - Symbolic link */
|
|
if (0 == strncmp(linktarget, t_thrd.proc_cxt.DataDir, strlen(t_thrd.proc_cxt.DataDir))) {
|
|
/* Symbolic link for relative location tablespace. */
|
|
nRet = sprintf_s(&h[1080], BUILD_PATH_LEN - 1080, "3");
|
|
securec_check_ss(nRet, "", "");
|
|
|
|
nRet = sprintf_s(&h[1081], BUILD_PATH_LEN - 1081, "%.1023s",
|
|
linktarget + strlen(t_thrd.proc_cxt.DataDir) + 1);
|
|
securec_check_ss(nRet, "", "");
|
|
} else {
|
|
nRet = sprintf_s(&h[1080], BUILD_PATH_LEN - 1080, "2");
|
|
securec_check_ss(nRet, "", "");
|
|
|
|
nRet = sprintf_s(&h[1081], BUILD_PATH_LEN - 1081, "%.1023s", linktarget);
|
|
securec_check_ss(nRet, "", "");
|
|
}
|
|
} else if (S_ISDIR(statbuf->st_mode)) {
|
|
/* Type - directory */
|
|
nRet = sprintf_s(&h[1080], BUILD_PATH_LEN - 1080, "5");
|
|
securec_check_ss(nRet, "", "");
|
|
} else {
|
|
/* Type - regular file */
|
|
nRet = sprintf_s(&h[1080], BUILD_PATH_LEN - 1080, "0");
|
|
securec_check_ss(nRet, "", "");
|
|
}
|
|
|
|
/* Link tag 100 (NULL) */
|
|
/* Now send the completed header. */
|
|
(void)pq_putmessage_noblock('d', h, BUILD_PATH_LEN);
|
|
}
|
|
|
|
void ut_save_xlogloc(const char *xloglocation)
|
|
{
|
|
save_xlogloc(xloglocation);
|
|
}
|