On Thu, Oct 6, 2022 at 4:50 AM Andres Freund <and...@anarazel.de> wrote:
>
> I'm doubtful it's a good idea to expose these to outside of xlog.c - they are
> very low level, and it's very easy to break stuff by using them wrongly.

Hm. Here's the v3 patch set without exposing WAL insert lock related
functions. Please have a look.

On Thu, Oct 6, 2022 at 4:22 AM Nathan Bossart <nathandboss...@gmail.com> wrote:
>
> Can we also replace the relevant code with calls to these functions in
> 0001?  That way, we can more easily review the changes you are making to
> this code separately from the large patch that just moves the code.

Done. Please have a look at 0001.

On Wed, Oct 5, 2022 at 11:28 PM Alvaro Herrera <alvhe...@alvh.no-ip.org> wrote:
>
> On 2022-Oct-05, Michael Paquier wrote:
>
> > And FWIW, the SQL interfaces for pg_backup_start() and
> > pg_backup_stop() could stay in xlogfuncs.c.  This has the advantage to
> > centralize in the same file all the SQL-function-specific checks.
>
> As I recall, that has the disadvantage that the API exposure is a bit
> higher -- I mean, with the patch as originally posted, there was less
> cross-inclusion of header files, but that is gone in the version Bharat
> posted as reply to this.  I'm not sure if that's caused by *this*
> comment, or even that it's terribly significant, but it seems worth
> considering at least.

FWIW, I'm attaching 0003 patch for moving backup functions from
xlogfuncs.c to xlogbackup.c. It's natural to have them there when
we're moving backup related things, this also reduces backup code
footprint. We can leave xlogfuncs.c for WAL related SQL-callable
functions.

Please review the attached v3 patch set.

--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
From 2ab3934f28fc986904afe8131aaa639713e4836c Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Thu, 6 Oct 2022 11:01:22 +0000
Subject: [PATCH v3] Add functions for xlogbackup.c to call back into xlog.c

---
 src/backend/access/transam/xlog.c | 191 ++++++++++++++++++++----------
 src/include/access/xlog.h         |  15 +++
 2 files changed, 142 insertions(+), 64 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 27085b15a8..3af9cc86c0 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -8315,10 +8315,7 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces,
 	 * forcePageWrites, to ensure adequate interlocking against
 	 * XLogInsertRecord().
 	 */
-	WALInsertLockAcquireExclusive();
-	XLogCtl->Insert.runningBackups++;
-	XLogCtl->Insert.forcePageWrites = true;
-	WALInsertLockRelease();
+	SetXLogBackupRelatedInfo();
 
 	/* Ensure we release forcePageWrites if fail below */
 	PG_ENSURE_ERROR_CLEANUP(pg_backup_start_callback, (Datum) 0);
@@ -8356,6 +8353,8 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces,
 		do
 		{
 			bool		checkpointfpw;
+			ControlFileData	*ControlFile;
+
 
 			/*
 			 * Force a CHECKPOINT.  Aside from being necessary to prevent torn
@@ -8384,6 +8383,8 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces,
 			 * to restore starting from the checkpoint is precisely the REDO
 			 * pointer.
 			 */
+			ControlFile = GetControlFile();
+
 			LWLockAcquire(ControlFileLock, LW_SHARED);
 			state->checkpointloc = ControlFile->checkPoint;
 			state->startpoint = ControlFile->checkPointCopy.redo;
@@ -8400,9 +8401,7 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces,
 				 * (i.e., since last restartpoint used as backup starting
 				 * checkpoint) contain full-page writes.
 				 */
-				SpinLockAcquire(&XLogCtl->info_lck);
-				recptr = XLogCtl->lastFpwDisableRecPtr;
-				SpinLockRelease(&XLogCtl->info_lck);
+				recptr = GetlastFpwDisableRecPtr();
 
 				if (!checkpointfpw || state->startpoint <= recptr)
 					ereport(ERROR,
@@ -8435,13 +8434,7 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces,
 			 * taking a checkpoint right after another is not that expensive
 			 * either because only few buffers have been dirtied yet.
 			 */
-			WALInsertLockAcquireExclusive();
-			if (XLogCtl->Insert.lastBackupStart < state->startpoint)
-			{
-				XLogCtl->Insert.lastBackupStart = state->startpoint;
-				gotUniqueStartpoint = true;
-			}
-			WALInsertLockRelease();
+			gotUniqueStartpoint = SetlastBackupStart(state->startpoint);
 		} while (!gotUniqueStartpoint);
 
 		/*
@@ -8546,16 +8539,7 @@ static void
 pg_backup_start_callback(int code, Datum arg)
 {
 	/* Update backup counters and forcePageWrites on failure */
-	WALInsertLockAcquireExclusive();
-
-	Assert(XLogCtl->Insert.runningBackups > 0);
-	XLogCtl->Insert.runningBackups--;
-
-	if (XLogCtl->Insert.runningBackups == 0)
-	{
-		XLogCtl->Insert.forcePageWrites = false;
-	}
-	WALInsertLockRelease();
+	ResetXLogBackupRelatedInfo(NULL);
 }
 
 /*
@@ -8592,6 +8576,7 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 	int			seconds_before_warning;
 	int			waits = 0;
 	bool		reported_waiting = false;
+	SessionBackupState	session_backup_state = SESSION_BACKUP_NONE;
 
 	Assert(state != NULL);
 
@@ -8613,33 +8598,11 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 	 * Note that CHECK_FOR_INTERRUPTS() must not occur while updating them.
 	 * Otherwise they can be updated inconsistently, and which might cause
 	 * do_pg_abort_backup() to fail.
-	 */
-	WALInsertLockAcquireExclusive();
-
-	/*
-	 * It is expected that each do_pg_backup_start() call is matched by
-	 * exactly one do_pg_backup_stop() call.
-	 */
-	Assert(XLogCtl->Insert.runningBackups > 0);
-	XLogCtl->Insert.runningBackups--;
-
-	if (XLogCtl->Insert.runningBackups == 0)
-	{
-		XLogCtl->Insert.forcePageWrites = false;
-	}
-
-	/*
-	 * Clean up session-level lock.
 	 *
-	 * You might think that WALInsertLockRelease() can be called before
-	 * cleaning up session-level lock because session-level lock doesn't need
-	 * to be protected with WAL insertion lock. But since
-	 * CHECK_FOR_INTERRUPTS() can occur in it, session-level lock must be
-	 * cleaned up before it.
+	 * It is expected that each do_pg_backup_start() call is matched by exactly
+	 * one do_pg_backup_stop() call.
 	 */
-	sessionBackupState = SESSION_BACKUP_NONE;
-
-	WALInsertLockRelease();
+	ResetXLogBackupRelatedInfo(&session_backup_state);
 
 	/*
 	 * If we are taking an online backup from the standby, we confirm that the
@@ -8684,14 +8647,13 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 	if (backup_stopped_in_recovery)
 	{
 		XLogRecPtr	recptr;
+		ControlFileData	*ControlFile;
 
 		/*
 		 * Check to see if all WAL replayed during online backup contain
 		 * full-page writes.
 		 */
-		SpinLockAcquire(&XLogCtl->info_lck);
-		recptr = XLogCtl->lastFpwDisableRecPtr;
-		SpinLockRelease(&XLogCtl->info_lck);
+		recptr = GetlastFpwDisableRecPtr();
 
 		if (state->startpoint <= recptr)
 			ereport(ERROR,
@@ -8704,6 +8666,8 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 							 "and then try an online backup again.")));
 
 
+		ControlFile = GetControlFile();
+
 		LWLockAcquire(ControlFileLock, LW_SHARED);
 		state->stoppoint = ControlFile->minRecoveryPoint;
 		state->stoptli = ControlFile->minRecoveryPointTLI;
@@ -8725,7 +8689,7 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 		 * Given that we're not in recovery, InsertTimeLineID is set and can't
 		 * change, so we can read it without a lock.
 		 */
