v1-0001-Base-backup-client-as-auxiliary-backend-process.patch

text/plain

Filename: v1-0001-Base-backup-client-as-auxiliary-backend-process.patch
Type: text/plain
Part: 0
Message: base backup client as auxiliary backend process

Patch

Format: format-patch
Series: patch v1-0001
Subject: Base backup client as auxiliary backend process
File+
src/backend/access/transam/xlog.c 7 7
src/backend/bootstrap/bootstrap.c 9 0
src/backend/postmaster/pgstat.c 6 0
src/backend/postmaster/postmaster.c 91 4
src/backend/replication/basebackup_client.c 33 0
src/backend/replication/libpqwalreceiver/libpqwalreceiver.c 308 0
src/backend/replication/Makefile 1 1
src/bin/initdb/initdb.c 35 4
src/include/access/xlog.h 4 0
src/include/miscadmin.h 2 0
src/include/pgstat.h 1 0
src/include/replication/basebackup_client.h 1 0
src/include/replication/walreceiver.h 4 0
From 1cf36db2514b04428570496fc9d1145591fda0fc Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Sat, 29 Jun 2019 21:52:21 +0200
Subject: [PATCH v1] Base backup client as auxiliary backend process

---
 src/backend/access/transam/xlog.c             |  14 +-
 src/backend/bootstrap/bootstrap.c             |   9 +
 src/backend/postmaster/pgstat.c               |   6 +
 src/backend/postmaster/postmaster.c           |  95 +++++-
 src/backend/replication/Makefile              |   2 +-
 src/backend/replication/basebackup_client.c   |  33 ++
 .../libpqwalreceiver/libpqwalreceiver.c       | 308 ++++++++++++++++++
 src/bin/initdb/initdb.c                       |  39 ++-
 src/include/access/xlog.h                     |   4 +
 src/include/miscadmin.h                       |   2 +
 src/include/pgstat.h                          |   1 +
 src/include/replication/basebackup_client.h   |   1 +
 src/include/replication/walreceiver.h         |   4 +
 13 files changed, 502 insertions(+), 16 deletions(-)
 create mode 100644 src/backend/replication/basebackup_client.c
 create mode 100644 src/include/replication/basebackup_client.h

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e08320e829..da97970703 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -905,7 +905,6 @@ static XLogRecord *ReadCheckpointRecord(XLogReaderState *xlogreader,
 										XLogRecPtr RecPtr, int whichChkpti, bool report);
 static bool rescanLatestTimeLine(void);
 static void WriteControlFile(void);
-static void ReadControlFile(void);
 static char *str_time(pg_time_t tnow);
 static bool CheckForStandbyTrigger(void);
 
@@ -4572,7 +4571,7 @@ WriteControlFile(void)
 						XLOG_CONTROL_FILE)));
 }
 
