On 2020-02-28 09:43, Michael Paquier wrote:
On Thu, Feb 27, 2020 at 06:29:34PM +0300, Alexey Kondratov wrote:
On 2020-02-27 16:41, Alexey Kondratov wrote:
>
> New version of the patch is attached. Thanks again for your review.
>

Last patch (v18) got a conflict with one of today commits (05d8449e73).
Rebased version is attached.

The shape of the patch is getting better.  I have found some issues
when reading through the patch, but nothing huge.

+   printf(_("  -c, --restore-target-wal       use restore_command in
target config\n"));
+   printf(_("                                 to retrieve WAL files
from archive\n"));
[...]
        {"progress", no_argument, NULL, 'P'},
+       {"restore-target-wal", no_argument, NULL, 'c'},
It may be better to reorder that alphabetically.


Sure, I put it in order. However, the recent -R option is out of order too.


+   if (rc != 0)
+       /* Sanity check, should never happen. */
+       elog(ERROR, "failed to build restore_command due to missing
parameters");
No point in having this comment IMO.


I would prefer to keep it, since there are plenty of similar comments near Asserts and elogs all over the Postgres. Otherwise it may look like a valid error state. It may be obvious now, but for someone who is not aware of BuildRestoreCommand refactoring it may be not. So from my perspective there is nothing bad in this extra one line comment.


+/* logging support */
+#define pg_fatal(...) do { pg_log_fatal(__VA_ARGS__); exit(1); } while(0)
Actually, I don't think that this is a good idea to name this
pg_fatal() as we have the same think in pg_rewind so it could be
confusing.


I have added explicit exit(1) calls, since pg_fatal was used only twice in the archive.c. Probably, pg_log_fatal from common/logging should obey the same logic as FATAL log-level in the backend and do exit the process, but for now including pg_rewind.h inside archive.c or vice versa does not look like a solution.


-   while ((c = getopt_long(argc, argv, "D:nNPR", long_options,
    &option_index)) != -1)
+   while ((c = getopt_long(argc, argv, "D:nNPRc", long_options,
    &option_index)) != -1)
Alphabetical order here.


Done.


+       rmdir($node_master->archive_dir);
rmtree() is used in all our other tests.


Done. There was an unobvious logic that rmdir only deletes empty directories, which is true in the case of archive_dir in that test, but I have unified it for consistency.


+               pg_log_error("archive file \"%s\" has wrong size: %lu
instead of %lu, %s",
+ xlogfname, (unsigned long) stat_buf.st_size, + (unsigned long) expectedSize, strerror(errno));
I think that the error message should be reworded: "unexpected WAL
file size for \"%s\": %lu instead of %lu".  Please note that there is
no need for strerror() here at all, as errno should be 0.

+    if (xlogfd < 0)
+ pg_log_error("could not open file \"%s\" restored from archive: %s\n",
+                     xlogpath, strerror(errno));
[...]
+ pg_log_error("could not stat file \"%s\" restored from archive: %s",
+                        xlogpath, strerror(errno));
No need for strerror() as you can just use %m.  And no need for the
extra newline at the end as pg_log_* routines do that by themselves.

+   pg_log_error("could not restore file \"%s\" from archive\n",
+                xlogfname);
No need for a newline here.


Thanks, I have cleaned up these log statements.


--
Alexey Kondratov

Postgres Professional https://www.postgrespro.com
The Russian Postgres Company

From ba20808ffddf3fe2eefe96d3385697fb6583ce9a Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorot...@postgresql.org>
Date: Tue, 25 Feb 2020 02:22:45 +0300
Subject: [PATCH v20] pg_rewind: Add options to restore WAL files from archive

    Currently, pg_rewind fails when it could not find required WAL files in the
    target data directory.  One have to manually figure out which WAL files are
    required and copy them back from archive.

    This commit implements new pg_rewind options, which allow pg_rewind to
    automatically retrieve missing WAL files from archival storage. The
    restore_command option is read from postgresql.conf.

    Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1%40postgrespro.ru
    Author: Alexey Kondratov
    Reviewed-by: Michael Paquier, Andrey Borodin, Alvaro Herrera
    Reviewed-by: Andres Freund, Alexander Korotkov
---
 doc/src/sgml/ref/pg_rewind.sgml          |  28 ++++--
 src/backend/access/transam/xlogarchive.c |  60 ++----------
 src/bin/pg_rewind/parsexlog.c            |  33 ++++++-
 src/bin/pg_rewind/pg_rewind.c            |  77 ++++++++++++++-
 src/bin/pg_rewind/pg_rewind.h            |   6 +-
 src/bin/pg_rewind/t/001_basic.pl         |   3 +-
 src/bin/pg_rewind/t/RewindTest.pm        |  66 ++++++++++++-
 src/common/Makefile                      |   2 +
 src/common/archive.c                     | 102 ++++++++++++++++++++
 src/common/fe_archive.c                  | 114 +++++++++++++++++++++++
 src/include/common/archive.h             |  22 +++++
 src/include/common/fe_archive.h          |  19 ++++
 src/tools/msvc/Mkvcbuild.pm              |   8 +-
 13 files changed, 460 insertions(+), 80 deletions(-)
 create mode 100644 src/common/archive.c
 create mode 100644 src/common/fe_archive.c
 create mode 100644 src/include/common/archive.h
 create mode 100644 src/include/common/fe_archive.h

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 42d29edd4e..64a6942031 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -66,11 +66,11 @@ PostgreSQL documentation
    can be found either on the target timeline, the source timeline, or their common
    ancestor. In the typical failover scenario where the target cluster was
    shut down soon after the divergence, this is not a problem, but if the
-   target cluster ran for a long time after the divergence, the old WAL
-   files might no longer be present. In that case, they can be manually
-   copied from the WAL archive to the <filename>pg_wal</filename> directory, or
-   fetched on startup by configuring <xref linkend="guc-primary-conninfo"/> or
-   <xref linkend="guc-restore-command"/>.  The use of
+   target cluster ran for a long time after the divergence, its old WAL
+   files might no longer be present. In this case, you can manually copy them
+   from the WAL archive to the <filename>pg_wal</filename> directory, or run
+   <application>pg_rewind</application> with the <literal>-c</literal> option to
+   automatically retrieve them from the WAL archive. The use of
    <application>pg_rewind</application> is not limited to failover, e.g.  a standby
    server can be promoted, run some write transactions, and then rewinded
    to become a standby again.
@@ -232,6 +232,19 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-c</option></term>
+      <term><option>--restore-target-wal</option></term>
+      <listitem>
+       <para>
+        Use the <varname>restore_command</varname> defined in
+        the target cluster configuration to retrieve WAL files from
+        the WAL archive if these files are no longer available in the
+        <filename>pg_wal</filename> directory.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
@@ -318,7 +331,10 @@ GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, b
       history forked off from the target cluster. For each WAL record,
       record each data block that was touched. This yields a list of all
       the data blocks that were changed in the target cluster, after the
-      source cluster forked off.
+      source cluster forked off. If some of the WAL files are no longer
+      available, try re-running <application>pg_rewind</application> with
+      the <option>-c</option> option to search for the missing files in
+      the WAL archive.
      </para>
     </step>
     <step>
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 188b73e752..f78a7e8f02 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -21,6 +21,7 @@
 
 #include "access/xlog.h"
 #include "access/xlog_internal.h"
+#include "common/archive.h"
 #include "miscadmin.h"
 #include "postmaster/startup.h"
 #include "replication/walsender.h"
@@ -55,9 +56,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 	char		xlogpath[MAXPGPATH];
 	char		xlogRestoreCmd[MAXPGPATH];
 	char		lastRestartPointFname[MAXPGPATH];
-	char	   *dp;
-	char	   *endp;
-	const char *sp;
 	int			rc;
 	struct stat stat_buf;
 	XLogSegNo	restartSegNo;
@@ -149,58 +147,12 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 	else
 		XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
 
-	/*
-	 * construct the command to be executed
-	 */
-	dp = xlogRestoreCmd;
-	endp = xlogRestoreCmd + MAXPGPATH - 1;
-	*endp = '\0';
+	rc = BuildRestoreCommand(recoveryRestoreCommand, xlogpath, xlogfname,
+							 lastRestartPointFname, xlogRestoreCmd);
 
-	for (sp = recoveryRestoreCommand; *sp; sp++)
-	{
-		if (*sp == '%')
-		{
-			switch (sp[1])
-			{
-				case 'p':
-					/* %p: relative path of target file */
-					sp++;
-					StrNCpy(dp, xlogpath, endp - dp);
-					make_native_path(dp);
-					dp += strlen(dp);
-					break;
-				case 'f':
-					/* %f: filename of desired file */
-					sp++;
-					StrNCpy(dp, xlogfname, endp - dp);
-					dp += strlen(dp);
-					break;
-				case 'r':
-					/* %r: filename of last restartpoint */
-					sp++;
-					StrNCpy(dp, lastRestartPointFname, endp - dp);
-					dp += strlen(dp);
-					break;
-				case '%':
-					/* convert %% to a single % */
-					sp++;
-					if (dp < endp)
-						*dp++ = *sp;
-					break;
-				default:
-					/* otherwise treat the % as not special */
-					if (dp < endp)
-						*dp++ = *sp;
-					break;
-			}
-		}
-		else
-		{
-			if (dp < endp)
-				*dp++ = *sp;
-		}
-	}
-	*dp = '\0';
+	if (rc != 0)
+		/* Sanity check, should never happen. */
+		elog(ERROR, "failed to build restore_command due to missing parameters");
 
 	ereport(DEBUG3,
 			(errmsg_internal("executing restore command \"%s\"",
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index eb61cb8803..16ae668f16 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_control.h"
 #include "catalog/storage_xlog.h"
 #include "commands/dbcommands_xlog.h"
+#include "common/fe_archive.h"
 #include "filemap.h"
 #include "pg_rewind.h"
 
@@ -41,6 +42,7 @@ static char xlogfpath[MAXPGPATH];
 
 typedef struct XLogPageReadPrivate
 {
+	const char *restoreCommand;
 	int			tliIndex;
 } XLogPageReadPrivate;
 
@@ -55,7 +57,7 @@ static int	SimpleXLogPageRead(XLogReaderState *xlogreader,
  */
 void
 extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
-			   XLogRecPtr endpoint)
+			   XLogRecPtr endpoint, const char *restore_command)
 {
 	XLogRecord *record;
 	XLogReaderState *xlogreader;
@@ -63,6 +65,7 @@ extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
 	XLogPageReadPrivate private;
 
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restore_command;
 	xlogreader = XLogReaderAllocate(WalSegSz, datadir, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -146,7 +149,7 @@ readOneRecord(const char *datadir, XLogRecPtr ptr, int tliIndex)
 void
 findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 				   XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
-				   XLogRecPtr *lastchkptredo)
+				   XLogRecPtr *lastchkptredo, const char *restoreCommand)
 {
 	/* Walk backwards, starting from the given record */
 	XLogRecord *record;
@@ -170,6 +173,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 	}
 
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	xlogreader = XLogReaderAllocate(WalSegSz, datadir, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -281,8 +285,29 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
 
 		if (xlogreadfd < 0)
 		{
-			pg_log_error("could not open file \"%s\": %m", xlogfpath);
-			return -1;
+			/*
+			 * If we have no restore_command to execute, then exit.
+			 */
+			if (private->restoreCommand == NULL)
+			{
+				pg_log_error("could not open file \"%s\": %m", xlogfpath);
+				return -1;
+			}
+
+			/*
+			 * Since we have restore_command, then try to retrieve
+			 * missing WAL file from the archive.
+			 */
+			xlogreadfd = RestoreArchivedWALFile(xlogreader->segcxt.ws_dir,
+												xlogfname,
+												WalSegSz,
+												private->restoreCommand);
+
+			if (xlogreadfd < 0)
+				return -1;
+			else
+				pg_log_debug("using file \"%s\" restored from archive",
+							 xlogfpath);
 		}
 	}
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c6d00bb0ab..290c71ce71 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -53,11 +53,13 @@ int			WalSegSz;
 char	   *datadir_target = NULL;
 char	   *datadir_source = NULL;
 char	   *connstr_source = NULL;
+char	   *restore_command = NULL;
 
 static bool debug = false;
 bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
+bool		restore_wal = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -74,6 +76,8 @@ usage(const char *progname)
 	printf(_("%s resynchronizes a PostgreSQL cluster with another copy of the cluster.\n\n"), progname);
 	printf(_("Usage:\n  %s [OPTION]...\n\n"), progname);
 	printf(_("Options:\n"));
+	printf(_("  -c, --restore-target-wal       use restore_command in target config\n"));
+	printf(_("                                 to retrieve WAL files from archive\n"));
 	printf(_("  -D, --target-pgdata=DIRECTORY  existing data directory to modify\n"));
 	printf(_("      --source-pgdata=DIRECTORY  source data directory to synchronize with\n"));
 	printf(_("      --source-server=CONNSTR    source server to synchronize with\n"));
@@ -105,6 +109,7 @@ main(int argc, char **argv)
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
+		{"restore-target-wal", no_argument, NULL, 'c'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
@@ -143,7 +148,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNPR", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "cD:nNPR", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -151,6 +156,10 @@ main(int argc, char **argv)
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
 				exit(1);
 
+			case 'c':
+				restore_wal = true;
+				break;
+
 			case 'P':
 				showprogress = true;
 				break;
@@ -252,6 +261,65 @@ main(int argc, char **argv)
 		exit(1);
 	}
 
+	if (restore_wal)
+	{
+		int			rc;
+		char		postgres_exec_path[MAXPGPATH],
+					postgres_cmd[MAXPGPATH],
+					cmd_output[MAXPGPATH];
+		FILE	   *output_fp;
+
+		/* Find postgres executable. */
+		rc = find_other_exec(argv[0], "postgres",
+							 PG_BACKEND_VERSIONSTR,
+							 postgres_exec_path);
+
+		if (rc < 0)
+		{
+			char		full_path[MAXPGPATH];
+
+			if (find_my_exec(argv[0], full_path) < 0)
+				strlcpy(full_path, progname, sizeof(full_path));
+
+			if (rc == -1)
+				pg_log_error("The program \"postgres\" is needed by %s but was not found in the\n"
+							 "same directory as \"%s\".\n"
+							 "Check your installation.",
+							 progname, full_path);
+			else
+				pg_log_error("The program \"postgres\" was found by \"%s\"\n"
+							 "but was not the same version as %s.\n"
+							 "Check your installation.",
+							 full_path, progname);
+			exit(1);
+		}
+
+		/*
+		 * Build a command to execute for restore_command GUC retrieval
+		 * if set.
+		 */
+		snprintf(postgres_cmd, sizeof(postgres_cmd), "\"%s\" -D \"%s\" -C restore_command",
+				 postgres_exec_path, datadir_target);
+
+		if ((output_fp = popen(postgres_cmd, "r")) == NULL ||
+			fgets(cmd_output, sizeof(cmd_output), output_fp) == NULL)
+			pg_fatal("could not get restore_command using %s: %s",
+					 postgres_cmd, strerror(errno));
+
+		pclose(output_fp);
+
+		/* Remove trailing newline */
+		if (strchr(cmd_output, '\n') != NULL)
+			*strchr(cmd_output, '\n') = '\0';
+
+		if (!strcmp(cmd_output, ""))
+			pg_fatal("restore_command is not set on the target cluster");
+
+		restore_command = pg_strdup(cmd_output);
+
+		pg_log_debug("using config variable restore_command=\'%s\'", restore_command);
+	}
+
 	umask(pg_mode_mask);
 
 	atexit(disconnect_atexit);
@@ -349,9 +417,8 @@ main(int argc, char **argv)
 		exit(0);
 	}
 
-	findLastCheckpoint(datadir_target, divergerec,
-					   lastcommontliIndex,
-					   &chkptrec, &chkpttli, &chkptredo);
+	findLastCheckpoint(datadir_target, divergerec, lastcommontliIndex,
+					   &chkptrec, &chkpttli, &chkptredo, restore_command);
 	pg_log_info("rewinding from last common checkpoint at %X/%X on timeline %u",
 				(uint32) (chkptrec >> 32), (uint32) chkptrec,
 				chkpttli);
@@ -377,7 +444,7 @@ main(int argc, char **argv)
 	if (showprogress)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
-				   ControlFile_target.checkPoint);
+				   ControlFile_target.checkPoint, restore_command);
 	filemap_finalize();
 
 	if (showprogress)
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h
index e4e8d23c32..b122ae43e5 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -42,11 +42,13 @@ extern uint64 fetch_done;
 
 /* in parsexlog.c */
 extern void extractPageMap(const char *datadir, XLogRecPtr startpoint,
-						   int tliIndex, XLogRecPtr endpoint);
+						   int tliIndex, XLogRecPtr endpoint,
+						   const char *restoreCommand);
 extern void findLastCheckpoint(const char *datadir, XLogRecPtr searchptr,
 							   int tliIndex,
 							   XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
-							   XLogRecPtr *lastchkptredo);
+							   XLogRecPtr *lastchkptredo,
+							   const char *restoreCommand);
 extern XLogRecPtr readOneRecord(const char *datadir, XLogRecPtr ptr,
 								int tliIndex);
 
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 95d8ccfced..d97e437741 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 20;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -171,5 +171,6 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 82fa220ac8..842782b146 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -38,6 +38,7 @@ use File::Copy;
 use File::Path qw(rmtree);
 use IPC::Run qw(run);
 use PostgresNode;
+use RecursiveCopy;
 use TestLib;
 use Test::More;
 
@@ -227,10 +228,23 @@ sub run_pg_rewind
 	# Append the rewind-specific role to the connection string.
 	$standby_connstr = "$standby_connstr user=rewind_user";
 
-	# Stop the master and be ready to perform the rewind.  The cluster
-	# needs recovery to finish once, and pg_rewind makes sure that it
-	# happens automatically.
-	$node_master->stop('immediate');
+	if ($test_mode eq 'archive')
+	{
+		# We test pg_rewind with restore_command by simply moving all WAL files
+		# to another location.  It leads to failed ensureCleanShutdown
+		# execution.  Since it is difficult to emulate a situation, when
+		# keeping the last WAL segment is enough for startup recovery, but
+		# not enough for successful pg_rewind run, we run these modes with
+		# --no-ensure-shutdown.  So stop the master gracefully.
+		$node_master->stop;
+	}
+	else
+	{
+		# Stop the master and be ready to perform the rewind.  The cluster
+		# needs recovery to finish once, and pg_rewind makes sure that it
+		# happens automatically.
+		$node_master->stop('immediate');
+	}
 
 	# At this point, the rewind processing is ready to run.
 	# We now have a very simple scenario with a few diverged WAL record.
@@ -284,6 +298,50 @@ sub run_pg_rewind
 		$node_standby->safe_psql('postgres',
 			"ALTER ROLE rewind_user WITH REPLICATION;");
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WAL archive.
+		# Old master should be stopped at this point.
+
+		# First, remove archive_dir, since RecursiveCopy::copypath
+		# does not support copying to existing directories.
+		# It should be empty in this test, so it is safe.
+		rmtree($node_master->archive_dir);
+
+		# Move all old master WAL files to the archive.
+		RecursiveCopy::copypath($node_master->data_dir . "/pg_wal",
+			$node_master->archive_dir);
+
+		# Fast way to remove entire directory content
+		rmtree($node_master->data_dir . "/pg_wal");
+		mkdir($node_master->data_dir . "/pg_wal");
+
+		# Ensure directories have right modes.  It's required by
+		# 'check PGDATA permissions' test.
+		chmod(0700, $node_master->archive_dir);
+		chmod(0700, $node_master->data_dir . "/pg_wal");
+
+		# Just to append an appropriate restore_command
+		# to postgresql.conf
+		$node_master->enable_restoring($node_master, 0);
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"--no-ensure-shutdown",
+				"-c"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 
diff --git a/src/common/Makefile b/src/common/Makefile
index ce01df68b9..a97c723fbd 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -46,6 +46,7 @@ LIBS += $(PTHREAD_LIBS)
 # If you add objects here, see also src/tools/msvc/Mkvcbuild.pm
 
 OBJS_COMMON = \
+	archive.o \
 	base64.o \
 	config_info.o \
 	controldata_utils.o \
@@ -87,6 +88,7 @@ endif
 # (Mkvcbuild.pm has a copy of this list, too)
 OBJS_FRONTEND = \
 	$(OBJS_COMMON) \
+	fe_archive.o \
 	fe_memutils.o \
 	file_utils.o \
 	logging.o \
diff --git a/src/common/archive.c b/src/common/archive.c
new file mode 100644
index 0000000000..8c79044a6b
--- /dev/null
+++ b/src/common/archive.c
@@ -0,0 +1,102 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive.c
+ *	  Common WAL archive routines
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/common/archive.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/archive.h"
+
+/*
+ * Constructs restore_command from template with %p, %f and %r aliases.
+ * Returns 0 if restore_command was successfuly built.
+ *
+ * If any of the required arguments is NULL, but corresponding alias is
+ * met, then -1 code will be returned.
+ */
+int
+BuildRestoreCommand(const char *restoreCommand,
+					const char *xlogpath,
+					const char *xlogfname,
+					const char *lastRestartPointFname,
+					char *result)
+{
+	char	   *dp,
+			   *endp;
+	const char *sp;
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = result;
+	endp = result + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *sp; sp++)
+	{
+		if (*sp == '%')
+		{
+			switch (sp[1])
+			{
+				case 'p':
+					/* %p: relative path of target file */
+					if (xlogpath == NULL)
+						return -1;
+					sp++;
+					StrNCpy(dp, xlogpath, endp - dp);
+					make_native_path(dp);
+					dp += strlen(dp);
+					break;
+				case 'f':
+					/* %f: filename of desired file */
+					if (xlogfname == NULL)
+						return -1;
+					sp++;
+					StrNCpy(dp, xlogfname, endp - dp);
+					dp += strlen(dp);
+					break;
+				case 'r':
+					/* %r: filename of last restartpoint */
+					if (lastRestartPointFname == NULL)
+						return -1;
+					sp++;
+					StrNCpy(dp, lastRestartPointFname, endp - dp);
+					dp += strlen(dp);
+					break;
+				case '%':
+					/* convert %% to a single % */
+					sp++;
+					if (dp < endp)
+						*dp++ = *sp;
+					break;
+				default:
+					/* otherwise treat the % as not special */
+					if (dp < endp)
+						*dp++ = *sp;
+					break;
+			}
+		}
+		else
+		{
+			if (dp < endp)
+				*dp++ = *sp;
+		}
+	}
+	*dp = '\0';
+
+	return 0;
+}
diff --git a/src/common/fe_archive.c b/src/common/fe_archive.c
new file mode 100644
index 0000000000..ea47d86494
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,114 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.c
+ *	  Routines to access WAL archive from frontend
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/common/fe_archive.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#error "This file is not expected to be compiled for backend code"
+#endif
+
+#include "postgres_fe.h"
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/fe_archive.h"
+#include "common/logging.h"
+
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return a file descriptor of the restored WAL file, else
+ * return -1.
+ *
+ * For fixed-size files, the caller may pass the expected size as an
+ * additional crosscheck on successful recovery.  If the file size is not
+ * known, set expectedSize = 0.
+ */
+int
+RestoreArchivedWALFile(const char *path, const char *xlogfname,
+					   off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH],
+				xlogRestoreCmd[MAXPGPATH];
+	int			rc,
+				xlogfd;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	rc = BuildRestoreCommand(restoreCommand, xlogpath, xlogfname, NULL, xlogRestoreCmd);
+
+	if (rc < 0)
+	{
+		pg_log_fatal("restore_command with %%r alias cannot be used.");
+		exit(1);
+	}
+
+	/*
+	 * Execute restore_command, which should copy the missing WAL file from
+	 * archival storage.
+	 */
+	rc = system(xlogRestoreCmd);
+
+	if (rc == 0)
+	{
+		/*
+		 * Command apparently succeeded, but let's make sure the file is
+		 * really there now and has the correct size.
+		 */
+		if (stat(xlogpath, &stat_buf) == 0)
+		{
+			if (expectedSize > 0 && stat_buf.st_size != expectedSize)
+			{
+				pg_log_error("unexpected WAL file size for \"%s\": %lu instead of %lu",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize);
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open file \"%s\" restored from archive: %m",
+								 xlogpath);
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\" restored from archive: %m",
+						 xlogpath);
+		}
+	}
+
+	/*
+	 * If the failure was due to any sort of signal, then it will be
+	 * misleading to return message 'could not restore file...' and propagate
+	 * result to the upper levels.  We should exit right now.
+	 */
+	if (wait_result_is_any_signal(rc, false))
+	{
+		pg_log_fatal("restore_command failed due to the signal: %s",
+					 wait_result_to_str(rc));
+		exit(1);
+	}
+
+	pg_log_error("could not restore file \"%s\" from archive",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/include/common/archive.h b/src/include/common/archive.h
new file mode 100644
index 0000000000..3e6e143cd8
--- /dev/null
+++ b/src/include/common/archive.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive.h
+ *	  Common WAL archive routines
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ARCHIVE_H
+#define ARCHIVE_H
+
+extern int	BuildRestoreCommand(const char *restoreCommand,
+								const char *xlogpath,
+								const char *xlogfname,
+								const char *lastRestartPointFname,
+								char *result);
+
+#endif							/* ARCHIVE_H */
diff --git a/src/include/common/fe_archive.h b/src/include/common/fe_archive.h
new file mode 100644
index 0000000000..af7763ebac
--- /dev/null
+++ b/src/include/common/fe_archive.h
@@ -0,0 +1,19 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.h
+ *	  Routines to access WAL archive from frontend
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/fe_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FE_ARCHIVE_H
+#define FE_ARCHIVE_H
+
+extern int	RestoreArchivedWALFile(const char *path, const char *xlogfname,
+								   off_t expectedSize, const char *restoreCommand);
+
+#endif							/* FE_ARCHIVE_H */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 834c2c39d1..25da1fa8ec 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -119,8 +119,8 @@ sub mkvcbuild
 	}
 
 	our @pgcommonallfiles = qw(
-	  base64.c config_info.c controldata_utils.c d2s.c encnames.c exec.c
-	  f2s.c file_perm.c hashfn.c ip.c jsonapi.c
+	  archive.c base64.c config_info.c controldata_utils.c d2s.c encnames.c
+	  exec.c f2s.c file_perm.c hashfn.c ip.c jsonapi.c
 	  keywords.c kwlookup.c link-canary.c md5.c
 	  pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
 	  saslprep.c scram-common.c string.c stringinfo.c unicode_norm.c username.c
@@ -137,8 +137,8 @@ sub mkvcbuild
 	}
 
 	our @pgcommonfrontendfiles = (
-		@pgcommonallfiles, qw(fe_memutils.c file_utils.c
-		  logging.c restricted_token.c));
+		@pgcommonallfiles, qw(fe_archive.c fe_memutils.c
+		  file_utils.c logging.c restricted_token.c));
 
 	our @pgcommonbkndfiles = @pgcommonallfiles;
 

base-commit: afb5465e0cfce7637066eaaaeecab30b0f23fbe3
-- 
2.19.1

Reply via email to