-		state->stoptli = XLogCtl->InsertTimeLineID;
+		state->stoptli = GetWALInsertionTimeLine();
 
 		/*
 		 * Force a switch to a new xlog segment file, so that the backup is
@@ -8860,6 +8824,7 @@ void
 do_pg_abort_backup(int code, Datum arg)
 {
 	bool		emit_warning = DatumGetBool(arg);
+	SessionBackupState	session_backup_state = SESSION_BACKUP_NONE;
 
 	/*
 	 * Quick exit if session does not have a running backup.
@@ -8867,17 +8832,7 @@ do_pg_abort_backup(int code, Datum arg)
 	if (sessionBackupState != SESSION_BACKUP_RUNNING)
 		return;
 
-	WALInsertLockAcquireExclusive();
-	Assert(XLogCtl->Insert.runningBackups > 0);
-	XLogCtl->Insert.runningBackups--;
-
-	if (XLogCtl->Insert.runningBackups == 0)
-	{
-		XLogCtl->Insert.forcePageWrites = false;
-	}
-
-	sessionBackupState = SESSION_BACKUP_NONE;
-	WALInsertLockRelease();
+	ResetXLogBackupRelatedInfo(&session_backup_state);
 
 	if (emit_warning)
 		ereport(WARNING,
@@ -8899,6 +8854,114 @@ register_persistent_abort_backup_handler(void)
 	already_done = true;
 }
 
+/*
+ * Utility routine to set the session-level status of a backup running.
+ */
+void
+set_backup_status(SessionBackupState state)
+{
+	sessionBackupState = state;
+}
+
+/*
+ * Get the ControlFile.
+ */
+ControlFileData *
+GetControlFile(void)
+{
+	return ControlFile;
+}
+
+/*
+ * Get the lastFpwDisableRecPtr.
+ */
+XLogRecPtr
+GetlastFpwDisableRecPtr(void)
+{
+	XLogRecPtr recptr;
+
+	SpinLockAcquire(&XLogCtl->info_lck);
+	recptr = XLogCtl->lastFpwDisableRecPtr;
+	SpinLockRelease(&XLogCtl->info_lck);
+
+	return recptr;
+}
+
+/*
+ * Set the lastBackupStar only if it is less than passed-in rectpr and return
+ * true. Otherwise return false.
+ *
+ * Note: For those who want to set lastBackupStar unconditionally, pass rectpr
+ * value as PG_UINT64_MAX, which is higher than any real XLogRecPtr value.
+ */
+bool
+SetlastBackupStart(XLogRecPtr recptr)
+{
+	bool is_set = false;
+
+	WALInsertLockAcquireExclusive();
+	if (XLogCtl->Insert.lastBackupStart < recptr)
+	{
+		XLogCtl->Insert.lastBackupStart = recptr;
+		is_set = true;
+	}
+	WALInsertLockRelease();
+
+	return is_set;
+}
+
+/*
+ * Decrement runningBackups, reset forcePageWrites and session-level status of
+ * a backup running.
+ *
+ * NOTE: This function is tailor-made for use in xlogbackup.c. It doesn't reset
+ * the respective XLogCtl members directly, and acquires and releases locks.
+ * Hence be careful when using it elsewhere.
+ */
+void
+ResetXLogBackupRelatedInfo(SessionBackupState* session_backup_state)
+{
+	WALInsertLockAcquireExclusive();
+
+	Assert(XLogCtl->Insert.runningBackups > 0);
+	XLogCtl->Insert.runningBackups--;
+
+	if (XLogCtl->Insert.runningBackups == 0)
+	{
+		XLogCtl->Insert.forcePageWrites = false;
+	}
+
+	/*
+	 * Set session-level lock if specified.
+	 *
+	 * You might think that WALInsertLockRelease() can be called before
+	 * cleaning up session-level lock because session-level lock doesn't need
+	 * to be protected with WAL insertion lock. But since
+	 * CHECK_FOR_INTERRUPTS() can occur in it, session-level lock must be
+	 * cleaned up before it.
+	 */
+	if (session_backup_state != NULL)
+		set_backup_status(*session_backup_state);
+
+	WALInsertLockRelease();
+}
+
+/*
+ * Increment runningBackups and forcePageWrites.
+ *
+ * NOTE: This function is tailor-made for use in xlogbackup.c. It doesn't set
+ * the respective XLogCtl members directly, and acquires and releases locks.
+ * Hence be careful when using it elsewhere.
+ */
+void
+SetXLogBackupRelatedInfo(void)
+{
+	WALInsertLockAcquireExclusive();
+	XLogCtl->Insert.runningBackups++;
+	XLogCtl->Insert.forcePageWrites = true;
+	WALInsertLockRelease();
+}
+
 /*
  * Get latest WAL insert pointer
  */
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index dce265098e..8be7aa9c8e 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -14,6 +14,7 @@
 #include "access/xlogbackup.h"
 #include "access/xlogdefs.h"
 #include "access/xlogreader.h"
+#include "catalog/pg_control.h"
 #include "datatype/timestamp.h"
 #include "lib/stringinfo.h"
 #include "nodes/pg_list.h"
@@ -285,6 +286,20 @@ extern void do_pg_backup_stop(BackupState *state, bool waitforarchive);
 extern void do_pg_abort_backup(int code, Datum arg);
 extern void register_persistent_abort_backup_handler(void);
 extern SessionBackupState get_backup_status(void);
+extern void set_backup_status(SessionBackupState state);
+
+/*
+ * Routines used by xlogbackup.c to call back into xlog.c during backup.
+ *
+ * NOTE: These functions are tailor-made for use in xlogbackup.c. They may or
+ * may not set or get the respective XLogCtl members directly, may acquire and
+ * release various locks. Hence be careful when using them elsewhere.
+ */
+extern ControlFileData *GetControlFile(void);
+extern XLogRecPtr GetlastFpwDisableRecPtr(void);
+extern bool SetlastBackupStart(XLogRecPtr recptr);
+extern void ResetXLogBackupRelatedInfo(SessionBackupState* session_backup_state);
+extern void SetXLogBackupRelatedInfo(void);
 
 /* File path names (all relative to $PGDATA) */
 #define RECOVERY_SIGNAL_FILE	"recovery.signal"
-- 
2.34.1

From c3aa731cbfde31c60faf25d6eff7acb3796aa09f Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Thu, 6 Oct 2022 11:33:46 +0000
Subject: [PATCH v3] Move backup-related code from xlog.c to xlogbackup.c

---
 src/backend/access/transam/xlog.c         | 670 +--------------------
 src/backend/access/transam/xlogbackup.c   | 678 ++++++++++++++++++++++
 src/backend/access/transam/xlogrecovery.c |   1 +
 src/include/access/xlog.h                 |  34 --
 src/include/access/xlogbackup.h           |  36 ++
 5 files changed, 717 insertions(+), 702 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 3af9cc86c0..9d6d054a7a 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -58,19 +58,19 @@
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
+#include "access/xlogbackup.h"
 #include "access/xloginsert.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
-#include "backup/basebackup.h"
 #include "catalog/catversion.h"
-#include "catalog/pg_control.h"
 #include "catalog/pg_database.h"
 #include "common/controldata_utils.h"
 #include "common/file_utils.h"
 #include "executor/instrument.h"
 #include "miscadmin.h"
+#include "nodes/pg_list.h"
 #include "pg_trace.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -86,7 +86,6 @@
 #include "replication/walsender.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
-#include "storage/ipc.h"
 #include "storage/large_object.h"
 #include "storage/latch.h"
 #include "storage/pmsignal.h"
@@ -393,12 +392,6 @@ typedef union WALInsertLockPadded
 	char		pad[PG_CACHE_LINE_SIZE];
 } WALInsertLockPadded;
 
-/*
- * Session status of running backup, used for sanity checks in SQL-callable
- * functions to start and stop backups.
- */
-static SessionBackupState sessionBackupState = SESSION_BACKUP_NONE;
-
 /*
  * Shared state data for WAL insertion.
  */
@@ -670,7 +663,6 @@ static void RemoveXlogFile(const struct dirent *segment_de,
 						   TimeLineID insertTLI);
 static void UpdateLastRemovedPtr(char *filename);
 static void ValidateXLOGDirectoryStructure(void);
-static void CleanupBackupHistory(void);
 static void UpdateMinRecoveryPoint(XLogRecPtr lsn, bool force);
 static bool PerformRecoveryXLogAction(void);
 static void InitControlFile(uint64 sysidentifier);
@@ -679,8 +671,6 @@ static void ReadControlFile(void);
 static void UpdateControlFile(void);
 static char *str_time(pg_time_t tnow);
 
-static void pg_backup_start_callback(int code, Datum arg);
-
 static int	get_sync_bit(int method);
 
 static void CopyXLogRecordToWAL(int write_len, bool isLogSwitch,
@@ -3821,38 +3811,6 @@ ValidateXLOGDirectoryStructure(void)
 	}
 }
 