-static void
+void
 ReadControlFile(void)
 {
 	pg_crc32c	crc;
@@ -6209,13 +6208,11 @@ StartupXLOG(void)
 	CurrentResourceOwner = AuxProcessResourceOwner;
 
 	/*
-	 * Verify XLOG status looks valid.
+	 * Check that contents look valid.
 	 */
-	if (ControlFile->state < DB_SHUTDOWNED ||
-		ControlFile->state > DB_IN_PRODUCTION ||
-		!XRecOffIsValid(ControlFile->checkPoint))
+	if (!XRecOffIsValid(ControlFile->checkPoint))
 		ereport(FATAL,
-				(errmsg("control file contains invalid data")));
+				(errmsg("control file contains invalid checkpoint location")));
 
 	if (ControlFile->state == DB_SHUTDOWNED)
 	{
@@ -6248,6 +6245,9 @@ StartupXLOG(void)
 		ereport(LOG,
 				(errmsg("database system was interrupted; last known up at %s",
 						str_time(ControlFile->time))));
+	else
+		ereport(FATAL,
+				(errmsg("control file contains invalid database cluster state")));
 
 	/* This is just to allow attaching to startup process with a debugger */
 #ifdef XLOG_REPLAY_DELAY
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 43627ab8f4..57769c160c 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -36,6 +36,7 @@
 #include "postmaster/bgwriter.h"
 #include "postmaster/startup.h"
 #include "postmaster/walwriter.h"
+#include "replication/basebackup_client.h"
 #include "replication/walreceiver.h"
 #include "storage/bufmgr.h"
 #include "storage/bufpage.h"
@@ -326,6 +327,9 @@ AuxiliaryProcessMain(int argc, char *argv[])
 			case StartupProcess:
 				statmsg = pgstat_get_backend_desc(B_STARTUP);
 				break;
+			case BaseBackupProcess:
+				statmsg = pgstat_get_backend_desc(B_BASE_BACKUP);
+				break;
 			case BgWriterProcess:
 				statmsg = pgstat_get_backend_desc(B_BG_WRITER);
 				break;
@@ -451,6 +455,11 @@ AuxiliaryProcessMain(int argc, char *argv[])
 			StartupProcessMain();
 			proc_exit(1);		/* should never return */
 
+		case BaseBackupProcess:
+			/* don't set signals, basebackup has its own agenda */
+			BaseBackupMain();
+			proc_exit(1);		/* should never return */
+
 		case BgWriterProcess:
 			/* don't set signals, bgwriter has its own agenda */
 			BackgroundWriterMain();
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index b4f2b28b51..6664932183 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2934,6 +2934,9 @@ pgstat_bestart(void)
 			case StartupProcess:
 				lbeentry.st_backendType = B_STARTUP;
 				break;
+			case BaseBackupProcess:
+				lbeentry.st_backendType = B_BASE_BACKUP;
+				break;
 			case BgWriterProcess:
 				lbeentry.st_backendType = B_BG_WRITER;
 				break;
@@ -4289,6 +4292,9 @@ pgstat_get_backend_desc(BackendType backendType)
 		case B_BG_WORKER:
 			backendDesc = "background worker";
 			break;
+		case B_BASE_BACKUP:
+			backendDesc = "base backup";
+			break;
 		case B_BG_WRITER:
 			backendDesc = "background writer";
 			break;
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 688ad439ed..287b233399 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -248,6 +248,7 @@ bool		restart_after_crash = true;
 
 /* PIDs of special child processes; 0 when not running */
 static pid_t StartupPID = 0,
+			BaseBackupPID = 0,
 			BgWriterPID = 0,
 			CheckpointerPID = 0,
 			WalWriterPID = 0,
@@ -539,6 +540,7 @@ static void ShmemBackendArrayRemove(Backend *bn);
 #endif							/* EXEC_BACKEND */
 
 #define StartupDataBase()		StartChildProcess(StartupProcess)
+#define StartBaseBackup()		StartChildProcess(BaseBackupProcess)
 #define StartBackgroundWriter() StartChildProcess(BgWriterProcess)
 #define StartCheckpointer()		StartChildProcess(CheckpointerProcess)
 #define StartWalWriter()		StartChildProcess(WalWriterProcess)
@@ -572,6 +574,8 @@ PostmasterMain(int argc, char *argv[])
 	bool		listen_addr_saved = false;
 	int			i;
 	char	   *output_config_variable = NULL;
+	struct stat stat_buf;
+	bool		basebackup_signal_file_found = false;
 
 	InitProcessGlobals();
 
@@ -877,12 +881,27 @@ PostmasterMain(int argc, char *argv[])
 	/* Verify that DataDir looks reasonable */
 	checkDataDir();
 
-	/* Check that pg_control exists */
-	checkControlFile();
-
 	/* And switch working directory into it */
 	ChangeToDataDir();
 
+	if (stat(BASEBACKUP_SIGNAL_FILE, &stat_buf) == 0)
+	{
+		int         fd;
+
+		fd = BasicOpenFilePerm(STANDBY_SIGNAL_FILE, O_RDWR | PG_BINARY,
+							   S_IRUSR | S_IWUSR);
+		if (fd >= 0)
+		{
+			(void) pg_fsync(fd);
+			close(fd);
+		}
+		basebackup_signal_file_found = true;
+	}
+
+	/* Check that pg_control exists */
+	if (!basebackup_signal_file_found)
+		checkControlFile();
+
 	/*
 	 * Check for invalid combinations of GUC settings.
 	 */
@@ -961,7 +980,8 @@ PostmasterMain(int argc, char *argv[])
 	 * processes will inherit the correct function pointer and not need to
 	 * repeat the test.
 	 */
-	LocalProcessControlFile(false);
+	if (!basebackup_signal_file_found)
+		LocalProcessControlFile(false);
 
 	/*
 	 * Initialize SSL library, if specified.
@@ -1363,6 +1383,33 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_STARTING);
 
+	if (basebackup_signal_file_found)
+	{
+		BaseBackupPID = StartBaseBackup();
+
+		/*
+		 * XXX wait until done
+		 */
+		while (BaseBackupPID != 0)
+		{
+			PG_SETMASK(&UnBlockSig);
+			sleep(2);
+			PG_SETMASK(&BlockSig);
+		}
+
+		/*
+		 * Base backup done, now signal standby mode.  XXX Is there a use for
+		 * switching into (non-standby) recovery here?  How would that be
+		 * configured?
+		 */
+		durable_rename(BASEBACKUP_SIGNAL_FILE, STANDBY_SIGNAL_FILE, FATAL);
+
+		/*
+		 * Reread the control file that came in with the base backup.
+		 */
+		ReadControlFile();
+	}
+
 	/*
 	 * We're ready to rock and roll...
 	 */