-/*
- * Remove previous backup history files.  This also retries creation of
- * .ready files for any backup history files for which XLogArchiveNotify
- * failed earlier.
- */
-static void
-CleanupBackupHistory(void)
-{
-	DIR		   *xldir;
-	struct dirent *xlde;
-	char		path[MAXPGPATH + sizeof(XLOGDIR)];
-
-	xldir = AllocateDir(XLOGDIR);
-
-	while ((xlde = ReadDir(xldir, XLOGDIR)) != NULL)
-	{
-		if (IsBackupHistoryFileName(xlde->d_name))
-		{
-			if (XLogArchiveCheckDone(xlde->d_name))
-			{
-				elog(DEBUG2, "removing WAL backup history file \"%s\"",
-					 xlde->d_name);
-				snprintf(path, sizeof(path), XLOGDIR "/%s", xlde->d_name);
-				unlink(path);
-				XLogArchiveCleanup(xlde->d_name);
-			}
-		}
-	}
-
-	FreeDir(xldir);
-}
-
 /*
  * I/O routines for pg_control
  *
@@ -8239,630 +8197,6 @@ issue_xlog_fsync(int fd, XLogSegNo segno, TimeLineID tli)
 	PendingWalStats.wal_sync++;
 }
 
-/*
- * do_pg_backup_start is the workhorse of the user-visible pg_backup_start()
- * function. It creates the necessary starting checkpoint and constructs the
- * backup state and tablespace map.
- *
- * Input parameters are "state" (the backup state), "fast" (if true, we do
- * the checkpoint in immediate mode to make it faster), and "tablespaces"
- * (if non-NULL, indicates a list of tablespaceinfo structs describing the
- * cluster's tablespaces.).
- *
- * The tablespace map contents are appended to passed-in parameter
- * tablespace_map and the caller is responsible for including it in the backup
- * archive as 'tablespace_map'. The tablespace_map file is required mainly for
- * tar format in windows as native windows utilities are not able to create
- * symlinks while extracting files from tar. However for consistency and
- * platform-independence, we do it the same way everywhere.
- *
- * It fills in "state" with the information required for the backup, such
- * as the minimum WAL location that must be present to restore from this
- * backup (starttli) and the corresponding timeline ID (starttli).
- *
- * Every successfully started backup must be stopped by calling
- * do_pg_backup_stop() or do_pg_abort_backup(). There can be many
- * backups active at the same time.
- *
- * It is the responsibility of the caller of this function to verify the
- * permissions of the calling user!
- */
-void
-do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces,
-				   BackupState *state, StringInfo tblspcmapfile)
-{
-	bool		backup_started_in_recovery = false;
-
-	Assert(state != NULL);
-	backup_started_in_recovery = RecoveryInProgress();
-
-	/*
-	 * During recovery, we don't need to check WAL level. Because, if WAL
-	 * level is not sufficient, it's impossible to get here during recovery.
-	 */
-	if (!backup_started_in_recovery && !XLogIsNeeded())
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("WAL level not sufficient for making an online backup"),
-				 errhint("wal_level must be set to \"replica\" or \"logical\" at server start.")));
-
-	if (strlen(backupidstr) > MAXPGPATH)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("backup label too long (max %d bytes)",
-						MAXPGPATH)));
-
-	memcpy(state->name, backupidstr, strlen(backupidstr));
-
-	/*
-	 * Mark backup active in shared memory.  We must do full-page WAL writes
-	 * during an on-line backup even if not doing so at other times, because
-	 * it's quite possible for the backup dump to obtain a "torn" (partially
-	 * written) copy of a database page if it reads the page concurrently with
-	 * our write to the same page.  This can be fixed as long as the first
-	 * write to the page in the WAL sequence is a full-page write. Hence, we
-	 * turn on forcePageWrites and then force a CHECKPOINT, to ensure there
-	 * are no dirty pages in shared memory that might get dumped while the
-	 * backup is in progress without having a corresponding WAL record.  (Once
-	 * the backup is complete, we need not force full-page writes anymore,
-	 * since we expect that any pages not modified during the backup interval
-	 * must have been correctly captured by the backup.)
-	 *
-	 * Note that forcePageWrites has no effect during an online backup from
-	 * the standby.
-	 *
-	 * We must hold all the insertion locks to change the value of
-	 * forcePageWrites, to ensure adequate interlocking against
-	 * XLogInsertRecord().
-	 */
-	SetXLogBackupRelatedInfo();
-
-	/* Ensure we release forcePageWrites if fail below */
-	PG_ENSURE_ERROR_CLEANUP(pg_backup_start_callback, (Datum) 0);
-	{
-		bool		gotUniqueStartpoint = false;
-		DIR		   *tblspcdir;
-		struct dirent *de;
-		tablespaceinfo *ti;
-		int			datadirpathlen;
-
-		/*
-		 * Force an XLOG file switch before the checkpoint, to ensure that the
-		 * WAL segment the checkpoint is written to doesn't contain pages with
-		 * old timeline IDs.  That would otherwise happen if you called
-		 * pg_backup_start() right after restoring from a PITR archive: the
-		 * first WAL segment containing the startup checkpoint has pages in
-		 * the beginning with the old timeline ID.  That can cause trouble at
-		 * recovery: we won't have a history file covering the old timeline if
-		 * pg_wal directory was not included in the base backup and the WAL
-		 * archive was cleared too before starting the backup.
-		 *
-		 * This also ensures that we have emitted a WAL page header that has
-		 * XLP_BKP_REMOVABLE off before we emit the checkpoint record.
-		 * Therefore, if a WAL archiver (such as pglesslog) is trying to
-		 * compress out removable backup blocks, it won't remove any that
-		 * occur after this point.
-		 *
-		 * During recovery, we skip forcing XLOG file switch, which means that
-		 * the backup taken during recovery is not available for the special
-		 * recovery case described above.
-		 */
-		if (!backup_started_in_recovery)
-			RequestXLogSwitch(false);
-
-		do
-		{
-			bool		checkpointfpw;
-			ControlFileData	*ControlFile;
-
-
-			/*
-			 * Force a CHECKPOINT.  Aside from being necessary to prevent torn
-			 * page problems, this guarantees that two successive backup runs
-			 * will have different checkpoint positions and hence different
-			 * history file names, even if nothing happened in between.
-			 *
-			 * During recovery, establish a restartpoint if possible. We use
-			 * the last restartpoint as the backup starting checkpoint. This
-			 * means that two successive backup runs can have same checkpoint
-			 * positions.
-			 *
-			 * Since the fact that we are executing do_pg_backup_start()
-			 * during recovery means that checkpointer is running, we can use
-			 * RequestCheckpoint() to establish a restartpoint.
-			 *
-			 * We use CHECKPOINT_IMMEDIATE only if requested by user (via
-			 * passing fast = true).  Otherwise this can take awhile.
-			 */
-			RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT |
-							  (fast ? CHECKPOINT_IMMEDIATE : 0));
-
-			/*
-			 * Now we need to fetch the checkpoint record location, and also
-			 * its REDO pointer.  The oldest point in WAL that would be needed
-			 * to restore starting from the checkpoint is precisely the REDO
-			 * pointer.
-			 */
-			ControlFile = GetControlFile();
-
-			LWLockAcquire(ControlFileLock, LW_SHARED);
-			state->checkpointloc = ControlFile->checkPoint;
-			state->startpoint = ControlFile->checkPointCopy.redo;
-			state->starttli = ControlFile->checkPointCopy.ThisTimeLineID;
-			checkpointfpw = ControlFile->checkPointCopy.fullPageWrites;
-			LWLockRelease(ControlFileLock);
-
-			if (backup_started_in_recovery)
-			{
-				XLogRecPtr	recptr;
-
-				/*
-				 * Check to see if all WAL replayed during online backup
-				 * (i.e., since last restartpoint used as backup starting
-				 * checkpoint) contain full-page writes.
-				 */
-				recptr = GetlastFpwDisableRecPtr();
-
-				if (!checkpointfpw || state->startpoint <= recptr)
-					ereport(ERROR,
-							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-							 errmsg("WAL generated with full_page_writes=off was replayed "
-									"since last restartpoint"),
-							 errhint("This means that the backup being taken on the standby "
-									 "is corrupt and should not be used. "
-									 "Enable full_page_writes and run CHECKPOINT on the primary, "
-									 "and then try an online backup again.")));
-
-				/*
-				 * During recovery, since we don't use the end-of-backup WAL
-				 * record and don't write the backup history file, the
-				 * starting WAL location doesn't need to be unique. This means
-				 * that two base backups started at the same time might use
-				 * the same checkpoint as starting locations.
-				 */
-				gotUniqueStartpoint = true;
-			}
-
-			/*
-			 * If two base backups are started at the same time (in WAL sender
-			 * processes), we need to make sure that they use different
-			 * checkpoints as starting locations, because we use the starting
-			 * WAL location as a unique identifier for the base backup in the
-			 * end-of-backup WAL record and when we write the backup history
-			 * file. Perhaps it would be better generate a separate unique ID
-			 * for each backup instead of forcing another checkpoint, but
-			 * taking a checkpoint right after another is not that expensive
-			 * either because only few buffers have been dirtied yet.
-			 */
-			gotUniqueStartpoint = SetlastBackupStart(state->startpoint);
-		} while (!gotUniqueStartpoint);
-
-		/*
-		 * Construct tablespace_map file.
-		 */
-		datadirpathlen = strlen(DataDir);
-
-		/* Collect information about all tablespaces */
-		tblspcdir = AllocateDir("pg_tblspc");
-		while ((de = ReadDir(tblspcdir, "pg_tblspc")) != NULL)
-		{
-			char		fullpath[MAXPGPATH + 10];
-			char		linkpath[MAXPGPATH];
-			char	   *relpath = NULL;
-			int			rllen;
-			StringInfoData escapedpath;
-			char	   *s;
-
-			/* Skip anything that doesn't look like a tablespace */
-			if (strspn(de->d_name, "0123456789") != strlen(de->d_name))
-				continue;
-
-			snprintf(fullpath, sizeof(fullpath), "pg_tblspc/%s", de->d_name);
-
-			/*
-			 * Skip anything that isn't a symlink/junction.  For testing only,
-			 * we sometimes use allow_in_place_tablespaces to create
-			 * directories directly under pg_tblspc, which would fail below.
-			 */
-			if (get_dirent_type(fullpath, de, false, ERROR) != PGFILETYPE_LNK)
-				continue;
-
-			rllen = readlink(fullpath, linkpath, sizeof(linkpath));
-			if (rllen < 0)
-			{
-				ereport(WARNING,
-						(errmsg("could not read symbolic link \"%s\": %m",
-								fullpath)));
-				continue;
-			}
-			else if (rllen >= sizeof(linkpath))
-			{
-				ereport(WARNING,
-						(errmsg("symbolic link \"%s\" target is too long",
-								fullpath)));
-				continue;
-			}
-			linkpath[rllen] = '\0';
-
-			/*
-			 * Build a backslash-escaped version of the link path to include
-			 * in the tablespace map file.
-			 */
-			initStringInfo(&escapedpath);
-			for (s = linkpath; *s; s++)
-			{
-				if (*s == '\n' || *s == '\r' || *s == '\\')
-					appendStringInfoChar(&escapedpath, '\\');
-				appendStringInfoChar(&escapedpath, *s);
-			}
-
-			/*
-			 * Relpath holds the relative path of the tablespace directory
-			 * when it's located within PGDATA, or NULL if it's located
-			 * elsewhere.
-			 */
-			if (rllen > datadirpathlen &&
-				strncmp(linkpath, DataDir, datadirpathlen) == 0 &&
-				IS_DIR_SEP(linkpath[datadirpathlen]))
-				relpath = linkpath + datadirpathlen + 1;
-
-			ti = palloc(sizeof(tablespaceinfo));
-			ti->oid = pstrdup(de->d_name);
-			ti->path = pstrdup(linkpath);
-			ti->rpath = relpath ? pstrdup(relpath) : NULL;
-			ti->size = -1;
-
-			if (tablespaces)
-				*tablespaces = lappend(*tablespaces, ti);
-
-			appendStringInfo(tblspcmapfile, "%s %s\n",
-							 ti->oid, escapedpath.data);
-
-			pfree(escapedpath.data);
-		}
-		FreeDir(tblspcdir);
-
-		state->starttime = (pg_time_t) time(NULL);
-	}
-	PG_END_ENSURE_ERROR_CLEANUP(pg_backup_start_callback, (Datum) 0);
-
-	state->started_in_recovery = backup_started_in_recovery;
-
-	/*
-	 * Mark that the start phase has correctly finished for the backup.
-	 */
-	sessionBackupState = SESSION_BACKUP_RUNNING;
-}
-
-/* Error cleanup callback for pg_backup_start */
-static void
-pg_backup_start_callback(int code, Datum arg)
-{
-	/* Update backup counters and forcePageWrites on failure */
-	ResetXLogBackupRelatedInfo(NULL);
-}
-
-/*
- * Utility routine to fetch the session-level status of a backup running.
- */
-SessionBackupState
-get_backup_status(void)
-{
-	return sessionBackupState;
-}
-
-/*
- * do_pg_backup_stop
- *
- * Utility function called at the end of an online backup.  It creates history
- * file (if required), resets sessionBackupState and so on.  It can optionally
- * wait for WAL segments to be archived.
- *
- * "state" is filled with the information necessary to restore from this
- * backup with its stop LSN (stoppoint), its timeline ID (stoptli), etc.
- *
- * It is the responsibility of the caller of this function to verify the
- * permissions of the calling user!
- */
-void
-do_pg_backup_stop(BackupState *state, bool waitforarchive)
-{
-	bool		backup_stopped_in_recovery = false;
-	char		histfilepath[MAXPGPATH];
-	char		lastxlogfilename[MAXFNAMELEN];
-	char		histfilename[MAXFNAMELEN];
-	XLogSegNo	_logSegNo;
-	FILE	   *fp;
-	int			seconds_before_warning;
-	int			waits = 0;
-	bool		reported_waiting = false;
-	SessionBackupState	session_backup_state = SESSION_BACKUP_NONE;
-
-	Assert(state != NULL);
-
-	backup_stopped_in_recovery = RecoveryInProgress();
-
-	/*
-	 * During recovery, we don't need to check WAL level. Because, if WAL
-	 * level is not sufficient, it's impossible to get here during recovery.
-	 */
-	if (!backup_stopped_in_recovery && !XLogIsNeeded())
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("WAL level not sufficient for making an online backup"),
-				 errhint("wal_level must be set to \"replica\" or \"logical\" at server start.")));
-
-	/*
-	 * OK to update backup counters, forcePageWrites, and session-level lock.
-	 *
-	 * Note that CHECK_FOR_INTERRUPTS() must not occur while updating them.
-	 * Otherwise they can be updated inconsistently, and which might cause
-	 * do_pg_abort_backup() to fail.
-	 *
-	 * It is expected that each do_pg_backup_start() call is matched by exactly
-	 * one do_pg_backup_stop() call.
-	 */
-	ResetXLogBackupRelatedInfo(&session_backup_state);
-
-	/*
-	 * If we are taking an online backup from the standby, we confirm that the
-	 * standby has not been promoted during the backup.
-	 */
-	if (state->started_in_recovery && !backup_stopped_in_recovery)
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("the standby was promoted during online backup"),
-				 errhint("This means that the backup being taken is corrupt "
-						 "and should not be used. "
-						 "Try taking another online backup.")));
-
-	/*
-	 * During recovery, we don't write an end-of-backup record. We assume that
-	 * pg_control was backed up last and its minimum recovery point can be
-	 * available as the backup end location. Since we don't have an
-	 * end-of-backup record, we use the pg_control value to check whether
-	 * we've reached the end of backup when starting recovery from this
-	 * backup. We have no way of checking if pg_control wasn't backed up last
-	 * however.
-	 *
-	 * We don't force a switch to new WAL file but it is still possible to
-	 * wait for all the required files to be archived if waitforarchive is
-	 * true. This is okay if we use the backup to start a standby and fetch
-	 * the missing WAL using streaming replication. But in the case of an
-	 * archive recovery, a user should set waitforarchive to true and wait for
-	 * them to be archived to ensure that all the required files are
-	 * available.
-	 *
-	 * We return the current minimum recovery point as the backup end
-	 * location. Note that it can be greater than the exact backup end
-	 * location if the minimum recovery point is updated after the backup of
-	 * pg_control. This is harmless for current uses.
-	 *
-	 * XXX currently a backup history file is for informational and debug
-	 * purposes only. It's not essential for an online backup. Furthermore,
-	 * even if it's created, it will not be archived during recovery because
-	 * an archiver is not invoked. So it doesn't seem worthwhile to write a
-	 * backup history file during recovery.
-	 */
-	if (backup_stopped_in_recovery)
-	{
-		XLogRecPtr	recptr;
-		ControlFileData	*ControlFile;
-
-		/*
-		 * Check to see if all WAL replayed during online backup contain
-		 * full-page writes.
-		 */
-		recptr = GetlastFpwDisableRecPtr();
-
-		if (state->startpoint <= recptr)
-			ereport(ERROR,
-					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-					 errmsg("WAL generated with full_page_writes=off was replayed "
-							"during online backup"),
-					 errhint("This means that the backup being taken on the standby "
-							 "is corrupt and should not be used. "
-							 "Enable full_page_writes and run CHECKPOINT on the primary, "
-							 "and then try an online backup again.")));
-
-
-		ControlFile = GetControlFile();
-
-		LWLockAcquire(ControlFileLock, LW_SHARED);
-		state->stoppoint = ControlFile->minRecoveryPoint;
-		state->stoptli = ControlFile->minRecoveryPointTLI;
-		LWLockRelease(ControlFileLock);
-	}
-	else
-	{
-		char	   *history_file;
-
-		/*
-		 * Write the backup-end xlog record
-		 */
-		XLogBeginInsert();
-		XLogRegisterData((char *) (&state->startpoint),
-						 sizeof(state->startpoint));
-		state->stoppoint = XLogInsert(RM_XLOG_ID, XLOG_BACKUP_END);
-
-		/*
-		 * Given that we're not in recovery, InsertTimeLineID is set and can't
-		 * change, so we can read it without a lock.
-		 */
-		state->stoptli = GetWALInsertionTimeLine();
-
-		/*
-		 * Force a switch to a new xlog segment file, so that the backup is
-		 * valid as soon as archiver moves out the current segment file.
-		 */
-		RequestXLogSwitch(false);
-
-		state->stoptime = (pg_time_t) time(NULL);
-
-		/*
-		 * Write the backup history file
-		 */
-		XLByteToSeg(state->startpoint, _logSegNo, wal_segment_size);
-		BackupHistoryFilePath(histfilepath, state->stoptli, _logSegNo,
-							  state->startpoint, wal_segment_size);
-		fp = AllocateFile(histfilepath, "w");
-		if (!fp)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not create file \"%s\": %m",
-							histfilepath)));
-
-		/* Build and save the contents of the backup history file */
-		history_file = build_backup_content(state, true);
-		fprintf(fp, "%s", history_file);
-		pfree(history_file);
-
-		if (fflush(fp) || ferror(fp) || FreeFile(fp))
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not write file \"%s\": %m",
-							histfilepath)));
-
-		/*
-		 * Clean out any no-longer-needed history files.  As a side effect,
-		 * this will post a .ready file for the newly created history file,
-		 * notifying the archiver that history file may be archived
-		 * immediately.
-		 */
-		CleanupBackupHistory();
-	}
-
-	/*
-	 * If archiving is enabled, wait for all the required WAL files to be
-	 * archived before returning. If archiving isn't enabled, the required WAL
-	 * needs to be transported via streaming replication (hopefully with
-	 * wal_keep_size set high enough), or some more exotic mechanism like
-	 * polling and copying files from pg_wal with script. We have no knowledge
-	 * of those mechanisms, so it's up to the user to ensure that he gets all
-	 * the required WAL.
-	 *
-	 * We wait until both the last WAL file filled during backup and the
-	 * history file have been archived, and assume that the alphabetic sorting
-	 * property of the WAL files ensures any earlier WAL files are safely
-	 * archived as well.
-	 *
-	 * We wait forever, since archive_command is supposed to work and we
-	 * assume the admin wanted his backup to work completely. If you don't
-	 * wish to wait, then either waitforarchive should be passed in as false,
-	 * or you can set statement_timeout.  Also, some notices are issued to
-	 * clue in anyone who might be doing this interactively.
-	 */
-
-	if (waitforarchive &&
-		((!backup_stopped_in_recovery && XLogArchivingActive()) ||
-		 (backup_stopped_in_recovery && XLogArchivingAlways())))
-	{
-		XLByteToPrevSeg(state->stoppoint, _logSegNo, wal_segment_size);
-		XLogFileName(lastxlogfilename, state->stoptli, _logSegNo,
-					 wal_segment_size);
-
-		XLByteToSeg(state->startpoint, _logSegNo, wal_segment_size);
-		BackupHistoryFileName(histfilename, state->stoptli, _logSegNo,
-							  state->startpoint, wal_segment_size);
-
-		seconds_before_warning = 60;
-		waits = 0;
-
-		while (XLogArchiveIsBusy(lastxlogfilename) ||
-			   XLogArchiveIsBusy(histfilename))
-		{
-			CHECK_FOR_INTERRUPTS();
-
-			if (!reported_waiting && waits > 5)
-			{
-				ereport(NOTICE,
-						(errmsg("base backup done, waiting for required WAL segments to be archived")));
-				reported_waiting = true;
-			}
-
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 1000L,
-							 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
-			ResetLatch(MyLatch);
-
-			if (++waits >= seconds_before_warning)
-			{
-				seconds_before_warning *= 2;	/* This wraps in >10 years... */
-				ereport(WARNING,
-						(errmsg("still waiting for all required WAL segments to be archived (%d seconds elapsed)",
-								waits),
-						 errhint("Check that your archive_command is executing properly.  "
-								 "You can safely cancel this backup, "
-								 "but the database backup will not be usable without all the WAL segments.")));
-			}
-		}
-
-		ereport(NOTICE,
-				(errmsg("all required WAL segments have been archived")));
-	}
-	else if (waitforarchive)
-		ereport(NOTICE,
-				(errmsg("WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup")));
-}
-
-
-/*
- * do_pg_abort_backup: abort a running backup
- *
- * This does just the most basic steps of do_pg_backup_stop(), by taking the
- * system out of backup mode, thus making it a lot more safe to call from
- * an error handler.
- *
- * The caller can pass 'arg' as 'true' or 'false' to control whether a warning
- * is emitted.
- *
- * NB: This gets used as a before_shmem_exit handler, hence the odd-looking
- * signature.
- */
-void
-do_pg_abort_backup(int code, Datum arg)
-{
-	bool		emit_warning = DatumGetBool(arg);
-	SessionBackupState	session_backup_state = SESSION_BACKUP_NONE;
-
-	/*
-	 * Quick exit if session does not have a running backup.
-	 */
-	if (sessionBackupState != SESSION_BACKUP_RUNNING)
-		return;
-
-	ResetXLogBackupRelatedInfo(&session_backup_state);
-
-	if (emit_warning)
-		ereport(WARNING,
-				(errmsg("aborting backup due to backend exiting before pg_backup_stop was called")));
-}
-
-/*
- * Register a handler that will warn about unterminated backups at end of
- * session, unless this has already been done.
- */
-void
-register_persistent_abort_backup_handler(void)
-{
-	static bool already_done = false;
-
-	if (already_done)
-		return;
-	before_shmem_exit(do_pg_abort_backup, DatumGetBool(true));
-	already_done = true;
-}
-
-/*
- * Utility routine to set the session-level status of a backup running.
- */
-void
-set_backup_status(SessionBackupState state)
-{
-	sessionBackupState = state;
-}
-
 /*
  * Get the ControlFile.
  */
diff --git a/src/backend/access/transam/xlogbackup.c b/src/backend/access/transam/xlogbackup.c
index 90b5273b02..61c26e09ed 100644
--- a/src/backend/access/transam/xlogbackup.c
+++ b/src/backend/access/transam/xlogbackup.c
@@ -13,9 +13,32 @@
 
 #include "postgres.h"
 
+#include <time.h>
+#include <unistd.h>
+
 #include "access/xlog.h"
 #include "access/xlog_internal.h"
+#include "access/xlogarchive.h"
 #include "access/xlogbackup.h"
+#include "access/xloginsert.h"
+#include "backup/basebackup.h"
+#include "common/file_utils.h"
+#include "miscadmin.h"
+#include "postmaster/bgwriter.h"
+#include "storage/fd.h"
+#include "storage/ipc.h"
+#include "storage/latch.h"
+#include "storage/lwlock.h"
+#include "utils/wait_event.h"
+
+/*
+ * Session status of running backup, used for sanity checks in SQL-callable
+ * functions to start and stop backups.
+ */
+static SessionBackupState sessionBackupState = SESSION_BACKUP_NONE;
+
+static void pg_backup_start_callback(int code, Datum arg);
+static void CleanupBackupHistory(void);
 
 /*
  * Build contents for backup_label or backup history file.
@@ -82,3 +105,658 @@ build_backup_content(BackupState *state, bool ishistoryfile)
 
 	return data;
 }
+
+/*
+ * Remove previous backup history files.  This also retries creation of
+ * .ready files for any backup history files for which XLogArchiveNotify
+ * failed earlier.
+ */
+static void
+CleanupBackupHistory(void)
+{
+	DIR		   *xldir;
+	struct dirent *xlde;
+	char		path[MAXPGPATH + sizeof(XLOGDIR)];
+
+	xldir = AllocateDir(XLOGDIR);
+
+	while ((xlde = ReadDir(xldir, XLOGDIR)) != NULL)
+	{
+		if (IsBackupHistoryFileName(xlde->d_name))
+		{
+			if (XLogArchiveCheckDone(xlde->d_name))
+			{
+				elog(DEBUG2, "removing WAL backup history file \"%s\"",
+					 xlde->d_name);
+				snprintf(path, sizeof(path), XLOGDIR "/%s", xlde->d_name);
+				unlink(path);
+				XLogArchiveCleanup(xlde->d_name);
+			}
+		}
+	}
+
+	FreeDir(xldir);
+}
+
+/*
+ * do_pg_backup_start is the workhorse of the user-visible pg_backup_start()
+ * function. It creates the necessary starting checkpoint and constructs the
+ * backup state and tablespace map.
+ *
+ * Input parameters are "state" (the backup state), "fast" (if true, we do
+ * the checkpoint in immediate mode to make it faster), and "tablespaces"
+ * (if non-NULL, indicates a list of tablespaceinfo structs describing the
+ * cluster's tablespaces.).
+ *
+ * The tablespace map contents are appended to passed-in parameter
+ * tablespace_map and the caller is responsible for including it in the backup
+ * archive as 'tablespace_map'. The tablespace_map file is required mainly for
+ * tar format in windows as native windows utilities are not able to create
+ * symlinks while extracting files from tar. However for consistency and
+ * platform-independence, we do it the same way everywhere.
+ *
+ * It fills in "state" with the information required for the backup, such
+ * as the minimum WAL location that must be present to restore from this
+ * backup (starttli) and the corresponding timeline ID (starttli).
+ *
+ * Every successfully started backup must be stopped by calling
+ * do_pg_backup_stop() or do_pg_abort_backup(). There can be many
+ * backups active at the same time.
+ *
+ * It is the responsibility of the caller of this function to verify the
+ * permissions of the calling user!
+ */
+void
+do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces,
+				   BackupState *state, StringInfo tblspcmapfile)
+{
+	bool		backup_started_in_recovery = false;
+
+	Assert(state != NULL);
+	backup_started_in_recovery = RecoveryInProgress();
+
+	/*
+	 * During recovery, we don't need to check WAL level. Because, if WAL
+	 * level is not sufficient, it's impossible to get here during recovery.
+	 */
+	if (!backup_started_in_recovery && !XLogIsNeeded())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAL level not sufficient for making an online backup"),
+				 errhint("wal_level must be set to \"replica\" or \"logical\" at server start.")));
+
+	if (strlen(backupidstr) > MAXPGPATH)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("backup label too long (max %d bytes)",
+						MAXPGPATH)));
+
+	memcpy(state->name, backupidstr, strlen(backupidstr));
+
+	/*
+	 * Mark backup active in shared memory.  We must do full-page WAL writes
+	 * during an on-line backup even if not doing so at other times, because
+	 * it's quite possible for the backup dump to obtain a "torn" (partially
+	 * written) copy of a database page if it reads the page concurrently with
+	 * our write to the same page.  This can be fixed as long as the first
+	 * write to the page in the WAL sequence is a full-page write. Hence, we
+	 * turn on forcePageWrites and then force a CHECKPOINT, to ensure there
+	 * are no dirty pages in shared memory that might get dumped while the
+	 * backup is in progress without having a corresponding WAL record.  (Once
+	 * the backup is complete, we need not force full-page writes anymore,
+	 * since we expect that any pages not modified during the backup interval
+	 * must have been correctly captured by the backup.)
+	 *
+	 * Note that forcePageWrites has no effect during an online backup from
+	 * the standby.
+	 *
+	 * We must hold all the insertion locks to change the value of
+	 * forcePageWrites, to ensure adequate interlocking against
+	 * XLogInsertRecord().
+	 */
+	SetXLogBackupRelatedInfo();
+
+	/* Ensure we release forcePageWrites if fail below */
+	PG_ENSURE_ERROR_CLEANUP(pg_backup_start_callback, (Datum) 0);
+	{
+		bool		gotUniqueStartpoint = false;
+		DIR		   *tblspcdir;
+		struct dirent *de;
+		tablespaceinfo *ti;
+		int			datadirpathlen;
+
+		/*
+		 * Force an XLOG file switch before the checkpoint, to ensure that the
+		 * WAL segment the checkpoint is written to doesn't contain pages with
+		 * old timeline IDs.  That would otherwise happen if you called
+		 * pg_backup_start() right after restoring from a PITR archive: the
+		 * first WAL segment containing the startup checkpoint has pages in
+		 * the beginning with the old timeline ID.  That can cause trouble at
+		 * recovery: we won't have a history file covering the old timeline if
+		 * pg_wal directory was not included in the base backup and the WAL
+		 * archive was cleared too before starting the backup.
+		 *
+		 * This also ensures that we have emitted a WAL page header that has
+		 * XLP_BKP_REMOVABLE off before we emit the checkpoint record.
+		 * Therefore, if a WAL archiver (such as pglesslog) is trying to
+		 * compress out removable backup blocks, it won't remove any that
+		 * occur after this point.
+		 *
+		 * During recovery, we skip forcing XLOG file switch, which means that
+		 * the backup taken during recovery is not available for the special
+		 * recovery case described above.
+		 */
+		if (!backup_started_in_recovery)
+			RequestXLogSwitch(false);
+
+		do
+		{
+			bool		checkpointfpw;
+			ControlFileData	*ControlFile;
+
+
+			/*
+			 * Force a CHECKPOINT.  Aside from being necessary to prevent torn
+			 * page problems, this guarantees that two successive backup runs
+			 * will have different checkpoint positions and hence different
+			 * history file names, even if nothing happened in between.
+			 *
+			 * During recovery, establish a restartpoint if possible. We use
+			 * the last restartpoint as the backup starting checkpoint. This
+			 * means that two successive backup runs can have same checkpoint
+			 * positions.
+			 *
+			 * Since the fact that we are executing do_pg_backup_start()
+			 * during recovery means that checkpointer is running, we can use
+			 * RequestCheckpoint() to establish a restartpoint.
+			 *
+			 * We use CHECKPOINT_IMMEDIATE only if requested by user (via
+			 * passing fast = true).  Otherwise this can take awhile.
+			 */
+			RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT |
+							  (fast ? CHECKPOINT_IMMEDIATE : 0));
+
+			/*
+			 * Now we need to fetch the checkpoint record location, and also
+			 * its REDO pointer.  The oldest point in WAL that would be needed
+			 * to restore starting from the checkpoint is precisely the REDO
+			 * pointer.
+			 */
+			ControlFile = GetControlFile();
+
+			LWLockAcquire(ControlFileLock, LW_SHARED);
+			state->checkpointloc = ControlFile->checkPoint;
+			state->startpoint = ControlFile->checkPointCopy.redo;
+			state->starttli = ControlFile->checkPointCopy.ThisTimeLineID;
+			checkpointfpw = ControlFile->checkPointCopy.fullPageWrites;
+			LWLockRelease(ControlFileLock);
+
+			if (backup_started_in_recovery)
+			{
+				XLogRecPtr	recptr;
+
+				/*
+				 * Check to see if all WAL replayed during online backup
+				 * (i.e., since last restartpoint used as backup starting
+				 * checkpoint) contain full-page writes.
+				 */
+				recptr = GetlastFpwDisableRecPtr();
+
+				if (!checkpointfpw || state->startpoint <= recptr)
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("WAL generated with full_page_writes=off was replayed "
+									"since last restartpoint"),
+							 errhint("This means that the backup being taken on the standby "
+									 "is corrupt and should not be used. "
+									 "Enable full_page_writes and run CHECKPOINT on the primary, "
+									 "and then try an online backup again.")));
+
+				/*
+				 * During recovery, since we don't use the end-of-backup WAL
+				 * record and don't write the backup history file, the
+				 * starting WAL location doesn't need to be unique. This means
+				 * that two base backups started at the same time might use
+				 * the same checkpoint as starting locations.
+				 */
+				gotUniqueStartpoint = true;
+			}
+
+			/*
+			 * If two base backups are started at the same time (in WAL sender
+			 * processes), we need to make sure that they use different
+			 * checkpoints as starting locations, because we use the starting
+			 * WAL location as a unique identifier for the base backup in the
+			 * end-of-backup WAL record and when we write the backup history
+			 * file. Perhaps it would be better generate a separate unique ID
+			 * for each backup instead of forcing another checkpoint, but
+			 * taking a checkpoint right after another is not that expensive
+			 * either because only few buffers have been dirtied yet.
+			 */
+			gotUniqueStartpoint = SetlastBackupStart(state->startpoint);
+		} while (!gotUniqueStartpoint);
+
+		/*
+		 * Construct tablespace_map file.
+		 */
+		datadirpathlen = strlen(DataDir);
+
+		/* Collect information about all tablespaces */
+		tblspcdir = AllocateDir("pg_tblspc");
+		while ((de = ReadDir(tblspcdir, "pg_tblspc")) != NULL)
+		{
+			char		fullpath[MAXPGPATH + 10];
+			char		linkpath[MAXPGPATH];
+			char	   *relpath = NULL;
+			int			rllen;
+			StringInfoData escapedpath;
+			char	   *s;
+
+			/* Skip anything that doesn't look like a tablespace */
+			if (strspn(de->d_name, "0123456789") != strlen(de->d_name))
+				continue;
+
+			snprintf(fullpath, sizeof(fullpath), "pg_tblspc/%s", de->d_name);
+
+			/*
+			 * Skip anything that isn't a symlink/junction.  For testing only,
+			 * we sometimes use allow_in_place_tablespaces to create
+			 * directories directly under pg_tblspc, which would fail below.
+			 */
+			if (get_dirent_type(fullpath, de, false, ERROR) != PGFILETYPE_LNK)
+				continue;
+
+			rllen = readlink(fullpath, linkpath, sizeof(linkpath));
+			if (rllen < 0)
+			{
+				ereport(WARNING,
+						(errmsg("could not read symbolic link \"%s\": %m",
+								fullpath)));
+				continue;
+			}
+			else if (rllen >= sizeof(linkpath))
+			{
+				ereport(WARNING,
+						(errmsg("symbolic link \"%s\" target is too long",
+								fullpath)));
+				continue;
+			}
+			linkpath[rllen] = '\0';
+
+			/*
+			 * Build a backslash-escaped version of the link path to include
+			 * in the tablespace map file.
+			 */
+			initStringInfo(&escapedpath);
+			for (s = linkpath; *s; s++)
+			{
+				if (*s == '\n' || *s == '\r' || *s == '\\')
+					appendStringInfoChar(&escapedpath, '\\');
+				appendStringInfoChar(&escapedpath, *s);
+			}
+
+			/*
+			 * Relpath holds the relative path of the tablespace directory
+			 * when it's located within PGDATA, or NULL if it's located
+			 * elsewhere.
+			 */
+			if (rllen > datadirpathlen &&
+				strncmp(linkpath, DataDir, datadirpathlen) == 0 &&
+				IS_DIR_SEP(linkpath[datadirpathlen]))
+				relpath = linkpath + datadirpathlen + 1;
+
+			ti = palloc(sizeof(tablespaceinfo));
+			ti->oid = pstrdup(de->d_name);
+			ti->path = pstrdup(linkpath);
+			ti->rpath = relpath ? pstrdup(relpath) : NULL;
+			ti->size = -1;
+
+			if (tablespaces)
+				*tablespaces = lappend(*tablespaces, ti);
+
+			appendStringInfo(tblspcmapfile, "%s %s\n",
+							 ti->oid, escapedpath.data);
+
+			pfree(escapedpath.data);
+		}
+		FreeDir(tblspcdir);
+
+		state->starttime = (pg_time_t) time(NULL);
+	}
+	PG_END_ENSURE_ERROR_CLEANUP(pg_backup_start_callback, (Datum) 0);
+
+	state->started_in_recovery = backup_started_in_recovery;
+
+	/*
+	 * Mark that the start phase has correctly finished for the backup.
+	 */
+	sessionBackupState = SESSION_BACKUP_RUNNING;
+}
+
+/* Error cleanup callback for pg_backup_start */
+static void
+pg_backup_start_callback(int code, Datum arg)
+{
+	/* Update backup counters and forcePageWrites on failure */
+	ResetXLogBackupRelatedInfo(NULL);
+}
+
+/*
+ * Utility routine to fetch the session-level status of a backup running.
+ */
+SessionBackupState
+get_backup_status(void)
+{
+	return sessionBackupState;
+}
+
+/*
+ * Utility routine to set the session-level status of a backup running.
+ */
+void
+set_backup_status(SessionBackupState state)
+{
+	sessionBackupState = state;
+}
+
+/*
+ * do_pg_backup_stop
+ *
+ * Utility function called at the end of an online backup.  It creates history
+ * file (if required), resets sessionBackupState and so on.  It can optionally
+ * wait for WAL segments to be archived.
+ *
+ * "state" is filled with the information necessary to restore from this
+ * backup with its stop LSN (stoppoint), its timeline ID (stoptli), etc.
+ *
+ * It is the responsibility of the caller of this function to verify the
+ * permissions of the calling user!
+ */
+void
+do_pg_backup_stop(BackupState *state, bool waitforarchive)
+{
+	bool		backup_stopped_in_recovery = false;
+	char		histfilepath[MAXPGPATH];
+	char		lastxlogfilename[MAXFNAMELEN];
+	char		histfilename[MAXFNAMELEN];
+	XLogSegNo	_logSegNo;
+	FILE	   *fp;
+	int			seconds_before_warning;
+	int			waits = 0;
+	bool		reported_waiting = false;
+	SessionBackupState	session_backup_state = SESSION_BACKUP_NONE;
+
+	Assert(state != NULL);
+
+	backup_stopped_in_recovery = RecoveryInProgress();
+
+	/*
+	 * During recovery, we don't need to check WAL level. Because, if WAL
+	 * level is not sufficient, it's impossible to get here during recovery.
+	 */
+	if (!backup_stopped_in_recovery && !XLogIsNeeded())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAL level not sufficient for making an online backup"),
+				 errhint("wal_level must be set to \"replica\" or \"logical\" at server start.")));
+
+	/*
+	 * OK to update backup counters, forcePageWrites, and session-level lock.
+	 *
+	 * Note that CHECK_FOR_INTERRUPTS() must not occur while updating them.
+	 * Otherwise they can be updated inconsistently, and which might cause
+	 * do_pg_abort_backup() to fail.
+	 *
+	 * It is expected that each do_pg_backup_start() call is matched by exactly
+	 * one do_pg_backup_stop() call.
+	 */
+	ResetXLogBackupRelatedInfo(&session_backup_state);
+
+	/*
+	 * If we are taking an online backup from the standby, we confirm that the
+	 * standby has not been promoted during the backup.
+	 */
+	if (state->started_in_recovery && !backup_stopped_in_recovery)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("the standby was promoted during online backup"),
+				 errhint("This means that the backup being taken is corrupt "
+						 "and should not be used. "
+						 "Try taking another online backup.")));
+
+	/*
+	 * During recovery, we don't write an end-of-backup record. We assume that
+	 * pg_control was backed up last and its minimum recovery point can be
+	 * available as the backup end location. Since we don't have an
+	 * end-of-backup record, we use the pg_control value to check whether
+	 * we've reached the end of backup when starting recovery from this
+	 * backup. We have no way of checking if pg_control wasn't backed up last
+	 * however.
+	 *
+	 * We don't force a switch to new WAL file but it is still possible to
+	 * wait for all the required files to be archived if waitforarchive is
+	 * true. This is okay if we use the backup to start a standby and fetch
+	 * the missing WAL using streaming replication. But in the case of an
+	 * archive recovery, a user should set waitforarchive to true and wait for
+	 * them to be archived to ensure that all the required files are
+	 * available.
+	 *
+	 * We return the current minimum recovery point as the backup end
+	 * location. Note that it can be greater than the exact backup end
+	 * location if the minimum recovery point is updated after the backup of
+	 * pg_control. This is harmless for current uses.
+	 *
+	 * XXX currently a backup history file is for informational and debug
+	 * purposes only. It's not essential for an online backup. Furthermore,
+	 * even if it's created, it will not be archived during recovery because
+	 * an archiver is not invoked. So it doesn't seem worthwhile to write a
+	 * backup history file during recovery.
+	 */
+	if (backup_stopped_in_recovery)
+	{
+		XLogRecPtr	recptr;
+		ControlFileData	*ControlFile;
+
+		/*
+		 * Check to see if all WAL replayed during online backup contain
+		 * full-page writes.
+		 */
+		recptr = GetlastFpwDisableRecPtr();
+
+		if (state->startpoint <= recptr)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("WAL generated with full_page_writes=off was replayed "
+							"during online backup"),
+					 errhint("This means that the backup being taken on the standby "
+							 "is corrupt and should not be used. "
+							 "Enable full_page_writes and run CHECKPOINT on the primary, "
+							 "and then try an online backup again.")));
+
+
+		ControlFile = GetControlFile();
+
+		LWLockAcquire(ControlFileLock, LW_SHARED);
+		state->stoppoint = ControlFile->minRecoveryPoint;
+		state->stoptli = ControlFile->minRecoveryPointTLI;
+		LWLockRelease(ControlFileLock);
+	}
+	else
+	{
+		char	   *history_file;
+
+		/*
+		 * Write the backup-end xlog record
+		 */
+		XLogBeginInsert();
+		XLogRegisterData((char *) (&state->startpoint),
+						 sizeof(state->startpoint));
+		state->stoppoint = XLogInsert(RM_XLOG_ID, XLOG_BACKUP_END);
+
+		/*
+		 * Given that we're not in recovery, InsertTimeLineID is set and can't
+		 * change, so we can read it without a lock.
+		 */
+		state->stoptli = GetWALInsertionTimeLine();
+
+		/*
+		 * Force a switch to a new xlog segment file, so that the backup is
+		 * valid as soon as archiver moves out the current segment file.
+		 */
+		RequestXLogSwitch(false);
+
+		state->stoptime = (pg_time_t) time(NULL);
+
+		/*
+		 * Write the backup history file
+		 */
+		XLByteToSeg(state->startpoint, _logSegNo, wal_segment_size);
+		BackupHistoryFilePath(histfilepath, state->stoptli, _logSegNo,
+							  state->startpoint, wal_segment_size);
+		fp = AllocateFile(histfilepath, "w");
+		if (!fp)
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not create file \"%s\": %m",
+							histfilepath)));
+
+		/* Build and save the contents of the backup history file */
+		history_file = build_backup_content(state, true);
+		fprintf(fp, "%s", history_file);
+		pfree(history_file);
+
+		if (fflush(fp) || ferror(fp) || FreeFile(fp))
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not write file \"%s\": %m",
+							histfilepath)));
+
+		/*
+		 * Clean out any no-longer-needed history files.  As a side effect,
+		 * this will post a .ready file for the newly created history file,
+		 * notifying the archiver that history file may be archived
+		 * immediately.
+		 */
+		CleanupBackupHistory();
+	}
+
+	/*
+	 * If archiving is enabled, wait for all the required WAL files to be
+	 * archived before returning. If archiving isn't enabled, the required WAL
+	 * needs to be transported via streaming replication (hopefully with
+	 * wal_keep_size set high enough), or some more exotic mechanism like
+	 * polling and copying files from pg_wal with script. We have no knowledge
+	 * of those mechanisms, so it's up to the user to ensure that he gets all
+	 * the required WAL.
+	 *
+	 * We wait until both the last WAL file filled during backup and the
+	 * history file have been archived, and assume that the alphabetic sorting
+	 * property of the WAL files ensures any earlier WAL files are safely
+	 * archived as well.
+	 *
+	 * We wait forever, since archive_command is supposed to work and we
+	 * assume the admin wanted his backup to work completely. If you don't
+	 * wish to wait, then either waitforarchive should be passed in as false,
+	 * or you can set statement_timeout.  Also, some notices are issued to
+	 * clue in anyone who might be doing this interactively.
+	 */
+
+	if (waitforarchive &&
+		((!backup_stopped_in_recovery && XLogArchivingActive()) ||
+		 (backup_stopped_in_recovery && XLogArchivingAlways())))
+	{
+		XLByteToPrevSeg(state->stoppoint, _logSegNo, wal_segment_size);
+		XLogFileName(lastxlogfilename, state->stoptli, _logSegNo,
+					 wal_segment_size);
+
+		XLByteToSeg(state->startpoint, _logSegNo, wal_segment_size);
+		BackupHistoryFileName(histfilename, state->stoptli, _logSegNo,
+							  state->startpoint, wal_segment_size);
+
+		seconds_before_warning = 60;
+		waits = 0;
+
+		while (XLogArchiveIsBusy(lastxlogfilename) ||
+			   XLogArchiveIsBusy(histfilename))
+		{
+			CHECK_FOR_INTERRUPTS();
+
+			if (!reported_waiting && waits > 5)
+			{
+				ereport(NOTICE,
+						(errmsg("base backup done, waiting for required WAL segments to be archived")));
+				reported_waiting = true;
+			}
+
+			(void) WaitLatch(MyLatch,
+							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 1000L,
+							 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
+			ResetLatch(MyLatch);
+
+			if (++waits >= seconds_before_warning)
+			{
+				seconds_before_warning *= 2;	/* This wraps in >10 years... */
+				ereport(WARNING,
+						(errmsg("still waiting for all required WAL segments to be archived (%d seconds elapsed)",
+								waits),
+						 errhint("Check that your archive_command is executing properly.  "
+								 "You can safely cancel this backup, "
+								 "but the database backup will not be usable without all the WAL segments.")));
+			}
+		}
+
+		ereport(NOTICE,
+				(errmsg("all required WAL segments have been archived")));
+	}
+	else if (waitforarchive)
+		ereport(NOTICE,
+				(errmsg("WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup")));
+}
+
+/*
+ * do_pg_abort_backup: abort a running backup
+ *
+ * This does just the most basic steps of do_pg_backup_stop(), by taking the
+ * system out of backup mode, thus making it a lot more safe to call from
+ * an error handler.
+ *
+ * The caller can pass 'arg' as 'true' or 'false' to control whether a warning
+ * is emitted.
+ *
+ * NB: This gets used as a before_shmem_exit handler, hence the odd-looking
+ * signature.
+ */
+void
+do_pg_abort_backup(int code, Datum arg)
+{
+	bool		emit_warning = DatumGetBool(arg);
+	SessionBackupState	session_backup_state = SESSION_BACKUP_NONE;
+
+	/*
+	 * Quick exit if session does not have a running backup.
+	 */
+	if (sessionBackupState != SESSION_BACKUP_RUNNING)
+		return;
+
+	ResetXLogBackupRelatedInfo(&session_backup_state);
+
+	if (emit_warning)
+		ereport(WARNING,
+				(errmsg("aborting backup due to backend exiting before pg_backup_stop was called")));
+}
+
+/*
+ * Register a handler that will warn about unterminated backups at end of
+ * session, unless this has already been done.
+ */
+void
+register_persistent_abort_backup_handler(void)
+{
+	static bool already_done = false;
+
+	if (already_done)
+		return;
+	before_shmem_exit(do_pg_abort_backup, DatumGetBool(true));
+	already_done = true;
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index cb07694aea..cd36d95b56 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -36,6 +36,7 @@
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
+#include "access/xlogbackup.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogreader.h"
 #include "access/xlogrecovery.h"
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 8be7aa9c8e..b647813f26 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -259,35 +259,6 @@ extern void SetInstallXLogFileSegmentActive(void);
 extern bool IsInstallXLogFileSegmentActive(void);
 extern void XLogShutdownWalRcv(void);
 
-/*
- * Routines to start, stop, and get status of a base backup.
- */
-
-/*
- * Session-level status of base backups
- *
- * This is used in parallel with the shared memory status to control parallel
- * execution of base backup functions for a given session, be it a backend
- * dedicated to replication or a normal backend connected to a database. The
- * update of the session-level status happens at the same time as the shared
- * memory counters to keep a consistent global and local state of the backups
- * running.
- */
-typedef enum SessionBackupState
-{
-	SESSION_BACKUP_NONE,
-	SESSION_BACKUP_RUNNING,
-} SessionBackupState;
-
-extern void do_pg_backup_start(const char *backupidstr, bool fast,
-							   List **tablespaces, BackupState *state,
-							   StringInfo tblspcmapfile);
-extern void do_pg_backup_stop(BackupState *state, bool waitforarchive);
-extern void do_pg_abort_backup(int code, Datum arg);
-extern void register_persistent_abort_backup_handler(void);
-extern SessionBackupState get_backup_status(void);
-extern void set_backup_status(SessionBackupState state);
-
 /*
  * Routines used by xlogbackup.c to call back into xlog.c during backup.
  *
@@ -304,11 +275,6 @@ extern void SetXLogBackupRelatedInfo(void);
 /* File path names (all relative to $PGDATA) */
 #define RECOVERY_SIGNAL_FILE	"recovery.signal"
 #define STANDBY_SIGNAL_FILE		"standby.signal"
-#define BACKUP_LABEL_FILE		"backup_label"
-#define BACKUP_LABEL_OLD		"backup_label.old"
-
-#define TABLESPACE_MAP			"tablespace_map"
-#define TABLESPACE_MAP_OLD		"tablespace_map.old"
 
 /* files to signal promotion to primary */
 #define PROMOTE_SIGNAL_FILE		"promote"
diff --git a/src/include/access/xlogbackup.h b/src/include/access/xlogbackup.h
index 8ec3d88b0a..2449c22d89 100644
--- a/src/include/access/xlogbackup.h
+++ b/src/include/access/xlogbackup.h
@@ -15,8 +15,15 @@
 #define XLOG_BACKUP_H
 
 #include "access/xlogdefs.h"
+#include "nodes/pg_list.h"
 #include "pgtime.h"
 
+/* File path names (all relative to $PGDATA) */
+#define BACKUP_LABEL_FILE		"backup_label"
+#define BACKUP_LABEL_OLD		"backup_label.old"
+#define TABLESPACE_MAP			"tablespace_map"
+#define TABLESPACE_MAP_OLD		"tablespace_map.old"
+
 /* Structure to hold backup state. */
 typedef struct BackupState
 {
@@ -38,4 +45,33 @@ typedef struct BackupState
 extern char *build_backup_content(BackupState *state,
 								  bool ishistoryfile);
 
+/*
+ * Routines to start, stop, and get status of a base backup.
+ */
+
+/*
+ * Session-level status of base backups
+ *
+ * This is used in parallel with the shared memory status to control parallel
+ * execution of base backup functions for a given session, be it a backend
+ * dedicated to replication or a normal backend connected to a database. The
+ * update of the session-level status happens at the same time as the shared
+ * memory counters to keep a consistent global and local state of the backups
+ * running.
+ */
+typedef enum SessionBackupState
+{
+	SESSION_BACKUP_NONE,
+	SESSION_BACKUP_RUNNING,
+} SessionBackupState;
+
+extern void do_pg_backup_start(const char *backupidstr, bool fast,
+							   List **tablespaces, BackupState *state,
+							   StringInfo tblspcmapfile);
+extern void do_pg_backup_stop(BackupState *state, bool waitforarchive);
+extern void do_pg_abort_backup(int code, Datum arg);
+extern void register_persistent_abort_backup_handler(void);
+extern SessionBackupState get_backup_status(void);
+extern void set_backup_status(SessionBackupState state);
+
 #endif							/* XLOG_BACKUP_H */
-- 
2.34.1

From 3fb27c77422e79c21e2a600f298636c59ba4abb0 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Thu, 6 Oct 2022 11:50:16 +0000
Subject: [PATCH v3] Move backup-related code from xlogfuncs.c to xlogbackup.c

---
 src/backend/access/transam/xlogbackup.c | 133 ++++++++++++++++++++++++
 src/backend/access/transam/xlogfuncs.c  | 130 -----------------------
 2 files changed, 133 insertions(+), 130 deletions(-)

diff --git a/src/backend/access/transam/xlogbackup.c b/src/backend/access/transam/xlogbackup.c
index 61c26e09ed..7d9375064c 100644
--- a/src/backend/access/transam/xlogbackup.c
+++ b/src/backend/access/transam/xlogbackup.c
@@ -23,12 +23,16 @@
 #include "access/xloginsert.h"
 #include "backup/basebackup.h"
 #include "common/file_utils.h"
+#include "funcapi.h"
 #include "miscadmin.h"
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
 #include "storage/lwlock.h"
+#include "utils/builtins.h"
+#include "utils/memutils.h"
+#include "utils/pg_lsn.h"
 #include "utils/wait_event.h"
 
 /*
@@ -40,6 +44,12 @@ static SessionBackupState sessionBackupState = SESSION_BACKUP_NONE;
 static void pg_backup_start_callback(int code, Datum arg);
 static void CleanupBackupHistory(void);
 
+/*
+ * Session-level backup-related variables for SQL-callable functions.
+ */
+static BackupState *backup_state = NULL;
+static StringInfo tablespace_map = NULL;
+
 /*
  * Build contents for backup_label or backup history file.
  *
@@ -760,3 +770,126 @@ register_persistent_abort_backup_handler(void)
 	before_shmem_exit(do_pg_abort_backup, DatumGetBool(true));
 	already_done = true;
 }
+
+/*
+ * pg_backup_start: set up for taking an on-line backup dump
+ *
+ * Essentially what this does is to create the contents required for the
+ * backup_label file and the tablespace map.
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_backup_start(PG_FUNCTION_ARGS)
+{
+	text	   *backupid = PG_GETARG_TEXT_PP(0);
+	bool		fast = PG_GETARG_BOOL(1);
+	char	   *backupidstr;
+	SessionBackupState status = get_backup_status();
+	MemoryContext oldcontext;
+
+	backupidstr = text_to_cstring(backupid);
+
+	if (status == SESSION_BACKUP_RUNNING)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("a backup is already in progress in this session")));
+
+	/*
+	 * backup_state and tablespace_map need to be long-lived as they are used
+	 * in pg_backup_stop().
+	 */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	/* Allocate backup state or reset it, if it comes from a previous run */
+	if (backup_state == NULL)
+		backup_state = (BackupState *) palloc0(sizeof(BackupState));
+	else
+		MemSet(backup_state, 0, sizeof(BackupState));
+
+	/*
+	 * tablespace_map may have been created in a previous backup, so take this
+	 * occasion to clean it.
+	 */
+	if (tablespace_map != NULL)
+	{
+		pfree(tablespace_map->data);
+		pfree(tablespace_map);
+		tablespace_map = NULL;
+	}
+
+	tablespace_map = makeStringInfo();
+	MemoryContextSwitchTo(oldcontext);
+
+	register_persistent_abort_backup_handler();
+	do_pg_backup_start(backupidstr, fast, NULL, backup_state, tablespace_map);
+
+	PG_RETURN_LSN(backup_state->startpoint);
+}
+
+
+/*
+ * pg_backup_stop: finish taking an on-line backup.
+ *
+ * The first parameter (variable 'waitforarchive'), which is optional,
+ * allows the user to choose if they want to wait for the WAL to be archived
+ * or if we should just return as soon as the WAL record is written.
+ *
+ * This function stops an in-progress backup, creates backup_label contents and
+ * it returns the backup stop LSN, backup_label and tablespace_map contents.
+ *
+ * The backup_label contains the user-supplied label string (typically this
+ * would be used to tell where the backup dump will be stored), the starting
+ * time, starting WAL location for the dump and so on.  It is the caller's
+ * responsibility to write the backup_label and tablespace_map files in the
+ * data folder that will be restored from this backup.
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_backup_stop(PG_FUNCTION_ARGS)
+{
+#define PG_BACKUP_STOP_V2_COLS 3
+	TupleDesc	tupdesc;
+	Datum		values[PG_BACKUP_STOP_V2_COLS] = {0};
+	bool		nulls[PG_BACKUP_STOP_V2_COLS] = {0};
+	bool		waitforarchive = PG_GETARG_BOOL(0);
+	char	   *backup_label;
+	SessionBackupState status = get_backup_status();
+
+	/* Initialize attributes information in the tuple descriptor */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (status != SESSION_BACKUP_RUNNING)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("backup is not in progress"),
+				 errhint("Did you call pg_backup_start()?")));
+
+	Assert(backup_state != NULL);
+	Assert(tablespace_map != NULL);
+
+	/* Stop the backup */
+	do_pg_backup_stop(backup_state, waitforarchive);
+
+	/* Build the contents of backup_label */
+	backup_label = build_backup_content(backup_state, false);
+
+	values[0] = LSNGetDatum(backup_state->stoppoint);
+	values[1] = CStringGetTextDatum(backup_label);
+	values[2] = CStringGetTextDatum(tablespace_map->data);
+
+	/* Deallocate backup-related variables */
+	pfree(backup_state);
+	backup_state = NULL;
+	pfree(tablespace_map->data);
+	pfree(tablespace_map);
+	tablespace_map = NULL;
+	pfree(backup_label);
+
+	/* Returns the record as Datum */
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
+}
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index a801a94fe8..4c12b99ec7 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -20,7 +20,6 @@
 
 #include "access/htup_details.h"
 #include "access/xlog_internal.h"