@@ -2631,6 +2678,8 @@ SIGHUP_handler(SIGNAL_ARGS)
 		SignalChildren(SIGHUP);
 		if (StartupPID != 0)
 			signal_child(StartupPID, SIGHUP);
+		if (BaseBackupPID != 0)
+			signal_child(BaseBackupPID, SIGHUP);
 		if (BgWriterPID != 0)
 			signal_child(BgWriterPID, SIGHUP);
 		if (CheckpointerPID != 0)
@@ -2782,6 +2831,8 @@ pmdie(SIGNAL_ARGS)
 
 			if (StartupPID != 0)
 				signal_child(StartupPID, SIGTERM);
+			if (BaseBackupPID != 0)
+				signal_child(BaseBackupPID, SIGTERM);
 			if (BgWriterPID != 0)
 				signal_child(BgWriterPID, SIGTERM);
 			if (WalReceiverPID != 0)
@@ -2997,6 +3048,22 @@ reaper(SIGNAL_ARGS)
 			continue;
 		}
 
+		/*
+		 * Was it the base backup process?
+		 */
+		if (pid == BaseBackupPID)
+		{
+			BaseBackupPID = 0;
+			if (EXIT_STATUS_0(exitstatus))
+				;
+			else if (EXIT_STATUS_1(exitstatus))
+				elog(FATAL, "base backup failed");
+			else
+				HandleChildCrash(pid, exitstatus,
+								 _("base backup process"));
+			continue;
+		}
+
 		/*
 		 * Was it the bgwriter?  Normal exit can be ignored; we'll start a new
 		 * one at the next iteration of the postmaster's main loop, if
@@ -3516,6 +3583,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname)
 		StartupStatus = STARTUP_SIGNALED;
 	}
 
+	/* Take care of the base backup process too */
+	if (pid == BaseBackupPID)
+		BaseBackupPID = 0;
+	else if (BaseBackupPID != 0 && take_action)
+	{
+		ereport(DEBUG2,
+				(errmsg_internal("sending %s to process %d",
+								 (SendStop ? "SIGSTOP" : "SIGQUIT"),
+								 (int) BaseBackupPID)));
+		signal_child(BaseBackupPID, (SendStop ? SIGSTOP : SIGQUIT));
+	}
+
 	/* Take care of the bgwriter too */
 	if (pid == BgWriterPID)
 		BgWriterPID = 0;