-#include "access/xlogbackup.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
 #include "catalog/pg_type.h"
@@ -33,140 +32,11 @@
 #include "storage/smgr.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
-#include "utils/memutils.h"
 #include "utils/numeric.h"
 #include "utils/pg_lsn.h"
 #include "utils/timestamp.h"
 #include "utils/tuplestore.h"
 
-/*
- * Backup-related variables.
- */
-static BackupState *backup_state = NULL;
-static StringInfo tablespace_map = NULL;
-
-/*
- * pg_backup_start: set up for taking an on-line backup dump
- *
- * Essentially what this does is to create the contents required for the
- * backup_label file and the tablespace map.
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_backup_start(PG_FUNCTION_ARGS)
-{
-	text	   *backupid = PG_GETARG_TEXT_PP(0);
-	bool		fast = PG_GETARG_BOOL(1);
-	char	   *backupidstr;
-	SessionBackupState status = get_backup_status();
-	MemoryContext oldcontext;
-
-	backupidstr = text_to_cstring(backupid);
-
-	if (status == SESSION_BACKUP_RUNNING)
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("a backup is already in progress in this session")));
-
-	/*
-	 * backup_state and tablespace_map need to be long-lived as they are used
-	 * in pg_backup_stop().
-	 */
-	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
-
-	/* Allocate backup state or reset it, if it comes from a previous run */
-	if (backup_state == NULL)
-		backup_state = (BackupState *) palloc0(sizeof(BackupState));
-	else
-		MemSet(backup_state, 0, sizeof(BackupState));
-
-	/*
-	 * tablespace_map may have been created in a previous backup, so take this
-	 * occasion to clean it.
-	 */
-	if (tablespace_map != NULL)
-	{
-		pfree(tablespace_map->data);
-		pfree(tablespace_map);
-		tablespace_map = NULL;
-	}
-
-	tablespace_map = makeStringInfo();
-	MemoryContextSwitchTo(oldcontext);
-
-	register_persistent_abort_backup_handler();
-	do_pg_backup_start(backupidstr, fast, NULL, backup_state, tablespace_map);
-
-	PG_RETURN_LSN(backup_state->startpoint);
-}
-
-
-/*
- * pg_backup_stop: finish taking an on-line backup.
- *
- * The first parameter (variable 'waitforarchive'), which is optional,
- * allows the user to choose if they want to wait for the WAL to be archived
- * or if we should just return as soon as the WAL record is written.
- *
- * This function stops an in-progress backup, creates backup_label contents and
- * it returns the backup stop LSN, backup_label and tablespace_map contents.
- *
- * The backup_label contains the user-supplied label string (typically this
- * would be used to tell where the backup dump will be stored), the starting
- * time, starting WAL location for the dump and so on.  It is the caller's
- * responsibility to write the backup_label and tablespace_map files in the
- * data folder that will be restored from this backup.
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_backup_stop(PG_FUNCTION_ARGS)
-{
-#define PG_BACKUP_STOP_V2_COLS 3
-	TupleDesc	tupdesc;
-	Datum		values[PG_BACKUP_STOP_V2_COLS] = {0};
-	bool		nulls[PG_BACKUP_STOP_V2_COLS] = {0};
-	bool		waitforarchive = PG_GETARG_BOOL(0);
-	char	   *backup_label;
-	SessionBackupState status = get_backup_status();
-
-	/* Initialize attributes information in the tuple descriptor */
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
-
-	if (status != SESSION_BACKUP_RUNNING)
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("backup is not in progress"),
-				 errhint("Did you call pg_backup_start()?")));
-
-	Assert(backup_state != NULL);
-	Assert(tablespace_map != NULL);
-
-	/* Stop the backup */
-	do_pg_backup_stop(backup_state, waitforarchive);
-
-	/* Build the contents of backup_label */
-	backup_label = build_backup_content(backup_state, false);
-
-	values[0] = LSNGetDatum(backup_state->stoppoint);
-	values[1] = CStringGetTextDatum(backup_label);
-	values[2] = CStringGetTextDatum(tablespace_map->data);
-
-	/* Deallocate backup-related variables */
-	pfree(backup_state);
-	backup_state = NULL;
-	pfree(tablespace_map->data);
-	pfree(tablespace_map);
-	tablespace_map = NULL;
-	pfree(backup_label);
-
-	/* Returns the record as Datum */
-	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
-}
 
 /*
  * pg_switch_wal: switch to next xlog file
-- 
2.34.1

Reply via email to