@@ -3750,6 +3829,7 @@ PostmasterStateMachine(void)
 		if (CountChildren(BACKEND_TYPE_NORMAL | BACKEND_TYPE_WORKER) == 0 &&
 			StartupPID == 0 &&
 			WalReceiverPID == 0 &&
+			BaseBackupPID == 0 &&
 			BgWriterPID == 0 &&
 			(CheckpointerPID == 0 ||
 			 (!FatalError && Shutdown < ImmediateShutdown)) &&
@@ -3844,6 +3924,7 @@ PostmasterStateMachine(void)
 			/* These other guys should be dead already */
 			Assert(StartupPID == 0);
 			Assert(WalReceiverPID == 0);
+			Assert(BaseBackupPID == 0);
 			Assert(BgWriterPID == 0);
 			Assert(CheckpointerPID == 0);
 			Assert(WalWriterPID == 0);
@@ -4027,6 +4108,8 @@ TerminateChildren(int signal)
 		if (signal == SIGQUIT || signal == SIGKILL)
 			StartupStatus = STARTUP_SIGNALED;
 	}
+	if (BaseBackupPID != 0)
+		signal_child(BgWriterPID, signal);
 	if (BgWriterPID != 0)
 		signal_child(BgWriterPID, signal);
 	if (CheckpointerPID != 0)
@@ -5400,6 +5483,10 @@ StartChildProcess(AuxProcType type)
 				ereport(LOG,
 						(errmsg("could not fork startup process: %m")));
 				break;
+			case BaseBackupProcess:
+				ereport(LOG,
+						(errmsg("could not fork base backup process: %m")));
+				break;
 			case BgWriterProcess:
 				ereport(LOG,
 						(errmsg("could not fork background writer process: %m")));
diff --git a/src/backend/replication/Makefile b/src/backend/replication/Makefile
index 562b55fbaa..748093100a 100644
--- a/src/backend/replication/Makefile
+++ b/src/backend/replication/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
 
-OBJS = walsender.o walreceiverfuncs.o walreceiver.o basebackup.o \
+OBJS = walsender.o walreceiverfuncs.o walreceiver.o basebackup.o basebackup_client.o \
 	repl_gram.o slot.o slotfuncs.o syncrep.o syncrep_gram.o
 
 SUBDIRS = logical
diff --git a/src/backend/replication/basebackup_client.c b/src/backend/replication/basebackup_client.c
new file mode 100644
index 0000000000..4a75a6091f
--- /dev/null
+++ b/src/backend/replication/basebackup_client.c
@@ -0,0 +1,33 @@
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "replication/basebackup_client.h"
+#include "replication/walreceiver.h"
+#include "storage/ipc.h"
+#include "utils/guc.h"
+
+void
+BaseBackupMain(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+	if (WalReceiverFunctions == NULL)
+		elog(ERROR, "libpqwalreceiver didn't initialize correctly");
+
+	/* Establish the connection to the primary */
+	wrconn = walrcv_connect(PrimaryConnInfo, false, cluster_name[0] ? cluster_name : "basebackup", &err);
+	if (!wrconn)
+		ereport(ERROR,
+				(errmsg("could not connect to the primary server: %s", err)));
+
+	walrcv_base_backup(wrconn);
+
+	walrcv_disconnect(wrconn);
+
+	elog(LOG, "base backup completed");
+	proc_exit(0);
+}
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 6eba08a920..b72d849fde 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -17,8 +17,10 @@
 #include "postgres.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 #include <sys/time.h>
 
+#include "common/string.h"
 #include "libpq-fe.h"
 #include "pqexpbuffer.h"
 #include "access/xlog.h"
@@ -27,6 +29,7 @@
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "pgtar.h"
 #include "replication/walreceiver.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
@@ -61,6 +64,7 @@ static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
 											 char **content, int *len);
+static void libpqrcv_base_backup(WalReceiverConn *conn);
 static bool libpqrcv_startstreaming(WalReceiverConn *conn,
 									const WalRcvStreamOptions *options);
 static void libpqrcv_endstreaming(WalReceiverConn *conn,
@@ -88,6 +92,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	libpqrcv_identify_system,
 	libpqrcv_server_version,
 	libpqrcv_readtimelinehistoryfile,
+	libpqrcv_base_backup,
 	libpqrcv_startstreaming,
 	libpqrcv_endstreaming,
 	libpqrcv_receive,
@@ -356,6 +361,309 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * XXX copied from pg_basebackup.c
+ */
+static void
+ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
+{
+	char		current_path[MAXPGPATH];
+	char		filename[MAXPGPATH];
+	pgoff_t		current_len_left = 0;
+	int			current_padding = 0;
+	char	   *copybuf = NULL;
+	FILE	   *file = NULL;
+
+	strlcpy(current_path, DataDir, sizeof(current_path));
+
+	/*
+	 * Get the COPY data
+	 */
+	res = PQgetResult(conn);
+	if (PQresultStatus(res) != PGRES_COPY_OUT)
+		ereport(ERROR,
+				(errmsg("could not get COPY data stream: %s",
+						PQerrorMessage(conn))));
+
+	while (1)
+	{
+		int			r;
+
+		if (copybuf != NULL)
+		{
+			PQfreemem(copybuf);
+			copybuf = NULL;
+		}
+
+		r = PQgetCopyData(conn, &copybuf, 0);
+
+		if (r == -1)
+		{
+			/*
+			 * End of chunk
+			 */
+			if (file)
+				fclose(file);
+
+			break;
+		}
+		else if (r == -2)
+		{
+			ereport(ERROR,
+					(errmsg("could not read COPY data: %s",
+							PQerrorMessage(conn))));
+		}
+
+		if (file == NULL)
+		{
+			int			filemode;
+
+			/*
+			 * No current file, so this must be the header for a new file
+			 */
+			if (r != 512)
+				ereport(ERROR,
+						(errmsg("invalid tar block header size: %d", r)));
+
+			current_len_left = read_tar_number(&copybuf[124], 12);
+
+			/* Set permissions on the file */
+			filemode = read_tar_number(&copybuf[100], 8);
+
+			/*
+			 * All files are padded up to 512 bytes
+			 */
+			current_padding =
+				((current_len_left + 511) & ~511) - current_len_left;
+
+			/*
+			 * First part of header is zero terminated filename
+			 */
+			snprintf(filename, sizeof(filename), "%s/%s", current_path,
+					 copybuf);
+			if (filename[strlen(filename) - 1] == '/')
+			{
+				/*
+				 * Ends in a slash means directory or symlink to directory
+				 */
+				if (copybuf[156] == '5')
+				{
+					/*
+					 * Directory
+					 */
+					filename[strlen(filename) - 1] = '\0';	/* Remove trailing slash */
+					if (MakePGDirectory(filename) != 0)
+					{
+						if (errno != EEXIST)
+						{
+							elog(ERROR, "could not create directory \"%s\": %m",
+								 filename);
+						}
+					}
+#ifndef WIN32
+					if (chmod(filename, (mode_t) filemode))
+						elog(ERROR, "could not set permissions on directory \"%s\": %m",
+							 filename);
+#endif
+				}
+				else if (copybuf[156] == '2')
+				{
+					/*
+					 * Symbolic link
+					 *
+					 * It's most likely a link in pg_tblspc directory, to the
+					 * location of a tablespace. Apply any tablespace mapping
+					 * given on the command line (--tablespace-mapping). (We
+					 * blindly apply the mapping without checking that the
+					 * link really is inside pg_tblspc. We don't expect there
+					 * to be other symlinks in a data directory, but if there
+					 * are, you can call it an undocumented feature that you
+					 * can map them too.)
+					 */
+#ifdef TODO
+					filename[strlen(filename) - 1] = '\0';	/* Remove trailing slash */
+
+					mapped_tblspc_path = get_tablespace_mapping(&copybuf[157]);
+					if (symlink(mapped_tblspc_path, filename) != 0)
+					{
+						pg_log_error("could not create symbolic link from \"%s\" to \"%s\": %m",
+									 filename, mapped_tblspc_path);
+						exit(1);
+					}
+#endif
+				}
+				else
+				{
+					elog(ERROR, "unrecognized link indicator \"%c\"",
+						 copybuf[156]);
+				}
+				continue;		/* directory or link handled */
+			}
+
+			/*
+			 * regular file
+			 */
+			file = fopen(filename, "wb");
+			if (!file)
+				elog(ERROR, "could not create file \"%s\": %m", filename);
+
+#ifndef WIN32
+			if (chmod(filename, (mode_t) filemode))
+				elog(ERROR, "could not set permissions on file \"%s\": %m",
+					 filename);
+#endif
+
+			if (current_len_left == 0)
+			{
+				/*
+				 * Done with this file, next one will be a new tar header
+				 */
+				fclose(file);
+				file = NULL;
+				continue;
+			}
+		}						/* new file */
+		else
+		{
+			/*
+			 * Continuing blocks in existing file
+			 */
+			if (current_len_left == 0 && r == current_padding)
+			{
+				/*
+				 * Received the padding block for this file, ignore it and
+				 * close the file, then move on to the next tar header.
+				 */
+				fclose(file);
+				file = NULL;
+				continue;
+			}
+
+			if (fwrite(copybuf, r, 1, file) != 1)
+				elog(ERROR, "could not write to file \"%s\": %m", filename);
+
+			current_len_left -= r;
+			if (current_len_left == 0 && current_padding == 0)
+			{
+				/*
+				 * Received the last block, and there is no padding to be
+				 * expected. Close the file and move on to the next tar
+				 * header.
+				 */
+				fclose(file);
+				file = NULL;
+				continue;
+			}
+		}						/* continuing data in existing file */
+	}							/* loop over all data blocks */
+
+	if (file != NULL)
+		elog(ERROR, "COPY stream ended before last file was finished");
+
+	if (copybuf != NULL)
+		PQfreemem(copybuf);
+}
+
+/*
+ * Make base backup from remote and write to local disk.
+ */
+static void
+libpqrcv_base_backup(WalReceiverConn *conn)
+{
+	PGresult   *res;
+
+	elog(LOG, "initiating base backup, waiting for remote checkpoint to complete");
+
+	if (PQsendQuery(conn->streamConn, "BASE_BACKUP") == 0)
+		ereport(ERROR,
+				(errmsg("could not start base backup on remote server: %s",
+						pchomp(PQerrorMessage(conn->streamConn)))));
+
+	/*
+	 * First result set: WAL start position and timeline ID; we skip it.
+	 */
+	res = PQgetResult(conn->streamConn);
+	if (PQresultStatus(res) != PGRES_TUPLES_OK)
+	{
+		PQclear(res);
+		ereport(ERROR,
+				(errmsg("could not start base backup on remote server: %s",
+						pchomp(PQerrorMessage(conn->streamConn)))));
+	}
+
+	ereport(LOG,
+			(errmsg("remote checkpoint completed")));
+
+	PQclear(res);
+
+	/*
+	 * Second result set: tablespace information
+	 */
+	res = PQgetResult(conn->streamConn);
+	if (PQresultStatus(res) != PGRES_TUPLES_OK)
+	{
+		PQclear(res);
+		ereport(ERROR,
+				(errmsg("could not get backup header: %s",
+						pchomp(PQerrorMessage(conn->streamConn)))));
+	}
+	if (PQntuples(res) < 1)
+	{
+		PQclear(res);
+		ereport(ERROR,
+				(errmsg("no data returned from server")));
+	}
+
+	/*
+	 * Start receiving chunks
+	 */
+	for (int i = 0; i < PQntuples(res); i++)
+	{
+		ReceiveAndUnpackTarFile(conn->streamConn, res, i);
+	}
+
+	PQclear(res);
+
+	/*
+	 * Final result set: WAL end position and timeline ID
+	 */
+	res = PQgetResult(conn->streamConn);
+	if (PQresultStatus(res) != PGRES_TUPLES_OK)
+	{
+		PQclear(res);
+		ereport(ERROR,
+				(errmsg("could not get write-ahead log end position from server: %s",
+						pchomp(PQerrorMessage(conn->streamConn)))));
+	}
+	if (PQntuples(res) != 1)
+	{
+		PQclear(res);
+		ereport(ERROR,
+				(errmsg("no write-ahead log end position returned from server")));
+	}
+	PQclear(res);
+
+	res = PQgetResult(conn->streamConn);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+#ifdef TODO
+		const char *sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
+
+		if (sqlstate &&
+			strcmp(sqlstate, ERRCODE_DATA_CORRUPTED) == 0)
+		{
+			elog(ERROR, "checksum error occurred");
+		}
+		else
+#endif
+		{
+			elog(ERROR, "final receive failed: %s",
+				 pchomp(PQerrorMessage(conn->streamConn)));
+		}
+	}
+	PQclear(res);
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index ad5cd4194a..4c4ec78095 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -136,6 +136,7 @@ static char *pwfilename = NULL;
 static char *superuser_password = NULL;
 static const char *authmethodhost = NULL;
 static const char *authmethodlocal = NULL;
+static bool minimal = false;
 static bool debug = false;
 static bool noclean = false;
 static bool do_sync = true;
@@ -2962,6 +2963,22 @@ initialize_data_directory(void)
 	/* Now create all the text config files */
 	setup_config();
 
+	/*
+	 * If minimal data directory requested, write basebackup.signal, and then
+	 * we are done here.
+	 */
+	if (minimal)
+	{
+		char	   *path;
+		char	   *lines[1] = {NULL};
+
+		path = psprintf("%s/basebackup.signal", pg_data);
+		writefile(path, lines);
+		free(path);
+
+		return;
+	}
+
 	/* Bootstrap template1 */
 	bootstrap_template1();
 
@@ -3053,6 +3070,7 @@ main(int argc, char *argv[])
 		{"wal-segsize", required_argument, NULL, 12},
 		{"data-checksums", no_argument, NULL, 'k'},
 		{"allow-group-access", no_argument, NULL, 'g'},
+		{"minimal", no_argument, NULL, 'm'},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3094,7 +3112,7 @@ main(int argc, char *argv[])
 
 	/* process command-line options */
 
-	while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "dD:E:kL:mnNU:WA:sST:X:g", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -3149,6 +3167,9 @@ main(int argc, char *argv[])
 			case 'L':
 				share_path = pg_strdup(optarg);
 				break;
+			case 'm':
+				minimal = true;
+				break;
 			case 1:
 				locale = pg_strdup(optarg);
 				break;
@@ -3361,9 +3382,19 @@ main(int argc, char *argv[])
 	/* translator: This is a placeholder in a shell command. */
 	appendPQExpBuffer(start_db_cmd, " -l %s start", _("logfile"));
 
-	printf(_("\nSuccess. You can now start the database server using:\n\n"
-			 "    %s\n\n"),
-		   start_db_cmd->data);
+	if (!minimal)
+	{
+		printf(_("\nSuccess. You can now start the database server using:\n\n"
+				 "    %s\n\n"),
+			   start_db_cmd->data);
+	}
+	else
+	{
+		printf(_("\nSo far so good. Now configure the replication connection in\n"
+				 "postgresql.conf, and then start the database server using:\n\n"
+				 "    %s\n\n"),
+			   start_db_cmd->data);
+	}
 
 	destroyPQExpBuffer(start_db_cmd);
 
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 237f4e0350..5f081923b2 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -299,6 +299,9 @@ extern Size XLOGShmemSize(void);
 extern void XLOGShmemInit(void);
 extern void BootStrapXLOG(void);
 extern void LocalProcessControlFile(bool reset);
+#ifndef FRONTEND
+extern void ReadControlFile(void);
+#endif
 extern void StartupXLOG(void);
 extern void ShutdownXLOG(int code, Datum arg);
 extern void InitXLOGAccess(void);
@@ -354,6 +357,7 @@ extern void do_pg_abort_backup(void);
 extern SessionBackupState get_backup_status(void);
 
 /* File path names (all relative to $PGDATA) */
+#define BASEBACKUP_SIGNAL_FILE	"basebackup.signal"
 #define RECOVERY_SIGNAL_FILE	"recovery.signal"
 #define STANDBY_SIGNAL_FILE		"standby.signal"
 #define BACKUP_LABEL_FILE		"backup_label"
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 61a24c2e3c..1f40d33290 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -398,6 +398,7 @@ typedef enum
 	CheckerProcess = 0,
 	BootstrapProcess,
 	StartupProcess,
+	BaseBackupProcess,
 	BgWriterProcess,
 	CheckpointerProcess,
 	WalWriterProcess,
@@ -410,6 +411,7 @@ extern AuxProcType MyAuxProcType;
 
 #define AmBootstrapProcess()		(MyAuxProcType == BootstrapProcess)
 #define AmStartupProcess()			(MyAuxProcType == StartupProcess)
+#define AmBaseBackupProcess()		(MyAuxProcType == BaseBackupProcess)
 #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess)
 #define AmCheckpointerProcess()		(MyAuxProcType == CheckpointerProcess)
 #define AmWalWriterProcess()		(MyAuxProcType == WalWriterProcess)
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 0a3ad3a188..e5c385306e 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -722,6 +722,7 @@ typedef enum BackendType
 	B_AUTOVAC_LAUNCHER,
 	B_AUTOVAC_WORKER,
 	B_BACKEND,
+	B_BASE_BACKUP,
 	B_BG_WORKER,
 	B_BG_WRITER,
 	B_CHECKPOINTER,
diff --git a/src/include/replication/basebackup_client.h b/src/include/replication/basebackup_client.h
new file mode 100644
index 0000000000..dcd10b96f2
--- /dev/null
+++ b/src/include/replication/basebackup_client.h
@@ -0,0 +1 @@
+extern void BaseBackupMain(void);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 86a8130051..bf895b1494 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -215,6 +215,7 @@ typedef void (*walrcv_readtimelinehistoryfile_fn) (WalReceiverConn *conn,
 												   TimeLineID tli,
 												   char **filename,
 												   char **content, int *size);
+typedef void (*walrcv_base_backup_fn) (WalReceiverConn *conn);
 typedef bool (*walrcv_startstreaming_fn) (WalReceiverConn *conn,
 										  const WalRcvStreamOptions *options);
 typedef void (*walrcv_endstreaming_fn) (WalReceiverConn *conn,
@@ -242,6 +243,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_identify_system_fn walrcv_identify_system;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
+	walrcv_base_backup_fn walrcv_base_backup;
 	walrcv_startstreaming_fn walrcv_startstreaming;
 	walrcv_endstreaming_fn walrcv_endstreaming;
 	walrcv_receive_fn walrcv_receive;
@@ -267,6 +269,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
 	WalReceiverFunctions->walrcv_readtimelinehistoryfile(conn, tli, filename, content, size)
+#define walrcv_base_backup(conn) \
+	WalReceiverFunctions->walrcv_base_backup(conn)
 #define walrcv_startstreaming(conn, options) \
 	WalReceiverFunctions->walrcv_startstreaming(conn, options)
 #define walrcv_endstreaming(conn, next_tli) \

base-commit: c0faa727507ed34db0d02769d21bbaaf9605e2e4
-- 
2.22.0