[Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Started by Alexey Kondratovabout 7 years ago100 messages
#1Alexey Kondratov
a.kondratov@postgrespro.ru
1 attachment(s)

Hi hackers,

Currently Postgres has options for continuous WAL files archiving, which
is quite often used along with master-replica setup. OK, then the worst
is happened and it's time to get your old master back and synchronize it
with new master (ex-replica) with pg_rewind. However, required WAL files
may be already archived and pg_rewind will fail. You can copy these
files manually, but it is difficult to calculate, which ones you need.
Anyway, it complicates building failover system with automatic failure
recovery.

I expect, that it will be a good idea to allow pg_rewind to look for a
restore_command in the target data directory recovery.conf or pass it is
as a command line argument. Then pg_rewind can use it to get missing WAL
files from the archive. I had a few talks with DBAs and came to
conclusion, that this is a highly requested feature.

I prepared a proof of concept patch (please, find attached), which does
exactly what I described above. I played with it a little and it seems
to be working, tests were accordingly updated to verify this archive
retrieval functionality too.

Patch is relatively simple excepting the one part: if we want to parse
recovery.conf (with all possible includes, etc.) and get
restore_command, then we should use guc-file.l parser, which is heavily
linked to backend, e.g. in error reporting part. So I copied it and made
frontend-safe version guc-file-fe.l. Personally, I don't think it's a
good idea, but nothing else came to mind. It is also possible to leave
the only one option -- passing restore_command as command line argument.

What do you think?

--

Alexey Kondratov

Postgres Professional: https://www.postgrespro.com

Russian Postgres Company

Attachments:

pg_rewind-restore_command-v1.0.patchtext/x-patch; name=pg_rewind-restore_command-v1.0.patchDownload
diff --combined src/bin/pg_rewind/Makefile
index a22fef1352,2bcfcc61af..0000000000
--- a/src/bin/pg_rewind/Makefile
+++ b/src/bin/pg_rewind/Makefile
@@@ -20,7 -20,6 +20,7 @@@ LDFLAGS_INTERNAL += $(libpq_pgport
  
  OBJS	= pg_rewind.o parsexlog.o xlogreader.o datapagemap.o timeline.o \
  	fetch.o file_ops.o copy_fetch.o libpq_fetch.o filemap.o logging.o \
 +	guc-file-fe.o \
  	$(WIN32RES)
  
  EXTRA_CLEAN = xlogreader.c
diff --combined src/bin/pg_rewind/RewindTest.pm
index 8dc39dbc05,1dce56d035..0000000000
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@@ -40,7 -40,6 +40,7 @@@ use Config
  use Exporter 'import';
  use File::Copy;
  use File::Path qw(rmtree);
 +use File::Glob;
  use IPC::Run qw(run);
  use PostgresNode;
  use TestLib;
@@@ -249,41 -248,6 +249,41 @@@ sub run_pg_rewin
  				"--no-sync"
  			],
  			'pg_rewind remote');
 +	}
 +	elsif ($test_mode eq "archive")
 +	{
 +
 +		# Do rewind using a local pgdata as source and
 +		# specified directory with target WALs archive.
 +		my $wals_archive_dir = "${TestLib::tmp_check}/master_wals_archive";
 +		my $test_master_datadir = $node_master->data_dir;
 +		my @wal_files = glob "$test_master_datadir/pg_wal/0000000*";
 +		my $restore_command;
 +
 +		rmtree($wals_archive_dir);
 +		mkdir($wals_archive_dir) or die;
 +
 +		# Move all old master WAL files to the archive.
 +		# Old master should be stopped at this point.
 +		foreach my $wal_file (@wal_files)
 +		{
 +			move($wal_file, "$wals_archive_dir/") or die;
 +		}
 +
 +		$restore_command = "cp $wals_archive_dir/\%f \%p";
 +
 +		# 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",
 +				"-R", $restore_command
 +			],
 +			'pg_rewind archive');
  	}
  	else
  	{
diff --combined src/bin/pg_rewind/parsexlog.c
index 11a9c26cd2,40028471bf..0000000000
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@@ -12,7 -12,6 +12,7 @@@
  #include "postgres_fe.h"
  
  #include <unistd.h>
 +#include <sys/stat.h>
  
  #include "pg_rewind.h"
  #include "filemap.h"
@@@ -46,10 -45,7 +46,10 @@@ static char xlogfpath[MAXPGPATH]
  typedef struct XLogPageReadPrivate
  {
  	const char *datadir;
 +	const char *restoreCommand;
  	int			tliIndex;
 +	XLogRecPtr  oldrecptr;
 +	TimeLineID  oldtli;
  } XLogPageReadPrivate;
  
  static int SimpleXLogPageRead(XLogReaderState *xlogreader,
@@@ -57,10 -53,6 +57,10 @@@
  				   int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
  				   TimeLineID *pageTLI);
  
 +static bool RestoreArchivedWAL(const char *path, const char *xlogfname, 
 +					off_t expectedSize, const char *restoreCommand,
 +					const char *lastRestartPointFname);
 +
  /*
   * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
   * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@@ -68,19 -60,15 +68,19 @@@
   */
  void
  extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
 -			   XLogRecPtr endpoint)
 +			   ControlFileData *targetCF, const char *restore_command)
  {
  	XLogRecord *record;
 +	XLogRecPtr endpoint = targetCF->checkPoint;
  	XLogReaderState *xlogreader;
  	char	   *errormsg;
  	XLogPageReadPrivate private;
  
  	private.datadir = datadir;
  	private.tliIndex = tliIndex;
 +	private.restoreCommand = restore_command;
 +	private.oldrecptr = targetCF->checkPointCopy.redo;
 +	private.oldtli = targetCF->checkPointCopy.ThisTimeLineID;
  	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
  									&private);
  	if (xlogreader == NULL)
@@@ -166,9 -154,9 +166,9 @@@ readOneRecord(const char *datadir, XLog
   * Find the previous checkpoint preceding given WAL location.
   */
  void
 -findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 +findLastCheckpoint(const char *datadir, ControlFileData *targetCF, XLogRecPtr forkptr, int tliIndex,
  				   XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
 -				   XLogRecPtr *lastchkptredo)
 +				   XLogRecPtr *lastchkptredo, const char *restoreCommand)
  {
  	/* Walk backwards, starting from the given record */
  	XLogRecord *record;
@@@ -193,9 -181,6 +193,9 @@@
  
  	private.datadir = datadir;
  	private.tliIndex = tliIndex;
 +	private.restoreCommand = restoreCommand;
 +	private.oldrecptr = targetCF->checkPointCopy.redo;
 +	private.oldtli = targetCF->checkPointCopy.ThisTimeLineID;
  	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
  									&private);
  	if (xlogreader == NULL)
@@@ -306,53 -291,9 +306,53 @@@ SimpleXLogPageRead(XLogReaderState *xlo
  
  		if (xlogreadfd < 0)
  		{
 -			printf(_("could not open file \"%s\": %s\n"), xlogfpath,
 +			bool  restore_ok;
 +			char  lastRestartPointFname[MAXFNAMELEN];
 +			XLogSegNo restartSegNo;
 +
 +			/*
 +			 * If we have no restore_command to execute, then exit.
 +			 */
 +			if (private->restoreCommand == NULL)
 +			{
 +				printf(_("could not open file \"%s\": %s\n"), xlogfpath,
  				   strerror(errno));
 -			return -1;
 +				return -1;
 +			}
 +			
 +			XLByteToSeg(private->oldrecptr, restartSegNo, WalSegSz);
 +			XLogFileName(lastRestartPointFname, private->oldtli, restartSegNo,
 +						 WalSegSz);
 +
 +			/*
 +			 * Since we have restore_command to execute, then try to retreive
 +			 * missing WAL file from the archive.
 +			 */
 +			restore_ok = RestoreArchivedWAL(private->datadir,
 +											xlogfname,
 +											WalSegSz,
 +											private->restoreCommand,
 +											lastRestartPointFname);
 +
 +			if (restore_ok)
 +			{
 +				xlogreadfd = open(xlogfpath, O_RDONLY | PG_BINARY, 0);
 +
 +				if (xlogreadfd < 0)
 +				{
 +					printf(_("could not open restored from archive file \"%s\": %s\n"), xlogfpath,
 +				    	   strerror(errno));
 +				    return -1;
 +				}
 +				else
 +					pg_log(PG_DEBUG, "using restored from archive version of file \"%s\"\n", xlogfpath);
 +			}
 +			else
 +			{
 +				printf(_("could not restore file \"%s\" from archive: %s\n"), xlogfname,
 +					   strerror(errno));
 +				return -1;
 +			}
  		}
  	}
  
@@@ -468,116 -409,3 +468,116 @@@ extractPageInfo(XLogReaderState *record
  		process_block_change(forknum, rnode, blkno);
  	}
  }
 +
 +/*
 + * Attempt to retrieve the specified file from off-line archival storage.
 + * If successful return true.
 + *
 + * 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.
 + * 
 + * This is an adapted to frontend version of
 + * RestoreArchivedFile function from transam/xlogarchive.c
 + */
 +bool
 +RestoreArchivedWAL(const char *path, const char *xlogfname, off_t expectedSize,
 +				   const char *restoreCommand, const char *lastRestartPointFname)
 +{
 +	char		xlogpath[MAXPGPATH];
 +	char		xlogRestoreCmd[MAXPGPATH];
 +	char	   *dp;
 +	char	   *endp;
 +	const char *sp;
 +	int			rc;
 +	struct stat stat_buf;
 +
 +	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
 +
 +	/*
 +	 * Construct the command to be executed.
 +	 */
 +	dp = xlogRestoreCmd;
 +	endp = xlogRestoreCmd + MAXPGPATH - 1;
 +	*endp = '\0';
 +
 +	for (sp = restoreCommand; *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';
 +
 +	/*
 +	 * 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)
 +			{
 +				printf(_("archive file \"%s\" has wrong size: %lu instead of %lu, %s"),
 +					   	xlogfname, (unsigned long) stat_buf.st_size, (unsigned long) expectedSize,
 +					   	strerror(errno));
 +			}
 +			else
 +				return true;
 +		}
 +		else
 +		{
 +			/* Stat failed */
 +			printf(_("could not stat file \"%s\": %s"),
 +				   	xlogpath,
 +					strerror(errno));
 +		}
 +	}
 +
 +	return false;
 +}
diff --combined src/bin/pg_rewind/pg_rewind.c
index 3acac817eb,9653106386..0000000000
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@@ -19,13 -19,11 +19,13 @@@
  #include "file_ops.h"
  #include "filemap.h"
  #include "logging.h"
 +#include "guc-file-fe.h"
  
  #include "access/timeline.h"
  #include "access/xlog_internal.h"
  #include "catalog/catversion.h"
  #include "catalog/pg_control.h"
 +#include "common/controldata_utils.h"
  #include "common/file_perm.h"
  #include "common/file_utils.h"
  #include "common/restricted_token.h"
@@@ -54,13 -52,11 +54,13 @@@ int			WalSegSz
  char	   *datadir_target = NULL;
  char	   *datadir_source = NULL;
  char	   *connstr_source = NULL;
 +char	   *restore_command = NULL;
  
  bool		debug = false;
  bool		showprogress = false;
  bool		dry_run = false;
  bool		do_sync = true;
 +bool		restore_wals = false;
  
  /* Target history */
  TimeLineHistoryEntry *targetHistory;
@@@ -79,9 -75,6 +79,9 @@@ usage(const char *progname
  	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"));
  	printf(_("                                 safely to disk\n"));
  	printf(_("  -P, --progress                 write progress messages\n"));
 +	printf(_("  -r, --use-recovery-conf        use restore_command in the recovery.conf to\n"));
 +	printf(_("                                 retreive WALs from archive\n"));
 +	printf(_("  -R, --restore-command=COMMAND  restore command\n"));
  	printf(_("      --debug                    write a lot of debug messages\n"));
  	printf(_("  -V, --version                  output version information, then exit\n"));
  	printf(_("  -?, --help                     show this help, then exit\n"));
@@@ -101,12 -94,9 +101,12 @@@ main(int argc, char **argv
  		{"dry-run", no_argument, NULL, 'n'},
  		{"no-sync", no_argument, NULL, 'N'},
  		{"progress", no_argument, NULL, 'P'},
 +		{"use-recovery-conf", no_argument, NULL, 'r'},
 +		{"restore-command", required_argument, NULL, 'R'},
  		{"debug", no_argument, NULL, 3},
  		{NULL, 0, NULL, 0}
  	};
 +	char		recfile_fullpath[MAXPGPATH];
  	int			option_index;
  	int			c;
  	XLogRecPtr	divergerec;
@@@ -139,7 -129,7 +139,7 @@@
  		}
  	}
  
 -	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
 +	while ((c = getopt_long(argc, argv, "DR:nNPr", long_options, &option_index)) != -1)
  	{
  		switch (c)
  		{
@@@ -150,10 -140,6 +150,10 @@@
  			case 'P':
  				showprogress = true;
  				break;
 +			
 +			case 'r':
 +				restore_wals = true;
 +				break;
  
  			case 'n':
  				dry_run = true;
@@@ -171,10 -157,6 +171,10 @@@
  				datadir_target = pg_strdup(optarg);
  				break;
  
 +			case 'R':
 +				restore_command = pg_strdup(optarg);
 +				break;
 +
  			case 1:				/* --source-pgdata */
  				datadir_source = pg_strdup(optarg);
  				break;
@@@ -241,71 -223,6 +241,71 @@@
  
  	umask(pg_mode_mask);
  
 +	if (restore_command != NULL)
 +	{
 +		pg_log(PG_DEBUG, "using command line restore_command=\'%s\'.\n", restore_command);
 +	}
 +	else if (restore_wals)
 +	{
 +		FILE	   *recovery_conf_file;
 +
 +		/* 
 +		 * Look for recovery.conf in the target data directory and
 +		 * try to get restore_command from there.
 +		 */
 +		snprintf(recfile_fullpath, sizeof(recfile_fullpath), "%s/%s", datadir_target, RECOVERY_COMMAND_FILE);
 +		recovery_conf_file = fopen(recfile_fullpath, "r");
 +
 +		if (recovery_conf_file == NULL)
 +		{
 +			fprintf(stderr, _("%s: option -r/--use-recovery-conf is specified, but recovery.conf is absent in the target directory\n"),
 +					progname);
 +			fprintf(stdout, _("You have to add recovery.conf or pass restore_command with -R/--restore-command option.\n"));
 +			exit(1);
 +		}
 +		else
 +		{
 +			ConfigVariable *item,
 +						   *head = NULL,
 +						   *tail = NULL;
 +			bool			config_is_parsed;
 +
 +			/*
 +			 * We pass a fullpath to the recovery.conf as calling_file here, since
 +			 * parser will use its parent directory as base for all further includes
 +			 * if any exist.
 +			 */
 +			config_is_parsed = ParseConfigFile(RECOVERY_COMMAND_FILE, true,
 +											   recfile_fullpath, 0, 0,
 +											   PG_WARNING, &head, &tail);
 +			fclose(recovery_conf_file);
 +
 +			if (config_is_parsed)
 +			{
 +				for (item = head; item; item = item->next)
 +				{
 +					if (strcmp(item->name, "restore_command") == 0)
 +					{
 +						if (restore_command != NULL)
 +						{
 +							pfree(restore_command);
 +							restore_command = NULL;
 +						}
 +						restore_command = pstrdup(item->value);
 +						pg_log(PG_DEBUG, "using restore_command=\'%s\' from recovery.conf.\n", restore_command);
 +					}
 +				}
 +
 +				if (restore_command == NULL)
 +					pg_fatal("could not find restore_command in recovery.conf file %s\n", recfile_fullpath);
 +			}
 +			else
 +				pg_fatal("could not parse recovery.conf file %s\n", recfile_fullpath);
 +
 +			FreeConfigVariables(head);
 +		}
 +	}
 +
  	/* Connect to remote server */
  	if (connstr_source)
  		libpqConnect(connstr_source);
@@@ -377,9 -294,9 +377,9 @@@
  		exit(0);
  	}
  
 -	findLastCheckpoint(datadir_target, divergerec,
 +	findLastCheckpoint(datadir_target, &ControlFile_target, divergerec,
  					   lastcommontliIndex,
 -					   &chkptrec, &chkpttli, &chkptredo);
 +					   &chkptrec, &chkpttli, &chkptredo, restore_command);
  	printf(_("rewinding from last common checkpoint at %X/%X on timeline %u\n"),
  		   (uint32) (chkptrec >> 32), (uint32) chkptrec,
  		   chkpttli);
@@@ -402,7 -319,7 +402,7 @@@
  	 */
  	pg_log(PG_PROGRESS, "reading WAL in target\n");
  	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 -				   ControlFile_target.checkPoint);
 +				   &ControlFile_target, restore_command);
  	filemap_finalize();
  
  	if (showprogress)
diff --combined src/bin/pg_rewind/pg_rewind.h
index ba9d13e9bd,3f4ba7a267..0000000000
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@@ -14,7 -14,6 +14,7 @@@
  #include "datapagemap.h"
  
  #include "access/timeline.h"
 +#include "catalog/pg_control.h"
  #include "storage/block.h"
  #include "storage/relfilenode.h"
  
@@@ -33,10 -32,11 +33,10 @@@ extern int	targetNentries
  
  /* in parsexlog.c */
  extern void extractPageMap(const char *datadir, XLogRecPtr startpoint,
 -			   int tliIndex, XLogRecPtr endpoint);
 -extern void findLastCheckpoint(const char *datadir, XLogRecPtr searchptr,
 -				   int tliIndex,
 -				   XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
 -				   XLogRecPtr *lastchkptredo);
 +			   int tliIndex, ControlFileData *targetCF, const char *restoreCommand);
 +extern void findLastCheckpoint(const char *datadir, ControlFileData *targetCF, XLogRecPtr searchptr,
 +				   int tliIndex, XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
 +				   XLogRecPtr *lastchkptredo, const char *restoreCommand);
  extern XLogRecPtr readOneRecord(const char *datadir, XLogRecPtr ptr,
  			  int tliIndex);
  
diff --combined src/bin/pg_rewind/t/001_basic.pl
index 22777bff82,53dbf45be2..0000000000
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@@ -1,7 -1,7 +1,7 @@@
  use strict;
  use warnings;
  use TestLib;
 -use Test::More tests => 10;
 +use Test::More tests => 15;
  
  use RewindTest;
  
@@@ -103,6 -103,5 +103,6 @@@ in master, before promotio
  # Run the test in both modes
  run_test('local');
  run_test('remote');
 +run_test('archive');
  
  exit(0);
diff --combined src/bin/pg_rewind/t/002_databases.pl
index 10c835efc1,2c9e427831..0000000000
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@@ -1,7 -1,7 +1,7 @@@
  use strict;
  use warnings;
  use TestLib;
 -use Test::More tests => 6;
 +use Test::More tests => 9;
  
  use RewindTest;
  
@@@ -59,6 -59,5 +59,6 @@@ template
  # Run the test in both modes.
  run_test('local');
  run_test('remote');
 +run_test('archive');
  
  exit(0);
diff --combined src/bin/pg_rewind/t/003_extrafiles.pl
index f94ce329f4,496f38c457..0000000000
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@@ -3,7 -3,7 +3,7 @@@
  use strict;
  use warnings;
  use TestLib;
 -use Test::More tests => 4;
 +use Test::More tests => 6;
  
  use File::Find;
  
@@@ -87,6 -87,5 +87,6 @@@ sub run_tes
  # Run the test in both modes.
  run_test('local');
  run_test('remote');
 +run_test('archive');
  
  exit(0);
diff --git a/src/bin/pg_rewind/guc-file-fe.h b/src/bin/pg_rewind/guc-file-fe.h
new file mode 100644
index 0000000000..cf480b806a
--- /dev/null
+++ b/src/bin/pg_rewind/guc-file-fe.h
@@ -0,0 +1,40 @@
+#ifndef PG_REWIND_GUC_FILE_FE_H
+#define PG_REWIND_GUC_FILE_FE_H
+
+#include "c.h"
+
+#define RECOVERY_COMMAND_FILE	"recovery.conf"
+
+/*
+ * Parsing the configuration file(s) will return a list of name-value pairs
+ * with source location info.  We also abuse this data structure to carry
+ * error reports about the config files.  An entry reporting an error will
+ * have errmsg != NULL, and might have NULLs for name, value, and/or filename.
+ *
+ * If "ignore" is true, don't attempt to apply the item (it might be an error
+ * report, or an item we determined to be duplicate).  "applied" is set true
+ * if we successfully applied, or could have applied, the setting.
+ */
+typedef struct ConfigVariable
+{
+	char	   *name;
+	char	   *value;
+	char	   *errmsg;
+	char	   *filename;
+	int			sourceline;
+	bool		ignore;
+	bool		applied;
+	struct ConfigVariable *next;
+} ConfigVariable;
+
+extern bool ParseConfigFile(const char *config_file, bool strict,
+				const char *calling_file, int calling_lineno,
+				int depth, int elevel,
+				ConfigVariable **head_p, ConfigVariable **tail_p);
+
+extern bool ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
+				ConfigVariable **head_p, ConfigVariable **tail_p);
+
+extern void FreeConfigVariables(ConfigVariable *list);
+
+#endif							/* PG_REWIND_GUC_FILE_FE_H */
diff --git a/src/bin/pg_rewind/guc-file-fe.l b/src/bin/pg_rewind/guc-file-fe.l
new file mode 100644
index 0000000000..13882191ba
--- /dev/null
+++ b/src/bin/pg_rewind/guc-file-fe.l
@@ -0,0 +1,774 @@
+/* -*-pgsql-c-*- */
+/*
+ * Configuration files parser for usage in frontend.
+ *
+ * Copyright (c) 2000-2018, PostgreSQL Global Development Group
+ *
+ * src/bin/pg_rewind/guc-file-fe.l
+ * Modified version of src/backend/utils/misc/guc-file.l
+ */
+
+%{
+
+#include <ctype.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <setjmp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "guc-file-fe.h"
+#include "logging.h"
+
+#include "common/fe_memutils.h"
+
+/*
+ * flex emits a yy_fatal_error() function that it calls in response to
+ * critical errors like malloc failure, file I/O errors, and detection of
+ * internal inconsistency.  That function prints a message and calls exit().
+ * Mutate it to instead call our handler, which jumps out of the parser.
+ */
+#undef fprintf
+#define fprintf(file, fmt, msg) GUC_flex_fatal(msg)
+
+enum
+{
+	GUC_ID = 1,
+	GUC_STRING = 2,
+	GUC_INTEGER = 3,
+	GUC_REAL = 4,
+	GUC_EQUALS = 5,
+	GUC_UNQUOTED_STRING = 6,
+	GUC_QUALIFIED_ID = 7,
+	GUC_EOL = 99,
+	GUC_ERROR = 100
+};
+
+static unsigned int ConfigFileLineno;
+static const char *GUC_flex_fatal_errmsg;
+static sigjmp_buf *GUC_flex_fatal_jmp;
+
+static void FreeConfigVariable(ConfigVariable *item);
+
+static void record_config_file_error(const char *errmsg,
+						 const char *config_file,
+						 int lineno,
+						 ConfigVariable **head_p,
+						 ConfigVariable **tail_p);
+
+static int	GUC_flex_fatal(const char *msg);
+static char *GUC_scanstr(const char *s);
+static bool ParseConfigDirectory(const char *includedir,
+					 const char *calling_file, int calling_lineno,
+					 int depth, int elevel,
+					 ConfigVariable **head_p,
+					 ConfigVariable **tail_p);
+
+/* LCOV_EXCL_START */
+
+%}
+
+%option 8bit
+%option never-interactive
+%option nodefault
+%option noinput
+%option nounput
+%option noyywrap
+%option warn
+%option prefix="GUC_yy"
+
+
+SIGN			("-"|"+")
+DIGIT			[0-9]
+HEXDIGIT		[0-9a-fA-F]
+
+UNIT_LETTER		[a-zA-Z]
+
+INTEGER			{SIGN}?({DIGIT}+|0x{HEXDIGIT}+){UNIT_LETTER}*
+
+EXPONENT		[Ee]{SIGN}?{DIGIT}+
+REAL			{SIGN}?{DIGIT}*"."{DIGIT}*{EXPONENT}?
+
+LETTER			[A-Za-z_\200-\377]
+LETTER_OR_DIGIT [A-Za-z_0-9\200-\377]
+
+ID				{LETTER}{LETTER_OR_DIGIT}*
+QUALIFIED_ID	{ID}"."{ID}
+
+UNQUOTED_STRING {LETTER}({LETTER_OR_DIGIT}|[-._:/])*
+STRING			\'([^'\\\n]|\\.|\'\')*\'
+
+%%
+
+\n				ConfigFileLineno++; return GUC_EOL;
+[ \t\r]+		/* eat whitespace */
+#.*				/* eat comment (.* matches anything until newline) */
+
+{ID}			return GUC_ID;
+{QUALIFIED_ID}	return GUC_QUALIFIED_ID;
+{STRING}		return GUC_STRING;
+{UNQUOTED_STRING} return GUC_UNQUOTED_STRING;
+{INTEGER}		return GUC_INTEGER;
+{REAL}			return GUC_REAL;
+=				return GUC_EQUALS;
+
+.				return GUC_ERROR;
+
+%%
+
+/* LCOV_EXCL_STOP */
+
+/*
+ * the bare comparison function for GUC names
+ */
+static int
+guc_name_compare(const char *namea, const char *nameb)
+{
+	/*
+	 * The temptation to use strcasecmp() here must be resisted, because the
+	 * array ordering has to remain stable across setlocale() calls. So, build
+	 * our own with a simple ASCII-only downcasing.
+	 */
+	while (*namea && *nameb)
+	{
+		char		cha = *namea++;
+		char		chb = *nameb++;
+
+		if (cha >= 'A' && cha <= 'Z')
+			cha += 'a' - 'A';
+		if (chb >= 'A' && chb <= 'Z')
+			chb += 'a' - 'A';
+		if (cha != chb)
+			return cha - chb;
+	}
+	if (*namea)
+		return 1;				/* a is longer */
+	if (*nameb)
+		return -1;				/* b is longer */
+	return 0;
+}
+
+/*
+ * Given a configuration file or directory location that may be a relative
+ * path, return an absolute one.  We consider the location to be relative to
+ * the directory holding the calling file.
+ */
+static char *
+AbsoluteConfigLocation(const char *location, const char *calling_file)
+{
+	char abs_path[MAXPGPATH];
+
+	if (is_absolute_path(location))
+		return pstrdup(location);
+	else
+	{
+		strlcpy(abs_path, calling_file, sizeof(abs_path));
+		get_parent_directory(abs_path);
+		join_path_components(abs_path, abs_path, location);
+		canonicalize_path(abs_path);
+
+		return pstrdup(abs_path);
+	}
+}
+
+/*
+ * Read and parse a single configuration file.  This function recurses
+ * to handle "include" directives.
+ *
+ * If "strict" is true, treat failure to open the config file as an error,
+ * otherwise just skip the file.
+ *
+ * calling_file/calling_lineno identify the source of the request.
+ * Pass NULL/0 if not recursing from an inclusion request.
+ *
+ * See ParseConfigFp for further details.  This one merely adds opening the
+ * config file rather than working from a caller-supplied file descriptor,
+ * and absolute-ifying the path name if necessary.
+ */
+bool
+ParseConfigFile(const char *config_file, bool strict,
+				const char *calling_file, int calling_lineno,
+				int depth, int elevel,
+				ConfigVariable **head_p,
+				ConfigVariable **tail_p)
+{
+	char	   *abs_path;
+	bool		OK = true;
+	FILE	   *fp;
+
+	/*
+	 * Reject too-deep include nesting depth.  This is just a safety check to
+	 * avoid dumping core due to stack overflow if an include file loops back
+	 * to itself.  The maximum nesting depth is pretty arbitrary.
+	 */
+	if (depth > 10)
+	{
+		pg_log(PG_WARNING, 
+				"could not open configuration file \"%s\": maximum nesting depth exceeded\n",
+				config_file);
+		record_config_file_error("nesting depth exceeded",
+								 calling_file, calling_lineno,
+								 head_p, tail_p);
+		return false;
+	}
+
+	abs_path = AbsoluteConfigLocation(config_file, calling_file);
+	fp = fopen(abs_path, "r");
+	if (!fp)
+	{
+		if (strict)
+		{
+			pg_log(PG_WARNING, 
+				"could not open configuration file \"%s\"\n",
+				abs_path);
+			record_config_file_error(psprintf("could not open file \"%s\"",
+											  abs_path),
+									 calling_file, calling_lineno,
+									 head_p, tail_p);
+			OK = false;
+		}
+		else
+		{
+			pg_log(PG_WARNING, 
+				"skipping missing configuration file \"%s\"\n",
+				abs_path);
+		}
+		goto cleanup;
+	}
+
+	OK = ParseConfigFp(fp, abs_path, depth, elevel, head_p, tail_p);
+
+cleanup:
+	if (fp)
+		fclose(fp);
+	pfree(abs_path);
+
+	return OK;
+}
+
+/*
+ * Capture an error message in the ConfigVariable list returned by
+ * config file parsing.
+ */
+static void
+record_config_file_error(const char *errmsg,
+						 const char *config_file,
+						 int lineno,
+						 ConfigVariable **head_p,
+						 ConfigVariable **tail_p)
+{
+	ConfigVariable *item;
+
+	item = palloc(sizeof *item);
+	item->name = NULL;
+	item->value = NULL;
+	item->errmsg = pstrdup(errmsg);
+	item->filename = config_file ? pstrdup(config_file) : NULL;
+	item->sourceline = lineno;
+	item->ignore = true;
+	item->applied = false;
+	item->next = NULL;
+	if (*head_p == NULL)
+		*head_p = item;
+	else
+		(*tail_p)->next = item;
+	*tail_p = item;
+}
+
+/*
+ * Flex fatal errors bring us here.  Stash the error message and jump back to
+ * ParseConfigFp().  Assume all msg arguments point to string constants; this
+ * holds for flex 2.5.31 (earliest we support) and flex 2.5.35 (latest as of
+ * this writing).  Otherwise, we would need to copy the message.
+ *
+ * We return "int" since this takes the place of calls to fprintf().
+*/
+static int
+GUC_flex_fatal(const char *msg)
+{
+	GUC_flex_fatal_errmsg = msg;
+	siglongjmp(*GUC_flex_fatal_jmp, 1);
+	return 0;					/* keep compiler quiet */
+}
+
+/*
+ * Read and parse a single configuration file.  This function recurses
+ * to handle "include" directives.
+ *
+ * Input parameters:
+ *	fp: file pointer from AllocateFile for the configuration file to parse
+ *	config_file: absolute or relative path name of the configuration file
+ *	depth: recursion depth (should be 0 in the outermost call)
+ *	elevel: error logging level to use
+ * Input/Output parameters:
+ *	head_p, tail_p: head and tail of linked list of name/value pairs
+ *
+ * *head_p and *tail_p must be initialized, either to NULL or valid pointers
+ * to a ConfigVariable list, before calling the outer recursion level.  Any
+ * name-value pairs read from the input file(s) will be appended to the list.
+ * Error reports will also be appended to the list, if elevel < ERROR.
+ *
+ * Returns TRUE if successful, FALSE if an error occurred.
+ *
+ * Note: this function is used to parse not only postgresql.conf, but
+ * various other configuration files that use the same "name = value"
+ * syntax.  Hence, do not do anything here or in the subsidiary routines
+ * ParseConfigFile/ParseConfigDirectory that assumes we are processing
+ * GUCs specifically.
+ */
+bool
+ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
+			  ConfigVariable **head_p, ConfigVariable **tail_p)
+{
+	volatile bool OK = true;
+	unsigned int save_ConfigFileLineno = ConfigFileLineno;
+	sigjmp_buf *save_GUC_flex_fatal_jmp = GUC_flex_fatal_jmp;
+	sigjmp_buf	flex_fatal_jmp;
+	volatile YY_BUFFER_STATE lex_buffer = NULL;
+	int			errorcount;
+	int			token;
+
+	if (sigsetjmp(flex_fatal_jmp, 1) == 0)
+		GUC_flex_fatal_jmp = &flex_fatal_jmp;
+	else
+	{
+		/*
+		 * Regain control after a fatal, internal flex error.  It may have
+		 * corrupted parser state.  Consequently, abandon the file, but trust
+		 * that the state remains sane enough for yy_delete_buffer().
+		 */
+		pg_log(PG_WARNING, "%s at file \"%s\" line %u\n", GUC_flex_fatal_errmsg,
+				config_file, ConfigFileLineno);
+		record_config_file_error(GUC_flex_fatal_errmsg,
+								 config_file, ConfigFileLineno,
+								 head_p, tail_p);
+		OK = false;
+		goto cleanup;
+	}
+
+	/*
+	 * Parse
+	 */
+	ConfigFileLineno = 1;
+	errorcount = 0;
+
+	lex_buffer = yy_create_buffer(fp, YY_BUF_SIZE);
+	yy_switch_to_buffer(lex_buffer);
+
+	/* This loop iterates once per logical line */
+	while ((token = yylex()))
+	{
+		char	   *opt_name = NULL;
+		char	   *opt_value = NULL;
+		ConfigVariable *item;
+
+		if (token == GUC_EOL)	/* empty or comment line */
+			continue;
+
+		/* first token on line is option name */
+		if (token != GUC_ID && token != GUC_QUALIFIED_ID)
+			goto parse_error;
+		opt_name = pstrdup(yytext);
+
+		/* next we have an optional equal sign; discard if present */
+		token = yylex();
+		if (token == GUC_EQUALS)
+			token = yylex();
+
+		/* now we must have the option value */
+		if (token != GUC_ID &&
+			token != GUC_STRING &&
+			token != GUC_INTEGER &&
+			token != GUC_REAL &&
+			token != GUC_UNQUOTED_STRING)
+			goto parse_error;
+		if (token == GUC_STRING)	/* strip quotes and escapes */
+			opt_value = GUC_scanstr(yytext);
+		else
+			opt_value = pstrdup(yytext);
+
+		/* now we'd like an end of line, or possibly EOF */
+		token = yylex();
+		if (token != GUC_EOL)
+		{
+			if (token != 0)
+				goto parse_error;
+			/* treat EOF like \n for line numbering purposes, cf bug 4752 */
+			ConfigFileLineno++;
+		}
+
+		/* OK, process the option name and value */
+		if (guc_name_compare(opt_name, "include_dir") == 0)
+		{
+			/*
+			 * An include_dir directive isn't a variable and should be
+			 * processed immediately.
+			 */
+			if (!ParseConfigDirectory(opt_value,
+									  config_file, ConfigFileLineno - 1,
+									  depth + 1, elevel,
+									  head_p, tail_p))
+				OK = false;
+			yy_switch_to_buffer(lex_buffer);
+			pfree(opt_name);
+			pfree(opt_value);
+		}
+		else if (guc_name_compare(opt_name, "include_if_exists") == 0)
+		{
+			/*
+			 * An include_if_exists directive isn't a variable and should be
+			 * processed immediately.
+			 */
+			if (!ParseConfigFile(opt_value, false,
+								 config_file, ConfigFileLineno - 1,
+								 depth + 1, elevel,
+								 head_p, tail_p))
+				OK = false;
+			yy_switch_to_buffer(lex_buffer);
+			pfree(opt_name);
+			pfree(opt_value);
+		}
+		else if (guc_name_compare(opt_name, "include") == 0)
+		{
+			/*
+			 * An include directive isn't a variable and should be processed
+			 * immediately.
+			 */
+			if (!ParseConfigFile(opt_value, true,
+								 config_file, ConfigFileLineno - 1,
+								 depth + 1, elevel,
+								 head_p, tail_p))
+				OK = false;
+			yy_switch_to_buffer(lex_buffer);
+			pfree(opt_name);
+			pfree(opt_value);
+		}
+		else
+		{
+			/* ordinary variable, append to list */
+			item = palloc(sizeof *item);
+			item->name = opt_name;
+			item->value = opt_value;
+			item->errmsg = NULL;
+			item->filename = pstrdup(config_file);
+			item->sourceline = ConfigFileLineno - 1;
+			item->ignore = false;
+			item->applied = false;
+			item->next = NULL;
+			if (*head_p == NULL)
+				*head_p = item;
+			else
+				(*tail_p)->next = item;
+			*tail_p = item;
+		}
+
+		/* break out of loop if read EOF, else loop for next line */
+		if (token == 0)
+			break;
+		continue;
+
+parse_error:
+		/* release storage if we allocated any on this line */
+		if (opt_name)
+			pfree(opt_name);
+		if (opt_value)
+			pfree(opt_value);
+
+		/* report the error */
+		if (token == GUC_EOL || token == 0)
+		{
+			pg_log(PG_WARNING, "syntax error in file \"%s\" line %u, near end of line\n",
+					config_file, ConfigFileLineno - 1);
+			record_config_file_error("syntax error",
+									 config_file, ConfigFileLineno - 1,
+									 head_p, tail_p);
+		}
+		else
+		{
+			pg_log(PG_WARNING, "syntax error in file \"%s\" line %u, near token \"%s\"\n",
+				config_file, ConfigFileLineno, yytext);
+			record_config_file_error("syntax error",
+									 config_file, ConfigFileLineno,
+									 head_p, tail_p);
+		}
+		OK = false;
+		errorcount++;
+
+		/*
+		 * To avoid producing too much noise when fed a totally bogus file,
+		 * give up after 100 syntax errors per file (an arbitrary number).
+		 */
+		if (errorcount >= 100)
+		{
+			pg_log(PG_WARNING, "too many syntax errors found, abandoning file \"%s\"\n",
+					config_file);
+			break;
+		}
+
+		/* resync to next end-of-line or EOF */
+		while (token != GUC_EOL && token != 0)
+			token = yylex();
+		/* break out of loop on EOF */
+		if (token == 0)
+			break;
+	}
+
+cleanup:
+	yy_delete_buffer(lex_buffer);
+	/* Each recursion level must save and restore these static variables. */
+	ConfigFileLineno = save_ConfigFileLineno;
+	GUC_flex_fatal_jmp = save_GUC_flex_fatal_jmp;
+	return OK;
+}
+
+/*
+ * Read and parse all config files in a subdirectory in alphabetical order
+ *
+ * includedir is the absolute or relative path to the subdirectory to scan.
+ *
+ * calling_file/calling_lineno identify the source of the request.
+ * Pass NULL/0 if not recursing from an inclusion request.
+ *
+ * See ParseConfigFp for further details.
+ */
+bool
+ParseConfigDirectory(const char *includedir,
+					 const char *calling_file, int calling_lineno,
+					 int depth, int elevel,
+					 ConfigVariable **head_p,
+					 ConfigVariable **tail_p)
+{
+	char	   *directory;
+	DIR		   *d;
+	struct dirent *de;
+	char	  **filenames;
+	int			num_filenames;
+	int			size_filenames;
+	bool		status;
+
+	directory = AbsoluteConfigLocation(includedir, calling_file);
+	d = opendir(directory);
+	if (d == NULL)
+	{
+		pg_log(PG_WARNING, "could not open configuration directory \"%s\"\n",
+			directory);
+		record_config_file_error(psprintf("could not open directory \"%s\"",
+										  directory),
+								 calling_file, calling_lineno,
+								 head_p, tail_p);
+		status = false;
+		goto cleanup;
+	}
+
+	/*
+	 * Read the directory and put the filenames in an array, so we can sort
+	 * them prior to processing the contents.
+	 */
+	size_filenames = 32;
+	filenames = (char **) palloc(size_filenames * sizeof(char *));
+	num_filenames = 0;
+
+	while ((de = readdir(d)) != NULL)
+	{
+		struct stat st;
+		char		filename[MAXPGPATH];
+
+		/*
+		 * Only parse files with names ending in ".conf".  Explicitly reject
+		 * files starting with ".".  This excludes things like "." and "..",
+		 * as well as typical hidden files, backup files, and editor debris.
+		 */
+		if (strlen(de->d_name) < 6)
+			continue;
+		if (de->d_name[0] == '.')
+			continue;
+		if (strcmp(de->d_name + strlen(de->d_name) - 5, ".conf") != 0)
+			continue;
+
+		join_path_components(filename, directory, de->d_name);
+		canonicalize_path(filename);
+		if (stat(filename, &st) == 0)
+		{
+			if (!S_ISDIR(st.st_mode))
+			{
+				/* Add file to array, increasing its size in blocks of 32 */
+				if (num_filenames >= size_filenames)
+				{
+					size_filenames += 32;
+					filenames = (char **) repalloc(filenames,
+											size_filenames * sizeof(char *));
+				}
+				filenames[num_filenames] = pstrdup(filename);
+				num_filenames++;
+			}
+		}
+		else
+		{
+			/*
+			 * stat does not care about permissions, so the most likely reason
+			 * a file can't be accessed now is if it was removed between the
+			 * directory listing and now.
+			 */
+			pg_log(PG_WARNING, "could not stat file \"%s\"\n", filename);
+			record_config_file_error(psprintf("could not stat file \"%s\"",
+											  filename),
+									 calling_file, calling_lineno,
+									 head_p, tail_p);
+			status = false;
+			goto cleanup;
+		}
+	}
+
+	if (num_filenames > 0)
+	{
+		int			i;
+
+		qsort(filenames, num_filenames, sizeof(char *), pg_qsort_strcmp);
+		for (i = 0; i < num_filenames; i++)
+		{
+			if (!ParseConfigFile(filenames[i], true,
+								 calling_file, calling_lineno,
+								 depth, elevel,
+								 head_p, tail_p))
+			{
+				status = false;
+				goto cleanup;
+			}
+		}
+	}
+	status = true;
+
+cleanup:
+	if (d)
+		closedir(d);
+	pfree(directory);
+	return status;
+}
+
+/*
+ * Free a list of ConfigVariables, including the names and the values
+ */
+void
+FreeConfigVariables(ConfigVariable *list)
+{
+	ConfigVariable *item;
+
+	item = list;
+	while (item)
+	{
+		ConfigVariable *next = item->next;
+
+		FreeConfigVariable(item);
+		item = next;
+	}
+}
+
+/*
+ * Free a single ConfigVariable
+ */
+static void
+FreeConfigVariable(ConfigVariable *item)
+{
+	if (item->name)
+		pfree(item->name);
+	if (item->value)
+		pfree(item->value);
+	if (item->errmsg)
+		pfree(item->errmsg);
+	if (item->filename)
+		pfree(item->filename);
+	pfree(item);
+}
+
+
+/*
+ *		scanstr
+ *
+ * Strip the quotes surrounding the given string, and collapse any embedded
+ * '' sequences and backslash escapes.
+ *
+ * the string returned is palloc'd and should eventually be pfree'd by the
+ * caller.
+ */
+static char *
+GUC_scanstr(const char *s)
+{
+	char	   *newStr;
+	int			len,
+				i,
+				j;
+
+	Assert(s != NULL && s[0] == '\'');
+	len = strlen(s);
+	Assert(len >= 2);
+	Assert(s[len - 1] == '\'');
+
+	/* Skip the leading quote; we'll handle the trailing quote below */
+	s++, len--;
+
+	/* Since len still includes trailing quote, this is enough space */
+	newStr = palloc(len);
+
+	for (i = 0, j = 0; i < len; i++)
+	{
+		if (s[i] == '\\')
+		{
+			i++;
+			switch (s[i])
+			{
+				case 'b':
+					newStr[j] = '\b';
+					break;
+				case 'f':
+					newStr[j] = '\f';
+					break;
+				case 'n':
+					newStr[j] = '\n';
+					break;
+				case 'r':
+					newStr[j] = '\r';
+					break;
+				case 't':
+					newStr[j] = '\t';
+					break;
+				case '0':
+				case '1':
+				case '2':
+				case '3':
+				case '4':
+				case '5':
+				case '6':
+				case '7':
+					{
+						int			k;
+						long		octVal = 0;
+
+						for (k = 0;
+							 s[i + k] >= '0' && s[i + k] <= '7' && k < 3;
+							 k++)
+							octVal = (octVal << 3) + (s[i + k] - '0');
+						i += k - 1;
+						newStr[j] = ((char) octVal);
+					}
+					break;
+				default:
+					newStr[j] = s[i];
+					break;
+			}					/* switch */
+		}
+		else if (s[i] == '\'' && s[i + 1] == '\'')
+		{
+			/* doubled quote becomes just one quote */
+			newStr[j] = s[++i];
+		}
+		else
+			newStr[j] = s[i];
+		j++;
+	}
+
+	/* We copied the ending quote to newStr, so replace with \0 */
+	Assert(j > 0 && j <= len);
+	newStr[--j] = '\0';
+
+	return newStr;
+}
#2Andrey Borodin
x4mmm@yandex-team.ru
In reply to: Alexey Kondratov (#1)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi, Alexey!

19 окт. 2018 г., в 22:49, Alexey Kondratov <a.kondratov@postgrespro.ru> написал(а):
I expect, that it will be a good idea to allow pg_rewind to look for a restore_command

+1
Normally you do not expect huge progress on failed master. But you still can get a lot of WAL if you have network partition and scheduled tasks like pg_repack.
I'm not actually aware what kind of problems led to these but we were considering some automation to fetch WALs for failed master to improve rewinded\resetuped ratio.

I prepared a proof of concept patch (please, find attached), which does exactly what I described above. I played with it a little and it seems to be working, tests were accordingly updated to verify this archive retrieval functionality too.

Patch is relatively simple excepting the one part: if we want to parse recovery.conf (with all possible includes, etc.) and get restore_command, then we should use guc-file.l parser, which is heavily linked to backend, e.g. in error reporting part. So I copied it and made frontend-safe version guc-file-fe.l. Personally, I don't think it's a good idea, but nothing else came to mind. It is also possible to leave the only one option -- passing restore_command as command line argument.

I think it is better to load restore_command from recovery.conf.

I didn't actually try patch yet, but the idea seems interesting. Will you add it to the commitfest?

Best regards, Andrey Borodin.

#3Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Andrey Borodin (#2)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi Andrey,

Thank you for your reply.

I think it is better to load restore_command from recovery.conf.

Yes, it seems to be the most native way. That's why I needed this
rewritten (mostly copy-pasted) frontend-safe version of parser (guc-file.l).

I didn't actually try patch yet, but the idea seems interesting. Will
you add it to the commitfest?

I am willing to add it to the November commitfest, but I have some
concerns regarding frontend version of GUC parser. Probably, it is
possible to refactor guc-file.l to use it on both front- and backend.
However, it requires usage of IFDEF and mocking up ereport for frontend,
which is a bit ugly.

--
Alexey Kondratov

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

#4Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alexey Kondratov (#3)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2018-Oct-22, Alexey Kondratov wrote:

I didn't actually try patch yet, but the idea seems interesting. Will
you add it to the commitfest?

I am willing to add it to the November commitfest, but I have some concerns
regarding frontend version of GUC parser. Probably, it is possible to
refactor guc-file.l to use it on both front- and backend. However, it
requires usage of IFDEF and mocking up ereport for frontend, which is a bit
ugly.

Hmm, I remember we had a project to have a new postmaster option that
would report the value of some GUC option, so instead of parsing the
file in the frontend, you'd invoke the backend to do the parsing. But I
don't know what became of that ... I don't see it in the postmaster
--help output.

Of course, recovery.conf options are not GUCs either ... that's another
pending patch.

We do have some backend-mock for frontends, e.g. in pg_waldump; plus
palloc is already implemented in libpgcommon. I don't know if what you
need to compile the lexer is a project much bigger than finishing the
other two patches I mention.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#5Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Alvaro Herrera (#4)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 22.10.2018 20:19, Alvaro Herrera wrote:

I didn't actually try patch yet, but the idea seems interesting. Will
you add it to the commitfest?

I am willing to add it to the November commitfest, but I have some concerns
regarding frontend version of GUC parser. Probably, it is possible to
refactor guc-file.l to use it on both front- and backend. However, it
requires usage of IFDEF and mocking up ereport for frontend, which is a bit
ugly.

Hmm, I remember we had a project to have a new postmaster option that
would report the value of some GUC option, so instead of parsing the
file in the frontend, you'd invoke the backend to do the parsing. But I
don't know what became of that ...

Brief searching in the mailing list doesn't return something relevant,
but the project seems to be pretty straightforward at first sight.

Of course, recovery.conf options are not GUCs either ... that's another
pending patch.

We do have some backend-mock for frontends, e.g. in pg_waldump; plus
palloc is already implemented in libpgcommon. I don't know if what you
need to compile the lexer is a project much bigger than finishing the
other two patches I mention.

This thing, in opposite, is a long-living, there are several threads
starting from the 2011th. I have found Michael's, Simon's, Fujii's
patches and Greg Smith's proposal (see, e.g. [1, 2]). If I get it right,
the main point is that if we turn all options in the recovery.conf into
GUCs, then it becomes possible to set them inside postgresql.conf and
get rid of recovery.conf. However, it ruins backward compatibility and
brings some other issues noted by Heikki
/messages/by-id/5152F778.2070205@vmware.com, while
keeping both options is excess and ambiguous.

Thus, though everyone agreed that recovery.conf options should be turned
into GUCs, there is still no consensus in details. I don't think that I
know Postgres architecture enough to start this discussion again, but
thank you for pointing me in this direction, it was quite interesting
from the historical perspective.

I will check guc-file.l again, maybe it is not so painful to make it
frontend-safe too.

[1]: /messages/by-id/CAHGQGwHi=4GV6neLRXF7rexTBkjhcAEqF9_xq+tRvFv2bVd59w@mail.gmail.com
/messages/by-id/CAHGQGwHi=4GV6neLRXF7rexTBkjhcAEqF9_xq+tRvFv2bVd59w@mail.gmail.com

[2]: /messages/by-id/CA+U5nMKyuDxr0=5PSen1DZJndauNdz8BuSREau=ScN-7DZ9acA@mail.gmail.com
/messages/by-id/CA+U5nMKyuDxr0=5PSen1DZJndauNdz8BuSREau=ScN-7DZ9acA@mail.gmail.com

--
Alexey Kondratov

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

#6Andrey Borodin
x4mmm@yandex-team.ru
In reply to: Alexey Kondratov (#5)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi, Alexey!

25 окт. 2018 г., в 17:37, Alexey Kondratov <a.kondratov@postgrespro.ru> написал(а):

Will you add this patch to CF?
I'm going to review it.

Best regards, Andrey Borodin

#7Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#4)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Mon, Oct 22, 2018 at 02:19:07PM -0300, Alvaro Herrera wrote:

Hmm, I remember we had a project to have a new postmaster option that
would report the value of some GUC option, so instead of parsing the
file in the frontend, you'd invoke the backend to do the parsing. But I
don't know what became of that ... I don't see it in the postmaster
--help output.

I did not recall this one.

Of course, recovery.conf options are not GUCs either ... that's another
pending patch.

But I do recall this one. Still isn't it q bit different? Because in
the case of pg_rewind let's remember that the origin cluster can be
offline or online, and that the target has to be offline. I am also not
sure that we would want to use the same command restore_command with
pg_rewind and an instance in recovery.

Something that we could think about is directly to provide a command to
pg_rewind via command line. Another possibility would be to have a
separate tool which scans a data folder and fetches by itself a range of
WAL segments wanted. I have implemented something like that for an
internal solution, able to handle as well timeline jumps across
segments with a server-side and a client-side implementation (that was
to allow a passive to catch up using a set of WAL segments, if the
active does not have a replication slot, and segments were fetched
through a Postgres instance which has a local archive).
--
Michael

#8Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Andrey Borodin (#6)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi Andrey,

Will you add this patch to CF?
I'm going to review it.

Best regards, Andrey Borodin

Here it is https://commitfest.postgresql.org/20/1849/

--
Alexey Kondratov

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

#9Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#7)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Something that we could think about is directly to provide a command to
pg_rewind via command line.

In my patch I added this option too. One can pass restore_command via -R
option, e.g.:

pg_rewind -P --target-pgdata=/path/to/master/pg_data
--source-pgdata=/path/to/standby/pg_data -R 'cp /path/to/wals_archive/%f %p'

Another possibility would be to have a
separate tool which scans a data folder and fetches by itself a range of
WAL segments wanted.

Currently in the patch, with dry-run option (-n) pg_rewind only fetches
missing WALs to be able to build file map, while doesn't touch any data
files. So I guess it behaves exactly as you described and we do not need
a separate tool.

--
Alexey Kondratov

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

#10Michael Paquier
michael@paquier.xyz
In reply to: Alexey Kondratov (#9)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Mon, Oct 29, 2018 at 12:09:21PM +0300, Alexey Kondratov wrote:

Currently in the patch, with dry-run option (-n) pg_rewind only fetches
missing WALs to be able to build file map, while doesn't touch any data
files. So I guess it behaves exactly as you described and we do not need a
separate tool.

Makes sense perhaps. Fetching only WAL segments which are needed for
the file map is critical, as you don't want to spend bandwidth for
nothing. Now, I look at your patch, and I can see things to complain
about, at least three at short glance:
- The TAP test added will fail on Windows.
- Simply copy-pasting RestoreArchivedWAL() from the backend code to
pg_rewind is not an acceptable option. You don't care about %r either
in this case.
- Reusing the GUC parser is something I would avoid as well. Not worth
the complexity.

Another thing I am wondering is: do we actually need something complex?
What we want to know is what data is necessary to build the file map, so
we could also add an option to pg_rewind which checks what segments are
necessary and lets the user know about them? This also avoids the
security-related problems of manipulating a command at option-level.
This kind of options makes folks willing to use more sensitive data on
command line, which is not always a good idea...
--
Michael

#11Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#10)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 30.10.2018 06:01, Michael Paquier wrote:

On Mon, Oct 29, 2018 at 12:09:21PM +0300, Alexey Kondratov wrote:

Currently in the patch, with dry-run option (-n) pg_rewind only fetches
missing WALs to be able to build file map, while doesn't touch any data
files. So I guess it behaves exactly as you described and we do not need a
separate tool.

Makes sense perhaps. Fetching only WAL segments which are needed for
the file map is critical, as you don't want to spend bandwidth for
nothing. Now, I look at your patch, and I can see things to complain
about, at least three at short glance:
- The TAP test added will fail on Windows.

Thank you for this. Build on Windows has been broken as well. I fixed it
in the new version of patch, please find attached.

- Simply copy-pasting RestoreArchivedWAL() from the backend code to
pg_rewind is not an acceptable option. You don't care about %r either
in this case.

According to the docs [1]https://www.postgresql.org/docs/11/archive-recovery-settings.html %r is a valid alias and may be used in
restore_command too, so if we take restore_command from recovery.conf it
might be there. If we just drop it, then restore_command may stop
working. Though I do not know real life examples of restore_command with
%r, we should treat it in expected way (as backend does), of course if
we want an option to take it from recovery.conf.

- Reusing the GUC parser is something I would avoid as well. Not worth
the complexity.

Yes, I don't like it either. I will try to make guc-file.l frontend safe.

[1]: https://www.postgresql.org/docs/11/archive-recovery-settings.html

--
Alexey Kondratov

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

Attachments:

pg_rewind-restore_command-v1.1.patchtext/x-patch; name=pg_rewind-restore_command-v1.1.patchDownload
diff --git a/src/bin/pg_rewind/Makefile b/src/bin/pg_rewind/Makefile
index 2bcfcc61af2e..a22fef1352b9 100644
--- a/src/bin/pg_rewind/Makefile
+++ b/src/bin/pg_rewind/Makefile
@@ -20,6 +20,7 @@ LDFLAGS_INTERNAL += $(libpq_pgport)
 
 OBJS	= pg_rewind.o parsexlog.o xlogreader.o datapagemap.o timeline.o \
 	fetch.o file_ops.o copy_fetch.o libpq_fetch.o filemap.o logging.o \
+	guc-file-fe.o \
 	$(WIN32RES)
 
 EXTRA_CLEAN = xlogreader.c
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 1dce56d0352e..a5499c7027b1 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -39,7 +39,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions 'catpath';
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -249,6 +251,48 @@ sub run_pg_rewind
 			],
 			'pg_rewind remote');
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $wals_archive_dir = catpath(${TestLib::tmp_check}, 'master_wals_archive');
+		my $test_master_datadir = $node_master->data_dir;
+		my @wal_files = bsd_glob catpath($test_master_datadir, 'pg_wal', '0000000*');
+		my $restore_command;
+
+		remove_tree($wals_archive_dir);
+		make_path($wals_archive_dir) or die;
+
+		# Move all old master WAL files to the archive.
+		# Old master should be stopped at this point.
+		foreach my $wal_file (@wal_files)
+		{
+			move($wal_file, "$wals_archive_dir/") or die;
+		}
+
+		if ($windows_os)
+		{
+			$restore_command = "copy $wals_archive_dir\\\%f \%p";
+		}
+		else
+		{
+			$restore_command = "cp $wals_archive_dir/\%f \%p";
+		}
+
+		# 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",
+				"-R", $restore_command
+			],
+			'pg_rewind archive');
+	}
 	else
 	{
 
diff --git a/src/bin/pg_rewind/guc-file-fe.h b/src/bin/pg_rewind/guc-file-fe.h
new file mode 100644
index 000000000000..cf480b806ae5
--- /dev/null
+++ b/src/bin/pg_rewind/guc-file-fe.h
@@ -0,0 +1,40 @@
+#ifndef PG_REWIND_GUC_FILE_FE_H
+#define PG_REWIND_GUC_FILE_FE_H
+
+#include "c.h"
+
+#define RECOVERY_COMMAND_FILE	"recovery.conf"
+
+/*
+ * Parsing the configuration file(s) will return a list of name-value pairs
+ * with source location info.  We also abuse this data structure to carry
+ * error reports about the config files.  An entry reporting an error will
+ * have errmsg != NULL, and might have NULLs for name, value, and/or filename.
+ *
+ * If "ignore" is true, don't attempt to apply the item (it might be an error
+ * report, or an item we determined to be duplicate).  "applied" is set true
+ * if we successfully applied, or could have applied, the setting.
+ */
+typedef struct ConfigVariable
+{
+	char	   *name;
+	char	   *value;
+	char	   *errmsg;
+	char	   *filename;
+	int			sourceline;
+	bool		ignore;
+	bool		applied;
+	struct ConfigVariable *next;
+} ConfigVariable;
+
+extern bool ParseConfigFile(const char *config_file, bool strict,
+				const char *calling_file, int calling_lineno,
+				int depth, int elevel,
+				ConfigVariable **head_p, ConfigVariable **tail_p);
+
+extern bool ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
+				ConfigVariable **head_p, ConfigVariable **tail_p);
+
+extern void FreeConfigVariables(ConfigVariable *list);
+
+#endif							/* PG_REWIND_GUC_FILE_FE_H */
diff --git a/src/bin/pg_rewind/guc-file-fe.l b/src/bin/pg_rewind/guc-file-fe.l
new file mode 100644
index 000000000000..b7ef47111a79
--- /dev/null
+++ b/src/bin/pg_rewind/guc-file-fe.l
@@ -0,0 +1,776 @@
+/* -*-pgsql-c-*- */
+/*
+ * Configuration files parser for usage in frontend.
+ *
+ * Copyright (c) 2000-2018, PostgreSQL Global Development Group
+ *
+ * src/bin/pg_rewind/guc-file-fe.l
+ * Modified version of src/backend/utils/misc/guc-file.l
+ */
+
+%{
+
+#include "postgres_fe.h"
+
+#include <ctype.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <setjmp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "guc-file-fe.h"
+#include "logging.h"
+
+#include "common/fe_memutils.h"
+
+/*
+ * flex emits a yy_fatal_error() function that it calls in response to
+ * critical errors like malloc failure, file I/O errors, and detection of
+ * internal inconsistency.  That function prints a message and calls exit().
+ * Mutate it to instead call our handler, which jumps out of the parser.
+ */
+#undef fprintf
+#define fprintf(file, fmt, msg) GUC_flex_fatal(msg)
+
+enum
+{
+	GUC_ID = 1,
+	GUC_STRING = 2,
+	GUC_INTEGER = 3,
+	GUC_REAL = 4,
+	GUC_EQUALS = 5,
+	GUC_UNQUOTED_STRING = 6,
+	GUC_QUALIFIED_ID = 7,
+	GUC_EOL = 99,
+	GUC_ERROR = 100
+};
+
+static unsigned int ConfigFileLineno;
+static const char *GUC_flex_fatal_errmsg;
+static sigjmp_buf *GUC_flex_fatal_jmp;
+
+static void FreeConfigVariable(ConfigVariable *item);
+
+static void record_config_file_error(const char *errmsg,
+						 const char *config_file,
+						 int lineno,
+						 ConfigVariable **head_p,
+						 ConfigVariable **tail_p);
+
+static int	GUC_flex_fatal(const char *msg);
+static char *GUC_scanstr(const char *s);
+static bool ParseConfigDirectory(const char *includedir,
+					 const char *calling_file, int calling_lineno,
+					 int depth, int elevel,
+					 ConfigVariable **head_p,
+					 ConfigVariable **tail_p);
+
+/* LCOV_EXCL_START */
+
+%}
+
+%option 8bit
+%option never-interactive
+%option nodefault
+%option noinput
+%option nounput
+%option noyywrap
+%option warn
+%option prefix="GUC_yy"
+
+
+SIGN			("-"|"+")
+DIGIT			[0-9]
+HEXDIGIT		[0-9a-fA-F]
+
+UNIT_LETTER		[a-zA-Z]
+
+INTEGER			{SIGN}?({DIGIT}+|0x{HEXDIGIT}+){UNIT_LETTER}*
+
+EXPONENT		[Ee]{SIGN}?{DIGIT}+
+REAL			{SIGN}?{DIGIT}*"."{DIGIT}*{EXPONENT}?
+
+LETTER			[A-Za-z_\200-\377]
+LETTER_OR_DIGIT [A-Za-z_0-9\200-\377]
+
+ID				{LETTER}{LETTER_OR_DIGIT}*
+QUALIFIED_ID	{ID}"."{ID}
+
+UNQUOTED_STRING {LETTER}({LETTER_OR_DIGIT}|[-._:/])*
+STRING			\'([^'\\\n]|\\.|\'\')*\'
+
+%%
+
+\n				ConfigFileLineno++; return GUC_EOL;
+[ \t\r]+		/* eat whitespace */
+#.*				/* eat comment (.* matches anything until newline) */
+
+{ID}			return GUC_ID;
+{QUALIFIED_ID}	return GUC_QUALIFIED_ID;
+{STRING}		return GUC_STRING;
+{UNQUOTED_STRING} return GUC_UNQUOTED_STRING;
+{INTEGER}		return GUC_INTEGER;
+{REAL}			return GUC_REAL;
+=				return GUC_EQUALS;
+
+.				return GUC_ERROR;
+
+%%
+
+/* LCOV_EXCL_STOP */
+
+/*
+ * the bare comparison function for GUC names
+ */
+static int
+guc_name_compare(const char *namea, const char *nameb)
+{
+	/*
+	 * The temptation to use strcasecmp() here must be resisted, because the
+	 * array ordering has to remain stable across setlocale() calls. So, build
+	 * our own with a simple ASCII-only downcasing.
+	 */
+	while (*namea && *nameb)
+	{
+		char		cha = *namea++;
+		char		chb = *nameb++;
+
+		if (cha >= 'A' && cha <= 'Z')
+			cha += 'a' - 'A';
+		if (chb >= 'A' && chb <= 'Z')
+			chb += 'a' - 'A';
+		if (cha != chb)
+			return cha - chb;
+	}
+	if (*namea)
+		return 1;				/* a is longer */
+	if (*nameb)
+		return -1;				/* b is longer */
+	return 0;
+}
+
+/*
+ * Given a configuration file or directory location that may be a relative
+ * path, return an absolute one.  We consider the location to be relative to
+ * the directory holding the calling file.
+ */
+static char *
+AbsoluteConfigLocation(const char *location, const char *calling_file)
+{
+	char abs_path[MAXPGPATH];
+
+	if (is_absolute_path(location))
+		return pstrdup(location);
+	else
+	{
+		strlcpy(abs_path, calling_file, sizeof(abs_path));
+		get_parent_directory(abs_path);
+		join_path_components(abs_path, abs_path, location);
+		canonicalize_path(abs_path);
+
+		return pstrdup(abs_path);
+	}
+}
+
+/*
+ * Read and parse a single configuration file.  This function recurses
+ * to handle "include" directives.
+ *
+ * If "strict" is true, treat failure to open the config file as an error,
+ * otherwise just skip the file.
+ *
+ * calling_file/calling_lineno identify the source of the request.
+ * Pass NULL/0 if not recursing from an inclusion request.
+ *
+ * See ParseConfigFp for further details.  This one merely adds opening the
+ * config file rather than working from a caller-supplied file descriptor,
+ * and absolute-ifying the path name if necessary.
+ */
+bool
+ParseConfigFile(const char *config_file, bool strict,
+				const char *calling_file, int calling_lineno,
+				int depth, int elevel,
+				ConfigVariable **head_p,
+				ConfigVariable **tail_p)
+{
+	char	   *abs_path;
+	bool		OK = true;
+	FILE	   *fp;
+
+	/*
+	 * Reject too-deep include nesting depth.  This is just a safety check to
+	 * avoid dumping core due to stack overflow if an include file loops back
+	 * to itself.  The maximum nesting depth is pretty arbitrary.
+	 */
+	if (depth > 10)
+	{
+		pg_log(PG_WARNING, 
+				"could not open configuration file \"%s\": maximum nesting depth exceeded\n",
+				config_file);
+		record_config_file_error("nesting depth exceeded",
+								 calling_file, calling_lineno,
+								 head_p, tail_p);
+		return false;
+	}
+
+	abs_path = AbsoluteConfigLocation(config_file, calling_file);
+	fp = fopen(abs_path, "r");
+	if (!fp)
+	{
+		if (strict)
+		{
+			pg_log(PG_WARNING, 
+				"could not open configuration file \"%s\"\n",
+				abs_path);
+			record_config_file_error(psprintf("could not open file \"%s\"",
+											  abs_path),
+									 calling_file, calling_lineno,
+									 head_p, tail_p);
+			OK = false;
+		}
+		else
+		{
+			pg_log(PG_WARNING, 
+				"skipping missing configuration file \"%s\"\n",
+				abs_path);
+		}
+		goto cleanup;
+	}
+
+	OK = ParseConfigFp(fp, abs_path, depth, elevel, head_p, tail_p);
+
+cleanup:
+	if (fp)
+		fclose(fp);
+	pfree(abs_path);
+
+	return OK;
+}
+
+/*
+ * Capture an error message in the ConfigVariable list returned by
+ * config file parsing.
+ */
+static void
+record_config_file_error(const char *errmsg,
+						 const char *config_file,
+						 int lineno,
+						 ConfigVariable **head_p,
+						 ConfigVariable **tail_p)
+{
+	ConfigVariable *item;
+
+	item = palloc(sizeof *item);
+	item->name = NULL;
+	item->value = NULL;
+	item->errmsg = pstrdup(errmsg);
+	item->filename = config_file ? pstrdup(config_file) : NULL;
+	item->sourceline = lineno;
+	item->ignore = true;
+	item->applied = false;
+	item->next = NULL;
+	if (*head_p == NULL)
+		*head_p = item;
+	else
+		(*tail_p)->next = item;
+	*tail_p = item;
+}
+
+/*
+ * Flex fatal errors bring us here.  Stash the error message and jump back to
+ * ParseConfigFp().  Assume all msg arguments point to string constants; this
+ * holds for flex 2.5.31 (earliest we support) and flex 2.5.35 (latest as of
+ * this writing).  Otherwise, we would need to copy the message.
+ *
+ * We return "int" since this takes the place of calls to fprintf().
+*/
+static int
+GUC_flex_fatal(const char *msg)
+{
+	GUC_flex_fatal_errmsg = msg;
+	siglongjmp(*GUC_flex_fatal_jmp, 1);
+	return 0;					/* keep compiler quiet */
+}
+
+/*
+ * Read and parse a single configuration file.  This function recurses
+ * to handle "include" directives.
+ *
+ * Input parameters:
+ *	fp: file pointer from AllocateFile for the configuration file to parse
+ *	config_file: absolute or relative path name of the configuration file
+ *	depth: recursion depth (should be 0 in the outermost call)
+ *	elevel: error logging level to use
+ * Input/Output parameters:
+ *	head_p, tail_p: head and tail of linked list of name/value pairs
+ *
+ * *head_p and *tail_p must be initialized, either to NULL or valid pointers
+ * to a ConfigVariable list, before calling the outer recursion level.  Any
+ * name-value pairs read from the input file(s) will be appended to the list.
+ * Error reports will also be appended to the list, if elevel < ERROR.
+ *
+ * Returns TRUE if successful, FALSE if an error occurred.
+ *
+ * Note: this function is used to parse not only postgresql.conf, but
+ * various other configuration files that use the same "name = value"
+ * syntax.  Hence, do not do anything here or in the subsidiary routines
+ * ParseConfigFile/ParseConfigDirectory that assumes we are processing
+ * GUCs specifically.
+ */
+bool
+ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
+			  ConfigVariable **head_p, ConfigVariable **tail_p)
+{
+	volatile bool OK = true;
+	unsigned int save_ConfigFileLineno = ConfigFileLineno;
+	sigjmp_buf *save_GUC_flex_fatal_jmp = GUC_flex_fatal_jmp;
+	sigjmp_buf	flex_fatal_jmp;
+	volatile YY_BUFFER_STATE lex_buffer = NULL;
+	int			errorcount;
+	int			token;
+
+	if (sigsetjmp(flex_fatal_jmp, 1) == 0)
+		GUC_flex_fatal_jmp = &flex_fatal_jmp;
+	else
+	{
+		/*
+		 * Regain control after a fatal, internal flex error.  It may have
+		 * corrupted parser state.  Consequently, abandon the file, but trust
+		 * that the state remains sane enough for yy_delete_buffer().
+		 */
+		pg_log(PG_WARNING, "%s at file \"%s\" line %u\n", GUC_flex_fatal_errmsg,
+				config_file, ConfigFileLineno);
+		record_config_file_error(GUC_flex_fatal_errmsg,
+								 config_file, ConfigFileLineno,
+								 head_p, tail_p);
+		OK = false;
+		goto cleanup;
+	}
+
+	/*
+	 * Parse
+	 */
+	ConfigFileLineno = 1;
+	errorcount = 0;
+
+	lex_buffer = yy_create_buffer(fp, YY_BUF_SIZE);
+	yy_switch_to_buffer(lex_buffer);
+
+	/* This loop iterates once per logical line */
+	while ((token = yylex()))
+	{
+		char	   *opt_name = NULL;
+		char	   *opt_value = NULL;
+		ConfigVariable *item;
+
+		if (token == GUC_EOL)	/* empty or comment line */
+			continue;
+
+		/* first token on line is option name */
+		if (token != GUC_ID && token != GUC_QUALIFIED_ID)
+			goto parse_error;
+		opt_name = pstrdup(yytext);
+
+		/* next we have an optional equal sign; discard if present */
+		token = yylex();
+		if (token == GUC_EQUALS)
+			token = yylex();
+
+		/* now we must have the option value */
+		if (token != GUC_ID &&
+			token != GUC_STRING &&
+			token != GUC_INTEGER &&
+			token != GUC_REAL &&
+			token != GUC_UNQUOTED_STRING)
+			goto parse_error;
+		if (token == GUC_STRING)	/* strip quotes and escapes */
+			opt_value = GUC_scanstr(yytext);
+		else
+			opt_value = pstrdup(yytext);
+
+		/* now we'd like an end of line, or possibly EOF */
+		token = yylex();
+		if (token != GUC_EOL)
+		{
+			if (token != 0)
+				goto parse_error;
+			/* treat EOF like \n for line numbering purposes, cf bug 4752 */
+			ConfigFileLineno++;
+		}
+
+		/* OK, process the option name and value */
+		if (guc_name_compare(opt_name, "include_dir") == 0)
+		{
+			/*
+			 * An include_dir directive isn't a variable and should be
+			 * processed immediately.
+			 */
+			if (!ParseConfigDirectory(opt_value,
+									  config_file, ConfigFileLineno - 1,
+									  depth + 1, elevel,
+									  head_p, tail_p))
+				OK = false;
+			yy_switch_to_buffer(lex_buffer);
+			pfree(opt_name);
+			pfree(opt_value);
+		}
+		else if (guc_name_compare(opt_name, "include_if_exists") == 0)
+		{
+			/*
+			 * An include_if_exists directive isn't a variable and should be
+			 * processed immediately.
+			 */
+			if (!ParseConfigFile(opt_value, false,
+								 config_file, ConfigFileLineno - 1,
+								 depth + 1, elevel,
+								 head_p, tail_p))
+				OK = false;
+			yy_switch_to_buffer(lex_buffer);
+			pfree(opt_name);
+			pfree(opt_value);
+		}
+		else if (guc_name_compare(opt_name, "include") == 0)
+		{
+			/*
+			 * An include directive isn't a variable and should be processed
+			 * immediately.
+			 */
+			if (!ParseConfigFile(opt_value, true,
+								 config_file, ConfigFileLineno - 1,
+								 depth + 1, elevel,
+								 head_p, tail_p))
+				OK = false;
+			yy_switch_to_buffer(lex_buffer);
+			pfree(opt_name);
+			pfree(opt_value);
+		}
+		else
+		{
+			/* ordinary variable, append to list */
+			item = palloc(sizeof *item);
+			item->name = opt_name;
+			item->value = opt_value;
+			item->errmsg = NULL;
+			item->filename = pstrdup(config_file);
+			item->sourceline = ConfigFileLineno - 1;
+			item->ignore = false;
+			item->applied = false;
+			item->next = NULL;
+			if (*head_p == NULL)
+				*head_p = item;
+			else
+				(*tail_p)->next = item;
+			*tail_p = item;
+		}
+
+		/* break out of loop if read EOF, else loop for next line */
+		if (token == 0)
+			break;
+		continue;
+
+parse_error:
+		/* release storage if we allocated any on this line */
+		if (opt_name)
+			pfree(opt_name);
+		if (opt_value)
+			pfree(opt_value);
+
+		/* report the error */
+		if (token == GUC_EOL || token == 0)
+		{
+			pg_log(PG_WARNING, "syntax error in file \"%s\" line %u, near end of line\n",
+					config_file, ConfigFileLineno - 1);
+			record_config_file_error("syntax error",
+									 config_file, ConfigFileLineno - 1,
+									 head_p, tail_p);
+		}
+		else
+		{
+			pg_log(PG_WARNING, "syntax error in file \"%s\" line %u, near token \"%s\"\n",
+				config_file, ConfigFileLineno, yytext);
+			record_config_file_error("syntax error",
+									 config_file, ConfigFileLineno,
+									 head_p, tail_p);
+		}
+		OK = false;
+		errorcount++;
+
+		/*
+		 * To avoid producing too much noise when fed a totally bogus file,
+		 * give up after 100 syntax errors per file (an arbitrary number).
+		 */
+		if (errorcount >= 100)
+		{
+			pg_log(PG_WARNING, "too many syntax errors found, abandoning file \"%s\"\n",
+					config_file);
+			break;
+		}
+
+		/* resync to next end-of-line or EOF */
+		while (token != GUC_EOL && token != 0)
+			token = yylex();
+		/* break out of loop on EOF */
+		if (token == 0)
+			break;
+	}
+
+cleanup:
+	yy_delete_buffer(lex_buffer);
+	/* Each recursion level must save and restore these static variables. */
+	ConfigFileLineno = save_ConfigFileLineno;
+	GUC_flex_fatal_jmp = save_GUC_flex_fatal_jmp;
+	return OK;
+}
+
+/*
+ * Read and parse all config files in a subdirectory in alphabetical order
+ *
+ * includedir is the absolute or relative path to the subdirectory to scan.
+ *
+ * calling_file/calling_lineno identify the source of the request.
+ * Pass NULL/0 if not recursing from an inclusion request.
+ *
+ * See ParseConfigFp for further details.
+ */
+bool
+ParseConfigDirectory(const char *includedir,
+					 const char *calling_file, int calling_lineno,
+					 int depth, int elevel,
+					 ConfigVariable **head_p,
+					 ConfigVariable **tail_p)
+{
+	char	   *directory;
+	DIR		   *d;
+	struct dirent *de;
+	char	  **filenames;
+	int			num_filenames;
+	int			size_filenames;
+	bool		status;
+
+	directory = AbsoluteConfigLocation(includedir, calling_file);
+	d = opendir(directory);
+	if (d == NULL)
+	{
+		pg_log(PG_WARNING, "could not open configuration directory \"%s\"\n",
+			directory);
+		record_config_file_error(psprintf("could not open directory \"%s\"",
+										  directory),
+								 calling_file, calling_lineno,
+								 head_p, tail_p);
+		status = false;
+		goto cleanup;
+	}
+
+	/*
+	 * Read the directory and put the filenames in an array, so we can sort
+	 * them prior to processing the contents.
+	 */
+	size_filenames = 32;
+	filenames = (char **) palloc(size_filenames * sizeof(char *));
+	num_filenames = 0;
+
+	while ((de = readdir(d)) != NULL)
+	{
+		struct stat st;
+		char		filename[MAXPGPATH];
+
+		/*
+		 * Only parse files with names ending in ".conf".  Explicitly reject
+		 * files starting with ".".  This excludes things like "." and "..",
+		 * as well as typical hidden files, backup files, and editor debris.
+		 */
+		if (strlen(de->d_name) < 6)
+			continue;
+		if (de->d_name[0] == '.')
+			continue;
+		if (strcmp(de->d_name + strlen(de->d_name) - 5, ".conf") != 0)
+			continue;
+
+		join_path_components(filename, directory, de->d_name);
+		canonicalize_path(filename);
+		if (stat(filename, &st) == 0)
+		{
+			if (!S_ISDIR(st.st_mode))
+			{
+				/* Add file to array, increasing its size in blocks of 32 */
+				if (num_filenames >= size_filenames)
+				{
+					size_filenames += 32;
+					filenames = (char **) repalloc(filenames,
+											size_filenames * sizeof(char *));
+				}
+				filenames[num_filenames] = pstrdup(filename);
+				num_filenames++;
+			}
+		}
+		else
+		{
+			/*
+			 * stat does not care about permissions, so the most likely reason
+			 * a file can't be accessed now is if it was removed between the
+			 * directory listing and now.
+			 */
+			pg_log(PG_WARNING, "could not stat file \"%s\"\n", filename);
+			record_config_file_error(psprintf("could not stat file \"%s\"",
+											  filename),
+									 calling_file, calling_lineno,
+									 head_p, tail_p);
+			status = false;
+			goto cleanup;
+		}
+	}
+
+	if (num_filenames > 0)
+	{
+		int			i;
+
+		qsort(filenames, num_filenames, sizeof(char *), pg_qsort_strcmp);
+		for (i = 0; i < num_filenames; i++)
+		{
+			if (!ParseConfigFile(filenames[i], true,
+								 calling_file, calling_lineno,
+								 depth, elevel,
+								 head_p, tail_p))
+			{
+				status = false;
+				goto cleanup;
+			}
+		}
+	}
+	status = true;
+
+cleanup:
+	if (d)
+		closedir(d);
+	pfree(directory);
+	return status;
+}
+
+/*
+ * Free a list of ConfigVariables, including the names and the values
+ */
+void
+FreeConfigVariables(ConfigVariable *list)
+{
+	ConfigVariable *item;
+
+	item = list;
+	while (item)
+	{
+		ConfigVariable *next = item->next;
+
+		FreeConfigVariable(item);
+		item = next;
+	}
+}
+
+/*
+ * Free a single ConfigVariable
+ */
+static void
+FreeConfigVariable(ConfigVariable *item)
+{
+	if (item->name)
+		pfree(item->name);
+	if (item->value)
+		pfree(item->value);
+	if (item->errmsg)
+		pfree(item->errmsg);
+	if (item->filename)
+		pfree(item->filename);
+	pfree(item);
+}
+
+
+/*
+ *		scanstr
+ *
+ * Strip the quotes surrounding the given string, and collapse any embedded
+ * '' sequences and backslash escapes.
+ *
+ * the string returned is palloc'd and should eventually be pfree'd by the
+ * caller.
+ */
+static char *
+GUC_scanstr(const char *s)
+{
+	char	   *newStr;
+	int			len,
+				i,
+				j;
+
+	Assert(s != NULL && s[0] == '\'');
+	len = strlen(s);
+	Assert(len >= 2);
+	Assert(s[len - 1] == '\'');
+
+	/* Skip the leading quote; we'll handle the trailing quote below */
+	s++, len--;
+
+	/* Since len still includes trailing quote, this is enough space */
+	newStr = palloc(len);
+
+	for (i = 0, j = 0; i < len; i++)
+	{
+		if (s[i] == '\\')
+		{
+			i++;
+			switch (s[i])
+			{
+				case 'b':
+					newStr[j] = '\b';
+					break;
+				case 'f':
+					newStr[j] = '\f';
+					break;
+				case 'n':
+					newStr[j] = '\n';
+					break;
+				case 'r':
+					newStr[j] = '\r';
+					break;
+				case 't':
+					newStr[j] = '\t';
+					break;
+				case '0':
+				case '1':
+				case '2':
+				case '3':
+				case '4':
+				case '5':
+				case '6':
+				case '7':
+					{
+						int			k;
+						long		octVal = 0;
+
+						for (k = 0;
+							 s[i + k] >= '0' && s[i + k] <= '7' && k < 3;
+							 k++)
+							octVal = (octVal << 3) + (s[i + k] - '0');
+						i += k - 1;
+						newStr[j] = ((char) octVal);
+					}
+					break;
+				default:
+					newStr[j] = s[i];
+					break;
+			}					/* switch */
+		}
+		else if (s[i] == '\'' && s[i + 1] == '\'')
+		{
+			/* doubled quote becomes just one quote */
+			newStr[j] = s[++i];
+		}
+		else
+			newStr[j] = s[i];
+		j++;
+	}
+
+	/* We copied the ending quote to newStr, so replace with \0 */
+	Assert(j > 0 && j <= len);
+	newStr[--j] = '\0';
+
+	return newStr;
+}
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 40028471bf6e..11a9c26cd23a 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "pg_rewind.h"
 #include "filemap.h"
@@ -45,7 +46,10 @@ static char xlogfpath[MAXPGPATH];
 typedef struct XLogPageReadPrivate
 {
 	const char *datadir;
+	const char *restoreCommand;
 	int			tliIndex;
+	XLogRecPtr  oldrecptr;
+	TimeLineID  oldtli;
 } XLogPageReadPrivate;
 
 static int SimpleXLogPageRead(XLogReaderState *xlogreader,
@@ -53,6 +57,10 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
 				   int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
 				   TimeLineID *pageTLI);
 
+static bool RestoreArchivedWAL(const char *path, const char *xlogfname, 
+					off_t expectedSize, const char *restoreCommand,
+					const char *lastRestartPointFname);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -60,15 +68,19 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
  */
 void
 extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
-			   XLogRecPtr endpoint)
+			   ControlFileData *targetCF, const char *restore_command)
 {
 	XLogRecord *record;
+	XLogRecPtr endpoint = targetCF->checkPoint;
 	XLogReaderState *xlogreader;
 	char	   *errormsg;
 	XLogPageReadPrivate private;
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restore_command;
+	private.oldrecptr = targetCF->checkPointCopy.redo;
+	private.oldtli = targetCF->checkPointCopy.ThisTimeLineID;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -154,9 +166,9 @@ readOneRecord(const char *datadir, XLogRecPtr ptr, int tliIndex)
  * Find the previous checkpoint preceding given WAL location.
  */
 void
-findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
+findLastCheckpoint(const char *datadir, ControlFileData *targetCF, XLogRecPtr forkptr, int tliIndex,
 				   XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
-				   XLogRecPtr *lastchkptredo)
+				   XLogRecPtr *lastchkptredo, const char *restoreCommand)
 {
 	/* Walk backwards, starting from the given record */
 	XLogRecord *record;
@@ -181,6 +193,9 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
+	private.oldrecptr = targetCF->checkPointCopy.redo;
+	private.oldtli = targetCF->checkPointCopy.ThisTimeLineID;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -291,9 +306,53 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
 
 		if (xlogreadfd < 0)
 		{
-			printf(_("could not open file \"%s\": %s\n"), xlogfpath,
+			bool  restore_ok;
+			char  lastRestartPointFname[MAXFNAMELEN];
+			XLogSegNo restartSegNo;
+
+			/*
+			 * If we have no restore_command to execute, then exit.
+			 */
+			if (private->restoreCommand == NULL)
+			{
+				printf(_("could not open file \"%s\": %s\n"), xlogfpath,
 				   strerror(errno));
-			return -1;
+				return -1;
+			}
+			
+			XLByteToSeg(private->oldrecptr, restartSegNo, WalSegSz);
+			XLogFileName(lastRestartPointFname, private->oldtli, restartSegNo,
+						 WalSegSz);
+
+			/*
+			 * Since we have restore_command to execute, then try to retreive
+			 * missing WAL file from the archive.
+			 */
+			restore_ok = RestoreArchivedWAL(private->datadir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand,
+											lastRestartPointFname);
+
+			if (restore_ok)
+			{
+				xlogreadfd = open(xlogfpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogreadfd < 0)
+				{
+					printf(_("could not open restored from archive file \"%s\": %s\n"), xlogfpath,
+				    	   strerror(errno));
+				    return -1;
+				}
+				else
+					pg_log(PG_DEBUG, "using restored from archive version of file \"%s\"\n", xlogfpath);
+			}
+			else
+			{
+				printf(_("could not restore file \"%s\" from archive: %s\n"), xlogfname,
+					   strerror(errno));
+				return -1;
+			}
 		}
 	}
 
@@ -409,3 +468,116 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return true.
+ *
+ * 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.
+ * 
+ * This is an adapted to frontend version of
+ * RestoreArchivedFile function from transam/xlogarchive.c
+ */
+bool
+RestoreArchivedWAL(const char *path, const char *xlogfname, off_t expectedSize,
+				   const char *restoreCommand, const char *lastRestartPointFname)
+{
+	char		xlogpath[MAXPGPATH];
+	char		xlogRestoreCmd[MAXPGPATH];
+	char	   *dp;
+	char	   *endp;
+	const char *sp;
+	int			rc;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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';
+
+	/*
+	 * 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)
+			{
+				printf(_("archive file \"%s\" has wrong size: %lu instead of %lu, %s"),
+					   	xlogfname, (unsigned long) stat_buf.st_size, (unsigned long) expectedSize,
+					   	strerror(errno));
+			}
+			else
+				return true;
+		}
+		else
+		{
+			/* Stat failed */
+			printf(_("could not stat file \"%s\": %s"),
+				   	xlogpath,
+					strerror(errno));
+		}
+	}
+
+	return false;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 96531063869c..3acac817eb4f 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -19,11 +19,13 @@
 #include "file_ops.h"
 #include "filemap.h"
 #include "logging.h"
+#include "guc-file-fe.h"
 
 #include "access/timeline.h"
 #include "access/xlog_internal.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
+#include "common/controldata_utils.h"
 #include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/restricted_token.h"
@@ -52,11 +54,13 @@ int			WalSegSz;
 char	   *datadir_target = NULL;
 char	   *datadir_source = NULL;
 char	   *connstr_source = NULL;
+char	   *restore_command = NULL;
 
 bool		debug = false;
 bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
+bool		restore_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -75,6 +79,9 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"));
 	printf(_("                                 safely to disk\n"));
 	printf(_("  -P, --progress                 write progress messages\n"));
+	printf(_("  -r, --use-recovery-conf        use restore_command in the recovery.conf to\n"));
+	printf(_("                                 retreive WALs from archive\n"));
+	printf(_("  -R, --restore-command=COMMAND  restore command\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
@@ -94,9 +101,12 @@ main(int argc, char **argv)
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
+		{"use-recovery-conf", no_argument, NULL, 'r'},
+		{"restore-command", required_argument, NULL, 'R'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
+	char		recfile_fullpath[MAXPGPATH];
 	int			option_index;
 	int			c;
 	XLogRecPtr	divergerec;
@@ -129,7 +139,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "DR:nNPr", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -140,6 +150,10 @@ main(int argc, char **argv)
 			case 'P':
 				showprogress = true;
 				break;
+			
+			case 'r':
+				restore_wals = true;
+				break;
 
 			case 'n':
 				dry_run = true;
@@ -157,6 +171,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'R':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -223,6 +241,71 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	if (restore_command != NULL)
+	{
+		pg_log(PG_DEBUG, "using command line restore_command=\'%s\'.\n", restore_command);
+	}
+	else if (restore_wals)
+	{
+		FILE	   *recovery_conf_file;
+
+		/* 
+		 * Look for recovery.conf in the target data directory and
+		 * try to get restore_command from there.
+		 */
+		snprintf(recfile_fullpath, sizeof(recfile_fullpath), "%s/%s", datadir_target, RECOVERY_COMMAND_FILE);
+		recovery_conf_file = fopen(recfile_fullpath, "r");
+
+		if (recovery_conf_file == NULL)
+		{
+			fprintf(stderr, _("%s: option -r/--use-recovery-conf is specified, but recovery.conf is absent in the target directory\n"),
+					progname);
+			fprintf(stdout, _("You have to add recovery.conf or pass restore_command with -R/--restore-command option.\n"));
+			exit(1);
+		}
+		else
+		{
+			ConfigVariable *item,
+						   *head = NULL,
+						   *tail = NULL;
+			bool			config_is_parsed;
+
+			/*
+			 * We pass a fullpath to the recovery.conf as calling_file here, since
+			 * parser will use its parent directory as base for all further includes
+			 * if any exist.
+			 */
+			config_is_parsed = ParseConfigFile(RECOVERY_COMMAND_FILE, true,
+											   recfile_fullpath, 0, 0,
+											   PG_WARNING, &head, &tail);
+			fclose(recovery_conf_file);
+
+			if (config_is_parsed)
+			{
+				for (item = head; item; item = item->next)
+				{
+					if (strcmp(item->name, "restore_command") == 0)
+					{
+						if (restore_command != NULL)
+						{
+							pfree(restore_command);
+							restore_command = NULL;
+						}
+						restore_command = pstrdup(item->value);
+						pg_log(PG_DEBUG, "using restore_command=\'%s\' from recovery.conf.\n", restore_command);
+					}
+				}
+
+				if (restore_command == NULL)
+					pg_fatal("could not find restore_command in recovery.conf file %s\n", recfile_fullpath);
+			}
+			else
+				pg_fatal("could not parse recovery.conf file %s\n", recfile_fullpath);
+
+			FreeConfigVariables(head);
+		}
+	}
+
 	/* Connect to remote server */
 	if (connstr_source)
 		libpqConnect(connstr_source);
@@ -294,9 +377,9 @@ main(int argc, char **argv)
 		exit(0);
 	}
 
-	findLastCheckpoint(datadir_target, divergerec,
+	findLastCheckpoint(datadir_target, &ControlFile_target, divergerec,
 					   lastcommontliIndex,
-					   &chkptrec, &chkpttli, &chkptredo);
+					   &chkptrec, &chkpttli, &chkptredo, restore_command);
 	printf(_("rewinding from last common checkpoint at %X/%X on timeline %u\n"),
 		   (uint32) (chkptrec >> 32), (uint32) chkptrec,
 		   chkpttli);
@@ -319,7 +402,7 @@ main(int argc, char **argv)
 	 */
 	pg_log(PG_PROGRESS, "reading WAL in target\n");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
-				   ControlFile_target.checkPoint);
+				   &ControlFile_target, 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 3f4ba7a26760..ba9d13e9bd04 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -14,6 +14,7 @@
 #include "datapagemap.h"
 
 #include "access/timeline.h"
+#include "catalog/pg_control.h"
 #include "storage/block.h"
 #include "storage/relfilenode.h"
 
@@ -32,11 +33,10 @@ extern int	targetNentries;
 
 /* in parsexlog.c */
 extern void extractPageMap(const char *datadir, XLogRecPtr startpoint,
-			   int tliIndex, XLogRecPtr endpoint);
-extern void findLastCheckpoint(const char *datadir, XLogRecPtr searchptr,
-				   int tliIndex,
-				   XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
-				   XLogRecPtr *lastchkptredo);
+			   int tliIndex, ControlFileData *targetCF, const char *restoreCommand);
+extern void findLastCheckpoint(const char *datadir, ControlFileData *targetCF, XLogRecPtr searchptr,
+				   int tliIndex, XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
+				   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 53dbf45be29d..22777bff82d4 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 => 10;
+use Test::More tests => 15;
 
 use RewindTest;
 
@@ -103,5 +103,6 @@ sub run_test
 # 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/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 2c9e42783113..10c835efc136 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 9;
 
 use RewindTest;
 
@@ -59,5 +59,6 @@ sub run_test
 # 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/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index 496f38c4570c..f94ce329f473 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
 
 use File::Find;
 
@@ -87,5 +87,6 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
 
 exit(0);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index b562044fa716..674e4a1df5ac 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -73,6 +73,7 @@ my $frontend_extraincludes = {
 };
 my $frontend_extrasource = {
 	'psql' => ['src/bin/psql/psqlscanslash.l'],
+	'pg_rewind' => ['src/bin/pg_rewind/guc-file-fe.l'],
 	'pgbench' =>
 	  [ 'src/bin/pgbench/exprscan.l', 'src/bin/pgbench/exprparse.y' ]
 };
#12Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Alexey Kondratov (#11)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Wed, Nov 7, 2018 at 10:58 AM Alexey Kondratov <a.kondratov@postgrespro.ru> wrote:

On 30.10.2018 06:01, Michael Paquier wrote:

On Mon, Oct 29, 2018 at 12:09:21PM +0300, Alexey Kondratov wrote:

Currently in the patch, with dry-run option (-n) pg_rewind only fetches
missing WALs to be able to build file map, while doesn't touch any data
files. So I guess it behaves exactly as you described and we do not need a
separate tool.

Makes sense perhaps. Fetching only WAL segments which are needed for
the file map is critical, as you don't want to spend bandwidth for
nothing. Now, I look at your patch, and I can see things to complain
about, at least three at short glance:
- The TAP test added will fail on Windows.

Thank you for this. Build on Windows has been broken as well. I fixed it
in the new version of patch, please find attached.

Just to confirm, patch still can be applied without conflicts, and pass all the
tests. Also I like the original motivation for the feature, sounds pretty
useful. For now I'm moving it to the next CF.

- Simply copy-pasting RestoreArchivedWAL() from the backend code to
pg_rewind is not an acceptable option. You don't care about %r either
in this case.

According to the docs [1] %r is a valid alias and may be used in
restore_command too, so if we take restore_command from recovery.conf it
might be there. If we just drop it, then restore_command may stop
working. Though I do not know real life examples of restore_command with
%r, we should treat it in expected way (as backend does), of course if
we want an option to take it from recovery.conf.

- Reusing the GUC parser is something I would avoid as well. Not worth
the complexity.

Yes, I don't like it either. I will try to make guc-file.l frontend safe.

Any success with that?

#13Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Dmitry Dolgov (#12)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi Dmitry,

On 30.11.2018 19:04, Dmitry Dolgov wrote:

Just to confirm, patch still can be applied without conflicts, and pass all the
tests. Also I like the original motivation for the feature, sounds pretty
useful. For now I'm moving it to the next CF.

Thanks, although I have slightly updated patch to handle recent merge of
the recovery.conf into GUCs and postgresq.conf [1]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=2dedf4d9a899b36d1a8ed29be5efbd1b31a8fe85, new patch is attached.

- Reusing the GUC parser is something I would avoid as well. Not worth
the complexity.

Yes, I don't like it either. I will try to make guc-file.l frontend safe.

Any success with that?

I looked into it and found that currently guc-file.c is built as part of
guc.c, so it seems to be even more complicated to unbound guc-file.c
from backend. Thus, I have some plan of how to proceed with patch:

1) Add guc-file.h and build guc-file.c separately from guc.c

2) Put guc-file.l / guc-file.h into common/*

3) Isolate all backend specific calls in guc-file.l with #ifdef FRONTEND

Though I am not sure that this work is worth doing against extra
redundancy added by simply adding frontend-safe copy of guc-file.l
lexer. If someone has any thoughts I would be glad to receive comments.

[1]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=2dedf4d9a899b36d1a8ed29be5efbd1b31a8fe85
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=2dedf4d9a899b36d1a8ed29be5efbd1b31a8fe85

Regards

--
Alexey Kondratov

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

Attachments:

0001-pg_rewind-options-to-use-restore_command-v1.2.patchtext/x-patch; name=0001-pg_rewind-options-to-use-restore_command-v1.2.patchDownload
From 521f62872d4e95cd02ddb535b8320256ff5e90cc Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <alex.lumir@gmail.com>
Date: Fri, 21 Dec 2018 14:00:30 +0300
Subject: [PATCH] pg_rewind: options to use restore_command from
 postgresql.conf or command line.

---
 src/bin/pg_rewind/Makefile            |   5 +-
 src/bin/pg_rewind/RewindTest.pm       |  46 +-
 src/bin/pg_rewind/guc-file-fe.h       |  40 ++
 src/bin/pg_rewind/guc-file-fe.l       | 776 ++++++++++++++++++++++++++
 src/bin/pg_rewind/parsexlog.c         | 182 +++++-
 src/bin/pg_rewind/pg_rewind.c         |  91 ++-
 src/bin/pg_rewind/pg_rewind.h         |  10 +-
 src/bin/pg_rewind/t/001_basic.pl      |   3 +-
 src/bin/pg_rewind/t/002_databases.pl  |   3 +-
 src/bin/pg_rewind/t/003_extrafiles.pl |   3 +-
 src/tools/msvc/Mkvcbuild.pm           |   1 +
 11 files changed, 1141 insertions(+), 19 deletions(-)
 create mode 100644 src/bin/pg_rewind/guc-file-fe.h
 create mode 100644 src/bin/pg_rewind/guc-file-fe.l

diff --git a/src/bin/pg_rewind/Makefile b/src/bin/pg_rewind/Makefile
index 2bcfcc61af..a0f5f97544 100644
--- a/src/bin/pg_rewind/Makefile
+++ b/src/bin/pg_rewind/Makefile
@@ -15,11 +15,12 @@ subdir = src/bin/pg_rewind
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-override CPPFLAGS := -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)
+override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)
 LDFLAGS_INTERNAL += $(libpq_pgport)
 
 OBJS	= pg_rewind.o parsexlog.o xlogreader.o datapagemap.o timeline.o \
 	fetch.o file_ops.o copy_fetch.o libpq_fetch.o filemap.o logging.o \
+	guc-file-fe.o \
 	$(WIN32RES)
 
 EXTRA_CLEAN = xlogreader.c
@@ -32,6 +33,8 @@ pg_rewind: $(OBJS) | submake-libpq submake-libpgport
 xlogreader.c: % : $(top_srcdir)/src/backend/access/transam/%
 	rm -f $@ && $(LN_S) $< .
 
+distprep: guc-file-fe.c
+
 install: all installdirs
 	$(INSTALL_PROGRAM) pg_rewind$(X) '$(DESTDIR)$(bindir)/pg_rewind$(X)'
 
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 3d07da5d94..b43c18a8c3 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -39,7 +39,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions 'catpath';
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -250,6 +252,48 @@ sub run_pg_rewind
 			],
 			'pg_rewind remote');
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $wals_archive_dir = catpath(${TestLib::tmp_check}, 'master_wals_archive');
+		my $test_master_datadir = $node_master->data_dir;
+		my @wal_files = bsd_glob catpath($test_master_datadir, 'pg_wal', '0000000*');
+		my $restore_command;
+
+		remove_tree($wals_archive_dir);
+		make_path($wals_archive_dir) or die;
+
+		# Move all old master WAL files to the archive.
+		# Old master should be stopped at this point.
+		foreach my $wal_file (@wal_files)
+		{
+			move($wal_file, "$wals_archive_dir/") or die;
+		}
+
+		if ($windows_os)
+		{
+			$restore_command = "copy $wals_archive_dir\\\%f \%p";
+		}
+		else
+		{
+			$restore_command = "cp $wals_archive_dir/\%f \%p";
+		}
+
+		# 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",
+				"-R", $restore_command
+			],
+			'pg_rewind archive');
+	}
 	else
 	{
 
diff --git a/src/bin/pg_rewind/guc-file-fe.h b/src/bin/pg_rewind/guc-file-fe.h
new file mode 100644
index 0000000000..3a078c5cdd
--- /dev/null
+++ b/src/bin/pg_rewind/guc-file-fe.h
@@ -0,0 +1,40 @@
+#ifndef PG_REWIND_GUC_FILE_FE_H
+#define PG_REWIND_GUC_FILE_FE_H
+
+#include "c.h"
+
+#define RECOVERY_COMMAND_FILE	"postgresql.conf"
+
+/*
+ * Parsing the configuration file(s) will return a list of name-value pairs
+ * with source location info.  We also abuse this data structure to carry
+ * error reports about the config files.  An entry reporting an error will
+ * have errmsg != NULL, and might have NULLs for name, value, and/or filename.
+ *
+ * If "ignore" is true, don't attempt to apply the item (it might be an error
+ * report, or an item we determined to be duplicate).  "applied" is set true
+ * if we successfully applied, or could have applied, the setting.
+ */
+typedef struct ConfigVariable
+{
+	char	   *name;
+	char	   *value;
+	char	   *errmsg;
+	char	   *filename;
+	int			sourceline;
+	bool		ignore;
+	bool		applied;
+	struct ConfigVariable *next;
+} ConfigVariable;
+
+extern bool ParseConfigFile(const char *config_file, bool strict,
+				const char *calling_file, int calling_lineno,
+				int depth, int elevel,
+				ConfigVariable **head_p, ConfigVariable **tail_p);
+
+extern bool ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
+				ConfigVariable **head_p, ConfigVariable **tail_p);
+
+extern void FreeConfigVariables(ConfigVariable *list);
+
+#endif							/* PG_REWIND_GUC_FILE_FE_H */
diff --git a/src/bin/pg_rewind/guc-file-fe.l b/src/bin/pg_rewind/guc-file-fe.l
new file mode 100644
index 0000000000..b7ef47111a
--- /dev/null
+++ b/src/bin/pg_rewind/guc-file-fe.l
@@ -0,0 +1,776 @@
+/* -*-pgsql-c-*- */
+/*
+ * Configuration files parser for usage in frontend.
+ *
+ * Copyright (c) 2000-2018, PostgreSQL Global Development Group
+ *
+ * src/bin/pg_rewind/guc-file-fe.l
+ * Modified version of src/backend/utils/misc/guc-file.l
+ */
+
+%{
+
+#include "postgres_fe.h"
+
+#include <ctype.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <setjmp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "guc-file-fe.h"
+#include "logging.h"
+
+#include "common/fe_memutils.h"
+
+/*
+ * flex emits a yy_fatal_error() function that it calls in response to
+ * critical errors like malloc failure, file I/O errors, and detection of
+ * internal inconsistency.  That function prints a message and calls exit().
+ * Mutate it to instead call our handler, which jumps out of the parser.
+ */
+#undef fprintf
+#define fprintf(file, fmt, msg) GUC_flex_fatal(msg)
+
+enum
+{
+	GUC_ID = 1,
+	GUC_STRING = 2,
+	GUC_INTEGER = 3,
+	GUC_REAL = 4,
+	GUC_EQUALS = 5,
+	GUC_UNQUOTED_STRING = 6,
+	GUC_QUALIFIED_ID = 7,
+	GUC_EOL = 99,
+	GUC_ERROR = 100
+};
+
+static unsigned int ConfigFileLineno;
+static const char *GUC_flex_fatal_errmsg;
+static sigjmp_buf *GUC_flex_fatal_jmp;
+
+static void FreeConfigVariable(ConfigVariable *item);
+
+static void record_config_file_error(const char *errmsg,
+						 const char *config_file,
+						 int lineno,
+						 ConfigVariable **head_p,
+						 ConfigVariable **tail_p);
+
+static int	GUC_flex_fatal(const char *msg);
+static char *GUC_scanstr(const char *s);
+static bool ParseConfigDirectory(const char *includedir,
+					 const char *calling_file, int calling_lineno,
+					 int depth, int elevel,
+					 ConfigVariable **head_p,
+					 ConfigVariable **tail_p);
+
+/* LCOV_EXCL_START */
+
+%}
+
+%option 8bit
+%option never-interactive
+%option nodefault
+%option noinput
+%option nounput
+%option noyywrap
+%option warn
+%option prefix="GUC_yy"
+
+
+SIGN			("-"|"+")
+DIGIT			[0-9]
+HEXDIGIT		[0-9a-fA-F]
+
+UNIT_LETTER		[a-zA-Z]
+
+INTEGER			{SIGN}?({DIGIT}+|0x{HEXDIGIT}+){UNIT_LETTER}*
+
+EXPONENT		[Ee]{SIGN}?{DIGIT}+
+REAL			{SIGN}?{DIGIT}*"."{DIGIT}*{EXPONENT}?
+
+LETTER			[A-Za-z_\200-\377]
+LETTER_OR_DIGIT [A-Za-z_0-9\200-\377]
+
+ID				{LETTER}{LETTER_OR_DIGIT}*
+QUALIFIED_ID	{ID}"."{ID}
+
+UNQUOTED_STRING {LETTER}({LETTER_OR_DIGIT}|[-._:/])*
+STRING			\'([^'\\\n]|\\.|\'\')*\'
+
+%%
+
+\n				ConfigFileLineno++; return GUC_EOL;
+[ \t\r]+		/* eat whitespace */
+#.*				/* eat comment (.* matches anything until newline) */
+
+{ID}			return GUC_ID;
+{QUALIFIED_ID}	return GUC_QUALIFIED_ID;
+{STRING}		return GUC_STRING;
+{UNQUOTED_STRING} return GUC_UNQUOTED_STRING;
+{INTEGER}		return GUC_INTEGER;
+{REAL}			return GUC_REAL;
+=				return GUC_EQUALS;
+
+.				return GUC_ERROR;
+
+%%
+
+/* LCOV_EXCL_STOP */
+
+/*
+ * the bare comparison function for GUC names
+ */
+static int
+guc_name_compare(const char *namea, const char *nameb)
+{
+	/*
+	 * The temptation to use strcasecmp() here must be resisted, because the
+	 * array ordering has to remain stable across setlocale() calls. So, build
+	 * our own with a simple ASCII-only downcasing.
+	 */
+	while (*namea && *nameb)
+	{
+		char		cha = *namea++;
+		char		chb = *nameb++;
+
+		if (cha >= 'A' && cha <= 'Z')
+			cha += 'a' - 'A';
+		if (chb >= 'A' && chb <= 'Z')
+			chb += 'a' - 'A';
+		if (cha != chb)
+			return cha - chb;
+	}
+	if (*namea)
+		return 1;				/* a is longer */
+	if (*nameb)
+		return -1;				/* b is longer */
+	return 0;
+}
+
+/*
+ * Given a configuration file or directory location that may be a relative
+ * path, return an absolute one.  We consider the location to be relative to
+ * the directory holding the calling file.
+ */
+static char *
+AbsoluteConfigLocation(const char *location, const char *calling_file)
+{
+	char abs_path[MAXPGPATH];
+
+	if (is_absolute_path(location))
+		return pstrdup(location);
+	else
+	{
+		strlcpy(abs_path, calling_file, sizeof(abs_path));
+		get_parent_directory(abs_path);
+		join_path_components(abs_path, abs_path, location);
+		canonicalize_path(abs_path);
+
+		return pstrdup(abs_path);
+	}
+}
+
+/*
+ * Read and parse a single configuration file.  This function recurses
+ * to handle "include" directives.
+ *
+ * If "strict" is true, treat failure to open the config file as an error,
+ * otherwise just skip the file.
+ *
+ * calling_file/calling_lineno identify the source of the request.
+ * Pass NULL/0 if not recursing from an inclusion request.
+ *
+ * See ParseConfigFp for further details.  This one merely adds opening the
+ * config file rather than working from a caller-supplied file descriptor,
+ * and absolute-ifying the path name if necessary.
+ */
+bool
+ParseConfigFile(const char *config_file, bool strict,
+				const char *calling_file, int calling_lineno,
+				int depth, int elevel,
+				ConfigVariable **head_p,
+				ConfigVariable **tail_p)
+{
+	char	   *abs_path;
+	bool		OK = true;
+	FILE	   *fp;
+
+	/*
+	 * Reject too-deep include nesting depth.  This is just a safety check to
+	 * avoid dumping core due to stack overflow if an include file loops back
+	 * to itself.  The maximum nesting depth is pretty arbitrary.
+	 */
+	if (depth > 10)
+	{
+		pg_log(PG_WARNING, 
+				"could not open configuration file \"%s\": maximum nesting depth exceeded\n",
+				config_file);
+		record_config_file_error("nesting depth exceeded",
+								 calling_file, calling_lineno,
+								 head_p, tail_p);
+		return false;
+	}
+
+	abs_path = AbsoluteConfigLocation(config_file, calling_file);
+	fp = fopen(abs_path, "r");
+	if (!fp)
+	{
+		if (strict)
+		{
+			pg_log(PG_WARNING, 
+				"could not open configuration file \"%s\"\n",
+				abs_path);
+			record_config_file_error(psprintf("could not open file \"%s\"",
+											  abs_path),
+									 calling_file, calling_lineno,
+									 head_p, tail_p);
+			OK = false;
+		}
+		else
+		{
+			pg_log(PG_WARNING, 
+				"skipping missing configuration file \"%s\"\n",
+				abs_path);
+		}
+		goto cleanup;
+	}
+
+	OK = ParseConfigFp(fp, abs_path, depth, elevel, head_p, tail_p);
+
+cleanup:
+	if (fp)
+		fclose(fp);
+	pfree(abs_path);
+
+	return OK;
+}
+
+/*
+ * Capture an error message in the ConfigVariable list returned by
+ * config file parsing.
+ */
+static void
+record_config_file_error(const char *errmsg,
+						 const char *config_file,
+						 int lineno,
+						 ConfigVariable **head_p,
+						 ConfigVariable **tail_p)
+{
+	ConfigVariable *item;
+
+	item = palloc(sizeof *item);
+	item->name = NULL;
+	item->value = NULL;
+	item->errmsg = pstrdup(errmsg);
+	item->filename = config_file ? pstrdup(config_file) : NULL;
+	item->sourceline = lineno;
+	item->ignore = true;
+	item->applied = false;
+	item->next = NULL;
+	if (*head_p == NULL)
+		*head_p = item;
+	else
+		(*tail_p)->next = item;
+	*tail_p = item;
+}
+
+/*
+ * Flex fatal errors bring us here.  Stash the error message and jump back to
+ * ParseConfigFp().  Assume all msg arguments point to string constants; this
+ * holds for flex 2.5.31 (earliest we support) and flex 2.5.35 (latest as of
+ * this writing).  Otherwise, we would need to copy the message.
+ *
+ * We return "int" since this takes the place of calls to fprintf().
+*/
+static int
+GUC_flex_fatal(const char *msg)
+{
+	GUC_flex_fatal_errmsg = msg;
+	siglongjmp(*GUC_flex_fatal_jmp, 1);
+	return 0;					/* keep compiler quiet */
+}
+
+/*
+ * Read and parse a single configuration file.  This function recurses
+ * to handle "include" directives.
+ *
+ * Input parameters:
+ *	fp: file pointer from AllocateFile for the configuration file to parse
+ *	config_file: absolute or relative path name of the configuration file
+ *	depth: recursion depth (should be 0 in the outermost call)
+ *	elevel: error logging level to use
+ * Input/Output parameters:
+ *	head_p, tail_p: head and tail of linked list of name/value pairs
+ *
+ * *head_p and *tail_p must be initialized, either to NULL or valid pointers
+ * to a ConfigVariable list, before calling the outer recursion level.  Any
+ * name-value pairs read from the input file(s) will be appended to the list.
+ * Error reports will also be appended to the list, if elevel < ERROR.
+ *
+ * Returns TRUE if successful, FALSE if an error occurred.
+ *
+ * Note: this function is used to parse not only postgresql.conf, but
+ * various other configuration files that use the same "name = value"
+ * syntax.  Hence, do not do anything here or in the subsidiary routines
+ * ParseConfigFile/ParseConfigDirectory that assumes we are processing
+ * GUCs specifically.
+ */
+bool
+ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
+			  ConfigVariable **head_p, ConfigVariable **tail_p)
+{
+	volatile bool OK = true;
+	unsigned int save_ConfigFileLineno = ConfigFileLineno;
+	sigjmp_buf *save_GUC_flex_fatal_jmp = GUC_flex_fatal_jmp;
+	sigjmp_buf	flex_fatal_jmp;
+	volatile YY_BUFFER_STATE lex_buffer = NULL;
+	int			errorcount;
+	int			token;
+
+	if (sigsetjmp(flex_fatal_jmp, 1) == 0)
+		GUC_flex_fatal_jmp = &flex_fatal_jmp;
+	else
+	{
+		/*
+		 * Regain control after a fatal, internal flex error.  It may have
+		 * corrupted parser state.  Consequently, abandon the file, but trust
+		 * that the state remains sane enough for yy_delete_buffer().
+		 */
+		pg_log(PG_WARNING, "%s at file \"%s\" line %u\n", GUC_flex_fatal_errmsg,
+				config_file, ConfigFileLineno);
+		record_config_file_error(GUC_flex_fatal_errmsg,
+								 config_file, ConfigFileLineno,
+								 head_p, tail_p);
+		OK = false;
+		goto cleanup;
+	}
+
+	/*
+	 * Parse
+	 */
+	ConfigFileLineno = 1;
+	errorcount = 0;
+
+	lex_buffer = yy_create_buffer(fp, YY_BUF_SIZE);
+	yy_switch_to_buffer(lex_buffer);
+
+	/* This loop iterates once per logical line */
+	while ((token = yylex()))
+	{
+		char	   *opt_name = NULL;
+		char	   *opt_value = NULL;
+		ConfigVariable *item;
+
+		if (token == GUC_EOL)	/* empty or comment line */
+			continue;
+
+		/* first token on line is option name */
+		if (token != GUC_ID && token != GUC_QUALIFIED_ID)
+			goto parse_error;
+		opt_name = pstrdup(yytext);
+
+		/* next we have an optional equal sign; discard if present */
+		token = yylex();
+		if (token == GUC_EQUALS)
+			token = yylex();
+
+		/* now we must have the option value */
+		if (token != GUC_ID &&
+			token != GUC_STRING &&
+			token != GUC_INTEGER &&
+			token != GUC_REAL &&
+			token != GUC_UNQUOTED_STRING)
+			goto parse_error;
+		if (token == GUC_STRING)	/* strip quotes and escapes */
+			opt_value = GUC_scanstr(yytext);
+		else
+			opt_value = pstrdup(yytext);
+
+		/* now we'd like an end of line, or possibly EOF */
+		token = yylex();
+		if (token != GUC_EOL)
+		{
+			if (token != 0)
+				goto parse_error;
+			/* treat EOF like \n for line numbering purposes, cf bug 4752 */
+			ConfigFileLineno++;
+		}
+
+		/* OK, process the option name and value */
+		if (guc_name_compare(opt_name, "include_dir") == 0)
+		{
+			/*
+			 * An include_dir directive isn't a variable and should be
+			 * processed immediately.
+			 */
+			if (!ParseConfigDirectory(opt_value,
+									  config_file, ConfigFileLineno - 1,
+									  depth + 1, elevel,
+									  head_p, tail_p))
+				OK = false;
+			yy_switch_to_buffer(lex_buffer);
+			pfree(opt_name);
+			pfree(opt_value);
+		}
+		else if (guc_name_compare(opt_name, "include_if_exists") == 0)
+		{
+			/*
+			 * An include_if_exists directive isn't a variable and should be
+			 * processed immediately.
+			 */
+			if (!ParseConfigFile(opt_value, false,
+								 config_file, ConfigFileLineno - 1,
+								 depth + 1, elevel,
+								 head_p, tail_p))
+				OK = false;
+			yy_switch_to_buffer(lex_buffer);
+			pfree(opt_name);
+			pfree(opt_value);
+		}
+		else if (guc_name_compare(opt_name, "include") == 0)
+		{
+			/*
+			 * An include directive isn't a variable and should be processed
+			 * immediately.
+			 */
+			if (!ParseConfigFile(opt_value, true,
+								 config_file, ConfigFileLineno - 1,
+								 depth + 1, elevel,
+								 head_p, tail_p))
+				OK = false;
+			yy_switch_to_buffer(lex_buffer);
+			pfree(opt_name);
+			pfree(opt_value);
+		}
+		else
+		{
+			/* ordinary variable, append to list */
+			item = palloc(sizeof *item);
+			item->name = opt_name;
+			item->value = opt_value;
+			item->errmsg = NULL;
+			item->filename = pstrdup(config_file);
+			item->sourceline = ConfigFileLineno - 1;
+			item->ignore = false;
+			item->applied = false;
+			item->next = NULL;
+			if (*head_p == NULL)
+				*head_p = item;
+			else
+				(*tail_p)->next = item;
+			*tail_p = item;
+		}
+
+		/* break out of loop if read EOF, else loop for next line */
+		if (token == 0)
+			break;
+		continue;
+
+parse_error:
+		/* release storage if we allocated any on this line */
+		if (opt_name)
+			pfree(opt_name);
+		if (opt_value)
+			pfree(opt_value);
+
+		/* report the error */
+		if (token == GUC_EOL || token == 0)
+		{
+			pg_log(PG_WARNING, "syntax error in file \"%s\" line %u, near end of line\n",
+					config_file, ConfigFileLineno - 1);
+			record_config_file_error("syntax error",
+									 config_file, ConfigFileLineno - 1,
+									 head_p, tail_p);
+		}
+		else
+		{
+			pg_log(PG_WARNING, "syntax error in file \"%s\" line %u, near token \"%s\"\n",
+				config_file, ConfigFileLineno, yytext);
+			record_config_file_error("syntax error",
+									 config_file, ConfigFileLineno,
+									 head_p, tail_p);
+		}
+		OK = false;
+		errorcount++;
+
+		/*
+		 * To avoid producing too much noise when fed a totally bogus file,
+		 * give up after 100 syntax errors per file (an arbitrary number).
+		 */
+		if (errorcount >= 100)
+		{
+			pg_log(PG_WARNING, "too many syntax errors found, abandoning file \"%s\"\n",
+					config_file);
+			break;
+		}
+
+		/* resync to next end-of-line or EOF */
+		while (token != GUC_EOL && token != 0)
+			token = yylex();
+		/* break out of loop on EOF */
+		if (token == 0)
+			break;
+	}
+
+cleanup:
+	yy_delete_buffer(lex_buffer);
+	/* Each recursion level must save and restore these static variables. */
+	ConfigFileLineno = save_ConfigFileLineno;
+	GUC_flex_fatal_jmp = save_GUC_flex_fatal_jmp;
+	return OK;
+}
+
+/*
+ * Read and parse all config files in a subdirectory in alphabetical order
+ *
+ * includedir is the absolute or relative path to the subdirectory to scan.
+ *
+ * calling_file/calling_lineno identify the source of the request.
+ * Pass NULL/0 if not recursing from an inclusion request.
+ *
+ * See ParseConfigFp for further details.
+ */
+bool
+ParseConfigDirectory(const char *includedir,
+					 const char *calling_file, int calling_lineno,
+					 int depth, int elevel,
+					 ConfigVariable **head_p,
+					 ConfigVariable **tail_p)
+{
+	char	   *directory;
+	DIR		   *d;
+	struct dirent *de;
+	char	  **filenames;
+	int			num_filenames;
+	int			size_filenames;
+	bool		status;
+
+	directory = AbsoluteConfigLocation(includedir, calling_file);
+	d = opendir(directory);
+	if (d == NULL)
+	{
+		pg_log(PG_WARNING, "could not open configuration directory \"%s\"\n",
+			directory);
+		record_config_file_error(psprintf("could not open directory \"%s\"",
+										  directory),
+								 calling_file, calling_lineno,
+								 head_p, tail_p);
+		status = false;
+		goto cleanup;
+	}
+
+	/*
+	 * Read the directory and put the filenames in an array, so we can sort
+	 * them prior to processing the contents.
+	 */
+	size_filenames = 32;
+	filenames = (char **) palloc(size_filenames * sizeof(char *));
+	num_filenames = 0;
+
+	while ((de = readdir(d)) != NULL)
+	{
+		struct stat st;
+		char		filename[MAXPGPATH];
+
+		/*
+		 * Only parse files with names ending in ".conf".  Explicitly reject
+		 * files starting with ".".  This excludes things like "." and "..",
+		 * as well as typical hidden files, backup files, and editor debris.
+		 */
+		if (strlen(de->d_name) < 6)
+			continue;
+		if (de->d_name[0] == '.')
+			continue;
+		if (strcmp(de->d_name + strlen(de->d_name) - 5, ".conf") != 0)
+			continue;
+
+		join_path_components(filename, directory, de->d_name);
+		canonicalize_path(filename);
+		if (stat(filename, &st) == 0)
+		{
+			if (!S_ISDIR(st.st_mode))
+			{
+				/* Add file to array, increasing its size in blocks of 32 */
+				if (num_filenames >= size_filenames)
+				{
+					size_filenames += 32;
+					filenames = (char **) repalloc(filenames,
+											size_filenames * sizeof(char *));
+				}
+				filenames[num_filenames] = pstrdup(filename);
+				num_filenames++;
+			}
+		}
+		else
+		{
+			/*
+			 * stat does not care about permissions, so the most likely reason
+			 * a file can't be accessed now is if it was removed between the
+			 * directory listing and now.
+			 */
+			pg_log(PG_WARNING, "could not stat file \"%s\"\n", filename);
+			record_config_file_error(psprintf("could not stat file \"%s\"",
+											  filename),
+									 calling_file, calling_lineno,
+									 head_p, tail_p);
+			status = false;
+			goto cleanup;
+		}
+	}
+
+	if (num_filenames > 0)
+	{
+		int			i;
+
+		qsort(filenames, num_filenames, sizeof(char *), pg_qsort_strcmp);
+		for (i = 0; i < num_filenames; i++)
+		{
+			if (!ParseConfigFile(filenames[i], true,
+								 calling_file, calling_lineno,
+								 depth, elevel,
+								 head_p, tail_p))
+			{
+				status = false;
+				goto cleanup;
+			}
+		}
+	}
+	status = true;
+
+cleanup:
+	if (d)
+		closedir(d);
+	pfree(directory);
+	return status;
+}
+
+/*
+ * Free a list of ConfigVariables, including the names and the values
+ */
+void
+FreeConfigVariables(ConfigVariable *list)
+{
+	ConfigVariable *item;
+
+	item = list;
+	while (item)
+	{
+		ConfigVariable *next = item->next;
+
+		FreeConfigVariable(item);
+		item = next;
+	}
+}
+
+/*
+ * Free a single ConfigVariable
+ */
+static void
+FreeConfigVariable(ConfigVariable *item)
+{
+	if (item->name)
+		pfree(item->name);
+	if (item->value)
+		pfree(item->value);
+	if (item->errmsg)
+		pfree(item->errmsg);
+	if (item->filename)
+		pfree(item->filename);
+	pfree(item);
+}
+
+
+/*
+ *		scanstr
+ *
+ * Strip the quotes surrounding the given string, and collapse any embedded
+ * '' sequences and backslash escapes.
+ *
+ * the string returned is palloc'd and should eventually be pfree'd by the
+ * caller.
+ */
+static char *
+GUC_scanstr(const char *s)
+{
+	char	   *newStr;
+	int			len,
+				i,
+				j;
+
+	Assert(s != NULL && s[0] == '\'');
+	len = strlen(s);
+	Assert(len >= 2);
+	Assert(s[len - 1] == '\'');
+
+	/* Skip the leading quote; we'll handle the trailing quote below */
+	s++, len--;
+
+	/* Since len still includes trailing quote, this is enough space */
+	newStr = palloc(len);
+
+	for (i = 0, j = 0; i < len; i++)
+	{
+		if (s[i] == '\\')
+		{
+			i++;
+			switch (s[i])
+			{
+				case 'b':
+					newStr[j] = '\b';
+					break;
+				case 'f':
+					newStr[j] = '\f';
+					break;
+				case 'n':
+					newStr[j] = '\n';
+					break;
+				case 'r':
+					newStr[j] = '\r';
+					break;
+				case 't':
+					newStr[j] = '\t';
+					break;
+				case '0':
+				case '1':
+				case '2':
+				case '3':
+				case '4':
+				case '5':
+				case '6':
+				case '7':
+					{
+						int			k;
+						long		octVal = 0;
+
+						for (k = 0;
+							 s[i + k] >= '0' && s[i + k] <= '7' && k < 3;
+							 k++)
+							octVal = (octVal << 3) + (s[i + k] - '0');
+						i += k - 1;
+						newStr[j] = ((char) octVal);
+					}
+					break;
+				default:
+					newStr[j] = s[i];
+					break;
+			}					/* switch */
+		}
+		else if (s[i] == '\'' && s[i + 1] == '\'')
+		{
+			/* doubled quote becomes just one quote */
+			newStr[j] = s[++i];
+		}
+		else
+			newStr[j] = s[i];
+		j++;
+	}
+
+	/* We copied the ending quote to newStr, so replace with \0 */
+	Assert(j > 0 && j <= len);
+	newStr[--j] = '\0';
+
+	return newStr;
+}
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 40028471bf..11a9c26cd2 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "pg_rewind.h"
 #include "filemap.h"
@@ -45,7 +46,10 @@ static char xlogfpath[MAXPGPATH];
 typedef struct XLogPageReadPrivate
 {
 	const char *datadir;
+	const char *restoreCommand;
 	int			tliIndex;
+	XLogRecPtr  oldrecptr;
+	TimeLineID  oldtli;
 } XLogPageReadPrivate;
 
 static int SimpleXLogPageRead(XLogReaderState *xlogreader,
@@ -53,6 +57,10 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
 				   int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
 				   TimeLineID *pageTLI);
 
+static bool RestoreArchivedWAL(const char *path, const char *xlogfname, 
+					off_t expectedSize, const char *restoreCommand,
+					const char *lastRestartPointFname);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -60,15 +68,19 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
  */
 void
 extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
-			   XLogRecPtr endpoint)
+			   ControlFileData *targetCF, const char *restore_command)
 {
 	XLogRecord *record;
+	XLogRecPtr endpoint = targetCF->checkPoint;
 	XLogReaderState *xlogreader;
 	char	   *errormsg;
 	XLogPageReadPrivate private;
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restore_command;
+	private.oldrecptr = targetCF->checkPointCopy.redo;
+	private.oldtli = targetCF->checkPointCopy.ThisTimeLineID;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -154,9 +166,9 @@ readOneRecord(const char *datadir, XLogRecPtr ptr, int tliIndex)
  * Find the previous checkpoint preceding given WAL location.
  */
 void
-findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
+findLastCheckpoint(const char *datadir, ControlFileData *targetCF, XLogRecPtr forkptr, int tliIndex,
 				   XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
-				   XLogRecPtr *lastchkptredo)
+				   XLogRecPtr *lastchkptredo, const char *restoreCommand)
 {
 	/* Walk backwards, starting from the given record */
 	XLogRecord *record;
@@ -181,6 +193,9 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
+	private.oldrecptr = targetCF->checkPointCopy.redo;
+	private.oldtli = targetCF->checkPointCopy.ThisTimeLineID;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -291,9 +306,53 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
 
 		if (xlogreadfd < 0)
 		{
-			printf(_("could not open file \"%s\": %s\n"), xlogfpath,
+			bool  restore_ok;
+			char  lastRestartPointFname[MAXFNAMELEN];
+			XLogSegNo restartSegNo;
+
+			/*
+			 * If we have no restore_command to execute, then exit.
+			 */
+			if (private->restoreCommand == NULL)
+			{
+				printf(_("could not open file \"%s\": %s\n"), xlogfpath,
 				   strerror(errno));
-			return -1;
+				return -1;
+			}
+			
+			XLByteToSeg(private->oldrecptr, restartSegNo, WalSegSz);
+			XLogFileName(lastRestartPointFname, private->oldtli, restartSegNo,
+						 WalSegSz);
+
+			/*
+			 * Since we have restore_command to execute, then try to retreive
+			 * missing WAL file from the archive.
+			 */
+			restore_ok = RestoreArchivedWAL(private->datadir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand,
+											lastRestartPointFname);
+
+			if (restore_ok)
+			{
+				xlogreadfd = open(xlogfpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogreadfd < 0)
+				{
+					printf(_("could not open restored from archive file \"%s\": %s\n"), xlogfpath,
+				    	   strerror(errno));
+				    return -1;
+				}
+				else
+					pg_log(PG_DEBUG, "using restored from archive version of file \"%s\"\n", xlogfpath);
+			}
+			else
+			{
+				printf(_("could not restore file \"%s\" from archive: %s\n"), xlogfname,
+					   strerror(errno));
+				return -1;
+			}
 		}
 	}
 
@@ -409,3 +468,116 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return true.
+ *
+ * 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.
+ * 
+ * This is an adapted to frontend version of
+ * RestoreArchivedFile function from transam/xlogarchive.c
+ */
+bool
+RestoreArchivedWAL(const char *path, const char *xlogfname, off_t expectedSize,
+				   const char *restoreCommand, const char *lastRestartPointFname)
+{
+	char		xlogpath[MAXPGPATH];
+	char		xlogRestoreCmd[MAXPGPATH];
+	char	   *dp;
+	char	   *endp;
+	const char *sp;
+	int			rc;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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';
+
+	/*
+	 * 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)
+			{
+				printf(_("archive file \"%s\" has wrong size: %lu instead of %lu, %s"),
+					   	xlogfname, (unsigned long) stat_buf.st_size, (unsigned long) expectedSize,
+					   	strerror(errno));
+			}
+			else
+				return true;
+		}
+		else
+		{
+			/* Stat failed */
+			printf(_("could not stat file \"%s\": %s"),
+				   	xlogpath,
+					strerror(errno));
+		}
+	}
+
+	return false;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 9653106386..816a91fd78 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -19,11 +19,13 @@
 #include "file_ops.h"
 #include "filemap.h"
 #include "logging.h"
+#include "guc-file-fe.h"
 
 #include "access/timeline.h"
 #include "access/xlog_internal.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
+#include "common/controldata_utils.h"
 #include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/restricted_token.h"
@@ -52,11 +54,13 @@ int			WalSegSz;
 char	   *datadir_target = NULL;
 char	   *datadir_source = NULL;
 char	   *connstr_source = NULL;
+char	   *restore_command = NULL;
 
 bool		debug = false;
 bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
+bool		restore_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -75,6 +79,9 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"));
 	printf(_("                                 safely to disk\n"));
 	printf(_("  -P, --progress                 write progress messages\n"));
+	printf(_("  -r, --use-postgresql-conf      use restore_command in the postgresql.conf to\n"));
+	printf(_("                                 retreive WALs from archive\n"));
+	printf(_("  -R, --restore-command=COMMAND  restore command\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
@@ -94,9 +101,12 @@ main(int argc, char **argv)
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
+		{"use-postgresql-conf", no_argument, NULL, 'r'},
+		{"restore-command", required_argument, NULL, 'R'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
+	char		recfile_fullpath[MAXPGPATH];
 	int			option_index;
 	int			c;
 	XLogRecPtr	divergerec;
@@ -129,7 +139,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "DR:nNPr", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -140,6 +150,10 @@ main(int argc, char **argv)
 			case 'P':
 				showprogress = true;
 				break;
+			
+			case 'r':
+				restore_wals = true;
+				break;
 
 			case 'n':
 				dry_run = true;
@@ -157,6 +171,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'R':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -223,6 +241,71 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	if (restore_command != NULL)
+	{
+		pg_log(PG_DEBUG, "using command line restore_command=\'%s\'.\n", restore_command);
+	}
+	else if (restore_wals)
+	{
+		FILE	   *recovery_conf_file;
+
+		/* 
+		 * Look for recovery.conf in the target data directory and
+		 * try to get restore_command from there.
+		 */
+		snprintf(recfile_fullpath, sizeof(recfile_fullpath), "%s/%s", datadir_target, RECOVERY_COMMAND_FILE);
+		recovery_conf_file = fopen(recfile_fullpath, "r");
+
+		if (recovery_conf_file == NULL)
+		{
+			fprintf(stderr, _("%s: option -r/--use-postgresql-conf is specified, but postgreslq.conf is absent in the target directory\n"),
+					progname);
+			fprintf(stdout, _("You have to add postgresql.conf or pass restore_command with -R/--restore-command option.\n"));
+			exit(1);
+		}
+		else
+		{
+			ConfigVariable *item,
+						   *head = NULL,
+						   *tail = NULL;
+			bool			config_is_parsed;
+
+			/*
+			 * We pass a fullpath to the recovery.conf as calling_file here, since
+			 * parser will use its parent directory as base for all further includes
+			 * if any exist.
+			 */
+			config_is_parsed = ParseConfigFile(RECOVERY_COMMAND_FILE, true,
+											   recfile_fullpath, 0, 0,
+											   PG_WARNING, &head, &tail);
+			fclose(recovery_conf_file);
+
+			if (config_is_parsed)
+			{
+				for (item = head; item; item = item->next)
+				{
+					if (strcmp(item->name, "restore_command") == 0)
+					{
+						if (restore_command != NULL)
+						{
+							pfree(restore_command);
+							restore_command = NULL;
+						}
+						restore_command = pstrdup(item->value);
+						pg_log(PG_DEBUG, "using restore_command=\'%s\' from recovery.conf.\n", restore_command);
+					}
+				}
+
+				if (restore_command == NULL)
+					pg_fatal("could not find restore_command in recovery.conf file %s\n", recfile_fullpath);
+			}
+			else
+				pg_fatal("could not parse recovery.conf file %s\n", recfile_fullpath);
+
+			FreeConfigVariables(head);
+		}
+	}
+
 	/* Connect to remote server */
 	if (connstr_source)
 		libpqConnect(connstr_source);
@@ -294,9 +377,9 @@ main(int argc, char **argv)
 		exit(0);
 	}
 
-	findLastCheckpoint(datadir_target, divergerec,
+	findLastCheckpoint(datadir_target, &ControlFile_target, divergerec,
 					   lastcommontliIndex,
-					   &chkptrec, &chkpttli, &chkptredo);
+					   &chkptrec, &chkpttli, &chkptredo, restore_command);
 	printf(_("rewinding from last common checkpoint at %X/%X on timeline %u\n"),
 		   (uint32) (chkptrec >> 32), (uint32) chkptrec,
 		   chkpttli);
@@ -319,7 +402,7 @@ main(int argc, char **argv)
 	 */
 	pg_log(PG_PROGRESS, "reading WAL in target\n");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
-				   ControlFile_target.checkPoint);
+				   &ControlFile_target, 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 3f4ba7a267..ba9d13e9bd 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -14,6 +14,7 @@
 #include "datapagemap.h"
 
 #include "access/timeline.h"
+#include "catalog/pg_control.h"
 #include "storage/block.h"
 #include "storage/relfilenode.h"
 
@@ -32,11 +33,10 @@ extern int	targetNentries;
 
 /* in parsexlog.c */
 extern void extractPageMap(const char *datadir, XLogRecPtr startpoint,
-			   int tliIndex, XLogRecPtr endpoint);
-extern void findLastCheckpoint(const char *datadir, XLogRecPtr searchptr,
-				   int tliIndex,
-				   XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
-				   XLogRecPtr *lastchkptredo);
+			   int tliIndex, ControlFileData *targetCF, const char *restoreCommand);
+extern void findLastCheckpoint(const char *datadir, ControlFileData *targetCF, XLogRecPtr searchptr,
+				   int tliIndex, XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
+				   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 53dbf45be2..22777bff82 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 => 10;
+use Test::More tests => 15;
 
 use RewindTest;
 
@@ -103,5 +103,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/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 2c9e427831..10c835efc1 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 9;
 
 use RewindTest;
 
@@ -59,5 +59,6 @@ template1
 # 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/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index 496f38c457..f94ce329f4 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
 
 use File::Find;
 
@@ -87,5 +87,6 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
 
 exit(0);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 2921d193a1..3da0fdc956 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -73,6 +73,7 @@ my $frontend_extraincludes = {
 };
 my $frontend_extrasource = {
 	'psql' => ['src/bin/psql/psqlscanslash.l'],
+	'pg_rewind' => ['src/bin/pg_rewind/guc-file-fe.l'],
 	'pgbench' =>
 	  [ 'src/bin/pgbench/exprscan.l', 'src/bin/pgbench/exprparse.y' ]
 };
-- 
2.17.1

#14Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Alexey Kondratov (#13)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Greetings,

- Reusing the GUC parser is something I would avoid as well.  Not
worth
the complexity.

Yes, I don't like it either. I will try to make guc-file.l frontend
safe.

Any success with that?

I looked into it and found that currently guc-file.c is built as part
of guc.c, so it seems to be even more complicated to unbound
guc-file.c from backend. Thus, I have some plan of how to proceed with
patch:

1) Add guc-file.h and build guc-file.c separately from guc.c

2) Put guc-file.l / guc-file.h into common/*

3) Isolate all backend specific calls in guc-file.l with #ifdef FRONTEND

Though I am not sure that this work is worth doing against extra
redundancy added by simply adding frontend-safe copy of guc-file.l
lexer. If someone has any thoughts I would be glad to receive comments.

I have finally worked it out. Now there is a common version of
guc-file.l and guc-file.c is built separately from guc.c. I had to use a
limited number of #ifndef FRONTEND, mostly to replace erreport calls.
Also, ProcessConfigFile and ProcessConfigFileInternal have been moved
inside guc.c explicitly as being a backend specific. So for me this
solution looks much more concise and neat.

Please, find the new version of patch attached. Tap tests have been
updated as well in order to handle both command line and postgresql.conf
specified restore_command.

Regards

--
Alexey Kondratov

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

Attachments:

0001-pg_rewind-options-to-use-restore_command-v2.0.patchtext/x-patch; name=0001-pg_rewind-options-to-use-restore_command-v2.0.patchDownload
From 8a6c9f89f45c9568d95e05b0586d1cc54905e6de Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <alex.lumir@gmail.com>
Date: Fri, 21 Dec 2018 14:00:30 +0300
Subject: [PATCH] pg_rewind: options to use restore_command from
 postgresql.conf or command line.

---
 src/backend/Makefile                          |   4 +-
 src/backend/commands/extension.c              |   1 +
 src/backend/utils/misc/Makefile               |   8 -
 src/backend/utils/misc/guc.c                  | 434 +++++++++++++--
 src/bin/pg_rewind/Makefile                    |   2 +-
 src/bin/pg_rewind/RewindTest.pm               |  96 +++-
 src/bin/pg_rewind/parsexlog.c                 | 182 ++++++-
 src/bin/pg_rewind/pg_rewind.c                 |  91 +++-
 src/bin/pg_rewind/pg_rewind.h                 |  12 +-
 src/bin/pg_rewind/t/001_basic.pl              |   4 +-
 src/bin/pg_rewind/t/002_databases.pl          |   4 +-
 src/bin/pg_rewind/t/003_extrafiles.pl         |   4 +-
 src/common/Makefile                           |   7 +-
 src/{backend/utils/misc => common}/guc-file.l | 514 ++++--------------
 src/include/common/guc-file.h                 |  50 ++
 src/include/utils/guc.h                       |  39 +-
 src/tools/msvc/Mkvcbuild.pm                   |   2 +-
 src/tools/msvc/clean.bat                      |   2 +-
 18 files changed, 952 insertions(+), 504 deletions(-)
 rename src/{backend/utils/misc => common}/guc-file.l (60%)
 create mode 100644 src/include/common/guc-file.h

diff --git a/src/backend/Makefile b/src/backend/Makefile
index 25eb043941..ddbe2f3fce 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -186,7 +186,7 @@ distprep:
 	$(MAKE) -C replication	repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
 	$(MAKE) -C storage/lmgr	lwlocknames.h lwlocknames.c
 	$(MAKE) -C utils	distprep
-	$(MAKE) -C utils/misc	guc-file.c
+	$(MAKE) -C common	guc-file.c
 	$(MAKE) -C utils/sort	qsort_tuple.c
 
 
@@ -307,7 +307,7 @@ maintainer-clean: distclean
 	      replication/syncrep_scanner.c \
 	      storage/lmgr/lwlocknames.c \
 	      storage/lmgr/lwlocknames.h \
-	      utils/misc/guc-file.c \
+	      common/guc-file.c \
 	      utils/sort/qsort_tuple.c
 
 
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 31dcfe7b11..ec0367d068 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -47,6 +47,7 @@
 #include "commands/defrem.h"
 #include "commands/extension.h"
 #include "commands/schemacmds.h"
+#include "common/guc-file.h"
 #include "funcapi.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index a53fcdf188..2e6a879c46 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -25,11 +25,3 @@ override CPPFLAGS += -DPG_KRB_SRVTAB='"$(krb_srvtab)"'
 endif
 
 include $(top_srcdir)/src/backend/common.mk
-
-# guc-file is compiled as part of guc
-guc.o: guc-file.c
-
-# Note: guc-file.c is not deleted by 'make clean',
-# since we want to ship it in distribution tarballs.
-clean:
-	@rm -f lex.yy.c
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6fe1939881..a866503186 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -41,6 +41,7 @@
 #include "commands/vacuum.h"
 #include "commands/variable.h"
 #include "commands/trigger.h"
+#include "common/guc-file.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "jit/jit.h"
@@ -210,7 +211,6 @@ static bool check_recovery_target_lsn(char **newval, void **extra, GucSource sou
 static void assign_recovery_target_lsn(const char *newval, void *extra);
 static bool check_primary_slot_name(char **newval, void **extra, GucSource source);
 
-/* Private functions in guc-file.l that need to be called from guc.c */
 static ConfigVariable *ProcessConfigFileInternal(GucContext context,
 						  bool applySettings, int elevel);
 
@@ -4445,7 +4445,6 @@ static int	GUCNestLevel = 0;	/* 1 when in main transaction */
 
 
 static int	guc_var_compare(const void *a, const void *b);
-static int	guc_name_compare(const char *namea, const char *nameb);
 static void InitializeGUCOptionsFromEnvironment(void);
 static void InitializeOneGUCOption(struct config_generic *gconf);
 static void push_old_value(struct config_generic *gconf, GucAction action);
@@ -4922,37 +4921,6 @@ guc_var_compare(const void *a, const void *b)
 	return guc_name_compare(confa->name, confb->name);
 }
 
-/*
- * the bare comparison function for GUC names
- */
-static int
-guc_name_compare(const char *namea, const char *nameb)
-{
-	/*
-	 * The temptation to use strcasecmp() here must be resisted, because the
-	 * array ordering has to remain stable across setlocale() calls. So, build
-	 * our own with a simple ASCII-only downcasing.
-	 */
-	while (*namea && *nameb)
-	{
-		char		cha = *namea++;
-		char		chb = *nameb++;
-
-		if (cha >= 'A' && cha <= 'Z')
-			cha += 'a' - 'A';
-		if (chb >= 'A' && chb <= 'Z')
-			chb += 'a' - 'A';
-		if (cha != chb)
-			return cha - chb;
-	}
-	if (*namea)
-		return 1;				/* a is longer */
-	if (*nameb)
-		return -1;				/* b is longer */
-	return 0;
-}
-
-
 /*
  * Initialize GUC options during program startup.
  *
@@ -11310,4 +11278,402 @@ check_primary_slot_name(char **newval, void **extra, GucSource source)
 	return true;
 }
 
-#include "guc-file.c"
+/*
+ * Exported function to read and process the configuration file. The
+ * parameter indicates in what context the file is being read --- either
+ * postmaster startup (including standalone-backend startup) or SIGHUP.
+ * All options mentioned in the configuration file are set to new values.
+ * If a hard error occurs, no values will be changed.  (There can also be
+ * errors that prevent just one value from being changed.)
+ */
+void
+ProcessConfigFile(GucContext context)
+{
+	int			elevel;
+	MemoryContext config_cxt;
+	MemoryContext caller_cxt;
+
+	/*
+	 * Config files are processed on startup (by the postmaster only) and on
+	 * SIGHUP (by the postmaster and its children)
+	 */
+	Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) ||
+		   context == PGC_SIGHUP);
+
+	/*
+	 * To avoid cluttering the log, only the postmaster bleats loudly about
+	 * problems with the config file.
+	 */
+	elevel = IsUnderPostmaster ? DEBUG2 : LOG;
+
+	/*
+	 * This function is usually called within a process-lifespan memory
+	 * context.  To ensure that any memory leaked during GUC processing does
+	 * not accumulate across repeated SIGHUP cycles, do the work in a private
+	 * context that we can free at exit.
+	 */
+	config_cxt = AllocSetContextCreate(CurrentMemoryContext,
+									   "config file processing",
+									   ALLOCSET_DEFAULT_SIZES);
+	caller_cxt = MemoryContextSwitchTo(config_cxt);
+
+	/*
+	 * Read and apply the config file.  We don't need to examine the result.
+	 */
+	(void) ProcessConfigFileInternal(context, true, elevel);
+
+	/* Clean up */
+	MemoryContextSwitchTo(caller_cxt);
+	MemoryContextDelete(config_cxt);
+}
+
+/*
+ * This function handles both actual config file (re)loads and execution of
+ * show_all_file_settings() (i.e., the pg_file_settings view).  In the latter
+ * case we don't apply any of the settings, but we make all the usual validity
+ * checks, and we return the ConfigVariable list so that it can be printed out
+ * by show_all_file_settings().
+ */
+static ConfigVariable *
+ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
+{
+	bool		error = false;
+	bool		applying = false;
+	const char *ConfFileWithError;
+	ConfigVariable *item,
+			   *head,
+			   *tail;
+	int			i;
+
+	/* Parse the main config file into a list of option names and values */
+	ConfFileWithError = ConfigFileName;
+	head = tail = NULL;
+
+	if (!ParseConfigFile(ConfigFileName, true,
+						 NULL, 0, 0, elevel,
+						 &head, &tail))
+	{
+		/* Syntax error(s) detected in the file, so bail out */
+		error = true;
+		goto bail_out;
+	}
+
+	/*
+	 * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to
+	 * replace any parameters set by ALTER SYSTEM command.  Because this file
+	 * is in the data directory, we can't read it until the DataDir has been
+	 * set.
+	 */
+	if (DataDir)
+	{
+		if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false,
+							 NULL, 0, 0, elevel,
+							 &head, &tail))
+		{
+			/* Syntax error(s) detected in the file, so bail out */
+			error = true;
+			ConfFileWithError = PG_AUTOCONF_FILENAME;
+			goto bail_out;
+		}
+	}
+	else
+	{
+		/*
+		 * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be
+		 * read.  In this case, we don't want to accept any settings but
+		 * data_directory from postgresql.conf, because they might be
+		 * overwritten with settings in the PG_AUTOCONF_FILENAME file which
+		 * will be read later. OTOH, since data_directory isn't allowed in the
+		 * PG_AUTOCONF_FILENAME file, it will never be overwritten later.
+		 */
+		ConfigVariable *newlist = NULL;
+
+		/*
+		 * Prune all items except the last "data_directory" from the list.
+		 */
+		for (item = head; item; item = item->next)
+		{
+			if (!item->ignore &&
+				strcmp(item->name, "data_directory") == 0)
+				newlist = item;
+		}
+
+		if (newlist)
+			newlist->next = NULL;
+		head = tail = newlist;
+
+		/*
+		 * Quick exit if data_directory is not present in file.
+		 *
+		 * We need not do any further processing, in particular we don't set
+		 * PgReloadTime; that will be set soon by subsequent full loading of
+		 * the config file.
+		 */
+		if (head == NULL)
+			goto bail_out;
+	}
+
+	/*
+	 * Mark all extant GUC variables as not present in the config file. We
+	 * need this so that we can tell below which ones have been removed from
+	 * the file since we last processed it.
+	 */
+	for (i = 0; i < num_guc_variables; i++)
+	{
+		struct config_generic *gconf = guc_variables[i];
+
+		gconf->status &= ~GUC_IS_IN_FILE;
+	}
+
+	/*
+	 * Check if all the supplied option names are valid, as an additional
+	 * quasi-syntactic check on the validity of the config file.  It is
+	 * important that the postmaster and all backends agree on the results of
+	 * this phase, else we will have strange inconsistencies about which
+	 * processes accept a config file update and which don't.  Hence, unknown
+	 * custom variable names have to be accepted without complaint.  For the
+	 * same reason, we don't attempt to validate the options' values here.
+	 *
+	 * In addition, the GUC_IS_IN_FILE flag is set on each existing GUC
+	 * variable mentioned in the file; and we detect duplicate entries in the
+	 * file and mark the earlier occurrences as ignorable.
+	 */
+	for (item = head; item; item = item->next)
+	{
+		struct config_generic *record;
+
+		/* Ignore anything already marked as ignorable */
+		if (item->ignore)
+			continue;
+
+		/*
+		 * Try to find the variable; but do not create a custom placeholder if
+		 * it's not there already.
+		 */
+		record = find_option(item->name, false, elevel);
+
+		if (record)
+		{
+			/* If it's already marked, then this is a duplicate entry */
+			if (record->status & GUC_IS_IN_FILE)
+			{
+				/*
+				 * Mark the earlier occurrence(s) as dead/ignorable.  We could
+				 * avoid the O(N^2) behavior here with some additional state,
+				 * but it seems unlikely to be worth the trouble.
+				 */
+				ConfigVariable *pitem;
+
+				for (pitem = head; pitem != item; pitem = pitem->next)
+				{
+					if (!pitem->ignore &&
+						strcmp(pitem->name, item->name) == 0)
+						pitem->ignore = true;
+				}
+			}
+			/* Now mark it as present in file */
+			record->status |= GUC_IS_IN_FILE;
+		}
+		else if (strchr(item->name, GUC_QUALIFIER_SEPARATOR) == NULL)
+		{
+			/* Invalid non-custom variable, so complain */
+			ereport(elevel,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %u",
+							item->name,
+							item->filename, item->sourceline)));
+			item->errmsg = pstrdup("unrecognized configuration parameter");
+			error = true;
+			ConfFileWithError = item->filename;
+		}
+	}
+
+	/*
+	 * If we've detected any errors so far, we don't want to risk applying any
+	 * changes.
+	 */
+	if (error)
+		goto bail_out;
+
+	/* Otherwise, set flag that we're beginning to apply changes */
+	applying = true;
+
+	/*
+	 * Check for variables having been removed from the config file, and
+	 * revert their reset values (and perhaps also effective values) to the
+	 * boot-time defaults.  If such a variable can't be changed after startup,
+	 * report that and continue.
+	 */
+	for (i = 0; i < num_guc_variables; i++)
+	{
+		struct config_generic *gconf = guc_variables[i];
+		GucStack   *stack;
+
+		if (gconf->reset_source != PGC_S_FILE ||
+			(gconf->status & GUC_IS_IN_FILE))
+			continue;
+		if (gconf->context < PGC_SIGHUP)
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
+					 errmsg("parameter \"%s\" cannot be changed without restarting the server",
+							gconf->name)));
+			record_config_file_error(psprintf("parameter \"%s\" cannot be changed without restarting the server",
+											  gconf->name),
+									 NULL, 0,
+									 &head, &tail);
+			error = true;
+			continue;
+		}
+
+		/* No more to do if we're just doing show_all_file_settings() */
+		if (!applySettings)
+			continue;
+
+		/*
+		 * Reset any "file" sources to "default", else set_config_option will
+		 * not override those settings.
+		 */
+		if (gconf->reset_source == PGC_S_FILE)
+			gconf->reset_source = PGC_S_DEFAULT;
+		if (gconf->source == PGC_S_FILE)
+			gconf->source = PGC_S_DEFAULT;
+		for (stack = gconf->stack; stack; stack = stack->prev)
+		{
+			if (stack->source == PGC_S_FILE)
+				stack->source = PGC_S_DEFAULT;
+		}
+
+		/* Now we can re-apply the wired-in default (i.e., the boot_val) */
+		if (set_config_option(gconf->name, NULL,
+							  context, PGC_S_DEFAULT,
+							  GUC_ACTION_SET, true, 0, false) > 0)
+		{
+			/* Log the change if appropriate */
+			if (context == PGC_SIGHUP)
+				ereport(elevel,
+						(errmsg("parameter \"%s\" removed from configuration file, reset to default",
+								gconf->name)));
+		}
+	}
+
+	/*
+	 * Restore any variables determined by environment variables or
+	 * dynamically-computed defaults.  This is a no-op except in the case
+	 * where one of these had been in the config file and is now removed.
+	 *
+	 * In particular, we *must not* do this during the postmaster's initial
+	 * loading of the file, since the timezone functions in particular should
+	 * be run only after initialization is complete.
+	 *
+	 * XXX this is an unmaintainable crock, because we have to know how to set
+	 * (or at least what to call to set) every variable that could potentially
+	 * have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. However, there's no
+	 * time to redesign it for 9.1.
+	 */
+	if (context == PGC_SIGHUP && applySettings)
+	{
+		InitializeGUCOptionsFromEnvironment();
+		pg_timezone_abbrev_initialize();
+		/* this selects SQL_ASCII in processes not connected to a database */
+		SetConfigOption("client_encoding", GetDatabaseEncodingName(),
+						PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT);
+	}
+
+	/*
+	 * Now apply the values from the config file.
+	 */
+	for (item = head; item; item = item->next)
+	{
+		char	   *pre_value = NULL;
+		int			scres;
+
+		/* Ignore anything marked as ignorable */
+		if (item->ignore)
+			continue;
+
+		/* In SIGHUP cases in the postmaster, we want to report changes */
+		if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster)
+		{
+			const char *preval = GetConfigOption(item->name, true, false);
+
+			/* If option doesn't exist yet or is NULL, treat as empty string */
+			if (!preval)
+				preval = "";
+			/* must dup, else might have dangling pointer below */
+			pre_value = pstrdup(preval);
+		}
+
+		scres = set_config_option(item->name, item->value,
+								  context, PGC_S_FILE,
+								  GUC_ACTION_SET, applySettings, 0, false);
+		if (scres > 0)
+		{
+			/* variable was updated, so log the change if appropriate */
+			if (pre_value)
+			{
+				const char *post_value = GetConfigOption(item->name, true, false);
+
+				if (!post_value)
+					post_value = "";
+				if (strcmp(pre_value, post_value) != 0)
+					ereport(elevel,
+							(errmsg("parameter \"%s\" changed to \"%s\"",
+									item->name, item->value)));
+			}
+			item->applied = true;
+		}
+		else if (scres == 0)
+		{
+			error = true;
+			item->errmsg = pstrdup("setting could not be applied");
+			ConfFileWithError = item->filename;
+		}
+		else
+		{
+			/* no error, but variable's active value was not changed */
+			item->applied = true;
+		}
+
+		/*
+		 * We should update source location unless there was an error, since
+		 * even if the active value didn't change, the reset value might have.
+		 * (In the postmaster, there won't be a difference, but it does matter
+		 * in backends.)
+		 */
+		if (scres != 0 && applySettings)
+			set_config_sourcefile(item->name, item->filename,
+								  item->sourceline);
+
+		if (pre_value)
+			pfree(pre_value);
+	}
+
+	/* Remember when we last successfully loaded the config file. */
+	if (applySettings)
+		PgReloadTime = GetCurrentTimestamp();
+
+bail_out:
+	if (error && applySettings)
+	{
+		/* During postmaster startup, any error is fatal */
+		if (context == PGC_POSTMASTER)
+			ereport(ERROR,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("configuration file \"%s\" contains errors",
+							ConfFileWithError)));
+		else if (applying)
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("configuration file \"%s\" contains errors; unaffected changes were applied",
+							ConfFileWithError)));
+		else
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("configuration file \"%s\" contains errors; no changes were applied",
+							ConfFileWithError)));
+	}
+
+	/* Successful or otherwise, return the collected data list */
+	return head;
+}
diff --git a/src/bin/pg_rewind/Makefile b/src/bin/pg_rewind/Makefile
index 2bcfcc61af..e2b99e32c3 100644
--- a/src/bin/pg_rewind/Makefile
+++ b/src/bin/pg_rewind/Makefile
@@ -15,7 +15,7 @@ subdir = src/bin/pg_rewind
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-override CPPFLAGS := -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)
+override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)
 LDFLAGS_INTERNAL += $(libpq_pgport)
 
 OBJS	= pg_rewind.o parsexlog.o xlogreader.o datapagemap.o timeline.o \
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 3d07da5d94..f650570741 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -39,7 +39,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catpath catfile);
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -250,6 +252,98 @@ sub run_pg_rewind
 			],
 			'pg_rewind remote');
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $wals_archive_dir = catpath(${TestLib::tmp_check}, 'master_wals_archive');
+		my @wal_files = bsd_glob catpath($master_pgdata, 'pg_wal', '0000000*');
+		my $restore_command;
+
+		remove_tree($wals_archive_dir);
+		make_path($wals_archive_dir) or die;
+
+		# Move all old master WAL files to the archive.
+		# Old master should be stopped at this point.
+		foreach my $wal_file (@wal_files)
+		{
+			move($wal_file, "$wals_archive_dir") or die;
+		}
+
+		if ($windows_os)
+		{
+			$restore_command = "copy $wals_archive_dir\\\%f \%p";
+		}
+		else
+		{
+			$restore_command = "cp $wals_archive_dir/\%f \%p";
+		}
+
+		# 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",
+				"-R", $restore_command
+			],
+			'pg_rewind archive');
+	}
+	elsif ($test_mode eq "archive_conf")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $wals_archive_dir = catpath(${TestLib::tmp_check}, 'master_wals_archive');
+		my @wal_files = bsd_glob catpath($master_pgdata, 'pg_wal', '0000000*');
+		my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');
+		my $restore_command;
+
+		remove_tree($wals_archive_dir);
+		make_path($wals_archive_dir) or die;
+
+		# Move all old master WAL files to the archive.
+		# Old master should be stopped at this point.
+		foreach my $wal_file (@wal_files)
+		{
+			move($wal_file, "$wals_archive_dir") or die;
+		}
+
+		if ($windows_os)
+		{
+			$restore_command = "copy $wals_archive_dir\\\%f \%p";
+		}
+		else
+		{
+			$restore_command = "cp $wals_archive_dir/\%f \%p";
+		}
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		print "Using restore_command=$restore_command\n";
+		print "Conf path $master_conf_path\n";
+
+		# Add restore_command to postgresql.conf of target cluster.
+		open(my $conf_fd, ">>", $master_conf_path) or die;
+		print $conf_fd "\nrestore_command='$restore_command'";
+		close $conf_fd;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"-r"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 40028471bf..f56eb89448 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "pg_rewind.h"
 #include "filemap.h"
@@ -45,7 +46,10 @@ static char xlogfpath[MAXPGPATH];
 typedef struct XLogPageReadPrivate
 {
 	const char *datadir;
+	const char *restoreCommand;
 	int			tliIndex;
+	XLogRecPtr  oldrecptr;
+	TimeLineID  oldtli;
 } XLogPageReadPrivate;
 
 static int SimpleXLogPageRead(XLogReaderState *xlogreader,
@@ -53,6 +57,10 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
 				   int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
 				   TimeLineID *pageTLI);
 
+static bool RestoreArchivedWAL(const char *path, const char *xlogfname,
+					off_t expectedSize, const char *restoreCommand,
+					const char *lastRestartPointFname);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -60,15 +68,19 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
  */
 void
 extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
-			   XLogRecPtr endpoint)
+			   ControlFileData *targetCF, const char *restore_command)
 {
 	XLogRecord *record;
+	XLogRecPtr endpoint = targetCF->checkPoint;
 	XLogReaderState *xlogreader;
 	char	   *errormsg;
 	XLogPageReadPrivate private;
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restore_command;
+	private.oldrecptr = targetCF->checkPointCopy.redo;
+	private.oldtli = targetCF->checkPointCopy.ThisTimeLineID;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -154,9 +166,9 @@ readOneRecord(const char *datadir, XLogRecPtr ptr, int tliIndex)
  * Find the previous checkpoint preceding given WAL location.
  */
 void
-findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
+findLastCheckpoint(const char *datadir, ControlFileData *targetCF, XLogRecPtr forkptr, int tliIndex,
 				   XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
-				   XLogRecPtr *lastchkptredo)
+				   XLogRecPtr *lastchkptredo, const char *restoreCommand)
 {
 	/* Walk backwards, starting from the given record */
 	XLogRecord *record;
@@ -181,6 +193,9 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
+	private.oldrecptr = targetCF->checkPointCopy.redo;
+	private.oldtli = targetCF->checkPointCopy.ThisTimeLineID;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -291,9 +306,53 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
 
 		if (xlogreadfd < 0)
 		{
-			printf(_("could not open file \"%s\": %s\n"), xlogfpath,
+			bool  restore_ok;
+			char  lastRestartPointFname[MAXFNAMELEN];
+			XLogSegNo restartSegNo;
+
+			/*
+			 * If we have no restore_command to execute, then exit.
+			 */
+			if (private->restoreCommand == NULL)
+			{
+				printf(_("could not open file \"%s\": %s\n"), xlogfpath,
 				   strerror(errno));
-			return -1;
+				return -1;
+			}
+
+			XLByteToSeg(private->oldrecptr, restartSegNo, WalSegSz);
+			XLogFileName(lastRestartPointFname, private->oldtli, restartSegNo,
+						 WalSegSz);
+
+			/*
+			 * Since we have restore_command to execute, then try to retreive
+			 * missing WAL file from the archive.
+			 */
+			restore_ok = RestoreArchivedWAL(private->datadir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand,
+											lastRestartPointFname);
+
+			if (restore_ok)
+			{
+				xlogreadfd = open(xlogfpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogreadfd < 0)
+				{
+					printf(_("could not open restored from archive file \"%s\": %s\n"), xlogfpath,
+							strerror(errno));
+					return -1;
+				}
+				else
+					pg_log(PG_DEBUG, "using restored from archive version of file \"%s\"\n", xlogfpath);
+			}
+			else
+			{
+				printf(_("could not restore file \"%s\" from archive: %s\n"), xlogfname,
+					   strerror(errno));
+				return -1;
+			}
 		}
 	}
 
@@ -409,3 +468,116 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return true.
+ *
+ * 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.
+ *
+ * This is an adapted to frontend version of
+ * RestoreArchivedFile function from transam/xlogarchive.c
+ */
+bool
+RestoreArchivedWAL(const char *path, const char *xlogfname, off_t expectedSize,
+				   const char *restoreCommand, const char *lastRestartPointFname)
+{
+	char		xlogpath[MAXPGPATH];
+	char		xlogRestoreCmd[MAXPGPATH];
+	char	   *dp;
+	char	   *endp;
+	const char *sp;
+	int			rc;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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';
+
+	/*
+	 * 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)
+			{
+				printf(_("archive file \"%s\" has wrong size: %lu instead of %lu, %s"),
+						xlogfname, (unsigned long) stat_buf.st_size, (unsigned long) expectedSize,
+						strerror(errno));
+			}
+			else
+				return true;
+		}
+		else
+		{
+			/* Stat failed */
+			printf(_("could not stat file \"%s\": %s"),
+					xlogpath,
+					strerror(errno));
+		}
+	}
+
+	return false;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 9653106386..9075ebba5d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,8 +24,10 @@
 #include "access/xlog_internal.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
+#include "common/controldata_utils.h"
 #include "common/file_perm.h"
 #include "common/file_utils.h"
+#include "common/guc-file.h"
 #include "common/restricted_token.h"
 #include "getopt_long.h"
 #include "storage/bufpage.h"
@@ -52,11 +54,13 @@ int			WalSegSz;
 char	   *datadir_target = NULL;
 char	   *datadir_source = NULL;
 char	   *connstr_source = NULL;
+char	   *restore_command = NULL;
 
 bool		debug = false;
 bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
+bool		restore_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -75,6 +79,9 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"));
 	printf(_("                                 safely to disk\n"));
 	printf(_("  -P, --progress                 write progress messages\n"));
+	printf(_("  -r, --use-postgresql-conf      use restore_command in the postgresql.conf to\n"));
+	printf(_("                                 retreive WALs from archive\n"));
+	printf(_("  -R, --restore-command=COMMAND  restore command\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
@@ -94,9 +101,12 @@ main(int argc, char **argv)
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
+		{"use-postgresql-conf", no_argument, NULL, 'r'},
+		{"restore-command", required_argument, NULL, 'R'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
+	char		recfile_fullpath[MAXPGPATH];
 	int			option_index;
 	int			c;
 	XLogRecPtr	divergerec;
@@ -129,7 +139,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "DR:nNPr", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -141,6 +151,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'r':
+				restore_wals = true;
+				break;
+
 			case 'n':
 				dry_run = true;
 				break;
@@ -157,6 +171,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'R':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -223,6 +241,71 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	if (restore_command != NULL)
+	{
+		pg_log(PG_DEBUG, "using command line restore_command=\'%s\'.\n", restore_command);
+	}
+	else if (restore_wals)
+	{
+		FILE	   *conf_file;
+
+		/*
+		 * Look for configuration file in the target data directory and
+		 * try to get restore_command from there.
+		 */
+		snprintf(recfile_fullpath, sizeof(recfile_fullpath), "%s/%s", datadir_target, RESTORE_COMMAND_FILE);
+		conf_file = fopen(recfile_fullpath, "r");
+
+		if (conf_file == NULL)
+		{
+			fprintf(stderr, _("%s: option -r/--use-postgresql-conf is specified, but postgreslq.conf is absent in the target directory\n"),
+					progname);
+			fprintf(stdout, _("You have to add postgresql.conf or pass restore_command with -R/--restore-command option.\n"));
+			exit(1);
+		}
+		else
+		{
+			ConfigVariable *item,
+						   *head = NULL,
+						   *tail = NULL;
+			bool			config_is_parsed;
+
+			/*
+			 * We pass a fullpath to the configuration file as calling_file here, since
+			 * parser will use its parent directory as base for all further includes
+			 * if any exist.
+			 */
+			config_is_parsed = ParseConfigFile(RESTORE_COMMAND_FILE, true,
+											   recfile_fullpath, 0, 0,
+											   PG_WARNING, &head, &tail);
+			fclose(conf_file);
+
+			if (config_is_parsed)
+			{
+				for (item = head; item; item = item->next)
+				{
+					if (strcmp(item->name, "restore_command") == 0)
+					{
+						if (restore_command != NULL)
+						{
+							pfree(restore_command);
+							restore_command = NULL;
+						}
+						restore_command = pstrdup(item->value);
+						pg_log(PG_DEBUG, "using restore_command=\'%s\' from %s.\n", restore_command, RESTORE_COMMAND_FILE);
+					}
+				}
+
+				if (restore_command == NULL)
+					pg_fatal("could not find restore_command in %s file %s\n", RESTORE_COMMAND_FILE, recfile_fullpath);
+			}
+			else
+				pg_fatal("could not parse %s file %s\n", RESTORE_COMMAND_FILE, recfile_fullpath);
+
+			FreeConfigVariables(head);
+		}
+	}
+
 	/* Connect to remote server */
 	if (connstr_source)
 		libpqConnect(connstr_source);
@@ -294,9 +377,9 @@ main(int argc, char **argv)
 		exit(0);
 	}
 
-	findLastCheckpoint(datadir_target, divergerec,
+	findLastCheckpoint(datadir_target, &ControlFile_target, divergerec,
 					   lastcommontliIndex,
-					   &chkptrec, &chkpttli, &chkptredo);
+					   &chkptrec, &chkpttli, &chkptredo, restore_command);
 	printf(_("rewinding from last common checkpoint at %X/%X on timeline %u\n"),
 		   (uint32) (chkptrec >> 32), (uint32) chkptrec,
 		   chkpttli);
@@ -319,7 +402,7 @@ main(int argc, char **argv)
 	 */
 	pg_log(PG_PROGRESS, "reading WAL in target\n");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
-				   ControlFile_target.checkPoint);
+				   &ControlFile_target, 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 3f4ba7a267..d4d04ca981 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -14,9 +14,12 @@
 #include "datapagemap.h"
 
 #include "access/timeline.h"
+#include "catalog/pg_control.h"
 #include "storage/block.h"
 #include "storage/relfilenode.h"
 
+#define RESTORE_COMMAND_FILE "postgresql.conf"
+
 /* Configuration options */
 extern char *datadir_target;
 extern char *datadir_source;
@@ -32,11 +35,10 @@ extern int	targetNentries;
 
 /* in parsexlog.c */
 extern void extractPageMap(const char *datadir, XLogRecPtr startpoint,
-			   int tliIndex, XLogRecPtr endpoint);
-extern void findLastCheckpoint(const char *datadir, XLogRecPtr searchptr,
-				   int tliIndex,
-				   XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
-				   XLogRecPtr *lastchkptredo);
+			   int tliIndex, ControlFileData *targetCF, const char *restoreCommand);
+extern void findLastCheckpoint(const char *datadir, ControlFileData *targetCF, XLogRecPtr searchptr,
+				   int tliIndex, XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
+				   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 53dbf45be2..1860ce2fe3 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 => 10;
+use Test::More tests => 20;
 
 use RewindTest;
 
@@ -103,5 +103,7 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 2c9e427831..42d708dfbd 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 12;
 
 use RewindTest;
 
@@ -59,5 +59,7 @@ template1
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index 496f38c457..96665ac2f4 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 8;
 
 use File::Find;
 
@@ -87,5 +87,7 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/common/Makefile b/src/common/Makefile
index ec8139f014..73f27cf8ac 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -48,7 +48,7 @@ OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o file_perm.o \
 	ip.o keywords.o link-canary.o md5.o pg_lzcompress.o \
 	pgfnames.o psprintf.o relpath.o \
 	rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
-	username.o wait_error.o
+	username.o wait_error.o guc-file.o
 
 ifeq ($(with_openssl),yes)
 OBJS_COMMON += sha2_openssl.o
@@ -65,6 +65,8 @@ OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
 
 all: libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
 
+distprep: guc-file.c
+
 # libpgcommon is needed by some contrib
 install: all installdirs
 	$(INSTALL_STLIB) libpgcommon.a '$(DESTDIR)$(libdir)/libpgcommon.a'
@@ -125,6 +127,9 @@ keywords.o: $(top_srcdir)/src/include/parser/kwlist.h
 keywords_shlib.o: $(top_srcdir)/src/include/parser/kwlist.h
 keywords_srv.o: $(top_builddir)/src/include/parser/gram.h $(top_srcdir)/src/include/parser/kwlist.h
 
+# Note: guc-file.c is not deleted by 'make clean',
+# since we want to ship it in distribution tarballs.
 clean distclean maintainer-clean:
 	rm -f libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
 	rm -f $(OBJS_FRONTEND) $(OBJS_SHLIB) $(OBJS_SRV)
+	@rm -f lex.yy.c
diff --git a/src/backend/utils/misc/guc-file.l b/src/common/guc-file.l
similarity index 60%
rename from src/backend/utils/misc/guc-file.l
rename to src/common/guc-file.l
index afe4fe6d77..58af8a1cab 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/common/guc-file.l
@@ -4,21 +4,33 @@
  *
  * Copyright (c) 2000-2018, PostgreSQL Global Development Group
  *
- * src/backend/utils/misc/guc-file.l
+ * src/common/guc-file.l
  */
 
 %{
 
-#include "postgres.h"
-
 #include <ctype.h>
 #include <unistd.h>
+#include <sys/stat.h>
+
+#ifndef FRONTEND
+#include "postgres.h"
 
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
 #include "utils/guc.h"
+#include "utils/elog.h"
+#else
+#include <dirent.h>
+#include <setjmp.h>
+
+#include "postgres_fe.h"
 
+#include "common/fe_memutils.h"
+#endif
+
+#include "common/guc-file.h"
 
 /*
  * flex emits a yy_fatal_error() function that it calls in response to
@@ -48,12 +60,6 @@ static sigjmp_buf *GUC_flex_fatal_jmp;
 
 static void FreeConfigVariable(ConfigVariable *item);
 
-static void record_config_file_error(const char *errmsg,
-						 const char *config_file,
-						 int lineno,
-						 ConfigVariable **head_p,
-						 ConfigVariable **tail_p);
-
 static int	GUC_flex_fatal(const char *msg);
 static char *GUC_scanstr(const char *s);
 
@@ -111,404 +117,35 @@ STRING			\'([^'\\\n]|\\.|\'\')*\'
 
 /* LCOV_EXCL_STOP */
 
-/*
- * Exported function to read and process the configuration file. The
- * parameter indicates in what context the file is being read --- either
- * postmaster startup (including standalone-backend startup) or SIGHUP.
- * All options mentioned in the configuration file are set to new values.
- * If a hard error occurs, no values will be changed.  (There can also be
- * errors that prevent just one value from being changed.)
- */
-void
-ProcessConfigFile(GucContext context)
-{
-	int			elevel;
-	MemoryContext config_cxt;
-	MemoryContext caller_cxt;
-
-	/*
-	 * Config files are processed on startup (by the postmaster only) and on
-	 * SIGHUP (by the postmaster and its children)
-	 */
-	Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) ||
-		   context == PGC_SIGHUP);
-
-	/*
-	 * To avoid cluttering the log, only the postmaster bleats loudly about
-	 * problems with the config file.
-	 */
-	elevel = IsUnderPostmaster ? DEBUG2 : LOG;
-
-	/*
-	 * This function is usually called within a process-lifespan memory
-	 * context.  To ensure that any memory leaked during GUC processing does
-	 * not accumulate across repeated SIGHUP cycles, do the work in a private
-	 * context that we can free at exit.
-	 */
-	config_cxt = AllocSetContextCreate(CurrentMemoryContext,
-									   "config file processing",
-									   ALLOCSET_DEFAULT_SIZES);
-	caller_cxt = MemoryContextSwitchTo(config_cxt);
-
-	/*
-	 * Read and apply the config file.  We don't need to examine the result.
-	 */
-	(void) ProcessConfigFileInternal(context, true, elevel);
-
-	/* Clean up */
-	MemoryContextSwitchTo(caller_cxt);
-	MemoryContextDelete(config_cxt);
-}
 
 /*
- * This function handles both actual config file (re)loads and execution of
- * show_all_file_settings() (i.e., the pg_file_settings view).  In the latter
- * case we don't apply any of the settings, but we make all the usual validity
- * checks, and we return the ConfigVariable list so that it can be printed out
- * by show_all_file_settings().
+ * The bare comparison function for GUC names.
  */
-static ConfigVariable *
-ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
+int
+guc_name_compare(const char *namea, const char *nameb)
 {
-	bool		error = false;
-	bool		applying = false;
-	const char *ConfFileWithError;
-	ConfigVariable *item,
-			   *head,
-			   *tail;
-	int			i;
-
-	/* Parse the main config file into a list of option names and values */
-	ConfFileWithError = ConfigFileName;
-	head = tail = NULL;
-
-	if (!ParseConfigFile(ConfigFileName, true,
-						 NULL, 0, 0, elevel,
-						 &head, &tail))
-	{
-		/* Syntax error(s) detected in the file, so bail out */
-		error = true;
-		goto bail_out;
-	}
-
-	/*
-	 * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to
-	 * replace any parameters set by ALTER SYSTEM command.  Because this file
-	 * is in the data directory, we can't read it until the DataDir has been
-	 * set.
-	 */
-	if (DataDir)
-	{
-		if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false,
-							 NULL, 0, 0, elevel,
-							 &head, &tail))
-		{
-			/* Syntax error(s) detected in the file, so bail out */
-			error = true;
-			ConfFileWithError = PG_AUTOCONF_FILENAME;
-			goto bail_out;
-		}
-	}
-	else
-	{
-		/*
-		 * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be
-		 * read.  In this case, we don't want to accept any settings but
-		 * data_directory from postgresql.conf, because they might be
-		 * overwritten with settings in the PG_AUTOCONF_FILENAME file which
-		 * will be read later. OTOH, since data_directory isn't allowed in the
-		 * PG_AUTOCONF_FILENAME file, it will never be overwritten later.
-		 */
-		ConfigVariable *newlist = NULL;
-
-		/*
-		 * Prune all items except the last "data_directory" from the list.
-		 */
-		for (item = head; item; item = item->next)
-		{
-			if (!item->ignore &&
-				strcmp(item->name, "data_directory") == 0)
-				newlist = item;
-		}
-
-		if (newlist)
-			newlist->next = NULL;
-		head = tail = newlist;
-
-		/*
-		 * Quick exit if data_directory is not present in file.
-		 *
-		 * We need not do any further processing, in particular we don't set
-		 * PgReloadTime; that will be set soon by subsequent full loading of
-		 * the config file.
-		 */
-		if (head == NULL)
-			goto bail_out;
-	}
-
 	/*
-	 * Mark all extant GUC variables as not present in the config file. We
-	 * need this so that we can tell below which ones have been removed from
-	 * the file since we last processed it.
+	 * The temptation to use strcasecmp() here must be resisted, because the
+	 * array ordering has to remain stable across setlocale() calls. So, build
+	 * our own with a simple ASCII-only downcasing.
 	 */
-	for (i = 0; i < num_guc_variables; i++)
+	while (*namea && *nameb)
 	{
-		struct config_generic *gconf = guc_variables[i];
-
-		gconf->status &= ~GUC_IS_IN_FILE;
+		char		cha = *namea++;
+		char		chb = *nameb++;
+
+		if (cha >= 'A' && cha <= 'Z')
+			cha += 'a' - 'A';
+		if (chb >= 'A' && chb <= 'Z')
+			chb += 'a' - 'A';
+		if (cha != chb)
+			return cha - chb;
 	}
-
-	/*
-	 * Check if all the supplied option names are valid, as an additional
-	 * quasi-syntactic check on the validity of the config file.  It is
-	 * important that the postmaster and all backends agree on the results of
-	 * this phase, else we will have strange inconsistencies about which
-	 * processes accept a config file update and which don't.  Hence, unknown
-	 * custom variable names have to be accepted without complaint.  For the
-	 * same reason, we don't attempt to validate the options' values here.
-	 *
-	 * In addition, the GUC_IS_IN_FILE flag is set on each existing GUC
-	 * variable mentioned in the file; and we detect duplicate entries in the
-	 * file and mark the earlier occurrences as ignorable.
-	 */
-	for (item = head; item; item = item->next)
-	{
-		struct config_generic *record;
-
-		/* Ignore anything already marked as ignorable */
-		if (item->ignore)
-			continue;
-
-		/*
-		 * Try to find the variable; but do not create a custom placeholder if
-		 * it's not there already.
-		 */
-		record = find_option(item->name, false, elevel);
-
-		if (record)
-		{
-			/* If it's already marked, then this is a duplicate entry */
-			if (record->status & GUC_IS_IN_FILE)
-			{
-				/*
-				 * Mark the earlier occurrence(s) as dead/ignorable.  We could
-				 * avoid the O(N^2) behavior here with some additional state,
-				 * but it seems unlikely to be worth the trouble.
-				 */
-				ConfigVariable *pitem;
-
-				for (pitem = head; pitem != item; pitem = pitem->next)
-				{
-					if (!pitem->ignore &&
-						strcmp(pitem->name, item->name) == 0)
-						pitem->ignore = true;
-				}
-			}
-			/* Now mark it as present in file */
-			record->status |= GUC_IS_IN_FILE;
-		}
-		else if (strchr(item->name, GUC_QUALIFIER_SEPARATOR) == NULL)
-		{
-			/* Invalid non-custom variable, so complain */
-			ereport(elevel,
-					(errcode(ERRCODE_UNDEFINED_OBJECT),
-					 errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %u",
-							item->name,
-							item->filename, item->sourceline)));
-			item->errmsg = pstrdup("unrecognized configuration parameter");
-			error = true;
-			ConfFileWithError = item->filename;
-		}
-	}
-
-	/*
-	 * If we've detected any errors so far, we don't want to risk applying any
-	 * changes.
-	 */
-	if (error)
-		goto bail_out;
-
-	/* Otherwise, set flag that we're beginning to apply changes */
-	applying = true;
-
-	/*
-	 * Check for variables having been removed from the config file, and
-	 * revert their reset values (and perhaps also effective values) to the
-	 * boot-time defaults.  If such a variable can't be changed after startup,
-	 * report that and continue.
-	 */
-	for (i = 0; i < num_guc_variables; i++)
-	{
-		struct config_generic *gconf = guc_variables[i];
-		GucStack   *stack;
-
-		if (gconf->reset_source != PGC_S_FILE ||
-			(gconf->status & GUC_IS_IN_FILE))
-			continue;
-		if (gconf->context < PGC_SIGHUP)
-		{
-			ereport(elevel,
-					(errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
-					 errmsg("parameter \"%s\" cannot be changed without restarting the server",
-							gconf->name)));
-			record_config_file_error(psprintf("parameter \"%s\" cannot be changed without restarting the server",
-											  gconf->name),
-									 NULL, 0,
-									 &head, &tail);
-			error = true;
-			continue;
-		}
-
-		/* No more to do if we're just doing show_all_file_settings() */
-		if (!applySettings)
-			continue;
-
-		/*
-		 * Reset any "file" sources to "default", else set_config_option will
-		 * not override those settings.
-		 */
-		if (gconf->reset_source == PGC_S_FILE)
-			gconf->reset_source = PGC_S_DEFAULT;
-		if (gconf->source == PGC_S_FILE)
-			gconf->source = PGC_S_DEFAULT;
-		for (stack = gconf->stack; stack; stack = stack->prev)
-		{
-			if (stack->source == PGC_S_FILE)
-				stack->source = PGC_S_DEFAULT;
-		}
-
-		/* Now we can re-apply the wired-in default (i.e., the boot_val) */
-		if (set_config_option(gconf->name, NULL,
-							  context, PGC_S_DEFAULT,
-							  GUC_ACTION_SET, true, 0, false) > 0)
-		{
-			/* Log the change if appropriate */
-			if (context == PGC_SIGHUP)
-				ereport(elevel,
-						(errmsg("parameter \"%s\" removed from configuration file, reset to default",
-								gconf->name)));
-		}
-	}
-
-	/*
-	 * Restore any variables determined by environment variables or
-	 * dynamically-computed defaults.  This is a no-op except in the case
-	 * where one of these had been in the config file and is now removed.
-	 *
-	 * In particular, we *must not* do this during the postmaster's initial
-	 * loading of the file, since the timezone functions in particular should
-	 * be run only after initialization is complete.
-	 *
-	 * XXX this is an unmaintainable crock, because we have to know how to set
-	 * (or at least what to call to set) every variable that could potentially
-	 * have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. However, there's no
-	 * time to redesign it for 9.1.
-	 */
-	if (context == PGC_SIGHUP && applySettings)
-	{
-		InitializeGUCOptionsFromEnvironment();
-		pg_timezone_abbrev_initialize();
-		/* this selects SQL_ASCII in processes not connected to a database */
-		SetConfigOption("client_encoding", GetDatabaseEncodingName(),
-						PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT);
-	}
-
-	/*
-	 * Now apply the values from the config file.
-	 */
-	for (item = head; item; item = item->next)
-	{
-		char	   *pre_value = NULL;
-		int			scres;
-
-		/* Ignore anything marked as ignorable */
-		if (item->ignore)
-			continue;
-
-		/* In SIGHUP cases in the postmaster, we want to report changes */
-		if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster)
-		{
-			const char *preval = GetConfigOption(item->name, true, false);
-
-			/* If option doesn't exist yet or is NULL, treat as empty string */
-			if (!preval)
-				preval = "";
-			/* must dup, else might have dangling pointer below */
-			pre_value = pstrdup(preval);
-		}
-
-		scres = set_config_option(item->name, item->value,
-								  context, PGC_S_FILE,
-								  GUC_ACTION_SET, applySettings, 0, false);
-		if (scres > 0)
-		{
-			/* variable was updated, so log the change if appropriate */
-			if (pre_value)
-			{
-				const char *post_value = GetConfigOption(item->name, true, false);
-
-				if (!post_value)
-					post_value = "";
-				if (strcmp(pre_value, post_value) != 0)
-					ereport(elevel,
-							(errmsg("parameter \"%s\" changed to \"%s\"",
-									item->name, item->value)));
-			}
-			item->applied = true;
-		}
-		else if (scres == 0)
-		{
-			error = true;
-			item->errmsg = pstrdup("setting could not be applied");
-			ConfFileWithError = item->filename;
-		}
-		else
-		{
-			/* no error, but variable's active value was not changed */
-			item->applied = true;
-		}
-
-		/*
-		 * We should update source location unless there was an error, since
-		 * even if the active value didn't change, the reset value might have.
-		 * (In the postmaster, there won't be a difference, but it does matter
-		 * in backends.)
-		 */
-		if (scres != 0 && applySettings)
-			set_config_sourcefile(item->name, item->filename,
-								  item->sourceline);
-
-		if (pre_value)
-			pfree(pre_value);
-	}
-
-	/* Remember when we last successfully loaded the config file. */
-	if (applySettings)
-		PgReloadTime = GetCurrentTimestamp();
-
-bail_out:
-	if (error && applySettings)
-	{
-		/* During postmaster startup, any error is fatal */
-		if (context == PGC_POSTMASTER)
-			ereport(ERROR,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("configuration file \"%s\" contains errors",
-							ConfFileWithError)));
-		else if (applying)
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("configuration file \"%s\" contains errors; unaffected changes were applied",
-							ConfFileWithError)));
-		else
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("configuration file \"%s\" contains errors; no changes were applied",
-							ConfFileWithError)));
-	}
-
-	/* Successful or otherwise, return the collected data list */
-	return head;
+	if (*namea)
+		return 1;				/* a is longer */
+	if (*nameb)
+		return -1;				/* b is longer */
+	return 0;
 }
 
 /*
@@ -525,6 +162,7 @@ AbsoluteConfigLocation(const char *location, const char *calling_file)
 		return pstrdup(location);
 	else
 	{
+		#ifndef FRONTEND
 		if (calling_file != NULL)
 		{
 			strlcpy(abs_path, calling_file, sizeof(abs_path));
@@ -538,6 +176,12 @@ AbsoluteConfigLocation(const char *location, const char *calling_file)
 			join_path_components(abs_path, DataDir, location);
 			canonicalize_path(abs_path);
 		}
+		#else
+		strlcpy(abs_path, calling_file, sizeof(abs_path));
+		get_parent_directory(abs_path);
+		join_path_components(abs_path, abs_path, location);
+		canonicalize_path(abs_path);
+		#endif
 		return pstrdup(abs_path);
 	}
 }
@@ -574,10 +218,15 @@ ParseConfigFile(const char *config_file, bool strict,
 	 */
 	if (depth > 10)
 	{
+		#ifndef FRONTEND
 		ereport(elevel,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("could not open configuration file \"%s\": maximum nesting depth exceeded",
 						config_file)));
+		#else
+		printf(_("could not open configuration file \"%s\": maximum nesting depth exceeded\n"),
+				config_file);
+		#endif
 		record_config_file_error("nesting depth exceeded",
 								 calling_file, calling_lineno,
 								 head_p, tail_p);
@@ -585,15 +234,23 @@ ParseConfigFile(const char *config_file, bool strict,
 	}
 
 	abs_path = AbsoluteConfigLocation(config_file, calling_file);
+	#ifndef FRONTEND
 	fp = AllocateFile(abs_path, "r");
+	#else
+	fp = fopen(abs_path, "r");
+	#endif
 	if (!fp)
 	{
 		if (strict)
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode_for_file_access(),
 					 errmsg("could not open configuration file \"%s\": %m",
 							abs_path)));
+			#else
+			printf(_("could not open configuration file \"%s\"\n"), abs_path);
+			#endif
 			record_config_file_error(psprintf("could not open file \"%s\"",
 											  abs_path),
 									 calling_file, calling_lineno,
@@ -602,9 +259,13 @@ ParseConfigFile(const char *config_file, bool strict,
 		}
 		else
 		{
+			#ifndef FRONTEND
 			ereport(LOG,
 					(errmsg("skipping missing configuration file \"%s\"",
 							abs_path)));
+			#else
+			printf(_("skipping missing configuration file \"%s\"\n"), abs_path);
+			#endif
 		}
 		goto cleanup;
 	}
@@ -613,7 +274,14 @@ ParseConfigFile(const char *config_file, bool strict,
 
 cleanup:
 	if (fp)
+	{
+		#ifndef FRONTEND
 		FreeFile(fp);
+		#else
+		fclose(fp);
+		#endif
+	}
+
 	pfree(abs_path);
 
 	return OK;
@@ -623,7 +291,7 @@ cleanup:
  * Capture an error message in the ConfigVariable list returned by
  * config file parsing.
  */
-static void
+void
 record_config_file_error(const char *errmsg,
 						 const char *config_file,
 						 int lineno,
@@ -715,8 +383,13 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
 		 * corrupted parser state.  Consequently, abandon the file, but trust
 		 * that the state remains sane enough for yy_delete_buffer().
 		 */
+		#ifndef FRONTEND
 		elog(elevel, "%s at file \"%s\" line %u",
 			 GUC_flex_fatal_errmsg, config_file, ConfigFileLineno);
+		#else
+		printf(_("%s at file \"%s\" line %u\n"),
+				GUC_flex_fatal_errmsg, config_file, ConfigFileLineno);
+		#endif
 		record_config_file_error(GUC_flex_fatal_errmsg,
 								 config_file, ConfigFileLineno,
 								 head_p, tail_p);
@@ -855,20 +528,30 @@ parse_error:
 		/* report the error */
 		if (token == GUC_EOL || token == 0)
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 			  errmsg("syntax error in file \"%s\" line %u, near end of line",
 					 config_file, ConfigFileLineno - 1)));
+			#else
+			printf(_("syntax error in file \"%s\" line %u, near end of line\n"),
+											config_file, ConfigFileLineno - 1);
+			#endif
 			record_config_file_error("syntax error",
 									 config_file, ConfigFileLineno - 1,
 									 head_p, tail_p);
 		}
 		else
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 			 errmsg("syntax error in file \"%s\" line %u, near token \"%s\"",
 					config_file, ConfigFileLineno, yytext)));
+			#else
+			printf(_("syntax error in file \"%s\" line %u, near token \"%s\"\n"),
+										config_file, ConfigFileLineno, yytext);
+			#endif
 			record_config_file_error("syntax error",
 									 config_file, ConfigFileLineno,
 									 head_p, tail_p);
@@ -883,12 +566,21 @@ parse_error:
 		 * as well give up immediately.  (This prevents postmaster children
 		 * from bloating the logs with duplicate complaints.)
 		 */
+		#ifndef FRONTEND
 		if (errorcount >= 100 || elevel <= DEBUG1)
+		#else
+		if (errorcount >= 100)
+		#endif
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 			   errmsg("too many syntax errors found, abandoning file \"%s\"",
 					  config_file)));
+			#else
+			printf(_("too many syntax errors found, abandoning file \"%s\"\n"),
+																config_file);
+			#endif
 			break;
 		}
 
@@ -934,13 +626,21 @@ ParseConfigDirectory(const char *includedir,
 	bool		status;
 
 	directory = AbsoluteConfigLocation(includedir, calling_file);
+	#ifndef FRONTEND
 	d = AllocateDir(directory);
+	#else
+	d = opendir(directory);
+	#endif
 	if (d == NULL)
 	{
+		#ifndef FRONTEND
 		ereport(elevel,
 				(errcode_for_file_access(),
 				 errmsg("could not open configuration directory \"%s\": %m",
 						directory)));
+		#else
+		printf(_("could not open configuration directory \"%s\"\n"), directory);
+		#endif
 		record_config_file_error(psprintf("could not open directory \"%s\"",
 										  directory),
 								 calling_file, calling_lineno,
@@ -957,7 +657,11 @@ ParseConfigDirectory(const char *includedir,
 	filenames = (char **) palloc(size_filenames * sizeof(char *));
 	num_filenames = 0;
 
+	#ifndef FRONTEND
 	while ((de = ReadDir(d, directory)) != NULL)
+	#else
+	while ((de = readdir(d)) != NULL)
+	#endif
 	{
 		struct stat st;
 		char		filename[MAXPGPATH];
@@ -998,10 +702,14 @@ ParseConfigDirectory(const char *includedir,
 			 * a file can't be accessed now is if it was removed between the
 			 * directory listing and now.
 			 */
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode_for_file_access(),
 					 errmsg("could not stat file \"%s\": %m",
 							filename)));
+			#else
+			printf(_("could not stat file \"%s\"\n"), filename);
+			#endif
 			record_config_file_error(psprintf("could not stat file \"%s\"",
 											  filename),
 									 calling_file, calling_lineno,
@@ -1032,7 +740,13 @@ ParseConfigDirectory(const char *includedir,
 
 cleanup:
 	if (d)
+	{
+		#ifndef FRONTEND
 		FreeDir(d);
+		#else
+		closedir(d);
+		#endif
+	}
 	pfree(directory);
 	return status;
 }
diff --git a/src/include/common/guc-file.h b/src/include/common/guc-file.h
new file mode 100644
index 0000000000..ca969e2aed
--- /dev/null
+++ b/src/include/common/guc-file.h
@@ -0,0 +1,50 @@
+#ifndef GUC_FILE_H
+#define GUC_FILE_H
+
+#include "c.h"
+
+/*
+ * Parsing the configuration file(s) will return a list of name-value pairs
+ * with source location info.  We also abuse this data structure to carry
+ * error reports about the config files.  An entry reporting an error will
+ * have errmsg != NULL, and might have NULLs for name, value, and/or filename.
+ *
+ * If "ignore" is true, don't attempt to apply the item (it might be an error
+ * report, or an item we determined to be duplicate).  "applied" is set true
+ * if we successfully applied, or could have applied, the setting.
+ */
+typedef struct ConfigVariable
+{
+	char	   *name;
+	char	   *value;
+	char	   *errmsg;
+	char	   *filename;
+	int			sourceline;
+	bool		ignore;
+	bool		applied;
+	struct ConfigVariable *next;
+} ConfigVariable;
+
+extern bool ParseConfigFile(const char *config_file, bool strict,
+				const char *calling_file, int calling_lineno,
+				int depth, int elevel,
+				ConfigVariable **head_p, ConfigVariable **tail_p);
+extern bool ParseConfigFp(FILE *fp, const char *config_file,
+			  int depth, int elevel,
+			  ConfigVariable **head_p, ConfigVariable **tail_p);
+extern bool ParseConfigDirectory(const char *includedir,
+					 const char *calling_file, int calling_lineno,
+					 int depth, int elevel,
+					 ConfigVariable **head_p,
+					 ConfigVariable **tail_p);
+extern void FreeConfigVariables(ConfigVariable *list);
+
+extern int	guc_name_compare(const char *namea, const char *nameb);
+
+extern void record_config_file_error(const char *errmsg,
+						 const char *config_file,
+						 int lineno,
+						 ConfigVariable **head_p,
+						 ConfigVariable **tail_p);
+
+#endif							/* GUC_FILE_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 64457c792a..fe37adbf87 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -1,8 +1,7 @@
 /*--------------------------------------------------------------------
  * guc.h
  *
- * External declarations pertaining to backend/utils/misc/guc.c and
- * backend/utils/misc/guc-file.l
+ * External declarations pertaining to backend/utils/misc/guc.c
  *
  * Copyright (c) 2000-2018, PostgreSQL Global Development Group
  * Written by Peter Eisentraut <peter_e@gmx.net>.
@@ -120,42 +119,6 @@ typedef enum
 	PGC_S_SESSION				/* SET command */
 } GucSource;
 
-/*
- * Parsing the configuration file(s) will return a list of name-value pairs
- * with source location info.  We also abuse this data structure to carry
- * error reports about the config files.  An entry reporting an error will
- * have errmsg != NULL, and might have NULLs for name, value, and/or filename.
- *
- * If "ignore" is true, don't attempt to apply the item (it might be an error
- * report, or an item we determined to be duplicate).  "applied" is set true
- * if we successfully applied, or could have applied, the setting.
- */
-typedef struct ConfigVariable
-{
-	char	   *name;
-	char	   *value;
-	char	   *errmsg;
-	char	   *filename;
-	int			sourceline;
-	bool		ignore;
-	bool		applied;
-	struct ConfigVariable *next;
-} ConfigVariable;
-
-extern bool ParseConfigFile(const char *config_file, bool strict,
-				const char *calling_file, int calling_lineno,
-				int depth, int elevel,
-				ConfigVariable **head_p, ConfigVariable **tail_p);
-extern bool ParseConfigFp(FILE *fp, const char *config_file,
-			  int depth, int elevel,
-			  ConfigVariable **head_p, ConfigVariable **tail_p);
-extern bool ParseConfigDirectory(const char *includedir,
-					 const char *calling_file, int calling_lineno,
-					 int depth, int elevel,
-					 ConfigVariable **head_p,
-					 ConfigVariable **tail_p);
-extern void FreeConfigVariables(ConfigVariable *list);
-
 /*
  * The possible values of an enum variable are specified by an array of
  * name-value pairs.  The "hidden" flag means the value is accepted but
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 2921d193a1..f9ebb63a9c 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -172,7 +172,7 @@ sub mkvcbuild
 	$postgres->AddFiles('src/backend/parser', 'scan.l', 'gram.y');
 	$postgres->AddFiles('src/backend/bootstrap', 'bootscanner.l',
 		'bootparse.y');
-	$postgres->AddFiles('src/backend/utils/misc', 'guc-file.l');
+	$postgres->AddFiles('src/common', 'guc-file.l');
 	$postgres->AddFiles(
 		'src/backend/replication', 'repl_scanner.l',
 		'repl_gram.y',             'syncrep_scanner.l',
diff --git a/src/tools/msvc/clean.bat b/src/tools/msvc/clean.bat
index 7a23a2b55f..f1adb56c96 100755
--- a/src/tools/msvc/clean.bat
+++ b/src/tools/msvc/clean.bat
@@ -75,7 +75,7 @@ if %DIST%==1 if exist src\backend\parser\scan.c del /q src\backend\parser\scan.c
 if %DIST%==1 if exist src\backend\parser\gram.c del /q src\backend\parser\gram.c
 if %DIST%==1 if exist src\backend\bootstrap\bootscanner.c del /q src\backend\bootstrap\bootscanner.c
 if %DIST%==1 if exist src\backend\bootstrap\bootparse.c del /q src\backend\bootstrap\bootparse.c
-if %DIST%==1 if exist src\backend\utils\misc\guc-file.c del /q src\backend\utils\misc\guc-file.c
+if %DIST%==1 if exist src\common\guc-file.c del /q src\common\guc-file.c
 if %DIST%==1 if exist src\backend\replication\repl_scanner.c del /q src\backend\replication\repl_scanner.c
 if %DIST%==1 if exist src\backend\replication\repl_gram.c del /q src\backend\replication\repl_gram.c
 if %DIST%==1 if exist src\backend\replication\syncrep_scanner.c del /q src\backend\replication\syncrep_scanner.c
-- 
2.17.1

#15Andrey Borodin
x4mmm@yandex-team.ru
In reply to: Alexey Kondratov (#14)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi!

Thanks for working on this feature, I believe it solves actual problem of HA systems.

30 окт. 2018 г., в 8:01, Michael Paquier <michael@paquier.xyz> написал(а):

Another thing I am wondering is: do we actually need something complex?
What we want to know is what data is necessary to build the file map, so
we could also add an option to pg_rewind which checks what segments are
necessary and lets the user know about them?

From my point of view fetching WALs automatically is much better option for automation.

This also avoids the
security-related problems of manipulating a command at option-level.
This kind of options makes folks willing to use more sensitive data on
command line, which is not always a good idea...

I do not see any new security problems here.. I'd be happy if anyone pointed me out where I can learn about them.

26 дек. 2018 г., в 19:11, Alexey Kondratov <a.kondratov@postgrespro.ru> написал(а):

Please, find the new version of patch attached.

The refactoring of guc-file looks sane, but I'm not an expert in frontend\backend modularity.

Here are some my notes:
1. RestoreArchivedWAL() shares a lot of code with RestoreArchivedFile(). Is it possible\viable to refactor and extract common part?
2. IMV pg_rewind with %r restore_command should fail. %r is designed to clean archive from WALs, nothing should be deleted in case of fetching WALs for rewind. Last restartpoint has no meaning during rewind. Or does it? If so, let's comment about it.
3. RestoreArchivedFile() checks for signals, is it done by pg_rewind elsewhere?
4. No documentation is updated
5. -R takes precedence over -r without notes. Shouldn't we complain? Or may be we should take one from config, iif nothing found use -R?

Thanks!

Best regards, Andrey Borodin.

#16Noname
a.kondratov@postgrespro.ru
In reply to: Andrey Borodin (#15)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi Andrey,

Thank you for the review! I have updated the patch according to your
comments and remarks. Please, find new version attached.

On 2019-01-07 12:12, Andrey Borodin wrote:

Here are some my notes:
1. RestoreArchivedWAL() shares a lot of code with
RestoreArchivedFile(). Is it possible\viable to refactor and extract
common part?

I am not sure, but I guess that differences in errors reporting and
points 2-3 are enough to leave new RestoreArchivedWAL() apart. There
are not many common parts left.

2. IMV pg_rewind with %r restore_command should fail. %r is designed
to clean archive from WALs, nothing should be deleted in case of
fetching WALs for rewind. Last restartpoint has no meaning during
rewind. Or does it? If so, let's comment about it.

Yes, during rewind we need the last common checkpoint, not restart
point.
Taking into account that Michael pointed me to this place too and I
cannot
propose a real-life example of restore_command with %r to use in
pg_rewind,
I decided to add an exception in such a case. So now pg_rewind will fail
with error.

3. RestoreArchivedFile() checks for signals, is it done by pg_rewind
elsewhere?

There is a comment part inside RestoreArchivedFile():

* However, if the failure was due to any sort of signal, it's best to
* punt and abort recovery. (If we "return false" here, upper levels
will
* assume that recovery is complete and start up the database!)

In other words, there is a possibility to start up the database assuming
that recovery went well, while actually it was terminated by signal. It
happens since failure is expected during the recovery, so some kind of
ambiguity occurs, which we trying to solve checking for termination
signals.

In contrast, we are looking in the archive for each missing WAL file
during
pg_rewind and if we failed to restore it by any means rewind will fail
indifferent of was it due to the termination signal or file is actually
missing in the archive. Thus, there no ambiguity occurs and we do not
need
to check for signals here.

That is how I understand it. Probably someone can explain why I am
wrong.

4. No documentation is updated

I have updated docs in order to reflect the new functionality as well.

5. -R takes precedence over -r without notes. Shouldn't we complain?
Or may be we should take one from config, iif nothing found use -R?

I do not think it is worth of additional complexity and we could expect,
that end-users know, what they want to use–either -r or -R–so I added
an error message due to the conflicting options.

Regards

--
Alexey Kondratov

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

Attachments:

0001-pg_rewind-options-to-use-restore_command-v2.1.patchtext/x-diff; name=0001-pg_rewind-options-to-use-restore_command-v2.1.patchDownload
From cc64d7943e220ba6434b76db63a579ccb547517a Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <alex.lumir@gmail.com>
Date: Fri, 21 Dec 2018 14:00:30 +0300
Subject: [PATCH] pg_rewind: options to use restore_command from
 postgresql.conf or command line.

---
 doc/src/sgml/ref/pg_rewind.sgml               |  30 +-
 src/backend/Makefile                          |   4 +-
 src/backend/commands/extension.c              |   1 +
 src/backend/utils/misc/Makefile               |   8 -
 src/backend/utils/misc/guc.c                  | 434 +++++++++++++--
 src/bin/pg_rewind/Makefile                    |   2 +-
 src/bin/pg_rewind/RewindTest.pm               |  96 +++-
 src/bin/pg_rewind/parsexlog.c                 | 180 +++++-
 src/bin/pg_rewind/pg_rewind.c                 | 100 +++-
 src/bin/pg_rewind/pg_rewind.h                 |  12 +-
 src/bin/pg_rewind/t/001_basic.pl              |   4 +-
 src/bin/pg_rewind/t/002_databases.pl          |   4 +-
 src/bin/pg_rewind/t/003_extrafiles.pl         |   4 +-
 src/common/Makefile                           |   9 +-
 src/{backend/utils/misc => common}/guc-file.l | 514 ++++--------------
 src/include/common/guc-file.h                 |  50 ++
 src/include/utils/guc.h                       |  39 +-
 src/tools/msvc/Mkvcbuild.pm                   |   2 +-
 src/tools/msvc/clean.bat                      |   2 +-
 19 files changed, 987 insertions(+), 508 deletions(-)
 rename src/{backend/utils/misc => common}/guc-file.l (60%)
 create mode 100644 src/include/common/guc-file.h

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 53a64ee29e..34046d6404 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -67,8 +67,10 @@ PostgreSQL documentation
    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
+   files might no longer be present. In that case, they can be automatically
+   copied by <application>pg_rewind</application> from the WAL archive to the 
+   <filename>pg_wal</filename> directory if either <literal>-r</literal> or
+   <literal>-R</literal> options are specified, or
    fetched on startup by configuring <xref linkend="guc-primary-conninfo"/> or
    <xref linkend="guc-restore-command"/>.  The use of
    <application>pg_rewind</application> is not limited to failover, e.g.  a standby
@@ -200,6 +202,30 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-r</option></term>
+      <term><option>--use-postgresql-conf</option></term>
+      <listitem>
+       <para>
+        Use restore_command in the <filename>postgresql.conf</filename> to
+        retreive missing in the target <filename>pg_wal</filename> directory
+        WAL files from the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-R <replaceable class="parameter">restore_command</replaceable></option></term>
+      <term><option>--restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the restore_command to use for retrieval of the missing
+        in the target <filename>pg_wal</filename> directory WAL files from
+        the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 478a96db9b..721cb57e89 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -186,7 +186,7 @@ distprep:
 	$(MAKE) -C replication	repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
 	$(MAKE) -C storage/lmgr	lwlocknames.h lwlocknames.c
 	$(MAKE) -C utils	distprep
-	$(MAKE) -C utils/misc	guc-file.c
+	$(MAKE) -C common	guc-file.c
 	$(MAKE) -C utils/sort	qsort_tuple.c
 
 
@@ -307,7 +307,7 @@ maintainer-clean: distclean
 	      replication/syncrep_scanner.c \
 	      storage/lmgr/lwlocknames.c \
 	      storage/lmgr/lwlocknames.h \
-	      utils/misc/guc-file.c \
+	      common/guc-file.c \
 	      utils/sort/qsort_tuple.c
 
 
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 4fe71196c4..b64dece236 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -49,6 +49,7 @@
 #include "commands/defrem.h"
 #include "commands/extension.h"
 #include "commands/schemacmds.h"
+#include "common/guc-file.h"
 #include "funcapi.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index ec7ec131e5..2205956a9f 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -25,11 +25,3 @@ override CPPFLAGS += -DPG_KRB_SRVTAB='"$(krb_srvtab)"'
 endif
 
 include $(top_srcdir)/src/backend/common.mk
-
-# guc-file is compiled as part of guc
-guc.o: guc-file.c
-
-# Note: guc-file.c is not deleted by 'make clean',
-# since we want to ship it in distribution tarballs.
-clean:
-	@rm -f lex.yy.c
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c216ed0922..c52d1bc7e5 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -41,6 +41,7 @@
 #include "commands/vacuum.h"
 #include "commands/variable.h"
 #include "commands/trigger.h"
+#include "common/guc-file.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "jit/jit.h"
@@ -211,7 +212,6 @@ static void assign_recovery_target_lsn(const char *newval, void *extra);
 static bool check_primary_slot_name(char **newval, void **extra, GucSource source);
 static bool check_default_with_oids(bool *newval, void **extra, GucSource source);
 
-/* Private functions in guc-file.l that need to be called from guc.c */
 static ConfigVariable *ProcessConfigFileInternal(GucContext context,
 						  bool applySettings, int elevel);
 
@@ -4467,7 +4467,6 @@ static int	GUCNestLevel = 0;	/* 1 when in main transaction */
 
 
 static int	guc_var_compare(const void *a, const void *b);
-static int	guc_name_compare(const char *namea, const char *nameb);
 static void InitializeGUCOptionsFromEnvironment(void);
 static void InitializeOneGUCOption(struct config_generic *gconf);
 static void push_old_value(struct config_generic *gconf, GucAction action);
@@ -4944,37 +4943,6 @@ guc_var_compare(const void *a, const void *b)
 	return guc_name_compare(confa->name, confb->name);
 }
 
-/*
- * the bare comparison function for GUC names
- */
-static int
-guc_name_compare(const char *namea, const char *nameb)
-{
-	/*
-	 * The temptation to use strcasecmp() here must be resisted, because the
-	 * array ordering has to remain stable across setlocale() calls. So, build
-	 * our own with a simple ASCII-only downcasing.
-	 */
-	while (*namea && *nameb)
-	{
-		char		cha = *namea++;
-		char		chb = *nameb++;
-
-		if (cha >= 'A' && cha <= 'Z')
-			cha += 'a' - 'A';
-		if (chb >= 'A' && chb <= 'Z')
-			chb += 'a' - 'A';
-		if (cha != chb)
-			return cha - chb;
-	}
-	if (*namea)
-		return 1;				/* a is longer */
-	if (*nameb)
-		return -1;				/* b is longer */
-	return 0;
-}
-
-
 /*
  * Initialize GUC options during program startup.
  *
@@ -11348,4 +11316,402 @@ check_default_with_oids(bool *newval, void **extra, GucSource source)
 	return true;
 }
 
-#include "guc-file.c"
+/*
+ * Exported function to read and process the configuration file. The
+ * parameter indicates in what context the file is being read --- either
+ * postmaster startup (including standalone-backend startup) or SIGHUP.
+ * All options mentioned in the configuration file are set to new values.
+ * If a hard error occurs, no values will be changed.  (There can also be
+ * errors that prevent just one value from being changed.)
+ */
+void
+ProcessConfigFile(GucContext context)
+{
+	int			elevel;
+	MemoryContext config_cxt;
+	MemoryContext caller_cxt;
+
+	/*
+	 * Config files are processed on startup (by the postmaster only) and on
+	 * SIGHUP (by the postmaster and its children)
+	 */
+	Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) ||
+		   context == PGC_SIGHUP);
+
+	/*
+	 * To avoid cluttering the log, only the postmaster bleats loudly about
+	 * problems with the config file.
+	 */
+	elevel = IsUnderPostmaster ? DEBUG2 : LOG;
+
+	/*
+	 * This function is usually called within a process-lifespan memory
+	 * context.  To ensure that any memory leaked during GUC processing does
+	 * not accumulate across repeated SIGHUP cycles, do the work in a private
+	 * context that we can free at exit.
+	 */
+	config_cxt = AllocSetContextCreate(CurrentMemoryContext,
+									   "config file processing",
+									   ALLOCSET_DEFAULT_SIZES);
+	caller_cxt = MemoryContextSwitchTo(config_cxt);
+
+	/*
+	 * Read and apply the config file.  We don't need to examine the result.
+	 */
+	(void) ProcessConfigFileInternal(context, true, elevel);
+
+	/* Clean up */
+	MemoryContextSwitchTo(caller_cxt);
+	MemoryContextDelete(config_cxt);
+}
+
+/*
+ * This function handles both actual config file (re)loads and execution of
+ * show_all_file_settings() (i.e., the pg_file_settings view).  In the latter
+ * case we don't apply any of the settings, but we make all the usual validity
+ * checks, and we return the ConfigVariable list so that it can be printed out
+ * by show_all_file_settings().
+ */
+static ConfigVariable *
+ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
+{
+	bool		error = false;
+	bool		applying = false;
+	const char *ConfFileWithError;
+	ConfigVariable *item,
+			   *head,
+			   *tail;
+	int			i;
+
+	/* Parse the main config file into a list of option names and values */
+	ConfFileWithError = ConfigFileName;
+	head = tail = NULL;
+
+	if (!ParseConfigFile(ConfigFileName, true,
+						 NULL, 0, 0, elevel,
+						 &head, &tail))
+	{
+		/* Syntax error(s) detected in the file, so bail out */
+		error = true;
+		goto bail_out;
+	}
+
+	/*
+	 * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to
+	 * replace any parameters set by ALTER SYSTEM command.  Because this file
+	 * is in the data directory, we can't read it until the DataDir has been
+	 * set.
+	 */
+	if (DataDir)
+	{
+		if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false,
+							 NULL, 0, 0, elevel,
+							 &head, &tail))
+		{
+			/* Syntax error(s) detected in the file, so bail out */
+			error = true;
+			ConfFileWithError = PG_AUTOCONF_FILENAME;
+			goto bail_out;
+		}
+	}
+	else
+	{
+		/*
+		 * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be
+		 * read.  In this case, we don't want to accept any settings but
+		 * data_directory from postgresql.conf, because they might be
+		 * overwritten with settings in the PG_AUTOCONF_FILENAME file which
+		 * will be read later. OTOH, since data_directory isn't allowed in the
+		 * PG_AUTOCONF_FILENAME file, it will never be overwritten later.
+		 */
+		ConfigVariable *newlist = NULL;
+
+		/*
+		 * Prune all items except the last "data_directory" from the list.
+		 */
+		for (item = head; item; item = item->next)
+		{
+			if (!item->ignore &&
+				strcmp(item->name, "data_directory") == 0)
+				newlist = item;
+		}
+
+		if (newlist)
+			newlist->next = NULL;
+		head = tail = newlist;
+
+		/*
+		 * Quick exit if data_directory is not present in file.
+		 *
+		 * We need not do any further processing, in particular we don't set
+		 * PgReloadTime; that will be set soon by subsequent full loading of
+		 * the config file.
+		 */
+		if (head == NULL)
+			goto bail_out;
+	}
+
+	/*
+	 * Mark all extant GUC variables as not present in the config file. We
+	 * need this so that we can tell below which ones have been removed from
+	 * the file since we last processed it.
+	 */
+	for (i = 0; i < num_guc_variables; i++)
+	{
+		struct config_generic *gconf = guc_variables[i];
+
+		gconf->status &= ~GUC_IS_IN_FILE;
+	}
+
+	/*
+	 * Check if all the supplied option names are valid, as an additional
+	 * quasi-syntactic check on the validity of the config file.  It is
+	 * important that the postmaster and all backends agree on the results of
+	 * this phase, else we will have strange inconsistencies about which
+	 * processes accept a config file update and which don't.  Hence, unknown
+	 * custom variable names have to be accepted without complaint.  For the
+	 * same reason, we don't attempt to validate the options' values here.
+	 *
+	 * In addition, the GUC_IS_IN_FILE flag is set on each existing GUC
+	 * variable mentioned in the file; and we detect duplicate entries in the
+	 * file and mark the earlier occurrences as ignorable.
+	 */
+	for (item = head; item; item = item->next)
+	{
+		struct config_generic *record;
+
+		/* Ignore anything already marked as ignorable */
+		if (item->ignore)
+			continue;
+
+		/*
+		 * Try to find the variable; but do not create a custom placeholder if
+		 * it's not there already.
+		 */
+		record = find_option(item->name, false, elevel);
+
+		if (record)
+		{
+			/* If it's already marked, then this is a duplicate entry */
+			if (record->status & GUC_IS_IN_FILE)
+			{
+				/*
+				 * Mark the earlier occurrence(s) as dead/ignorable.  We could
+				 * avoid the O(N^2) behavior here with some additional state,
+				 * but it seems unlikely to be worth the trouble.
+				 */
+				ConfigVariable *pitem;
+
+				for (pitem = head; pitem != item; pitem = pitem->next)
+				{
+					if (!pitem->ignore &&
+						strcmp(pitem->name, item->name) == 0)
+						pitem->ignore = true;
+				}
+			}
+			/* Now mark it as present in file */
+			record->status |= GUC_IS_IN_FILE;
+		}
+		else if (strchr(item->name, GUC_QUALIFIER_SEPARATOR) == NULL)
+		{
+			/* Invalid non-custom variable, so complain */
+			ereport(elevel,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %u",
+							item->name,
+							item->filename, item->sourceline)));
+			item->errmsg = pstrdup("unrecognized configuration parameter");
+			error = true;
+			ConfFileWithError = item->filename;
+		}
+	}
+
+	/*
+	 * If we've detected any errors so far, we don't want to risk applying any
+	 * changes.
+	 */
+	if (error)
+		goto bail_out;
+
+	/* Otherwise, set flag that we're beginning to apply changes */
+	applying = true;
+
+	/*
+	 * Check for variables having been removed from the config file, and
+	 * revert their reset values (and perhaps also effective values) to the
+	 * boot-time defaults.  If such a variable can't be changed after startup,
+	 * report that and continue.
+	 */
+	for (i = 0; i < num_guc_variables; i++)
+	{
+		struct config_generic *gconf = guc_variables[i];
+		GucStack   *stack;
+
+		if (gconf->reset_source != PGC_S_FILE ||
+			(gconf->status & GUC_IS_IN_FILE))
+			continue;
+		if (gconf->context < PGC_SIGHUP)
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
+					 errmsg("parameter \"%s\" cannot be changed without restarting the server",
+							gconf->name)));
+			record_config_file_error(psprintf("parameter \"%s\" cannot be changed without restarting the server",
+											  gconf->name),
+									 NULL, 0,
+									 &head, &tail);
+			error = true;
+			continue;
+		}
+
+		/* No more to do if we're just doing show_all_file_settings() */
+		if (!applySettings)
+			continue;
+
+		/*
+		 * Reset any "file" sources to "default", else set_config_option will
+		 * not override those settings.
+		 */
+		if (gconf->reset_source == PGC_S_FILE)
+			gconf->reset_source = PGC_S_DEFAULT;
+		if (gconf->source == PGC_S_FILE)
+			gconf->source = PGC_S_DEFAULT;
+		for (stack = gconf->stack; stack; stack = stack->prev)
+		{
+			if (stack->source == PGC_S_FILE)
+				stack->source = PGC_S_DEFAULT;
+		}
+
+		/* Now we can re-apply the wired-in default (i.e., the boot_val) */
+		if (set_config_option(gconf->name, NULL,
+							  context, PGC_S_DEFAULT,
+							  GUC_ACTION_SET, true, 0, false) > 0)
+		{
+			/* Log the change if appropriate */
+			if (context == PGC_SIGHUP)
+				ereport(elevel,
+						(errmsg("parameter \"%s\" removed from configuration file, reset to default",
+								gconf->name)));
+		}
+	}
+
+	/*
+	 * Restore any variables determined by environment variables or
+	 * dynamically-computed defaults.  This is a no-op except in the case
+	 * where one of these had been in the config file and is now removed.
+	 *
+	 * In particular, we *must not* do this during the postmaster's initial
+	 * loading of the file, since the timezone functions in particular should
+	 * be run only after initialization is complete.
+	 *
+	 * XXX this is an unmaintainable crock, because we have to know how to set
+	 * (or at least what to call to set) every variable that could potentially
+	 * have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. However, there's no
+	 * time to redesign it for 9.1.
+	 */
+	if (context == PGC_SIGHUP && applySettings)
+	{
+		InitializeGUCOptionsFromEnvironment();
+		pg_timezone_abbrev_initialize();
+		/* this selects SQL_ASCII in processes not connected to a database */
+		SetConfigOption("client_encoding", GetDatabaseEncodingName(),
+						PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT);
+	}
+
+	/*
+	 * Now apply the values from the config file.
+	 */
+	for (item = head; item; item = item->next)
+	{
+		char	   *pre_value = NULL;
+		int			scres;
+
+		/* Ignore anything marked as ignorable */
+		if (item->ignore)
+			continue;
+
+		/* In SIGHUP cases in the postmaster, we want to report changes */
+		if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster)
+		{
+			const char *preval = GetConfigOption(item->name, true, false);
+
+			/* If option doesn't exist yet or is NULL, treat as empty string */
+			if (!preval)
+				preval = "";
+			/* must dup, else might have dangling pointer below */
+			pre_value = pstrdup(preval);
+		}
+
+		scres = set_config_option(item->name, item->value,
+								  context, PGC_S_FILE,
+								  GUC_ACTION_SET, applySettings, 0, false);
+		if (scres > 0)
+		{
+			/* variable was updated, so log the change if appropriate */
+			if (pre_value)
+			{
+				const char *post_value = GetConfigOption(item->name, true, false);
+
+				if (!post_value)
+					post_value = "";
+				if (strcmp(pre_value, post_value) != 0)
+					ereport(elevel,
+							(errmsg("parameter \"%s\" changed to \"%s\"",
+									item->name, item->value)));
+			}
+			item->applied = true;
+		}
+		else if (scres == 0)
+		{
+			error = true;
+			item->errmsg = pstrdup("setting could not be applied");
+			ConfFileWithError = item->filename;
+		}
+		else
+		{
+			/* no error, but variable's active value was not changed */
+			item->applied = true;
+		}
+
+		/*
+		 * We should update source location unless there was an error, since
+		 * even if the active value didn't change, the reset value might have.
+		 * (In the postmaster, there won't be a difference, but it does matter
+		 * in backends.)
+		 */
+		if (scres != 0 && applySettings)
+			set_config_sourcefile(item->name, item->filename,
+								  item->sourceline);
+
+		if (pre_value)
+			pfree(pre_value);
+	}
+
+	/* Remember when we last successfully loaded the config file. */
+	if (applySettings)
+		PgReloadTime = GetCurrentTimestamp();
+
+bail_out:
+	if (error && applySettings)
+	{
+		/* During postmaster startup, any error is fatal */
+		if (context == PGC_POSTMASTER)
+			ereport(ERROR,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("configuration file \"%s\" contains errors",
+							ConfFileWithError)));
+		else if (applying)
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("configuration file \"%s\" contains errors; unaffected changes were applied",
+							ConfFileWithError)));
+		else
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("configuration file \"%s\" contains errors; no changes were applied",
+							ConfFileWithError)));
+	}
+
+	/* Successful or otherwise, return the collected data list */
+	return head;
+}
diff --git a/src/bin/pg_rewind/Makefile b/src/bin/pg_rewind/Makefile
index 04f3b8f520..65ebf3c78c 100644
--- a/src/bin/pg_rewind/Makefile
+++ b/src/bin/pg_rewind/Makefile
@@ -15,7 +15,7 @@ subdir = src/bin/pg_rewind
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-override CPPFLAGS := -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)
+override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)
 LDFLAGS_INTERNAL += $(libpq_pgport)
 
 OBJS	= pg_rewind.o parsexlog.o xlogreader.o datapagemap.o timeline.o \
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 85cae7e47b..b07ca3617f 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -39,7 +39,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catpath catfile);
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -249,6 +251,98 @@ sub run_pg_rewind
 			],
 			'pg_rewind remote');
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $wals_archive_dir = catpath(${TestLib::tmp_check}, 'master_wals_archive');
+		my @wal_files = bsd_glob catpath($master_pgdata, 'pg_wal', '0000000*');
+		my $restore_command;
+
+		remove_tree($wals_archive_dir);
+		make_path($wals_archive_dir) or die;
+
+		# Move all old master WAL files to the archive.
+		# Old master should be stopped at this point.
+		foreach my $wal_file (@wal_files)
+		{
+			move($wal_file, "$wals_archive_dir") or die;
+		}
+
+		if ($windows_os)
+		{
+			$restore_command = "copy $wals_archive_dir\\\%f \%p";
+		}
+		else
+		{
+			$restore_command = "cp $wals_archive_dir/\%f \%p";
+		}
+
+		# 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",
+				"--restore-command=$restore_command"
+			],
+			'pg_rewind archive');
+	}
+	elsif ($test_mode eq "archive_conf")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $wals_archive_dir = catpath(${TestLib::tmp_check}, 'master_wals_archive');
+		my @wal_files = bsd_glob catpath($master_pgdata, 'pg_wal', '0000000*');
+		my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');
+		my $restore_command;
+
+		remove_tree($wals_archive_dir);
+		make_path($wals_archive_dir) or die;
+
+		# Move all old master WAL files to the archive.
+		# Old master should be stopped at this point.
+		foreach my $wal_file (@wal_files)
+		{
+			move($wal_file, "$wals_archive_dir") or die;
+		}
+
+		if ($windows_os)
+		{
+			$restore_command = "copy $wals_archive_dir\\\%f \%p";
+		}
+		else
+		{
+			$restore_command = "cp $wals_archive_dir/\%f \%p";
+		}
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		print "Using restore_command=$restore_command\n";
+		print "Conf path $master_conf_path\n";
+
+		# Add restore_command to postgresql.conf of target cluster.
+		open(my $conf_fd, ">>", $master_conf_path) or die;
+		print $conf_fd "\nrestore_command='$restore_command'";
+		close $conf_fd;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"-r"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index e19c265cbb..537d50e231 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "pg_rewind.h"
 #include "filemap.h"
@@ -45,7 +46,10 @@ static char xlogfpath[MAXPGPATH];
 typedef struct XLogPageReadPrivate
 {
 	const char *datadir;
+	const char *restoreCommand;
 	int			tliIndex;
+	XLogRecPtr  oldrecptr;
+	TimeLineID  oldtli;
 } XLogPageReadPrivate;
 
 static int SimpleXLogPageRead(XLogReaderState *xlogreader,
@@ -53,6 +57,10 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
 				   int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
 				   TimeLineID *pageTLI);
 
+static bool RestoreArchivedWAL(const char *path, const char *xlogfname,
+					off_t expectedSize, const char *restoreCommand,
+					const char *lastRestartPointFname);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -60,15 +68,19 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
  */
 void
 extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
-			   XLogRecPtr endpoint)
+			   ControlFileData *targetCF, const char *restore_command)
 {
 	XLogRecord *record;
+	XLogRecPtr endpoint = targetCF->checkPoint;
 	XLogReaderState *xlogreader;
 	char	   *errormsg;
 	XLogPageReadPrivate private;
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restore_command;
+	private.oldrecptr = targetCF->checkPointCopy.redo;
+	private.oldtli = targetCF->checkPointCopy.ThisTimeLineID;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -154,9 +166,9 @@ readOneRecord(const char *datadir, XLogRecPtr ptr, int tliIndex)
  * Find the previous checkpoint preceding given WAL location.
  */
 void
-findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
+findLastCheckpoint(const char *datadir, ControlFileData *targetCF, XLogRecPtr forkptr, int tliIndex,
 				   XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
-				   XLogRecPtr *lastchkptredo)
+				   XLogRecPtr *lastchkptredo, const char *restoreCommand)
 {
 	/* Walk backwards, starting from the given record */
 	XLogRecord *record;
@@ -181,6 +193,9 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
+	private.oldrecptr = targetCF->checkPointCopy.redo;
+	private.oldtli = targetCF->checkPointCopy.ThisTimeLineID;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -291,9 +306,53 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
 
 		if (xlogreadfd < 0)
 		{
-			printf(_("could not open file \"%s\": %s\n"), xlogfpath,
+			bool  restore_ok;
+			char  lastRestartPointFname[MAXFNAMELEN];
+			XLogSegNo restartSegNo;
+
+			/*
+			 * If we have no restore_command to execute, then exit.
+			 */
+			if (private->restoreCommand == NULL)
+			{
+				printf(_("could not open file \"%s\": %s\n"), xlogfpath,
 				   strerror(errno));
-			return -1;
+				return -1;
+			}
+
+			XLByteToSeg(private->oldrecptr, restartSegNo, WalSegSz);
+			XLogFileName(lastRestartPointFname, private->oldtli, restartSegNo,
+						 WalSegSz);
+
+			/*
+			 * Since we have restore_command to execute, then try to retreive
+			 * missing WAL file from the archive.
+			 */
+			restore_ok = RestoreArchivedWAL(private->datadir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand,
+											lastRestartPointFname);
+
+			if (restore_ok)
+			{
+				xlogreadfd = open(xlogfpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogreadfd < 0)
+				{
+					printf(_("could not open restored from archive file \"%s\": %s\n"), xlogfpath,
+							strerror(errno));
+					return -1;
+				}
+				else
+					pg_log(PG_DEBUG, "using restored from archive version of file \"%s\"\n", xlogfpath);
+			}
+			else
+			{
+				printf(_("could not restore file \"%s\" from archive: %s\n"), xlogfname,
+					   strerror(errno));
+				return -1;
+			}
 		}
 	}
 
@@ -409,3 +468,114 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return true.
+ *
+ * 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.
+ *
+ * This is a simplified and adapted to frontend version of
+ * RestoreArchivedFile function from transam/xlogarchive.c
+ */
+bool
+RestoreArchivedWAL(const char *path, const char *xlogfname, off_t expectedSize,
+				   const char *restoreCommand, const char *lastRestartPointFname)
+{
+	char		xlogpath[MAXPGPATH];
+	char		xlogRestoreCmd[MAXPGPATH];
+	char	   *dp;
+	char	   *endp;
+	const char *sp;
+	int			rc;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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 */
+					pg_fatal("restore_command with %%r cannot be used during rewind process.\n");
+					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';
+
+	/*
+	 * 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)
+			{
+				printf(_("archive file \"%s\" has wrong size: %lu instead of %lu, %s"),
+						xlogfname, (unsigned long) stat_buf.st_size, (unsigned long) expectedSize,
+						strerror(errno));
+			}
+			else
+				return true;
+		}
+		else
+		{
+			/* Stat failed */
+			printf(_("could not stat file \"%s\": %s"),
+					xlogpath,
+					strerror(errno));
+		}
+	}
+
+	return false;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 7ccde5c87f..abe593a588 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,8 +24,10 @@
 #include "access/xlog_internal.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
+#include "common/controldata_utils.h"
 #include "common/file_perm.h"
 #include "common/file_utils.h"
+#include "common/guc-file.h"
 #include "common/restricted_token.h"
 #include "getopt_long.h"
 #include "storage/bufpage.h"
@@ -52,11 +54,13 @@ int			WalSegSz;
 char	   *datadir_target = NULL;
 char	   *datadir_source = NULL;
 char	   *connstr_source = NULL;
+char	   *restore_command = NULL;
 
 bool		debug = false;
 bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
+bool		restore_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -75,6 +79,9 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"));
 	printf(_("                                 safely to disk\n"));
 	printf(_("  -P, --progress                 write progress messages\n"));
+	printf(_("  -r, --use-postgresql-conf      use restore_command in the postgresql.conf to\n"));
+	printf(_("                                 retreive WALs from archive\n"));
+	printf(_("  -R, --restore-command=COMMAND  restore_command\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
@@ -94,9 +101,12 @@ main(int argc, char **argv)
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
+		{"use-postgresql-conf", no_argument, NULL, 'r'},
+		{"restore-command", required_argument, NULL, 'R'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
+	char		recfile_fullpath[MAXPGPATH];
 	int			option_index;
 	int			c;
 	XLogRecPtr	divergerec;
@@ -129,7 +139,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "D:nNPR:r", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -141,6 +151,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'r':
+				restore_wals = true;
+				break;
+
 			case 'n':
 				dry_run = true;
 				break;
@@ -157,6 +171,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'R':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -223,6 +241,80 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	if (restore_command != NULL)
+	{
+		if (restore_wals)
+		{
+			fprintf(stderr, _("%s: redundant options: both -r and -R are specified\n"),
+				progname);
+			fprintf(stderr, _("You must run %s with either -r/--use-postgresql-conf or -R/--restore-command.\n"),
+				progname);
+			exit(1);
+		}
+
+		pg_log(PG_DEBUG, "using command line restore_command=\'%s\'.\n", restore_command);
+	}
+	else if (restore_wals)
+	{
+		FILE	*conf_file;
+
+		/*
+		 * Look for configuration file in the target data directory and
+		 * try to get restore_command from there.
+		 */
+		snprintf(recfile_fullpath, sizeof(recfile_fullpath), "%s/%s", datadir_target, RESTORE_COMMAND_FILE);
+		conf_file = fopen(recfile_fullpath, "r");
+
+		if (conf_file == NULL)
+		{
+			fprintf(stderr, _("%s: option -r/--use-postgresql-conf is specified, but postgreslq.conf is absent in the target directory\n"),
+					progname);
+			fprintf(stderr, _("You have to add postgresql.conf or pass restore_command with -R/--restore-command option.\n"));
+			exit(1);
+		}
+		else
+		{
+			ConfigVariable *item,
+						   *head = NULL,
+						   *tail = NULL;
+			bool			config_is_parsed;
+
+			/*
+			 * We pass a fullpath to the configuration file as calling_file here, since
+			 * parser will use its parent directory as base for all further includes
+			 * if any exist.
+			 */
+			config_is_parsed = ParseConfigFile(RESTORE_COMMAND_FILE, true,
+											   recfile_fullpath, 0, 0,
+											   PG_WARNING, &head, &tail);
+			fclose(conf_file);
+
+			if (config_is_parsed)
+			{
+				for (item = head; item; item = item->next)
+				{
+					if (strcmp(item->name, "restore_command") == 0)
+					{
+						if (restore_command != NULL)
+						{
+							pfree(restore_command);
+							restore_command = NULL;
+						}
+						restore_command = pstrdup(item->value);
+						pg_log(PG_DEBUG, "using restore_command=\'%s\' from %s.\n", restore_command, RESTORE_COMMAND_FILE);
+					}
+				}
+
+				if (restore_command == NULL)
+					pg_fatal("could not find restore_command in %s file %s\n", RESTORE_COMMAND_FILE, recfile_fullpath);
+			}
+			else
+				pg_fatal("could not parse %s file %s\n", RESTORE_COMMAND_FILE, recfile_fullpath);
+
+			FreeConfigVariables(head);
+		}
+	}
+
 	/* Connect to remote server */
 	if (connstr_source)
 		libpqConnect(connstr_source);
@@ -294,9 +386,9 @@ main(int argc, char **argv)
 		exit(0);
 	}
 
-	findLastCheckpoint(datadir_target, divergerec,
+	findLastCheckpoint(datadir_target, &ControlFile_target, divergerec,
 					   lastcommontliIndex,
-					   &chkptrec, &chkpttli, &chkptredo);
+					   &chkptrec, &chkpttli, &chkptredo, restore_command);
 	printf(_("rewinding from last common checkpoint at %X/%X on timeline %u\n"),
 		   (uint32) (chkptrec >> 32), (uint32) chkptrec,
 		   chkpttli);
@@ -319,7 +411,7 @@ main(int argc, char **argv)
 	 */
 	pg_log(PG_PROGRESS, "reading WAL in target\n");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
-				   ControlFile_target.checkPoint);
+				   &ControlFile_target, 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 83b2898b8b..5dc6f5b1ba 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -14,9 +14,12 @@
 #include "datapagemap.h"
 
 #include "access/timeline.h"
+#include "catalog/pg_control.h"
 #include "storage/block.h"
 #include "storage/relfilenode.h"
 
+#define RESTORE_COMMAND_FILE "postgresql.conf"
+
 /* Configuration options */
 extern char *datadir_target;
 extern char *datadir_source;
@@ -32,11 +35,10 @@ extern int	targetNentries;
 
 /* in parsexlog.c */
 extern void extractPageMap(const char *datadir, XLogRecPtr startpoint,
-			   int tliIndex, XLogRecPtr endpoint);
-extern void findLastCheckpoint(const char *datadir, XLogRecPtr searchptr,
-				   int tliIndex,
-				   XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
-				   XLogRecPtr *lastchkptredo);
+			   int tliIndex, ControlFileData *targetCF, const char *restoreCommand);
+extern void findLastCheckpoint(const char *datadir, ControlFileData *targetCF, XLogRecPtr searchptr,
+				   int tliIndex, XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
+				   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 53dbf45be2..1860ce2fe3 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 => 10;
+use Test::More tests => 20;
 
 use RewindTest;
 
@@ -103,5 +103,7 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 2c9e427831..42d708dfbd 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 12;
 
 use RewindTest;
 
@@ -59,5 +59,7 @@ template1
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index 496f38c457..96665ac2f4 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 8;
 
 use File::Find;
 
@@ -87,5 +87,7 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/common/Makefile b/src/common/Makefile
index d0c2b970eb..3d7da47f56 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -48,7 +48,7 @@ OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o file_perm.o \
 	ip.o keywords.o kwlookup.o link-canary.o md5.o pg_lzcompress.o \
 	pgfnames.o psprintf.o relpath.o \
 	rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
-	username.o wait_error.o
+	username.o wait_error.o guc-file.o
 
 ifeq ($(with_openssl),yes)
 OBJS_COMMON += sha2_openssl.o
@@ -70,7 +70,7 @@ GEN_KEYWORDLIST_DEPS = $(TOOLSDIR)/gen_keywordlist.pl $(TOOLSDIR)/PerfectHash.pm
 
 all: libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
 
-distprep: kwlist_d.h
+distprep: kwlist_d.h guc-file.c
 
 # libpgcommon is needed by some contrib
 install: all installdirs
@@ -130,10 +130,13 @@ kwlist_d.h: $(top_srcdir)/src/include/parser/kwlist.h $(GEN_KEYWORDLIST_DEPS)
 # that you don't get broken parsing code, even in a non-enable-depend build.
 keywords.o keywords_shlib.o keywords_srv.o: kwlist_d.h
 
-# kwlist_d.h is in the distribution tarball, so it is not cleaned here.
+# Note: guc-file.c and kwlist_d.h are not deleted by 'make clean',
+# since we want to ship them in distribution tarballs.
 clean distclean:
 	rm -f libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
 	rm -f $(OBJS_FRONTEND) $(OBJS_SHLIB) $(OBJS_SRV)
+	@rm -f lex.yy.c
 
 maintainer-clean: distclean
 	rm -f kwlist_d.h
+	rm -f guc-file.c
diff --git a/src/backend/utils/misc/guc-file.l b/src/common/guc-file.l
similarity index 60%
rename from src/backend/utils/misc/guc-file.l
rename to src/common/guc-file.l
index 1c8b5f7d84..4117a1dcbc 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/common/guc-file.l
@@ -4,21 +4,33 @@
  *
  * Copyright (c) 2000-2019, PostgreSQL Global Development Group
  *
- * src/backend/utils/misc/guc-file.l
+ * src/common/guc-file.l
  */
 
 %{
 
-#include "postgres.h"
-
 #include <ctype.h>
 #include <unistd.h>
+#include <sys/stat.h>
+
+#ifndef FRONTEND
+#include "postgres.h"
 
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
 #include "utils/guc.h"
+#include "utils/elog.h"
+#else
+#include <dirent.h>
+#include <setjmp.h>
+
+#include "postgres_fe.h"
 
+#include "common/fe_memutils.h"
+#endif
+
+#include "common/guc-file.h"
 
 /*
  * flex emits a yy_fatal_error() function that it calls in response to
@@ -48,12 +60,6 @@ static sigjmp_buf *GUC_flex_fatal_jmp;
 
 static void FreeConfigVariable(ConfigVariable *item);
 
-static void record_config_file_error(const char *errmsg,
-						 const char *config_file,
-						 int lineno,
-						 ConfigVariable **head_p,
-						 ConfigVariable **tail_p);
-
 static int	GUC_flex_fatal(const char *msg);
 static char *GUC_scanstr(const char *s);
 
@@ -111,404 +117,35 @@ STRING			\'([^'\\\n]|\\.|\'\')*\'
 
 /* LCOV_EXCL_STOP */
 
-/*
- * Exported function to read and process the configuration file. The
- * parameter indicates in what context the file is being read --- either
- * postmaster startup (including standalone-backend startup) or SIGHUP.
- * All options mentioned in the configuration file are set to new values.
- * If a hard error occurs, no values will be changed.  (There can also be
- * errors that prevent just one value from being changed.)
- */
-void
-ProcessConfigFile(GucContext context)
-{
-	int			elevel;
-	MemoryContext config_cxt;
-	MemoryContext caller_cxt;
-
-	/*
-	 * Config files are processed on startup (by the postmaster only) and on
-	 * SIGHUP (by the postmaster and its children)
-	 */
-	Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) ||
-		   context == PGC_SIGHUP);
-
-	/*
-	 * To avoid cluttering the log, only the postmaster bleats loudly about
-	 * problems with the config file.
-	 */
-	elevel = IsUnderPostmaster ? DEBUG2 : LOG;
-
-	/*
-	 * This function is usually called within a process-lifespan memory
-	 * context.  To ensure that any memory leaked during GUC processing does
-	 * not accumulate across repeated SIGHUP cycles, do the work in a private
-	 * context that we can free at exit.
-	 */
-	config_cxt = AllocSetContextCreate(CurrentMemoryContext,
-									   "config file processing",
-									   ALLOCSET_DEFAULT_SIZES);
-	caller_cxt = MemoryContextSwitchTo(config_cxt);
-
-	/*
-	 * Read and apply the config file.  We don't need to examine the result.
-	 */
-	(void) ProcessConfigFileInternal(context, true, elevel);
-
-	/* Clean up */
-	MemoryContextSwitchTo(caller_cxt);
-	MemoryContextDelete(config_cxt);
-}
 
 /*
- * This function handles both actual config file (re)loads and execution of
- * show_all_file_settings() (i.e., the pg_file_settings view).  In the latter
- * case we don't apply any of the settings, but we make all the usual validity
- * checks, and we return the ConfigVariable list so that it can be printed out
- * by show_all_file_settings().
+ * The bare comparison function for GUC names.
  */
-static ConfigVariable *
-ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
+int
+guc_name_compare(const char *namea, const char *nameb)
 {
-	bool		error = false;
-	bool		applying = false;
-	const char *ConfFileWithError;
-	ConfigVariable *item,
-			   *head,
-			   *tail;
-	int			i;
-
-	/* Parse the main config file into a list of option names and values */
-	ConfFileWithError = ConfigFileName;
-	head = tail = NULL;
-
-	if (!ParseConfigFile(ConfigFileName, true,
-						 NULL, 0, 0, elevel,
-						 &head, &tail))
-	{
-		/* Syntax error(s) detected in the file, so bail out */
-		error = true;
-		goto bail_out;
-	}
-
-	/*
-	 * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to
-	 * replace any parameters set by ALTER SYSTEM command.  Because this file
-	 * is in the data directory, we can't read it until the DataDir has been
-	 * set.
-	 */
-	if (DataDir)
-	{
-		if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false,
-							 NULL, 0, 0, elevel,
-							 &head, &tail))
-		{
-			/* Syntax error(s) detected in the file, so bail out */
-			error = true;
-			ConfFileWithError = PG_AUTOCONF_FILENAME;
-			goto bail_out;
-		}
-	}
-	else
-	{
-		/*
-		 * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be
-		 * read.  In this case, we don't want to accept any settings but
-		 * data_directory from postgresql.conf, because they might be
-		 * overwritten with settings in the PG_AUTOCONF_FILENAME file which
-		 * will be read later. OTOH, since data_directory isn't allowed in the
-		 * PG_AUTOCONF_FILENAME file, it will never be overwritten later.
-		 */
-		ConfigVariable *newlist = NULL;
-
-		/*
-		 * Prune all items except the last "data_directory" from the list.
-		 */
-		for (item = head; item; item = item->next)
-		{
-			if (!item->ignore &&
-				strcmp(item->name, "data_directory") == 0)
-				newlist = item;
-		}
-
-		if (newlist)
-			newlist->next = NULL;
-		head = tail = newlist;
-
-		/*
-		 * Quick exit if data_directory is not present in file.
-		 *
-		 * We need not do any further processing, in particular we don't set
-		 * PgReloadTime; that will be set soon by subsequent full loading of
-		 * the config file.
-		 */
-		if (head == NULL)
-			goto bail_out;
-	}
-
 	/*
-	 * Mark all extant GUC variables as not present in the config file. We
-	 * need this so that we can tell below which ones have been removed from
-	 * the file since we last processed it.
+	 * The temptation to use strcasecmp() here must be resisted, because the
+	 * array ordering has to remain stable across setlocale() calls. So, build
+	 * our own with a simple ASCII-only downcasing.
 	 */
-	for (i = 0; i < num_guc_variables; i++)
+	while (*namea && *nameb)
 	{
-		struct config_generic *gconf = guc_variables[i];
-
-		gconf->status &= ~GUC_IS_IN_FILE;
+		char		cha = *namea++;
+		char		chb = *nameb++;
+
+		if (cha >= 'A' && cha <= 'Z')
+			cha += 'a' - 'A';
+		if (chb >= 'A' && chb <= 'Z')
+			chb += 'a' - 'A';
+		if (cha != chb)
+			return cha - chb;
 	}
-
-	/*
-	 * Check if all the supplied option names are valid, as an additional
-	 * quasi-syntactic check on the validity of the config file.  It is
-	 * important that the postmaster and all backends agree on the results of
-	 * this phase, else we will have strange inconsistencies about which
-	 * processes accept a config file update and which don't.  Hence, unknown
-	 * custom variable names have to be accepted without complaint.  For the
-	 * same reason, we don't attempt to validate the options' values here.
-	 *
-	 * In addition, the GUC_IS_IN_FILE flag is set on each existing GUC
-	 * variable mentioned in the file; and we detect duplicate entries in the
-	 * file and mark the earlier occurrences as ignorable.
-	 */
-	for (item = head; item; item = item->next)
-	{
-		struct config_generic *record;
-
-		/* Ignore anything already marked as ignorable */
-		if (item->ignore)
-			continue;
-
-		/*
-		 * Try to find the variable; but do not create a custom placeholder if
-		 * it's not there already.
-		 */
-		record = find_option(item->name, false, elevel);
-
-		if (record)
-		{
-			/* If it's already marked, then this is a duplicate entry */
-			if (record->status & GUC_IS_IN_FILE)
-			{
-				/*
-				 * Mark the earlier occurrence(s) as dead/ignorable.  We could
-				 * avoid the O(N^2) behavior here with some additional state,
-				 * but it seems unlikely to be worth the trouble.
-				 */
-				ConfigVariable *pitem;
-
-				for (pitem = head; pitem != item; pitem = pitem->next)
-				{
-					if (!pitem->ignore &&
-						strcmp(pitem->name, item->name) == 0)
-						pitem->ignore = true;
-				}
-			}
-			/* Now mark it as present in file */
-			record->status |= GUC_IS_IN_FILE;
-		}
-		else if (strchr(item->name, GUC_QUALIFIER_SEPARATOR) == NULL)
-		{
-			/* Invalid non-custom variable, so complain */
-			ereport(elevel,
-					(errcode(ERRCODE_UNDEFINED_OBJECT),
-					 errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %u",
-							item->name,
-							item->filename, item->sourceline)));
-			item->errmsg = pstrdup("unrecognized configuration parameter");
-			error = true;
-			ConfFileWithError = item->filename;
-		}
-	}
-
-	/*
-	 * If we've detected any errors so far, we don't want to risk applying any
-	 * changes.
-	 */
-	if (error)
-		goto bail_out;
-
-	/* Otherwise, set flag that we're beginning to apply changes */
-	applying = true;
-
-	/*
-	 * Check for variables having been removed from the config file, and
-	 * revert their reset values (and perhaps also effective values) to the
-	 * boot-time defaults.  If such a variable can't be changed after startup,
-	 * report that and continue.
-	 */
-	for (i = 0; i < num_guc_variables; i++)
-	{
-		struct config_generic *gconf = guc_variables[i];
-		GucStack   *stack;
-
-		if (gconf->reset_source != PGC_S_FILE ||
-			(gconf->status & GUC_IS_IN_FILE))
-			continue;
-		if (gconf->context < PGC_SIGHUP)
-		{
-			ereport(elevel,
-					(errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
-					 errmsg("parameter \"%s\" cannot be changed without restarting the server",
-							gconf->name)));
-			record_config_file_error(psprintf("parameter \"%s\" cannot be changed without restarting the server",
-											  gconf->name),
-									 NULL, 0,
-									 &head, &tail);
-			error = true;
-			continue;
-		}
-
-		/* No more to do if we're just doing show_all_file_settings() */
-		if (!applySettings)
-			continue;
-
-		/*
-		 * Reset any "file" sources to "default", else set_config_option will
-		 * not override those settings.
-		 */
-		if (gconf->reset_source == PGC_S_FILE)
-			gconf->reset_source = PGC_S_DEFAULT;
-		if (gconf->source == PGC_S_FILE)
-			gconf->source = PGC_S_DEFAULT;
-		for (stack = gconf->stack; stack; stack = stack->prev)
-		{
-			if (stack->source == PGC_S_FILE)
-				stack->source = PGC_S_DEFAULT;
-		}
-
-		/* Now we can re-apply the wired-in default (i.e., the boot_val) */
-		if (set_config_option(gconf->name, NULL,
-							  context, PGC_S_DEFAULT,
-							  GUC_ACTION_SET, true, 0, false) > 0)
-		{
-			/* Log the change if appropriate */
-			if (context == PGC_SIGHUP)
-				ereport(elevel,
-						(errmsg("parameter \"%s\" removed from configuration file, reset to default",
-								gconf->name)));
-		}
-	}
-
-	/*
-	 * Restore any variables determined by environment variables or
-	 * dynamically-computed defaults.  This is a no-op except in the case
-	 * where one of these had been in the config file and is now removed.
-	 *
-	 * In particular, we *must not* do this during the postmaster's initial
-	 * loading of the file, since the timezone functions in particular should
-	 * be run only after initialization is complete.
-	 *
-	 * XXX this is an unmaintainable crock, because we have to know how to set
-	 * (or at least what to call to set) every variable that could potentially
-	 * have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. However, there's no
-	 * time to redesign it for 9.1.
-	 */
-	if (context == PGC_SIGHUP && applySettings)
-	{
-		InitializeGUCOptionsFromEnvironment();
-		pg_timezone_abbrev_initialize();
-		/* this selects SQL_ASCII in processes not connected to a database */
-		SetConfigOption("client_encoding", GetDatabaseEncodingName(),
-						PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT);
-	}
-
-	/*
-	 * Now apply the values from the config file.
-	 */
-	for (item = head; item; item = item->next)
-	{
-		char	   *pre_value = NULL;
-		int			scres;
-
-		/* Ignore anything marked as ignorable */
-		if (item->ignore)
-			continue;
-
-		/* In SIGHUP cases in the postmaster, we want to report changes */
-		if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster)
-		{
-			const char *preval = GetConfigOption(item->name, true, false);
-
-			/* If option doesn't exist yet or is NULL, treat as empty string */
-			if (!preval)
-				preval = "";
-			/* must dup, else might have dangling pointer below */
-			pre_value = pstrdup(preval);
-		}
-
-		scres = set_config_option(item->name, item->value,
-								  context, PGC_S_FILE,
-								  GUC_ACTION_SET, applySettings, 0, false);
-		if (scres > 0)
-		{
-			/* variable was updated, so log the change if appropriate */
-			if (pre_value)
-			{
-				const char *post_value = GetConfigOption(item->name, true, false);
-
-				if (!post_value)
-					post_value = "";
-				if (strcmp(pre_value, post_value) != 0)
-					ereport(elevel,
-							(errmsg("parameter \"%s\" changed to \"%s\"",
-									item->name, item->value)));
-			}
-			item->applied = true;
-		}
-		else if (scres == 0)
-		{
-			error = true;
-			item->errmsg = pstrdup("setting could not be applied");
-			ConfFileWithError = item->filename;
-		}
-		else
-		{
-			/* no error, but variable's active value was not changed */
-			item->applied = true;
-		}
-
-		/*
-		 * We should update source location unless there was an error, since
-		 * even if the active value didn't change, the reset value might have.
-		 * (In the postmaster, there won't be a difference, but it does matter
-		 * in backends.)
-		 */
-		if (scres != 0 && applySettings)
-			set_config_sourcefile(item->name, item->filename,
-								  item->sourceline);
-
-		if (pre_value)
-			pfree(pre_value);
-	}
-
-	/* Remember when we last successfully loaded the config file. */
-	if (applySettings)
-		PgReloadTime = GetCurrentTimestamp();
-
-bail_out:
-	if (error && applySettings)
-	{
-		/* During postmaster startup, any error is fatal */
-		if (context == PGC_POSTMASTER)
-			ereport(ERROR,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("configuration file \"%s\" contains errors",
-							ConfFileWithError)));
-		else if (applying)
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("configuration file \"%s\" contains errors; unaffected changes were applied",
-							ConfFileWithError)));
-		else
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("configuration file \"%s\" contains errors; no changes were applied",
-							ConfFileWithError)));
-	}
-
-	/* Successful or otherwise, return the collected data list */
-	return head;
+	if (*namea)
+		return 1;				/* a is longer */
+	if (*nameb)
+		return -1;				/* b is longer */
+	return 0;
 }
 
 /*
@@ -525,6 +162,7 @@ AbsoluteConfigLocation(const char *location, const char *calling_file)
 		return pstrdup(location);
 	else
 	{
+		#ifndef FRONTEND
 		if (calling_file != NULL)
 		{
 			strlcpy(abs_path, calling_file, sizeof(abs_path));
@@ -538,6 +176,12 @@ AbsoluteConfigLocation(const char *location, const char *calling_file)
 			join_path_components(abs_path, DataDir, location);
 			canonicalize_path(abs_path);
 		}
+		#else
+		strlcpy(abs_path, calling_file, sizeof(abs_path));
+		get_parent_directory(abs_path);
+		join_path_components(abs_path, abs_path, location);
+		canonicalize_path(abs_path);
+		#endif
 		return pstrdup(abs_path);
 	}
 }
@@ -574,10 +218,15 @@ ParseConfigFile(const char *config_file, bool strict,
 	 */
 	if (depth > 10)
 	{
+		#ifndef FRONTEND
 		ereport(elevel,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("could not open configuration file \"%s\": maximum nesting depth exceeded",
 						config_file)));
+		#else
+		printf(_("could not open configuration file \"%s\": maximum nesting depth exceeded\n"),
+				config_file);
+		#endif
 		record_config_file_error("nesting depth exceeded",
 								 calling_file, calling_lineno,
 								 head_p, tail_p);
@@ -585,15 +234,23 @@ ParseConfigFile(const char *config_file, bool strict,
 	}
 
 	abs_path = AbsoluteConfigLocation(config_file, calling_file);
+	#ifndef FRONTEND
 	fp = AllocateFile(abs_path, "r");
+	#else
+	fp = fopen(abs_path, "r");
+	#endif
 	if (!fp)
 	{
 		if (strict)
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode_for_file_access(),
 					 errmsg("could not open configuration file \"%s\": %m",
 							abs_path)));
+			#else
+			printf(_("could not open configuration file \"%s\"\n"), abs_path);
+			#endif
 			record_config_file_error(psprintf("could not open file \"%s\"",
 											  abs_path),
 									 calling_file, calling_lineno,
@@ -602,9 +259,13 @@ ParseConfigFile(const char *config_file, bool strict,
 		}
 		else
 		{
+			#ifndef FRONTEND
 			ereport(LOG,
 					(errmsg("skipping missing configuration file \"%s\"",
 							abs_path)));
+			#else
+			printf(_("skipping missing configuration file \"%s\"\n"), abs_path);
+			#endif
 		}
 		goto cleanup;
 	}
@@ -613,7 +274,14 @@ ParseConfigFile(const char *config_file, bool strict,
 
 cleanup:
 	if (fp)
+	{
+		#ifndef FRONTEND
 		FreeFile(fp);
+		#else
+		fclose(fp);
+		#endif
+	}
+
 	pfree(abs_path);
 
 	return OK;
@@ -623,7 +291,7 @@ cleanup:
  * Capture an error message in the ConfigVariable list returned by
  * config file parsing.
  */
-static void
+void
 record_config_file_error(const char *errmsg,
 						 const char *config_file,
 						 int lineno,
@@ -715,8 +383,13 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
 		 * corrupted parser state.  Consequently, abandon the file, but trust
 		 * that the state remains sane enough for yy_delete_buffer().
 		 */
+		#ifndef FRONTEND
 		elog(elevel, "%s at file \"%s\" line %u",
 			 GUC_flex_fatal_errmsg, config_file, ConfigFileLineno);
+		#else
+		printf(_("%s at file \"%s\" line %u\n"),
+				GUC_flex_fatal_errmsg, config_file, ConfigFileLineno);
+		#endif
 		record_config_file_error(GUC_flex_fatal_errmsg,
 								 config_file, ConfigFileLineno,
 								 head_p, tail_p);
@@ -855,20 +528,30 @@ parse_error:
 		/* report the error */
 		if (token == GUC_EOL || token == 0)
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 			  errmsg("syntax error in file \"%s\" line %u, near end of line",
 					 config_file, ConfigFileLineno - 1)));
+			#else
+			printf(_("syntax error in file \"%s\" line %u, near end of line\n"),
+											config_file, ConfigFileLineno - 1);
+			#endif
 			record_config_file_error("syntax error",
 									 config_file, ConfigFileLineno - 1,
 									 head_p, tail_p);
 		}
 		else
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 			 errmsg("syntax error in file \"%s\" line %u, near token \"%s\"",
 					config_file, ConfigFileLineno, yytext)));
+			#else
+			printf(_("syntax error in file \"%s\" line %u, near token \"%s\"\n"),
+										config_file, ConfigFileLineno, yytext);
+			#endif
 			record_config_file_error("syntax error",
 									 config_file, ConfigFileLineno,
 									 head_p, tail_p);
@@ -883,12 +566,21 @@ parse_error:
 		 * as well give up immediately.  (This prevents postmaster children
 		 * from bloating the logs with duplicate complaints.)
 		 */
+		#ifndef FRONTEND
 		if (errorcount >= 100 || elevel <= DEBUG1)
+		#else
+		if (errorcount >= 100)
+		#endif
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 			   errmsg("too many syntax errors found, abandoning file \"%s\"",
 					  config_file)));
+			#else
+			printf(_("too many syntax errors found, abandoning file \"%s\"\n"),
+																config_file);
+			#endif
 			break;
 		}
 
@@ -934,13 +626,21 @@ ParseConfigDirectory(const char *includedir,
 	bool		status;
 
 	directory = AbsoluteConfigLocation(includedir, calling_file);
+	#ifndef FRONTEND
 	d = AllocateDir(directory);
+	#else
+	d = opendir(directory);
+	#endif
 	if (d == NULL)
 	{
+		#ifndef FRONTEND
 		ereport(elevel,
 				(errcode_for_file_access(),
 				 errmsg("could not open configuration directory \"%s\": %m",
 						directory)));
+		#else
+		printf(_("could not open configuration directory \"%s\"\n"), directory);
+		#endif
 		record_config_file_error(psprintf("could not open directory \"%s\"",
 										  directory),
 								 calling_file, calling_lineno,
@@ -957,7 +657,11 @@ ParseConfigDirectory(const char *includedir,
 	filenames = (char **) palloc(size_filenames * sizeof(char *));
 	num_filenames = 0;
 
+	#ifndef FRONTEND
 	while ((de = ReadDir(d, directory)) != NULL)
+	#else
+	while ((de = readdir(d)) != NULL)
+	#endif
 	{
 		struct stat st;
 		char		filename[MAXPGPATH];
@@ -998,10 +702,14 @@ ParseConfigDirectory(const char *includedir,
 			 * a file can't be accessed now is if it was removed between the
 			 * directory listing and now.
 			 */
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode_for_file_access(),
 					 errmsg("could not stat file \"%s\": %m",
 							filename)));
+			#else
+			printf(_("could not stat file \"%s\"\n"), filename);
+			#endif
 			record_config_file_error(psprintf("could not stat file \"%s\"",
 											  filename),
 									 calling_file, calling_lineno,
@@ -1032,7 +740,13 @@ ParseConfigDirectory(const char *includedir,
 
 cleanup:
 	if (d)
+	{
+		#ifndef FRONTEND
 		FreeDir(d);
+		#else
+		closedir(d);
+		#endif
+	}
 	pfree(directory);
 	return status;
 }
diff --git a/src/include/common/guc-file.h b/src/include/common/guc-file.h
new file mode 100644
index 0000000000..ca969e2aed
--- /dev/null
+++ b/src/include/common/guc-file.h
@@ -0,0 +1,50 @@
+#ifndef GUC_FILE_H
+#define GUC_FILE_H
+
+#include "c.h"
+
+/*
+ * Parsing the configuration file(s) will return a list of name-value pairs
+ * with source location info.  We also abuse this data structure to carry
+ * error reports about the config files.  An entry reporting an error will
+ * have errmsg != NULL, and might have NULLs for name, value, and/or filename.
+ *
+ * If "ignore" is true, don't attempt to apply the item (it might be an error
+ * report, or an item we determined to be duplicate).  "applied" is set true
+ * if we successfully applied, or could have applied, the setting.
+ */
+typedef struct ConfigVariable
+{
+	char	   *name;
+	char	   *value;
+	char	   *errmsg;
+	char	   *filename;
+	int			sourceline;
+	bool		ignore;
+	bool		applied;
+	struct ConfigVariable *next;
+} ConfigVariable;
+
+extern bool ParseConfigFile(const char *config_file, bool strict,
+				const char *calling_file, int calling_lineno,
+				int depth, int elevel,
+				ConfigVariable **head_p, ConfigVariable **tail_p);
+extern bool ParseConfigFp(FILE *fp, const char *config_file,
+			  int depth, int elevel,
+			  ConfigVariable **head_p, ConfigVariable **tail_p);
+extern bool ParseConfigDirectory(const char *includedir,
+					 const char *calling_file, int calling_lineno,
+					 int depth, int elevel,
+					 ConfigVariable **head_p,
+					 ConfigVariable **tail_p);
+extern void FreeConfigVariables(ConfigVariable *list);
+
+extern int	guc_name_compare(const char *namea, const char *nameb);
+
+extern void record_config_file_error(const char *errmsg,
+						 const char *config_file,
+						 int lineno,
+						 ConfigVariable **head_p,
+						 ConfigVariable **tail_p);
+
+#endif							/* GUC_FILE_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index c07e7b945e..0b3391004c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -1,8 +1,7 @@
 /*--------------------------------------------------------------------
  * guc.h
  *
- * External declarations pertaining to backend/utils/misc/guc.c and
- * backend/utils/misc/guc-file.l
+ * External declarations pertaining to backend/utils/misc/guc.c
  *
  * Copyright (c) 2000-2019, PostgreSQL Global Development Group
  * Written by Peter Eisentraut <peter_e@gmx.net>.
@@ -120,42 +119,6 @@ typedef enum
 	PGC_S_SESSION				/* SET command */
 } GucSource;
 
-/*
- * Parsing the configuration file(s) will return a list of name-value pairs
- * with source location info.  We also abuse this data structure to carry
- * error reports about the config files.  An entry reporting an error will
- * have errmsg != NULL, and might have NULLs for name, value, and/or filename.
- *
- * If "ignore" is true, don't attempt to apply the item (it might be an error
- * report, or an item we determined to be duplicate).  "applied" is set true
- * if we successfully applied, or could have applied, the setting.
- */
-typedef struct ConfigVariable
-{
-	char	   *name;
-	char	   *value;
-	char	   *errmsg;
-	char	   *filename;
-	int			sourceline;
-	bool		ignore;
-	bool		applied;
-	struct ConfigVariable *next;
-} ConfigVariable;
-
-extern bool ParseConfigFile(const char *config_file, bool strict,
-				const char *calling_file, int calling_lineno,
-				int depth, int elevel,
-				ConfigVariable **head_p, ConfigVariable **tail_p);
-extern bool ParseConfigFp(FILE *fp, const char *config_file,
-			  int depth, int elevel,
-			  ConfigVariable **head_p, ConfigVariable **tail_p);
-extern bool ParseConfigDirectory(const char *includedir,
-					 const char *calling_file, int calling_lineno,
-					 int depth, int elevel,
-					 ConfigVariable **head_p,
-					 ConfigVariable **tail_p);
-extern void FreeConfigVariables(ConfigVariable *list);
-
 /*
  * The possible values of an enum variable are specified by an array of
  * name-value pairs.  The "hidden" flag means the value is accepted but
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 56192f1b20..6952e60550 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -172,7 +172,7 @@ sub mkvcbuild
 	$postgres->AddFiles('src/backend/parser', 'scan.l', 'gram.y');
 	$postgres->AddFiles('src/backend/bootstrap', 'bootscanner.l',
 		'bootparse.y');
-	$postgres->AddFiles('src/backend/utils/misc', 'guc-file.l');
+	$postgres->AddFiles('src/common', 'guc-file.l');
 	$postgres->AddFiles(
 		'src/backend/replication', 'repl_scanner.l',
 		'repl_gram.y',             'syncrep_scanner.l',
diff --git a/src/tools/msvc/clean.bat b/src/tools/msvc/clean.bat
index 069d6eb569..53d77aaf0a 100755
--- a/src/tools/msvc/clean.bat
+++ b/src/tools/msvc/clean.bat
@@ -80,7 +80,7 @@ if %DIST%==1 if exist src\backend\parser\scan.c del /q src\backend\parser\scan.c
 if %DIST%==1 if exist src\backend\parser\gram.c del /q src\backend\parser\gram.c
 if %DIST%==1 if exist src\backend\bootstrap\bootscanner.c del /q src\backend\bootstrap\bootscanner.c
 if %DIST%==1 if exist src\backend\bootstrap\bootparse.c del /q src\backend\bootstrap\bootparse.c
-if %DIST%==1 if exist src\backend\utils\misc\guc-file.c del /q src\backend\utils\misc\guc-file.c
+if %DIST%==1 if exist src\common\guc-file.c del /q src\common\guc-file.c
 if %DIST%==1 if exist src\backend\replication\repl_scanner.c del /q src\backend\replication\repl_scanner.c
 if %DIST%==1 if exist src\backend\replication\repl_gram.c del /q src\backend\replication\repl_gram.c
 if %DIST%==1 if exist src\backend\replication\syncrep_scanner.c del /q src\backend\replication\syncrep_scanner.c
-- 
2.19.1

#17Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Noname (#16)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 21.01.2019 23:50, a.kondratov@postgrespro.ru wrote:

Thank you for the review! I have updated the patch according to your
comments and remarks. Please, find new version attached.

During the self-reviewing of the code and tests, I discovered some
problems with build on Windows. New version of the patch is attached and
it fixes this issue as well as includes some minor code revisions.

Regards

--
Alexey Kondratov

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

Attachments:

0001-pg_rewind-options-to-use-restore_command-v2.2.patchtext/x-patch; name=0001-pg_rewind-options-to-use-restore_command-v2.2.patchDownload
From 99c6d94f37a797400d41545a271ff111b92e9361 Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <alex.lumir@gmail.com>
Date: Fri, 21 Dec 2018 14:00:30 +0300
Subject: [PATCH] pg_rewind: options to use restore_command from
 postgresql.conf or command line.

---
 doc/src/sgml/ref/pg_rewind.sgml               |  30 +-
 src/backend/Makefile                          |   4 +-
 src/backend/commands/extension.c              |   1 +
 src/backend/utils/misc/.gitignore             |   1 -
 src/backend/utils/misc/Makefile               |   8 -
 src/backend/utils/misc/guc.c                  | 434 +++++++++++++--
 src/bin/pg_rewind/Makefile                    |   2 +-
 src/bin/pg_rewind/parsexlog.c                 | 166 +++++-
 src/bin/pg_rewind/pg_rewind.c                 | 100 +++-
 src/bin/pg_rewind/pg_rewind.h                 |  10 +-
 src/bin/pg_rewind/t/001_basic.pl              |   4 +-
 src/bin/pg_rewind/t/002_databases.pl          |   4 +-
 src/bin/pg_rewind/t/003_extrafiles.pl         |   4 +-
 src/bin/pg_rewind/t/RewindTest.pm             |  93 +++-
 src/common/.gitignore                         |   1 +
 src/common/Makefile                           |   9 +-
 src/{backend/utils/misc => common}/guc-file.l | 518 ++++--------------
 src/include/common/guc-file.h                 |  50 ++
 src/include/utils/guc.h                       |  39 +-
 src/tools/msvc/Mkvcbuild.pm                   |   7 +-
 src/tools/msvc/clean.bat                      |   2 +-
 21 files changed, 973 insertions(+), 514 deletions(-)
 delete mode 100644 src/backend/utils/misc/.gitignore
 rename src/{backend/utils/misc => common}/guc-file.l (60%)
 create mode 100644 src/include/common/guc-file.h

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 53a64ee29e..0c2441afa7 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -67,8 +67,10 @@ PostgreSQL documentation
    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
+   files might no longer be present. In that case, they can be automatically
+   copied by <application>pg_rewind</application> from the WAL archive to the 
+   <filename>pg_wal</filename> directory if either <literal>-r</literal> or
+   <literal>-R</literal> option is specified, or
    fetched on startup by configuring <xref linkend="guc-primary-conninfo"/> or
    <xref linkend="guc-restore-command"/>.  The use of
    <application>pg_rewind</application> is not limited to failover, e.g.  a standby
@@ -200,6 +202,30 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-r</option></term>
+      <term><option>--use-postgresql-conf</option></term>
+      <listitem>
+       <para>
+        Use restore_command in the <filename>postgresql.conf</filename> to
+        retreive missing in the target <filename>pg_wal</filename> directory
+        WAL files from the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-R <replaceable class="parameter">restore_command</replaceable></option></term>
+      <term><option>--restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the restore_command to use for retrieval of the missing
+        in the target <filename>pg_wal</filename> directory WAL files from
+        the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 478a96db9b..721cb57e89 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -186,7 +186,7 @@ distprep:
 	$(MAKE) -C replication	repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
 	$(MAKE) -C storage/lmgr	lwlocknames.h lwlocknames.c
 	$(MAKE) -C utils	distprep
-	$(MAKE) -C utils/misc	guc-file.c
+	$(MAKE) -C common	guc-file.c
 	$(MAKE) -C utils/sort	qsort_tuple.c
 
 
@@ -307,7 +307,7 @@ maintainer-clean: distclean
 	      replication/syncrep_scanner.c \
 	      storage/lmgr/lwlocknames.c \
 	      storage/lmgr/lwlocknames.h \
-	      utils/misc/guc-file.c \
+	      common/guc-file.c \
 	      utils/sort/qsort_tuple.c
 
 
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index daf3f51636..195eb8a821 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -50,6 +50,7 @@
 #include "commands/defrem.h"
 #include "commands/extension.h"
 #include "commands/schemacmds.h"
+#include "common/guc-file.h"
 #include "funcapi.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
diff --git a/src/backend/utils/misc/.gitignore b/src/backend/utils/misc/.gitignore
deleted file mode 100644
index 495b1aec76..0000000000
--- a/src/backend/utils/misc/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/guc-file.c
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index ec7ec131e5..2205956a9f 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -25,11 +25,3 @@ override CPPFLAGS += -DPG_KRB_SRVTAB='"$(krb_srvtab)"'
 endif
 
 include $(top_srcdir)/src/backend/common.mk
-
-# guc-file is compiled as part of guc
-guc.o: guc-file.c
-
-# Note: guc-file.c is not deleted by 'make clean',
-# since we want to ship it in distribution tarballs.
-clean:
-	@rm -f lex.yy.c
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index ea5444c6f1..eff5b5aefa 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -41,6 +41,7 @@
 #include "commands/vacuum.h"
 #include "commands/variable.h"
 #include "commands/trigger.h"
+#include "common/guc-file.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "jit/jit.h"
@@ -212,7 +213,6 @@ static void assign_recovery_target_lsn(const char *newval, void *extra);
 static bool check_primary_slot_name(char **newval, void **extra, GucSource source);
 static bool check_default_with_oids(bool *newval, void **extra, GucSource source);
 
-/* Private functions in guc-file.l that need to be called from guc.c */
 static ConfigVariable *ProcessConfigFileInternal(GucContext context,
 						  bool applySettings, int elevel);
 
@@ -4491,7 +4491,6 @@ static int	GUCNestLevel = 0;	/* 1 when in main transaction */
 
 
 static int	guc_var_compare(const void *a, const void *b);
-static int	guc_name_compare(const char *namea, const char *nameb);
 static void InitializeGUCOptionsFromEnvironment(void);
 static void InitializeOneGUCOption(struct config_generic *gconf);
 static void push_old_value(struct config_generic *gconf, GucAction action);
@@ -4968,37 +4967,6 @@ guc_var_compare(const void *a, const void *b)
 	return guc_name_compare(confa->name, confb->name);
 }
 
-/*
- * the bare comparison function for GUC names
- */
-static int
-guc_name_compare(const char *namea, const char *nameb)
-{
-	/*
-	 * The temptation to use strcasecmp() here must be resisted, because the
-	 * array ordering has to remain stable across setlocale() calls. So, build
-	 * our own with a simple ASCII-only downcasing.
-	 */
-	while (*namea && *nameb)
-	{
-		char		cha = *namea++;
-		char		chb = *nameb++;
-
-		if (cha >= 'A' && cha <= 'Z')
-			cha += 'a' - 'A';
-		if (chb >= 'A' && chb <= 'Z')
-			chb += 'a' - 'A';
-		if (cha != chb)
-			return cha - chb;
-	}
-	if (*namea)
-		return 1;				/* a is longer */
-	if (*nameb)
-		return -1;				/* b is longer */
-	return 0;
-}
-
-
 /*
  * Initialize GUC options during program startup.
  *
@@ -11372,4 +11340,402 @@ check_default_with_oids(bool *newval, void **extra, GucSource source)
 	return true;
 }
 
-#include "guc-file.c"
+/*
+ * Exported function to read and process the configuration file. The
+ * parameter indicates in what context the file is being read --- either
+ * postmaster startup (including standalone-backend startup) or SIGHUP.
+ * All options mentioned in the configuration file are set to new values.
+ * If a hard error occurs, no values will be changed.  (There can also be
+ * errors that prevent just one value from being changed.)
+ */
+void
+ProcessConfigFile(GucContext context)
+{
+	int			elevel;
+	MemoryContext config_cxt;
+	MemoryContext caller_cxt;
+
+	/*
+	 * Config files are processed on startup (by the postmaster only) and on
+	 * SIGHUP (by the postmaster and its children)
+	 */
+	Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) ||
+		   context == PGC_SIGHUP);
+
+	/*
+	 * To avoid cluttering the log, only the postmaster bleats loudly about
+	 * problems with the config file.
+	 */
+	elevel = IsUnderPostmaster ? DEBUG2 : LOG;
+
+	/*
+	 * This function is usually called within a process-lifespan memory
+	 * context.  To ensure that any memory leaked during GUC processing does
+	 * not accumulate across repeated SIGHUP cycles, do the work in a private
+	 * context that we can free at exit.
+	 */
+	config_cxt = AllocSetContextCreate(CurrentMemoryContext,
+									   "config file processing",
+									   ALLOCSET_DEFAULT_SIZES);
+	caller_cxt = MemoryContextSwitchTo(config_cxt);
+
+	/*
+	 * Read and apply the config file.  We don't need to examine the result.
+	 */
+	(void) ProcessConfigFileInternal(context, true, elevel);
+
+	/* Clean up */
+	MemoryContextSwitchTo(caller_cxt);
+	MemoryContextDelete(config_cxt);
+}
+
+/*
+ * This function handles both actual config file (re)loads and execution of
+ * show_all_file_settings() (i.e., the pg_file_settings view).  In the latter
+ * case we don't apply any of the settings, but we make all the usual validity
+ * checks, and we return the ConfigVariable list so that it can be printed out
+ * by show_all_file_settings().
+ */
+static ConfigVariable *
+ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
+{
+	bool		error = false;
+	bool		applying = false;
+	const char *ConfFileWithError;
+	ConfigVariable *item,
+			   *head,
+			   *tail;
+	int			i;
+
+	/* Parse the main config file into a list of option names and values */
+	ConfFileWithError = ConfigFileName;
+	head = tail = NULL;
+
+	if (!ParseConfigFile(ConfigFileName, true,
+						 NULL, 0, 0, elevel,
+						 &head, &tail))
+	{
+		/* Syntax error(s) detected in the file, so bail out */
+		error = true;
+		goto bail_out;
+	}
+
+	/*
+	 * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to
+	 * replace any parameters set by ALTER SYSTEM command.  Because this file
+	 * is in the data directory, we can't read it until the DataDir has been
+	 * set.
+	 */
+	if (DataDir)
+	{
+		if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false,
+							 NULL, 0, 0, elevel,
+							 &head, &tail))
+		{
+			/* Syntax error(s) detected in the file, so bail out */
+			error = true;
+			ConfFileWithError = PG_AUTOCONF_FILENAME;
+			goto bail_out;
+		}
+	}
+	else
+	{
+		/*
+		 * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be
+		 * read.  In this case, we don't want to accept any settings but
+		 * data_directory from postgresql.conf, because they might be
+		 * overwritten with settings in the PG_AUTOCONF_FILENAME file which
+		 * will be read later. OTOH, since data_directory isn't allowed in the
+		 * PG_AUTOCONF_FILENAME file, it will never be overwritten later.
+		 */
+		ConfigVariable *newlist = NULL;
+
+		/*
+		 * Prune all items except the last "data_directory" from the list.
+		 */
+		for (item = head; item; item = item->next)
+		{
+			if (!item->ignore &&
+				strcmp(item->name, "data_directory") == 0)
+				newlist = item;
+		}
+
+		if (newlist)
+			newlist->next = NULL;
+		head = tail = newlist;
+
+		/*
+		 * Quick exit if data_directory is not present in file.
+		 *
+		 * We need not do any further processing, in particular we don't set
+		 * PgReloadTime; that will be set soon by subsequent full loading of
+		 * the config file.
+		 */
+		if (head == NULL)
+			goto bail_out;
+	}
+
+	/*
+	 * Mark all extant GUC variables as not present in the config file. We
+	 * need this so that we can tell below which ones have been removed from
+	 * the file since we last processed it.
+	 */
+	for (i = 0; i < num_guc_variables; i++)
+	{
+		struct config_generic *gconf = guc_variables[i];
+
+		gconf->status &= ~GUC_IS_IN_FILE;
+	}
+
+	/*
+	 * Check if all the supplied option names are valid, as an additional
+	 * quasi-syntactic check on the validity of the config file.  It is
+	 * important that the postmaster and all backends agree on the results of
+	 * this phase, else we will have strange inconsistencies about which
+	 * processes accept a config file update and which don't.  Hence, unknown
+	 * custom variable names have to be accepted without complaint.  For the
+	 * same reason, we don't attempt to validate the options' values here.
+	 *
+	 * In addition, the GUC_IS_IN_FILE flag is set on each existing GUC
+	 * variable mentioned in the file; and we detect duplicate entries in the
+	 * file and mark the earlier occurrences as ignorable.
+	 */
+	for (item = head; item; item = item->next)
+	{
+		struct config_generic *record;
+
+		/* Ignore anything already marked as ignorable */
+		if (item->ignore)
+			continue;
+
+		/*
+		 * Try to find the variable; but do not create a custom placeholder if
+		 * it's not there already.
+		 */
+		record = find_option(item->name, false, elevel);
+
+		if (record)
+		{
+			/* If it's already marked, then this is a duplicate entry */
+			if (record->status & GUC_IS_IN_FILE)
+			{
+				/*
+				 * Mark the earlier occurrence(s) as dead/ignorable.  We could
+				 * avoid the O(N^2) behavior here with some additional state,
+				 * but it seems unlikely to be worth the trouble.
+				 */
+				ConfigVariable *pitem;
+
+				for (pitem = head; pitem != item; pitem = pitem->next)
+				{
+					if (!pitem->ignore &&
+						strcmp(pitem->name, item->name) == 0)
+						pitem->ignore = true;
+				}
+			}
+			/* Now mark it as present in file */
+			record->status |= GUC_IS_IN_FILE;
+		}
+		else if (strchr(item->name, GUC_QUALIFIER_SEPARATOR) == NULL)
+		{
+			/* Invalid non-custom variable, so complain */
+			ereport(elevel,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %u",
+							item->name,
+							item->filename, item->sourceline)));
+			item->errmsg = pstrdup("unrecognized configuration parameter");
+			error = true;
+			ConfFileWithError = item->filename;
+		}
+	}
+
+	/*
+	 * If we've detected any errors so far, we don't want to risk applying any
+	 * changes.
+	 */
+	if (error)
+		goto bail_out;
+
+	/* Otherwise, set flag that we're beginning to apply changes */
+	applying = true;
+
+	/*
+	 * Check for variables having been removed from the config file, and
+	 * revert their reset values (and perhaps also effective values) to the
+	 * boot-time defaults.  If such a variable can't be changed after startup,
+	 * report that and continue.
+	 */
+	for (i = 0; i < num_guc_variables; i++)
+	{
+		struct config_generic *gconf = guc_variables[i];
+		GucStack   *stack;
+
+		if (gconf->reset_source != PGC_S_FILE ||
+			(gconf->status & GUC_IS_IN_FILE))
+			continue;
+		if (gconf->context < PGC_SIGHUP)
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
+					 errmsg("parameter \"%s\" cannot be changed without restarting the server",
+							gconf->name)));
+			record_config_file_error(psprintf("parameter \"%s\" cannot be changed without restarting the server",
+											  gconf->name),
+									 NULL, 0,
+									 &head, &tail);
+			error = true;
+			continue;
+		}
+
+		/* No more to do if we're just doing show_all_file_settings() */
+		if (!applySettings)
+			continue;
+
+		/*
+		 * Reset any "file" sources to "default", else set_config_option will
+		 * not override those settings.
+		 */
+		if (gconf->reset_source == PGC_S_FILE)
+			gconf->reset_source = PGC_S_DEFAULT;
+		if (gconf->source == PGC_S_FILE)
+			gconf->source = PGC_S_DEFAULT;
+		for (stack = gconf->stack; stack; stack = stack->prev)
+		{
+			if (stack->source == PGC_S_FILE)
+				stack->source = PGC_S_DEFAULT;
+		}
+
+		/* Now we can re-apply the wired-in default (i.e., the boot_val) */
+		if (set_config_option(gconf->name, NULL,
+							  context, PGC_S_DEFAULT,
+							  GUC_ACTION_SET, true, 0, false) > 0)
+		{
+			/* Log the change if appropriate */
+			if (context == PGC_SIGHUP)
+				ereport(elevel,
+						(errmsg("parameter \"%s\" removed from configuration file, reset to default",
+								gconf->name)));
+		}
+	}
+
+	/*
+	 * Restore any variables determined by environment variables or
+	 * dynamically-computed defaults.  This is a no-op except in the case
+	 * where one of these had been in the config file and is now removed.
+	 *
+	 * In particular, we *must not* do this during the postmaster's initial
+	 * loading of the file, since the timezone functions in particular should
+	 * be run only after initialization is complete.
+	 *
+	 * XXX this is an unmaintainable crock, because we have to know how to set
+	 * (or at least what to call to set) every variable that could potentially
+	 * have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. However, there's no
+	 * time to redesign it for 9.1.
+	 */
+	if (context == PGC_SIGHUP && applySettings)
+	{
+		InitializeGUCOptionsFromEnvironment();
+		pg_timezone_abbrev_initialize();
+		/* this selects SQL_ASCII in processes not connected to a database */
+		SetConfigOption("client_encoding", GetDatabaseEncodingName(),
+						PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT);
+	}
+
+	/*
+	 * Now apply the values from the config file.
+	 */
+	for (item = head; item; item = item->next)
+	{
+		char	   *pre_value = NULL;
+		int			scres;
+
+		/* Ignore anything marked as ignorable */
+		if (item->ignore)
+			continue;
+
+		/* In SIGHUP cases in the postmaster, we want to report changes */
+		if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster)
+		{
+			const char *preval = GetConfigOption(item->name, true, false);
+
+			/* If option doesn't exist yet or is NULL, treat as empty string */
+			if (!preval)
+				preval = "";
+			/* must dup, else might have dangling pointer below */
+			pre_value = pstrdup(preval);
+		}
+
+		scres = set_config_option(item->name, item->value,
+								  context, PGC_S_FILE,
+								  GUC_ACTION_SET, applySettings, 0, false);
+		if (scres > 0)
+		{
+			/* variable was updated, so log the change if appropriate */
+			if (pre_value)
+			{
+				const char *post_value = GetConfigOption(item->name, true, false);
+
+				if (!post_value)
+					post_value = "";
+				if (strcmp(pre_value, post_value) != 0)
+					ereport(elevel,
+							(errmsg("parameter \"%s\" changed to \"%s\"",
+									item->name, item->value)));
+			}
+			item->applied = true;
+		}
+		else if (scres == 0)
+		{
+			error = true;
+			item->errmsg = pstrdup("setting could not be applied");
+			ConfFileWithError = item->filename;
+		}
+		else
+		{
+			/* no error, but variable's active value was not changed */
+			item->applied = true;
+		}
+
+		/*
+		 * We should update source location unless there was an error, since
+		 * even if the active value didn't change, the reset value might have.
+		 * (In the postmaster, there won't be a difference, but it does matter
+		 * in backends.)
+		 */
+		if (scres != 0 && applySettings)
+			set_config_sourcefile(item->name, item->filename,
+								  item->sourceline);
+
+		if (pre_value)
+			pfree(pre_value);
+	}
+
+	/* Remember when we last successfully loaded the config file. */
+	if (applySettings)
+		PgReloadTime = GetCurrentTimestamp();
+
+bail_out:
+	if (error && applySettings)
+	{
+		/* During postmaster startup, any error is fatal */
+		if (context == PGC_POSTMASTER)
+			ereport(ERROR,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("configuration file \"%s\" contains errors",
+							ConfFileWithError)));
+		else if (applying)
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("configuration file \"%s\" contains errors; unaffected changes were applied",
+							ConfFileWithError)));
+		else
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("configuration file \"%s\" contains errors; no changes were applied",
+							ConfFileWithError)));
+	}
+
+	/* Successful or otherwise, return the collected data list */
+	return head;
+}
diff --git a/src/bin/pg_rewind/Makefile b/src/bin/pg_rewind/Makefile
index 04f3b8f520..65ebf3c78c 100644
--- a/src/bin/pg_rewind/Makefile
+++ b/src/bin/pg_rewind/Makefile
@@ -15,7 +15,7 @@ subdir = src/bin/pg_rewind
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-override CPPFLAGS := -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)
+override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)
 LDFLAGS_INTERNAL += $(libpq_pgport)
 
 OBJS	= pg_rewind.o parsexlog.o xlogreader.o datapagemap.o timeline.o \
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index e19c265cbb..39cbe78f84 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "pg_rewind.h"
 #include "filemap.h"
@@ -45,6 +46,7 @@ static char xlogfpath[MAXPGPATH];
 typedef struct XLogPageReadPrivate
 {
 	const char *datadir;
+	const char *restoreCommand;
 	int			tliIndex;
 } XLogPageReadPrivate;
 
@@ -53,6 +55,9 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
 				   int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
 				   TimeLineID *pageTLI);
 
+static bool RestoreArchivedWAL(const char *path, const char *xlogfname,
+					off_t expectedSize, const char *restoreCommand);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -60,15 +65,17 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
  */
 void
 extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
-			   XLogRecPtr endpoint)
+			   ControlFileData *targetCF, const char *restore_command)
 {
 	XLogRecord *record;
+	XLogRecPtr endpoint = targetCF->checkPoint;
 	XLogReaderState *xlogreader;
 	char	   *errormsg;
 	XLogPageReadPrivate private;
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restore_command;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -156,7 +163,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;
@@ -181,6 +188,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -291,9 +299,46 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
 
 		if (xlogreadfd < 0)
 		{
-			printf(_("could not open file \"%s\": %s\n"), xlogfpath,
-				   strerror(errno));
-			return -1;
+			bool  restore_ok;
+
+			/*
+			 * If we have no restore_command to execute, then exit.
+			 */
+			if (private->restoreCommand == NULL)
+			{
+				printf(_("could not open file \"%s\": %s\n"), xlogfpath,
+					   strerror(errno));
+				return -1;
+			}
+
+			/*
+			 * Since we have restore_command to execute, then try to retreive
+			 * missing WAL file from the archive.
+			 */
+			restore_ok = RestoreArchivedWAL(private->datadir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand);
+
+			if (restore_ok)
+			{
+				xlogreadfd = open(xlogfpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogreadfd < 0)
+				{
+					printf(_("could not open restored from archive file \"%s\": %s\n"), xlogfpath,
+							strerror(errno));
+					return -1;
+				}
+				else
+					pg_log(PG_DEBUG, "using restored from archive version of file \"%s\"\n", xlogfpath);
+			}
+			else
+			{
+				printf(_("could not restore file \"%s\" from archive: %s\n"), xlogfname,
+					   strerror(errno));
+				return -1;
+			}
 		}
 	}
 
@@ -409,3 +454,114 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return true.
+ *
+ * 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.
+ *
+ * This is a simplified and adapted to frontend version of
+ * RestoreArchivedFile function from transam/xlogarchive.c
+ */
+bool
+RestoreArchivedWAL(const char *path, const char *xlogfname, off_t expectedSize,
+				   const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH];
+	char		xlogRestoreCmd[MAXPGPATH];
+	char	   *dp;
+	char	   *endp;
+	const char *sp;
+	int			rc;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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 */
+					pg_fatal("restore_command with %%r cannot be used during rewind process.\n");
+					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';
+
+	/*
+	 * 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)
+			{
+				printf(_("archive file \"%s\" has wrong size: %lu instead of %lu, %s"),
+						xlogfname, (unsigned long) stat_buf.st_size, (unsigned long) expectedSize,
+						strerror(errno));
+			}
+			else
+				return true;
+		}
+		else
+		{
+			/* Stat failed */
+			printf(_("could not stat file \"%s\": %s"),
+					xlogpath,
+					strerror(errno));
+		}
+	}
+
+	return false;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 7ccde5c87f..a89fdde7cc 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -26,6 +26,7 @@
 #include "catalog/pg_control.h"
 #include "common/file_perm.h"
 #include "common/file_utils.h"
+#include "common/guc-file.h"
 #include "common/restricted_token.h"
 #include "getopt_long.h"
 #include "storage/bufpage.h"
@@ -52,11 +53,13 @@ int			WalSegSz;
 char	   *datadir_target = NULL;
 char	   *datadir_source = NULL;
 char	   *connstr_source = NULL;
+char	   *restore_command = NULL;
 
 bool		debug = false;
 bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
+bool		restore_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -75,6 +78,9 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"));
 	printf(_("                                 safely to disk\n"));
 	printf(_("  -P, --progress                 write progress messages\n"));
+	printf(_("  -r, --use-postgresql-conf      use restore_command in the postgresql.conf to\n"));
+	printf(_("                                 retreive WALs from archive\n"));
+	printf(_("  -R, --restore-command=COMMAND  restore_command\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
@@ -94,9 +100,12 @@ main(int argc, char **argv)
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
+		{"use-postgresql-conf", no_argument, NULL, 'r'},
+		{"restore-command", required_argument, NULL, 'R'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
+	char		recfile_fullpath[MAXPGPATH];
 	int			option_index;
 	int			c;
 	XLogRecPtr	divergerec;
@@ -129,7 +138,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "D:nNPR:r", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -141,6 +150,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'r':
+				restore_wals = true;
+				break;
+
 			case 'n':
 				dry_run = true;
 				break;
@@ -157,6 +170,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'R':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -223,6 +240,80 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	if (restore_command != NULL)
+	{
+		if (restore_wals)
+		{
+			fprintf(stderr, _("%s: conflicting options: both -r and -R are specified\n"),
+				progname);
+			fprintf(stderr, _("You must run %s with either -r/--use-postgresql-conf or -R/--restore-command.\n"),
+				progname);
+			exit(1);
+		}
+
+		pg_log(PG_DEBUG, "using command line restore_command=\'%s\'.\n", restore_command);
+	}
+	else if (restore_wals)
+	{
+		FILE	*conf_file;
+
+		/*
+		 * Look for configuration file in the target data directory and
+		 * try to get restore_command from there.
+		 */
+		snprintf(recfile_fullpath, sizeof(recfile_fullpath), "%s/%s", datadir_target, RESTORE_COMMAND_FILE);
+		conf_file = fopen(recfile_fullpath, "r");
+
+		if (conf_file == NULL)
+		{
+			fprintf(stderr, _("%s: option -r/--use-postgresql-conf is specified, but postgreslq.conf is absent in the target directory\n"),
+					progname);
+			fprintf(stderr, _("You have to add postgresql.conf or pass restore_command with -R/--restore-command option.\n"));
+			exit(1);
+		}
+		else
+		{
+			ConfigVariable *item,
+						   *head = NULL,
+						   *tail = NULL;
+			bool			config_is_parsed;
+
+			/*
+			 * We pass a fullpath to the configuration file as calling_file here, since
+			 * parser will use its parent directory as base for all further includes
+			 * if any exist.
+			 */
+			config_is_parsed = ParseConfigFile(RESTORE_COMMAND_FILE, true,
+											   recfile_fullpath, 0, 0,
+											   PG_WARNING, &head, &tail);
+			fclose(conf_file);
+
+			if (config_is_parsed)
+			{
+				for (item = head; item; item = item->next)
+				{
+					if (strcmp(item->name, "restore_command") == 0)
+					{
+						if (restore_command != NULL)
+						{
+							pfree(restore_command);
+							restore_command = NULL;
+						}
+						restore_command = pstrdup(item->value);
+						pg_log(PG_DEBUG, "using restore_command=\'%s\' from %s.\n", restore_command, RESTORE_COMMAND_FILE);
+					}
+				}
+
+				if (restore_command == NULL)
+					pg_fatal("could not find restore_command in %s file %s\n", RESTORE_COMMAND_FILE, recfile_fullpath);
+			}
+			else
+				pg_fatal("could not parse %s file %s\n", RESTORE_COMMAND_FILE, recfile_fullpath);
+
+			FreeConfigVariables(head);
+		}
+	}
+
 	/* Connect to remote server */
 	if (connstr_source)
 		libpqConnect(connstr_source);
@@ -294,9 +385,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);
 	printf(_("rewinding from last common checkpoint at %X/%X on timeline %u\n"),
 		   (uint32) (chkptrec >> 32), (uint32) chkptrec,
 		   chkpttli);
@@ -319,7 +409,7 @@ main(int argc, char **argv)
 	 */
 	pg_log(PG_PROGRESS, "reading WAL in target\n");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
-				   ControlFile_target.checkPoint);
+				   &ControlFile_target, 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 83b2898b8b..0df8637e6d 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -14,9 +14,12 @@
 #include "datapagemap.h"
 
 #include "access/timeline.h"
+#include "catalog/pg_control.h"
 #include "storage/block.h"
 #include "storage/relfilenode.h"
 
+#define RESTORE_COMMAND_FILE "postgresql.conf"
+
 /* Configuration options */
 extern char *datadir_target;
 extern char *datadir_source;
@@ -32,11 +35,10 @@ extern int	targetNentries;
 
 /* in parsexlog.c */
 extern void extractPageMap(const char *datadir, XLogRecPtr startpoint,
-			   int tliIndex, XLogRecPtr endpoint);
+			   int tliIndex, ControlFileData *targetCF, const char *restoreCommand);
 extern void findLastCheckpoint(const char *datadir, XLogRecPtr searchptr,
-				   int tliIndex,
-				   XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
-				   XLogRecPtr *lastchkptredo);
+				   int tliIndex, XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
+				   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 115192170e..8a6fa33016 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 => 10;
+use Test::More tests => 20;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -106,5 +106,7 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 6dc05720a1..99ea821cfe 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 12;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -62,5 +62,7 @@ template1
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index c4040bd562..24cec256de 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 8;
 
 use File::Find;
 
@@ -90,5 +90,7 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 85cae7e47b..79bd6434b4 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -39,7 +39,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catpath catfile);
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -249,6 +251,95 @@ sub run_pg_rewind
 			],
 			'pg_rewind remote');
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $wals_archive_dir = catpath(${TestLib::tmp_check}, 'master_wals_archive');
+		my @wal_files = bsd_glob catpath($master_pgdata, 'pg_wal', '0000000*');
+		my $restore_command;
+
+		remove_tree($wals_archive_dir);
+		make_path($wals_archive_dir) or die;
+
+		# Move all old master WAL files to the archive.
+		# Old master should be stopped at this point.
+		foreach my $wal_file (@wal_files)
+		{
+			move($wal_file, "$wals_archive_dir") or die;
+		}
+
+		if ($windows_os)
+		{
+			$restore_command = "copy $wals_archive_dir\\\%f \%p";
+		}
+		else
+		{
+			$restore_command = "cp $wals_archive_dir/\%f \%p";
+		}
+
+		# 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",
+				"--restore-command=$restore_command"
+			],
+			'pg_rewind archive');
+	}
+	elsif ($test_mode eq "archive_conf")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $wals_archive_dir = catpath(${TestLib::tmp_check}, 'master_wals_archive');
+		my @wal_files = bsd_glob catpath($master_pgdata, 'pg_wal', '0000000*');
+		my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');
+		my $restore_command;
+
+		remove_tree($wals_archive_dir);
+		make_path($wals_archive_dir) or die;
+
+		# Move all old master WAL files to the archive.
+		# Old master should be stopped at this point.
+		foreach my $wal_file (@wal_files)
+		{
+			move($wal_file, "$wals_archive_dir") or die;
+		}
+
+		if ($windows_os)
+		{
+			$restore_command = "copy $wals_archive_dir\\\%f \%p";
+		}
+		else
+		{
+			$restore_command = "cp $wals_archive_dir/\%f \%p";
+		}
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Add restore_command to postgresql.conf of target cluster.
+		open(my $conf_fd, ">>", $master_conf_path) or die;
+		print $conf_fd "\nrestore_command='$restore_command'";
+		close $conf_fd;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"-r"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 
diff --git a/src/common/.gitignore b/src/common/.gitignore
index ffa3284fbf..424beacbc9 100644
--- a/src/common/.gitignore
+++ b/src/common/.gitignore
@@ -1 +1,2 @@
 /kwlist_d.h
+/guc-file.c
diff --git a/src/common/Makefile b/src/common/Makefile
index d0c2b970eb..3d7da47f56 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -48,7 +48,7 @@ OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o file_perm.o \
 	ip.o keywords.o kwlookup.o link-canary.o md5.o pg_lzcompress.o \
 	pgfnames.o psprintf.o relpath.o \
 	rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
-	username.o wait_error.o
+	username.o wait_error.o guc-file.o
 
 ifeq ($(with_openssl),yes)
 OBJS_COMMON += sha2_openssl.o
@@ -70,7 +70,7 @@ GEN_KEYWORDLIST_DEPS = $(TOOLSDIR)/gen_keywordlist.pl $(TOOLSDIR)/PerfectHash.pm
 
 all: libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
 
-distprep: kwlist_d.h
+distprep: kwlist_d.h guc-file.c
 
 # libpgcommon is needed by some contrib
 install: all installdirs
@@ -130,10 +130,13 @@ kwlist_d.h: $(top_srcdir)/src/include/parser/kwlist.h $(GEN_KEYWORDLIST_DEPS)
 # that you don't get broken parsing code, even in a non-enable-depend build.
 keywords.o keywords_shlib.o keywords_srv.o: kwlist_d.h
 
-# kwlist_d.h is in the distribution tarball, so it is not cleaned here.
+# Note: guc-file.c and kwlist_d.h are not deleted by 'make clean',
+# since we want to ship them in distribution tarballs.
 clean distclean:
 	rm -f libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
 	rm -f $(OBJS_FRONTEND) $(OBJS_SHLIB) $(OBJS_SRV)
+	@rm -f lex.yy.c
 
 maintainer-clean: distclean
 	rm -f kwlist_d.h
+	rm -f guc-file.c
diff --git a/src/backend/utils/misc/guc-file.l b/src/common/guc-file.l
similarity index 60%
rename from src/backend/utils/misc/guc-file.l
rename to src/common/guc-file.l
index 1c8b5f7d84..175685c9b5 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/common/guc-file.l
@@ -4,21 +4,31 @@
  *
  * Copyright (c) 2000-2019, PostgreSQL Global Development Group
  *
- * src/backend/utils/misc/guc-file.l
+ * src/common/guc-file.l
  */
 
 %{
 
+#ifndef FRONTEND
 #include "postgres.h"
 
-#include <ctype.h>
-#include <unistd.h>
-
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
 #include "utils/guc.h"
+#include "utils/elog.h"
+#else
+#include "postgres_fe.h"
+
+#include <dirent.h>
+#include <setjmp.h>
+#endif
+
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/stat.h>
 
+#include "common/guc-file.h"
 
 /*
  * flex emits a yy_fatal_error() function that it calls in response to
@@ -48,12 +58,6 @@ static sigjmp_buf *GUC_flex_fatal_jmp;
 
 static void FreeConfigVariable(ConfigVariable *item);
 
-static void record_config_file_error(const char *errmsg,
-						 const char *config_file,
-						 int lineno,
-						 ConfigVariable **head_p,
-						 ConfigVariable **tail_p);
-
 static int	GUC_flex_fatal(const char *msg);
 static char *GUC_scanstr(const char *s);
 
@@ -111,404 +115,35 @@ STRING			\'([^'\\\n]|\\.|\'\')*\'
 
 /* LCOV_EXCL_STOP */
 
-/*
- * Exported function to read and process the configuration file. The
- * parameter indicates in what context the file is being read --- either
- * postmaster startup (including standalone-backend startup) or SIGHUP.
- * All options mentioned in the configuration file are set to new values.
- * If a hard error occurs, no values will be changed.  (There can also be
- * errors that prevent just one value from being changed.)
- */
-void
-ProcessConfigFile(GucContext context)
-{
-	int			elevel;
-	MemoryContext config_cxt;
-	MemoryContext caller_cxt;
-
-	/*
-	 * Config files are processed on startup (by the postmaster only) and on
-	 * SIGHUP (by the postmaster and its children)
-	 */
-	Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) ||
-		   context == PGC_SIGHUP);
-
-	/*
-	 * To avoid cluttering the log, only the postmaster bleats loudly about
-	 * problems with the config file.
-	 */
-	elevel = IsUnderPostmaster ? DEBUG2 : LOG;
-
-	/*
-	 * This function is usually called within a process-lifespan memory
-	 * context.  To ensure that any memory leaked during GUC processing does
-	 * not accumulate across repeated SIGHUP cycles, do the work in a private
-	 * context that we can free at exit.
-	 */
-	config_cxt = AllocSetContextCreate(CurrentMemoryContext,
-									   "config file processing",
-									   ALLOCSET_DEFAULT_SIZES);
-	caller_cxt = MemoryContextSwitchTo(config_cxt);
-
-	/*
-	 * Read and apply the config file.  We don't need to examine the result.
-	 */
-	(void) ProcessConfigFileInternal(context, true, elevel);
-
-	/* Clean up */
-	MemoryContextSwitchTo(caller_cxt);
-	MemoryContextDelete(config_cxt);
-}
 
 /*
- * This function handles both actual config file (re)loads and execution of
- * show_all_file_settings() (i.e., the pg_file_settings view).  In the latter
- * case we don't apply any of the settings, but we make all the usual validity
- * checks, and we return the ConfigVariable list so that it can be printed out
- * by show_all_file_settings().
+ * The bare comparison function for GUC names.
  */
-static ConfigVariable *
-ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
+int
+guc_name_compare(const char *namea, const char *nameb)
 {
-	bool		error = false;
-	bool		applying = false;
-	const char *ConfFileWithError;
-	ConfigVariable *item,
-			   *head,
-			   *tail;
-	int			i;
-
-	/* Parse the main config file into a list of option names and values */
-	ConfFileWithError = ConfigFileName;
-	head = tail = NULL;
-
-	if (!ParseConfigFile(ConfigFileName, true,
-						 NULL, 0, 0, elevel,
-						 &head, &tail))
-	{
-		/* Syntax error(s) detected in the file, so bail out */
-		error = true;
-		goto bail_out;
-	}
-
-	/*
-	 * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to
-	 * replace any parameters set by ALTER SYSTEM command.  Because this file
-	 * is in the data directory, we can't read it until the DataDir has been
-	 * set.
-	 */
-	if (DataDir)
-	{
-		if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false,
-							 NULL, 0, 0, elevel,
-							 &head, &tail))
-		{
-			/* Syntax error(s) detected in the file, so bail out */
-			error = true;
-			ConfFileWithError = PG_AUTOCONF_FILENAME;
-			goto bail_out;
-		}
-	}
-	else
-	{
-		/*
-		 * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be
-		 * read.  In this case, we don't want to accept any settings but
-		 * data_directory from postgresql.conf, because they might be
-		 * overwritten with settings in the PG_AUTOCONF_FILENAME file which
-		 * will be read later. OTOH, since data_directory isn't allowed in the
-		 * PG_AUTOCONF_FILENAME file, it will never be overwritten later.
-		 */
-		ConfigVariable *newlist = NULL;
-
-		/*
-		 * Prune all items except the last "data_directory" from the list.
-		 */
-		for (item = head; item; item = item->next)
-		{
-			if (!item->ignore &&
-				strcmp(item->name, "data_directory") == 0)
-				newlist = item;
-		}
-
-		if (newlist)
-			newlist->next = NULL;
-		head = tail = newlist;
-
-		/*
-		 * Quick exit if data_directory is not present in file.
-		 *
-		 * We need not do any further processing, in particular we don't set
-		 * PgReloadTime; that will be set soon by subsequent full loading of
-		 * the config file.
-		 */
-		if (head == NULL)
-			goto bail_out;
-	}
-
 	/*
-	 * Mark all extant GUC variables as not present in the config file. We
-	 * need this so that we can tell below which ones have been removed from
-	 * the file since we last processed it.
+	 * The temptation to use strcasecmp() here must be resisted, because the
+	 * array ordering has to remain stable across setlocale() calls. So, build
+	 * our own with a simple ASCII-only downcasing.
 	 */
-	for (i = 0; i < num_guc_variables; i++)
+	while (*namea && *nameb)
 	{
-		struct config_generic *gconf = guc_variables[i];
-
-		gconf->status &= ~GUC_IS_IN_FILE;
-	}
-
-	/*
-	 * Check if all the supplied option names are valid, as an additional
-	 * quasi-syntactic check on the validity of the config file.  It is
-	 * important that the postmaster and all backends agree on the results of
-	 * this phase, else we will have strange inconsistencies about which
-	 * processes accept a config file update and which don't.  Hence, unknown
-	 * custom variable names have to be accepted without complaint.  For the
-	 * same reason, we don't attempt to validate the options' values here.
-	 *
-	 * In addition, the GUC_IS_IN_FILE flag is set on each existing GUC
-	 * variable mentioned in the file; and we detect duplicate entries in the
-	 * file and mark the earlier occurrences as ignorable.
-	 */
-	for (item = head; item; item = item->next)
-	{
-		struct config_generic *record;
-
-		/* Ignore anything already marked as ignorable */
-		if (item->ignore)
-			continue;
-
-		/*
-		 * Try to find the variable; but do not create a custom placeholder if
-		 * it's not there already.
-		 */
-		record = find_option(item->name, false, elevel);
-
-		if (record)
-		{
-			/* If it's already marked, then this is a duplicate entry */
-			if (record->status & GUC_IS_IN_FILE)
-			{
-				/*
-				 * Mark the earlier occurrence(s) as dead/ignorable.  We could
-				 * avoid the O(N^2) behavior here with some additional state,
-				 * but it seems unlikely to be worth the trouble.
-				 */
-				ConfigVariable *pitem;
-
-				for (pitem = head; pitem != item; pitem = pitem->next)
-				{
-					if (!pitem->ignore &&
-						strcmp(pitem->name, item->name) == 0)
-						pitem->ignore = true;
-				}
-			}
-			/* Now mark it as present in file */
-			record->status |= GUC_IS_IN_FILE;
-		}
-		else if (strchr(item->name, GUC_QUALIFIER_SEPARATOR) == NULL)
-		{
-			/* Invalid non-custom variable, so complain */
-			ereport(elevel,
-					(errcode(ERRCODE_UNDEFINED_OBJECT),
-					 errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %u",
-							item->name,
-							item->filename, item->sourceline)));
-			item->errmsg = pstrdup("unrecognized configuration parameter");
-			error = true;
-			ConfFileWithError = item->filename;
-		}
+		char		cha = *namea++;
+		char		chb = *nameb++;
+
+		if (cha >= 'A' && cha <= 'Z')
+			cha += 'a' - 'A';
+		if (chb >= 'A' && chb <= 'Z')
+			chb += 'a' - 'A';
+		if (cha != chb)
+			return cha - chb;
 	}
-
-	/*
-	 * If we've detected any errors so far, we don't want to risk applying any
-	 * changes.
-	 */
-	if (error)
-		goto bail_out;
-
-	/* Otherwise, set flag that we're beginning to apply changes */
-	applying = true;
-
-	/*
-	 * Check for variables having been removed from the config file, and
-	 * revert their reset values (and perhaps also effective values) to the
-	 * boot-time defaults.  If such a variable can't be changed after startup,
-	 * report that and continue.
-	 */
-	for (i = 0; i < num_guc_variables; i++)
-	{
-		struct config_generic *gconf = guc_variables[i];
-		GucStack   *stack;
-
-		if (gconf->reset_source != PGC_S_FILE ||
-			(gconf->status & GUC_IS_IN_FILE))
-			continue;
-		if (gconf->context < PGC_SIGHUP)
-		{
-			ereport(elevel,
-					(errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
-					 errmsg("parameter \"%s\" cannot be changed without restarting the server",
-							gconf->name)));
-			record_config_file_error(psprintf("parameter \"%s\" cannot be changed without restarting the server",
-											  gconf->name),
-									 NULL, 0,
-									 &head, &tail);
-			error = true;
-			continue;
-		}
-
-		/* No more to do if we're just doing show_all_file_settings() */
-		if (!applySettings)
-			continue;
-
-		/*
-		 * Reset any "file" sources to "default", else set_config_option will
-		 * not override those settings.
-		 */
-		if (gconf->reset_source == PGC_S_FILE)
-			gconf->reset_source = PGC_S_DEFAULT;
-		if (gconf->source == PGC_S_FILE)
-			gconf->source = PGC_S_DEFAULT;
-		for (stack = gconf->stack; stack; stack = stack->prev)
-		{
-			if (stack->source == PGC_S_FILE)
-				stack->source = PGC_S_DEFAULT;
-		}
-
-		/* Now we can re-apply the wired-in default (i.e., the boot_val) */
-		if (set_config_option(gconf->name, NULL,
-							  context, PGC_S_DEFAULT,
-							  GUC_ACTION_SET, true, 0, false) > 0)
-		{
-			/* Log the change if appropriate */
-			if (context == PGC_SIGHUP)
-				ereport(elevel,
-						(errmsg("parameter \"%s\" removed from configuration file, reset to default",
-								gconf->name)));
-		}
-	}
-
-	/*
-	 * Restore any variables determined by environment variables or
-	 * dynamically-computed defaults.  This is a no-op except in the case
-	 * where one of these had been in the config file and is now removed.
-	 *
-	 * In particular, we *must not* do this during the postmaster's initial
-	 * loading of the file, since the timezone functions in particular should
-	 * be run only after initialization is complete.
-	 *
-	 * XXX this is an unmaintainable crock, because we have to know how to set
-	 * (or at least what to call to set) every variable that could potentially
-	 * have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. However, there's no
-	 * time to redesign it for 9.1.
-	 */
-	if (context == PGC_SIGHUP && applySettings)
-	{
-		InitializeGUCOptionsFromEnvironment();
-		pg_timezone_abbrev_initialize();
-		/* this selects SQL_ASCII in processes not connected to a database */
-		SetConfigOption("client_encoding", GetDatabaseEncodingName(),
-						PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT);
-	}
-
-	/*
-	 * Now apply the values from the config file.
-	 */
-	for (item = head; item; item = item->next)
-	{
-		char	   *pre_value = NULL;
-		int			scres;
-
-		/* Ignore anything marked as ignorable */
-		if (item->ignore)
-			continue;
-
-		/* In SIGHUP cases in the postmaster, we want to report changes */
-		if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster)
-		{
-			const char *preval = GetConfigOption(item->name, true, false);
-
-			/* If option doesn't exist yet or is NULL, treat as empty string */
-			if (!preval)
-				preval = "";
-			/* must dup, else might have dangling pointer below */
-			pre_value = pstrdup(preval);
-		}
-
-		scres = set_config_option(item->name, item->value,
-								  context, PGC_S_FILE,
-								  GUC_ACTION_SET, applySettings, 0, false);
-		if (scres > 0)
-		{
-			/* variable was updated, so log the change if appropriate */
-			if (pre_value)
-			{
-				const char *post_value = GetConfigOption(item->name, true, false);
-
-				if (!post_value)
-					post_value = "";
-				if (strcmp(pre_value, post_value) != 0)
-					ereport(elevel,
-							(errmsg("parameter \"%s\" changed to \"%s\"",
-									item->name, item->value)));
-			}
-			item->applied = true;
-		}
-		else if (scres == 0)
-		{
-			error = true;
-			item->errmsg = pstrdup("setting could not be applied");
-			ConfFileWithError = item->filename;
-		}
-		else
-		{
-			/* no error, but variable's active value was not changed */
-			item->applied = true;
-		}
-
-		/*
-		 * We should update source location unless there was an error, since
-		 * even if the active value didn't change, the reset value might have.
-		 * (In the postmaster, there won't be a difference, but it does matter
-		 * in backends.)
-		 */
-		if (scres != 0 && applySettings)
-			set_config_sourcefile(item->name, item->filename,
-								  item->sourceline);
-
-		if (pre_value)
-			pfree(pre_value);
-	}
-
-	/* Remember when we last successfully loaded the config file. */
-	if (applySettings)
-		PgReloadTime = GetCurrentTimestamp();
-
-bail_out:
-	if (error && applySettings)
-	{
-		/* During postmaster startup, any error is fatal */
-		if (context == PGC_POSTMASTER)
-			ereport(ERROR,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("configuration file \"%s\" contains errors",
-							ConfFileWithError)));
-		else if (applying)
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("configuration file \"%s\" contains errors; unaffected changes were applied",
-							ConfFileWithError)));
-		else
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("configuration file \"%s\" contains errors; no changes were applied",
-							ConfFileWithError)));
-	}
-
-	/* Successful or otherwise, return the collected data list */
-	return head;
+	if (*namea)
+		return 1;				/* a is longer */
+	if (*nameb)
+		return -1;				/* b is longer */
+	return 0;
 }
 
 /*
@@ -525,6 +160,7 @@ AbsoluteConfigLocation(const char *location, const char *calling_file)
 		return pstrdup(location);
 	else
 	{
+		#ifndef FRONTEND
 		if (calling_file != NULL)
 		{
 			strlcpy(abs_path, calling_file, sizeof(abs_path));
@@ -538,6 +174,12 @@ AbsoluteConfigLocation(const char *location, const char *calling_file)
 			join_path_components(abs_path, DataDir, location);
 			canonicalize_path(abs_path);
 		}
+		#else
+		strlcpy(abs_path, calling_file, sizeof(abs_path));
+		get_parent_directory(abs_path);
+		join_path_components(abs_path, abs_path, location);
+		canonicalize_path(abs_path);
+		#endif
 		return pstrdup(abs_path);
 	}
 }
@@ -574,10 +216,15 @@ ParseConfigFile(const char *config_file, bool strict,
 	 */
 	if (depth > 10)
 	{
+		#ifndef FRONTEND
 		ereport(elevel,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("could not open configuration file \"%s\": maximum nesting depth exceeded",
 						config_file)));
+		#else
+		printf(_("could not open configuration file \"%s\": maximum nesting depth exceeded\n"),
+				config_file);
+		#endif
 		record_config_file_error("nesting depth exceeded",
 								 calling_file, calling_lineno,
 								 head_p, tail_p);
@@ -585,15 +232,23 @@ ParseConfigFile(const char *config_file, bool strict,
 	}
 
 	abs_path = AbsoluteConfigLocation(config_file, calling_file);
+	#ifndef FRONTEND
 	fp = AllocateFile(abs_path, "r");
+	#else
+	fp = fopen(abs_path, "r");
+	#endif
 	if (!fp)
 	{
 		if (strict)
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode_for_file_access(),
 					 errmsg("could not open configuration file \"%s\": %m",
 							abs_path)));
+			#else
+			printf(_("could not open configuration file \"%s\"\n"), abs_path);
+			#endif
 			record_config_file_error(psprintf("could not open file \"%s\"",
 											  abs_path),
 									 calling_file, calling_lineno,
@@ -602,9 +257,13 @@ ParseConfigFile(const char *config_file, bool strict,
 		}
 		else
 		{
+			#ifndef FRONTEND
 			ereport(LOG,
 					(errmsg("skipping missing configuration file \"%s\"",
 							abs_path)));
+			#else
+			printf(_("skipping missing configuration file \"%s\"\n"), abs_path);
+			#endif
 		}
 		goto cleanup;
 	}
@@ -613,7 +272,14 @@ ParseConfigFile(const char *config_file, bool strict,
 
 cleanup:
 	if (fp)
+	{
+		#ifndef FRONTEND
 		FreeFile(fp);
+		#else
+		fclose(fp);
+		#endif
+	}
+
 	pfree(abs_path);
 
 	return OK;
@@ -623,7 +289,7 @@ cleanup:
  * Capture an error message in the ConfigVariable list returned by
  * config file parsing.
  */
-static void
+void
 record_config_file_error(const char *errmsg,
 						 const char *config_file,
 						 int lineno,
@@ -715,8 +381,13 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
 		 * corrupted parser state.  Consequently, abandon the file, but trust
 		 * that the state remains sane enough for yy_delete_buffer().
 		 */
+		#ifndef FRONTEND
 		elog(elevel, "%s at file \"%s\" line %u",
 			 GUC_flex_fatal_errmsg, config_file, ConfigFileLineno);
+		#else
+		printf(_("%s at file \"%s\" line %u\n"),
+				GUC_flex_fatal_errmsg, config_file, ConfigFileLineno);
+		#endif
 		record_config_file_error(GUC_flex_fatal_errmsg,
 								 config_file, ConfigFileLineno,
 								 head_p, tail_p);
@@ -855,20 +526,30 @@ parse_error:
 		/* report the error */
 		if (token == GUC_EOL || token == 0)
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 			  errmsg("syntax error in file \"%s\" line %u, near end of line",
 					 config_file, ConfigFileLineno - 1)));
+			#else
+			printf(_("syntax error in file \"%s\" line %u, near end of line\n"),
+											config_file, ConfigFileLineno - 1);
+			#endif
 			record_config_file_error("syntax error",
 									 config_file, ConfigFileLineno - 1,
 									 head_p, tail_p);
 		}
 		else
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 			 errmsg("syntax error in file \"%s\" line %u, near token \"%s\"",
 					config_file, ConfigFileLineno, yytext)));
+			#else
+			printf(_("syntax error in file \"%s\" line %u, near token \"%s\"\n"),
+										config_file, ConfigFileLineno, yytext);
+			#endif
 			record_config_file_error("syntax error",
 									 config_file, ConfigFileLineno,
 									 head_p, tail_p);
@@ -883,14 +564,23 @@ parse_error:
 		 * as well give up immediately.  (This prevents postmaster children
 		 * from bloating the logs with duplicate complaints.)
 		 */
+		#ifndef FRONTEND
 		if (errorcount >= 100 || elevel <= DEBUG1)
 		{
 			ereport(elevel,
 					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-			   errmsg("too many syntax errors found, abandoning file \"%s\"",
-					  config_file)));
+					 errmsg("too many syntax errors found, abandoning file \"%s\"",
+							config_file)));
 			break;
 		}
+		#else
+		if (errorcount >= 100)
+		{
+			printf(_("too many syntax errors found, abandoning file \"%s\"\n"),
+					config_file);
+			break;
+		}
+		#endif
 
 		/* resync to next end-of-line or EOF */
 		while (token != GUC_EOL && token != 0)
@@ -934,13 +624,21 @@ ParseConfigDirectory(const char *includedir,
 	bool		status;
 
 	directory = AbsoluteConfigLocation(includedir, calling_file);
+	#ifndef FRONTEND
 	d = AllocateDir(directory);
+	#else
+	d = opendir(directory);
+	#endif
 	if (d == NULL)
 	{
+		#ifndef FRONTEND
 		ereport(elevel,
 				(errcode_for_file_access(),
 				 errmsg("could not open configuration directory \"%s\": %m",
 						directory)));
+		#else
+		printf(_("could not open configuration directory \"%s\"\n"), directory);
+		#endif
 		record_config_file_error(psprintf("could not open directory \"%s\"",
 										  directory),
 								 calling_file, calling_lineno,
@@ -957,7 +655,11 @@ ParseConfigDirectory(const char *includedir,
 	filenames = (char **) palloc(size_filenames * sizeof(char *));
 	num_filenames = 0;
 
+	#ifndef FRONTEND
 	while ((de = ReadDir(d, directory)) != NULL)
+	#else
+	while ((de = readdir(d)) != NULL)
+	#endif
 	{
 		struct stat st;
 		char		filename[MAXPGPATH];
@@ -998,10 +700,14 @@ ParseConfigDirectory(const char *includedir,
 			 * a file can't be accessed now is if it was removed between the
 			 * directory listing and now.
 			 */
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode_for_file_access(),
 					 errmsg("could not stat file \"%s\": %m",
 							filename)));
+			#else
+			printf(_("could not stat file \"%s\"\n"), filename);
+			#endif
 			record_config_file_error(psprintf("could not stat file \"%s\"",
 											  filename),
 									 calling_file, calling_lineno,
@@ -1032,7 +738,13 @@ ParseConfigDirectory(const char *includedir,
 
 cleanup:
 	if (d)
+	{
+		#ifndef FRONTEND
 		FreeDir(d);
+		#else
+		closedir(d);
+		#endif
+	}
 	pfree(directory);
 	return status;
 }
diff --git a/src/include/common/guc-file.h b/src/include/common/guc-file.h
new file mode 100644
index 0000000000..ca969e2aed
--- /dev/null
+++ b/src/include/common/guc-file.h
@@ -0,0 +1,50 @@
+#ifndef GUC_FILE_H
+#define GUC_FILE_H
+
+#include "c.h"
+
+/*
+ * Parsing the configuration file(s) will return a list of name-value pairs
+ * with source location info.  We also abuse this data structure to carry
+ * error reports about the config files.  An entry reporting an error will
+ * have errmsg != NULL, and might have NULLs for name, value, and/or filename.
+ *
+ * If "ignore" is true, don't attempt to apply the item (it might be an error
+ * report, or an item we determined to be duplicate).  "applied" is set true
+ * if we successfully applied, or could have applied, the setting.
+ */
+typedef struct ConfigVariable
+{
+	char	   *name;
+	char	   *value;
+	char	   *errmsg;
+	char	   *filename;
+	int			sourceline;
+	bool		ignore;
+	bool		applied;
+	struct ConfigVariable *next;
+} ConfigVariable;
+
+extern bool ParseConfigFile(const char *config_file, bool strict,
+				const char *calling_file, int calling_lineno,
+				int depth, int elevel,
+				ConfigVariable **head_p, ConfigVariable **tail_p);
+extern bool ParseConfigFp(FILE *fp, const char *config_file,
+			  int depth, int elevel,
+			  ConfigVariable **head_p, ConfigVariable **tail_p);
+extern bool ParseConfigDirectory(const char *includedir,
+					 const char *calling_file, int calling_lineno,
+					 int depth, int elevel,
+					 ConfigVariable **head_p,
+					 ConfigVariable **tail_p);
+extern void FreeConfigVariables(ConfigVariable *list);
+
+extern int	guc_name_compare(const char *namea, const char *nameb);
+
+extern void record_config_file_error(const char *errmsg,
+						 const char *config_file,
+						 int lineno,
+						 ConfigVariable **head_p,
+						 ConfigVariable **tail_p);
+
+#endif							/* GUC_FILE_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index c07e7b945e..0b3391004c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -1,8 +1,7 @@
 /*--------------------------------------------------------------------
  * guc.h
  *
- * External declarations pertaining to backend/utils/misc/guc.c and
- * backend/utils/misc/guc-file.l
+ * External declarations pertaining to backend/utils/misc/guc.c
  *
  * Copyright (c) 2000-2019, PostgreSQL Global Development Group
  * Written by Peter Eisentraut <peter_e@gmx.net>.
@@ -120,42 +119,6 @@ typedef enum
 	PGC_S_SESSION				/* SET command */
 } GucSource;
 
-/*
- * Parsing the configuration file(s) will return a list of name-value pairs
- * with source location info.  We also abuse this data structure to carry
- * error reports about the config files.  An entry reporting an error will
- * have errmsg != NULL, and might have NULLs for name, value, and/or filename.
- *
- * If "ignore" is true, don't attempt to apply the item (it might be an error
- * report, or an item we determined to be duplicate).  "applied" is set true
- * if we successfully applied, or could have applied, the setting.
- */
-typedef struct ConfigVariable
-{
-	char	   *name;
-	char	   *value;
-	char	   *errmsg;
-	char	   *filename;
-	int			sourceline;
-	bool		ignore;
-	bool		applied;
-	struct ConfigVariable *next;
-} ConfigVariable;
-
-extern bool ParseConfigFile(const char *config_file, bool strict,
-				const char *calling_file, int calling_lineno,
-				int depth, int elevel,
-				ConfigVariable **head_p, ConfigVariable **tail_p);
-extern bool ParseConfigFp(FILE *fp, const char *config_file,
-			  int depth, int elevel,
-			  ConfigVariable **head_p, ConfigVariable **tail_p);
-extern bool ParseConfigDirectory(const char *includedir,
-					 const char *calling_file, int calling_lineno,
-					 int depth, int elevel,
-					 ConfigVariable **head_p,
-					 ConfigVariable **tail_p);
-extern void FreeConfigVariables(ConfigVariable *list);
-
 /*
  * The possible values of an enum variable are specified by an array of
  * name-value pairs.  The "hidden" flag means the value is accepted but
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 56192f1b20..dd83329681 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -117,8 +117,8 @@ sub mkvcbuild
 	}
 
 	our @pgcommonallfiles = qw(
-	  base64.c config_info.c controldata_utils.c exec.c file_perm.c ip.c
-	  keywords.c kwlookup.c link-canary.c md5.c
+	  base64.c config_info.c controldata_utils.c exec.c file_perm.c
+	  guc-file.c ip.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 unicode_norm.c username.c
 	  wait_error.c);
@@ -147,6 +147,7 @@ sub mkvcbuild
 
 	$libpgcommon = $solution->AddProject('libpgcommon', 'lib', 'misc');
 	$libpgcommon->AddDefine('FRONTEND');
+	$libpgcommon->AddFiles('src/common', 'guc-file.l');
 	$libpgcommon->AddFiles('src/common', @pgcommonfrontendfiles);
 
 	$libpgfeutils = $solution->AddProject('libpgfeutils', 'lib', 'misc');
@@ -163,6 +164,7 @@ sub mkvcbuild
 	$postgres->ReplaceFile('src/backend/port/pg_shmem.c',
 		'src/backend/port/win32_shmem.c');
 	$postgres->AddFiles('src/port',   @pgportfiles);
+	$postgres->AddFiles('src/common', 'guc-file.l');
 	$postgres->AddFiles('src/common', @pgcommonbkndfiles);
 	$postgres->AddDir('src/timezone');
 
@@ -172,7 +174,6 @@ sub mkvcbuild
 	$postgres->AddFiles('src/backend/parser', 'scan.l', 'gram.y');
 	$postgres->AddFiles('src/backend/bootstrap', 'bootscanner.l',
 		'bootparse.y');
-	$postgres->AddFiles('src/backend/utils/misc', 'guc-file.l');
 	$postgres->AddFiles(
 		'src/backend/replication', 'repl_scanner.l',
 		'repl_gram.y',             'syncrep_scanner.l',
diff --git a/src/tools/msvc/clean.bat b/src/tools/msvc/clean.bat
index 069d6eb569..53d77aaf0a 100755
--- a/src/tools/msvc/clean.bat
+++ b/src/tools/msvc/clean.bat
@@ -80,7 +80,7 @@ if %DIST%==1 if exist src\backend\parser\scan.c del /q src\backend\parser\scan.c
 if %DIST%==1 if exist src\backend\parser\gram.c del /q src\backend\parser\gram.c
 if %DIST%==1 if exist src\backend\bootstrap\bootscanner.c del /q src\backend\bootstrap\bootscanner.c
 if %DIST%==1 if exist src\backend\bootstrap\bootparse.c del /q src\backend\bootstrap\bootparse.c
-if %DIST%==1 if exist src\backend\utils\misc\guc-file.c del /q src\backend\utils\misc\guc-file.c
+if %DIST%==1 if exist src\common\guc-file.c del /q src\common\guc-file.c
 if %DIST%==1 if exist src\backend\replication\repl_scanner.c del /q src\backend\replication\repl_scanner.c
 if %DIST%==1 if exist src\backend\replication\repl_gram.c del /q src\backend\replication\repl_gram.c
 if %DIST%==1 if exist src\backend\replication\syncrep_scanner.c del /q src\backend\replication\syncrep_scanner.c

base-commit: 46682705986207795fa3eec9459df0b975c5ce03
-- 
2.17.1

#18Andrey Borodin
x4mmm@yandex-team.ru
In reply to: Alexey Kondratov (#17)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi! Thanks for the next version!

8 февр. 2019 г., в 18:30, Alexey Kondratov <a.kondratov@postgrespro.ru> написал(а):

On 21.01.2019 23:50, a.kondratov@postgrespro.ru wrote:

Thank you for the review! I have updated the patch according to your
comments and remarks. Please, find new version attached.

During the self-reviewing of the code and tests, I discovered some problems with build on Windows. New version of the patch is attached and it fixes this issue as well as includes some minor code revisions.

I've made one more pass through code and found no important problems.

The patch moves code including these lines
* XXX this is an unmaintainable crock, because we have to know how to set
* (or at least what to call to set) every variable that could potentially
* have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. However, there's no
* time to redesign it for 9.1.
But I think it's not the point of this patch to refactor that code.

Here's a typo in postgreslq.conf
+ fprintf(stderr, _("%s: option -r/--use-postgresql-conf is specified, but postgreslq.conf is absent in the target directory\n"),

I'm still not sure guc-file refactoring you made is architecturally correct, I do not feel that my expertise is enough to judge, but everything works.

Besides this, I think you can switch patch to "Ready for committer".

check-world is passing on macbook, docs are here, feature is implemented and tested.

Best regards, Andrey Borodin.

#19Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Andrey Borodin (#18)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi!

On 09.02.2019 14:31, Andrey Borodin wrote:

Here's a typo in postgreslq.conf
+ fprintf(stderr, _("%s: option -r/--use-postgresql-conf is specified, but postgreslq.conf is absent in the target directory\n"),

Fixed, thanks. I do not attach new version of the patch for just one
typo, maybe there will be some more remarks from others.

Besides this, I think you can switch patch to "Ready for committer".

check-world is passing on macbook, docs are here, feature is implemented and tested.

OK, cfbot [1]http://cfbot.cputube.org/alexey-kondratov.html does not complain about anything on Linux and Windows as
well, so I am setting it to "Ready for committer" for the next commitfest.

[1]: http://cfbot.cputube.org/alexey-kondratov.html

Regards

--
Alexey Kondratov

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

#20Andres Freund
andres@anarazel.de
In reply to: Alexey Kondratov (#11)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi,

On 2018-11-07 12:58:11 +0300, Alexey Kondratov wrote:

On 30.10.2018 06:01, Michael Paquier wrote:

- Reusing the GUC parser is something I would avoid as well. Not worth
the complexity.

Yes, I don't like it either. I will try to make guc-file.l frontend safe.

It sounds like a seriously bad idea to use a different parser for
pg_rewind. Why don't you just use postgres for it? As in
/path/to/postgres -D /path/to/datadir/ -C shared_buffers
?

#21Andres Freund
andres@anarazel.de
In reply to: Alexey Kondratov (#17)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi,

On 2019-02-08 18:30:18 +0300, Alexey Kondratov wrote:

From 99c6d94f37a797400d41545a271ff111b92e9361 Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <alex.lumir@gmail.com>
Date: Fri, 21 Dec 2018 14:00:30 +0300
Subject: [PATCH] pg_rewind: options to use restore_command from
postgresql.conf or command line.

---
doc/src/sgml/ref/pg_rewind.sgml | 30 +-
src/backend/Makefile | 4 +-
src/backend/commands/extension.c | 1 +
src/backend/utils/misc/.gitignore | 1 -
src/backend/utils/misc/Makefile | 8 -
src/backend/utils/misc/guc.c | 434 +++++++++++++--
src/bin/pg_rewind/Makefile | 2 +-
src/bin/pg_rewind/parsexlog.c | 166 +++++-
src/bin/pg_rewind/pg_rewind.c | 100 +++-
src/bin/pg_rewind/pg_rewind.h | 10 +-
src/bin/pg_rewind/t/001_basic.pl | 4 +-
src/bin/pg_rewind/t/002_databases.pl | 4 +-
src/bin/pg_rewind/t/003_extrafiles.pl | 4 +-
src/bin/pg_rewind/t/RewindTest.pm | 93 +++-
src/common/.gitignore | 1 +
src/common/Makefile | 9 +-
src/{backend/utils/misc => common}/guc-file.l | 518 ++++--------------
src/include/common/guc-file.h | 50 ++
src/include/utils/guc.h | 39 +-
src/tools/msvc/Mkvcbuild.pm | 7 +-
src/tools/msvc/clean.bat | 2 +-
21 files changed, 973 insertions(+), 514 deletions(-)
delete mode 100644 src/backend/utils/misc/.gitignore
rename src/{backend/utils/misc => common}/guc-file.l (60%)
create mode 100644 src/include/common/guc-file.h

As noted in a message of a few minutes ago, I'm very doubtful that the
approach of using guc-file.l like that is a good idea. But if we go for
that, that part of the patch *NEEDS* to be split into a separate
commit/patch. It's too hard to see functional changes otherwise.

@@ -291,9 +299,46 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,

if (xlogreadfd < 0)
{
-			printf(_("could not open file \"%s\": %s\n"), xlogfpath,
-				   strerror(errno));
-			return -1;
+			bool  restore_ok;
+
+			/*
+			 * If we have no restore_command to execute, then exit.
+			 */
+			if (private->restoreCommand == NULL)
+			{
+				printf(_("could not open file \"%s\": %s\n"), xlogfpath,
+					   strerror(errno));
+				return -1;
+			}
+
+			/*
+			 * Since we have restore_command to execute, then try to retreive
+			 * missing WAL file from the archive.
+			 */
+			restore_ok = RestoreArchivedWAL(private->datadir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand);
+
+			if (restore_ok)
+			{
+				xlogreadfd = open(xlogfpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogreadfd < 0)
+				{
+					printf(_("could not open restored from archive file \"%s\": %s\n"), xlogfpath,
+							strerror(errno));
+					return -1;
+				}
+				else
+					pg_log(PG_DEBUG, "using restored from archive version of file \"%s\"\n", xlogfpath);
+			}
+			else
+			{
+				printf(_("could not restore file \"%s\" from archive: %s\n"), xlogfname,
+					   strerror(errno));
+				return -1;
+			}
}
}

I suggest moving this to a separate function.

+	if (restore_command != NULL)
+	{
+		if (restore_wals)
+		{
+			fprintf(stderr, _("%s: conflicting options: both -r and -R are specified\n"),
+				progname);
+			fprintf(stderr, _("You must run %s with either -r/--use-postgresql-conf or -R/--restore-command.\n"),
+				progname);
+			exit(1);
+		}
+
+		pg_log(PG_DEBUG, "using command line restore_command=\'%s\'.\n", restore_command);
+	}
+	else if (restore_wals)
+	{
+		FILE	*conf_file;
+
+		/*
+		 * Look for configuration file in the target data directory and
+		 * try to get restore_command from there.
+		 */
+		snprintf(recfile_fullpath, sizeof(recfile_fullpath), "%s/%s", datadir_target, RESTORE_COMMAND_FILE);
+		conf_file = fopen(recfile_fullpath, "r");
+
+		if (conf_file == NULL)
+		{
+			fprintf(stderr, _("%s: option -r/--use-postgresql-conf is specified, but postgreslq.conf is absent in the target directory\n"),
+					progname);
+			fprintf(stderr, _("You have to add postgresql.conf or pass restore_command with -R/--restore-command option.\n"));
+			exit(1);
+		}
+		else
+		{
+			ConfigVariable *item,
+						   *head = NULL,
+						   *tail = NULL;
+			bool			config_is_parsed;
+
+			/*
+			 * We pass a fullpath to the configuration file as calling_file here, since
+			 * parser will use its parent directory as base for all further includes
+			 * if any exist.
+			 */
+			config_is_parsed = ParseConfigFile(RESTORE_COMMAND_FILE, true,
+											   recfile_fullpath, 0, 0,
+											   PG_WARNING, &head, &tail);
+			fclose(conf_file);
+
+			if (config_is_parsed)
+			{
+				for (item = head; item; item = item->next)
+				{
+					if (strcmp(item->name, "restore_command") == 0)
+					{
+						if (restore_command != NULL)
+						{
+							pfree(restore_command);
+							restore_command = NULL;
+						}
+						restore_command = pstrdup(item->value);
+						pg_log(PG_DEBUG, "using restore_command=\'%s\' from %s.\n", restore_command, RESTORE_COMMAND_FILE);
+					}
+				}
+
+				if (restore_command == NULL)
+					pg_fatal("could not find restore_command in %s file %s\n", RESTORE_COMMAND_FILE, recfile_fullpath);
+			}
+			else
+				pg_fatal("could not parse %s file %s\n", RESTORE_COMMAND_FILE, recfile_fullpath);
+
+			FreeConfigVariables(head);
+		}
+	}
+

Isn't this entirely broken? restore_command could be set in a different
file no?

Greetings,

Andres Freund

#22Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Andres Freund (#21)
2 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi Andres,

Thank you for your feedback.

On 16.02.2019 6:41, Andres Freund wrote:

It sounds like a seriously bad idea to use a different parser for
pg_rewind. Why don't you just use postgres for it? As in
/path/to/postgres -D /path/to/datadir/ -C shared_buffers
?

Initially, when I started working on this patch, recovery options were
not a part of GUCs, so it was not possible. Now, recovery.conf is a part
of postgresql.conf and postgres -C only reads config files, initializes
GUCs, prints required parameter and shuts down. Thus, it seems like an
acceptable solution for me. Though I am still a little bit afraid to
start up a server, which is meant to be shut down during rewind process,
even for such a short period of time.

The only thing I am concerned most about is that pg_rewind always has
been a standalone utility, so you were able to simply rewind two
separated data directories one relatively another without any need for
other postgres binaries. If we rely on postgres -C this would be tricky
in some cases:

- end user should always care about postmaster binaries availability;
- even so, appropriate postgres executable may be absent in the ENV/PATH;
- locations of pg_rewind and postgres may be arbitrary depending on the
distribution, which may be custom as well.

I cannot propose a reliable way of detecting path to postgres executable
without directly asking users to provide it via PATH, command line
option, etc. If someone can suggest anything, then it would be possible
to make patch simpler in some way, but I always wanted to keep pg_rewind
standalone and as simple as possible for end users.

Anyway, currently I do not use a different parser for pg_rewind. A few
versions back I have made guc-file.l common for frontend/backend. So
technically speaking it is the same parser as postmaster use, only small
number of sophisticated error reporting is wrapped with IFDEF.

But if we go for that, that part of the patch *NEEDS* to be split
into a separate commit/patch. It's too hard to see functional
changes otherwise.

Yes, sure, please find attached new version of the patch set consisting
of two separated patches. First is for making guc-file.l common between
frontend/backend and second one is for adding new options into pg_rewind.

+			if (restore_ok)
+			{
+				xlogreadfd = open(xlogfpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogreadfd < 0)
+				{
+					printf(_("could not open restored from archive file \"%s\": %s\n"), xlogfpath,
+							strerror(errno));
+					return -1;
+				}
+				else
+					pg_log(PG_DEBUG, "using restored from archive version of file \"%s\"\n", xlogfpath);
+			}
+			else
+			{
+				printf(_("could not restore file \"%s\" from archive: %s\n"), xlogfname,
+					   strerror(errno));
+				return -1;
+			}
}
}
I suggest moving this to a separate function.

OK, I have slightly refactored and simplified this part. All checks of
the recovered file have been moved into RestoreArchivedWAL. Hope it
looks better now.

Isn't this entirely broken? restore_command could be set in a different
file no?

Maybe I got it wrong, but I do not think so. Since recovery options are
now a part of GUCs, restore_command may be only set inside
postgresql.conf or any files/subdirs which are included there to take an
effect, isn't it? Parser will walk postgresql.conf with all includes
recursively and should eventually find it, if it was set.

Regards

--
Alexey Kondratov

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

Attachments:

v3-0002-Options-to-use-restore_command-with-pg_rewind.patchtext/x-patch; name=v3-0002-Options-to-use-restore_command-with-pg_rewind.patchDownload
From c012e1e1149d04abc39bb4099fe1e18a4cd2ca2d Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <kondratov.aleksey@gmail.com>
Date: Mon, 18 Feb 2019 12:23:37 +0300
Subject: [PATCH v3 2/2] Options to use restore_command with pg_rewind

---
 doc/src/sgml/ref/pg_rewind.sgml       |  30 ++++-
 src/bin/pg_rewind/Makefile            |   2 +-
 src/bin/pg_rewind/parsexlog.c         | 163 +++++++++++++++++++++++++-
 src/bin/pg_rewind/pg_rewind.c         | 100 +++++++++++++++-
 src/bin/pg_rewind/pg_rewind.h         |  10 +-
 src/bin/pg_rewind/t/001_basic.pl      |   4 +-
 src/bin/pg_rewind/t/002_databases.pl  |   4 +-
 src/bin/pg_rewind/t/003_extrafiles.pl |   4 +-
 src/bin/pg_rewind/t/RewindTest.pm     |  93 ++++++++++++++-
 9 files changed, 388 insertions(+), 22 deletions(-)

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 53a64ee29e..0c2441afa7 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -67,8 +67,10 @@ PostgreSQL documentation
    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
+   files might no longer be present. In that case, they can be automatically
+   copied by <application>pg_rewind</application> from the WAL archive to the 
+   <filename>pg_wal</filename> directory if either <literal>-r</literal> or
+   <literal>-R</literal> option is specified, or
    fetched on startup by configuring <xref linkend="guc-primary-conninfo"/> or
    <xref linkend="guc-restore-command"/>.  The use of
    <application>pg_rewind</application> is not limited to failover, e.g.  a standby
@@ -200,6 +202,30 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-r</option></term>
+      <term><option>--use-postgresql-conf</option></term>
+      <listitem>
+       <para>
+        Use restore_command in the <filename>postgresql.conf</filename> to
+        retreive missing in the target <filename>pg_wal</filename> directory
+        WAL files from the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-R <replaceable class="parameter">restore_command</replaceable></option></term>
+      <term><option>--restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the restore_command to use for retrieval of the missing
+        in the target <filename>pg_wal</filename> directory WAL files from
+        the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
diff --git a/src/bin/pg_rewind/Makefile b/src/bin/pg_rewind/Makefile
index 04f3b8f520..65ebf3c78c 100644
--- a/src/bin/pg_rewind/Makefile
+++ b/src/bin/pg_rewind/Makefile
@@ -15,7 +15,7 @@ subdir = src/bin/pg_rewind
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-override CPPFLAGS := -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)
+override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)
 LDFLAGS_INTERNAL += $(libpq_pgport)
 
 OBJS	= pg_rewind.o parsexlog.o xlogreader.o datapagemap.o timeline.o \
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index e19c265cbb..35c75cd8b9 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "pg_rewind.h"
 #include "filemap.h"
@@ -45,6 +46,7 @@ static char xlogfpath[MAXPGPATH];
 typedef struct XLogPageReadPrivate
 {
 	const char *datadir;
+	const char *restoreCommand;
 	int			tliIndex;
 } XLogPageReadPrivate;
 
@@ -53,6 +55,9 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
 				   int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
 				   TimeLineID *pageTLI);
 
+static int RestoreArchivedWAL(const char *path, const char *xlogfname,
+					off_t expectedSize, const char *restoreCommand);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -60,7 +65,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;
@@ -69,6 +74,7 @@ extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restore_command;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -156,7 +162,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;
@@ -181,6 +187,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -285,15 +292,37 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
 		XLogFileName(xlogfname, targetHistory[private->tliIndex].tli,
 					 xlogreadsegno, WalSegSz);
 
-		snprintf(xlogfpath, MAXPGPATH, "%s/" XLOGDIR "/%s", private->datadir, xlogfname);
+		snprintf(xlogfpath, MAXPGPATH, "%s/" XLOGDIR "/%s",
+				 private->datadir, xlogfname);
 
 		xlogreadfd = open(xlogfpath, O_RDONLY | PG_BINARY, 0);
 
 		if (xlogreadfd < 0)
 		{
-			printf(_("could not open file \"%s\": %s\n"), xlogfpath,
-				   strerror(errno));
-			return -1;
+			/*
+			 * If we have no restore_command to execute, then exit.
+			 */
+			if (private->restoreCommand == NULL)
+			{
+				printf(_("could not open file \"%s\": %s\n"), xlogfpath,
+					   strerror(errno));
+				return -1;
+			}
+
+			/*
+			 * Since we have restore_command to execute, then try to retreive
+			 * missing WAL file from the archive.
+			 */
+			xlogreadfd = RestoreArchivedWAL(private->datadir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand);
+
+			if (xlogreadfd < 0)
+				return -1;
+			else
+				pg_log(PG_DEBUG, "using restored from archive version of file \"%s\"\n",
+					   xlogfpath);
 		}
 	}
 
@@ -409,3 +438,125 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return true.
+ *
+ * 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.
+ *
+ * This is a simplified and adapted to frontend version of
+ * RestoreArchivedFile function from transam/xlogarchive.c
+ */
+static int
+RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH];
+	char		xlogRestoreCmd[MAXPGPATH];
+	char	   *dp;
+	char	   *endp;
+	const char *sp;
+	int			rc,
+				xlogfd;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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 */
+					pg_fatal("restore_command with %%r cannot be used during rewind process.\n");
+					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';
+
+	/*
+	 * 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)
+			{
+				printf(_("archive file \"%s\" has wrong size: %lu instead of %lu, %s"),
+						xlogfname, (unsigned long) stat_buf.st_size,
+						(unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					printf(_("could not open restored from archive file \"%s\": %s\n"),
+							xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			printf(_("could not stat file \"%s\": %s"),
+					xlogpath, strerror(errno));
+		}
+	}
+
+	printf(_("could not restore file \"%s\" from archive: %s\n"),
+			xlogfname, strerror(errno));
+
+	return -1;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 7ccde5c87f..5abfeec26c 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -26,6 +26,7 @@
 #include "catalog/pg_control.h"
 #include "common/file_perm.h"
 #include "common/file_utils.h"
+#include "common/guc-file.h"
 #include "common/restricted_token.h"
 #include "getopt_long.h"
 #include "storage/bufpage.h"
@@ -52,11 +53,13 @@ int			WalSegSz;
 char	   *datadir_target = NULL;
 char	   *datadir_source = NULL;
 char	   *connstr_source = NULL;
+char	   *restore_command = NULL;
 
 bool		debug = false;
 bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
+bool		restore_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -75,6 +78,9 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"));
 	printf(_("                                 safely to disk\n"));
 	printf(_("  -P, --progress                 write progress messages\n"));
+	printf(_("  -r, --use-postgresql-conf      use restore_command in the postgresql.conf to\n"));
+	printf(_("                                 retreive WALs from archive\n"));
+	printf(_("  -R, --restore-command=COMMAND  restore_command\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
@@ -94,9 +100,12 @@ main(int argc, char **argv)
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
+		{"use-postgresql-conf", no_argument, NULL, 'r'},
+		{"restore-command", required_argument, NULL, 'R'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
+	char		recfile_fullpath[MAXPGPATH];
 	int			option_index;
 	int			c;
 	XLogRecPtr	divergerec;
@@ -129,7 +138,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "D:nNPR:r", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -141,6 +150,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'r':
+				restore_wals = true;
+				break;
+
 			case 'n':
 				dry_run = true;
 				break;
@@ -157,6 +170,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'R':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -223,6 +240,80 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	if (restore_command != NULL)
+	{
+		if (restore_wals)
+		{
+			fprintf(stderr, _("%s: conflicting options: both -r and -R are specified\n"),
+				progname);
+			fprintf(stderr, _("You must run %s with either -r/--use-postgresql-conf or -R/--restore-command.\n"),
+				progname);
+			exit(1);
+		}
+
+		pg_log(PG_DEBUG, "using command line restore_command=\'%s\'.\n", restore_command);
+	}
+	else if (restore_wals)
+	{
+		FILE	*conf_file;
+
+		/*
+		 * Look for configuration file in the target data directory and
+		 * try to get restore_command from there.
+		 */
+		snprintf(recfile_fullpath, sizeof(recfile_fullpath), "%s/%s", datadir_target, RESTORE_COMMAND_FILE);
+		conf_file = fopen(recfile_fullpath, "r");
+
+		if (conf_file == NULL)
+		{
+			fprintf(stderr, _("%s: option -r/--use-postgresql-conf is specified, but postgresql.conf is absent in the target directory\n"),
+					progname);
+			fprintf(stderr, _("You have to add postgresql.conf or pass restore_command with -R/--restore-command option.\n"));
+			exit(1);
+		}
+		else
+		{
+			ConfigVariable *item,
+						   *head = NULL,
+						   *tail = NULL;
+			bool			config_is_parsed;
+
+			/*
+			 * We pass a fullpath to the configuration file as calling_file here, since
+			 * parser will use its parent directory as base for all further includes
+			 * if any exist.
+			 */
+			config_is_parsed = ParseConfigFile(RESTORE_COMMAND_FILE, true,
+											   recfile_fullpath, 0, 0,
+											   PG_WARNING, &head, &tail);
+			fclose(conf_file);
+
+			if (config_is_parsed)
+			{
+				for (item = head; item; item = item->next)
+				{
+					if (strcmp(item->name, "restore_command") == 0)
+					{
+						if (restore_command != NULL)
+						{
+							pfree(restore_command);
+							restore_command = NULL;
+						}
+						restore_command = pstrdup(item->value);
+						pg_log(PG_DEBUG, "using restore_command=\'%s\' from %s.\n", restore_command, RESTORE_COMMAND_FILE);
+					}
+				}
+
+				if (restore_command == NULL)
+					pg_fatal("could not find restore_command in %s file %s\n", RESTORE_COMMAND_FILE, recfile_fullpath);
+			}
+			else
+				pg_fatal("could not parse %s file %s\n", RESTORE_COMMAND_FILE, recfile_fullpath);
+
+			FreeConfigVariables(head);
+		}
+	}
+
 	/* Connect to remote server */
 	if (connstr_source)
 		libpqConnect(connstr_source);
@@ -294,9 +385,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);
 	printf(_("rewinding from last common checkpoint at %X/%X on timeline %u\n"),
 		   (uint32) (chkptrec >> 32), (uint32) chkptrec,
 		   chkpttli);
@@ -319,7 +409,7 @@ main(int argc, char **argv)
 	 */
 	pg_log(PG_PROGRESS, "reading WAL in target\n");
 	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 83b2898b8b..23b9526f5d 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -14,9 +14,12 @@
 #include "datapagemap.h"
 
 #include "access/timeline.h"
+#include "catalog/pg_control.h"
 #include "storage/block.h"
 #include "storage/relfilenode.h"
 
+#define RESTORE_COMMAND_FILE "postgresql.conf"
+
 /* Configuration options */
 extern char *datadir_target;
 extern char *datadir_source;
@@ -32,11 +35,10 @@ extern int	targetNentries;
 
 /* 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);
+				   int tliIndex, XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
+				   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 115192170e..8a6fa33016 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 => 10;
+use Test::More tests => 20;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -106,5 +106,7 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 6dc05720a1..99ea821cfe 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 12;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -62,5 +62,7 @@ template1
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index c4040bd562..24cec256de 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 8;
 
 use File::Find;
 
@@ -90,5 +90,7 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 85cae7e47b..79bd6434b4 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -39,7 +39,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catpath catfile);
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -249,6 +251,95 @@ sub run_pg_rewind
 			],
 			'pg_rewind remote');
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $wals_archive_dir = catpath(${TestLib::tmp_check}, 'master_wals_archive');
+		my @wal_files = bsd_glob catpath($master_pgdata, 'pg_wal', '0000000*');
+		my $restore_command;
+
+		remove_tree($wals_archive_dir);
+		make_path($wals_archive_dir) or die;
+
+		# Move all old master WAL files to the archive.
+		# Old master should be stopped at this point.
+		foreach my $wal_file (@wal_files)
+		{
+			move($wal_file, "$wals_archive_dir") or die;
+		}
+
+		if ($windows_os)
+		{
+			$restore_command = "copy $wals_archive_dir\\\%f \%p";
+		}
+		else
+		{
+			$restore_command = "cp $wals_archive_dir/\%f \%p";
+		}
+
+		# 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",
+				"--restore-command=$restore_command"
+			],
+			'pg_rewind archive');
+	}
+	elsif ($test_mode eq "archive_conf")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $wals_archive_dir = catpath(${TestLib::tmp_check}, 'master_wals_archive');
+		my @wal_files = bsd_glob catpath($master_pgdata, 'pg_wal', '0000000*');
+		my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');
+		my $restore_command;
+
+		remove_tree($wals_archive_dir);
+		make_path($wals_archive_dir) or die;
+
+		# Move all old master WAL files to the archive.
+		# Old master should be stopped at this point.
+		foreach my $wal_file (@wal_files)
+		{
+			move($wal_file, "$wals_archive_dir") or die;
+		}
+
+		if ($windows_os)
+		{
+			$restore_command = "copy $wals_archive_dir\\\%f \%p";
+		}
+		else
+		{
+			$restore_command = "cp $wals_archive_dir/\%f \%p";
+		}
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Add restore_command to postgresql.conf of target cluster.
+		open(my $conf_fd, ">>", $master_conf_path) or die;
+		print $conf_fd "\nrestore_command='$restore_command'";
+		close $conf_fd;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"-r"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 
-- 
2.17.1

v3-0001-Make-guc-file.l-common-for-frontend-and-backend.patchtext/x-patch; name=v3-0001-Make-guc-file.l-common-for-frontend-and-backend.patchDownload
From 898caa8e7a46db63a1f182085dfde8ea1478ad62 Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <kondratov.aleksey@gmail.com>
Date: Mon, 18 Feb 2019 12:22:12 +0300
Subject: [PATCH v3 1/2] Make guc-file.l common for frontend and backend

---
 src/backend/Makefile                          |   4 +-
 src/backend/commands/extension.c              |   1 +
 src/backend/utils/misc/.gitignore             |   1 -
 src/backend/utils/misc/Makefile               |   8 -
 src/backend/utils/misc/guc.c                  | 434 +++++++++++++--
 src/common/.gitignore                         |   1 +
 src/common/Makefile                           |   9 +-
 src/{backend/utils/misc => common}/guc-file.l | 518 ++++--------------
 src/include/common/guc-file.h                 |  50 ++
 src/include/utils/guc.h                       |  39 +-
 src/tools/msvc/Mkvcbuild.pm                   |   7 +-
 src/tools/msvc/clean.bat                      |   2 +-
 12 files changed, 581 insertions(+), 493 deletions(-)
 delete mode 100644 src/backend/utils/misc/.gitignore
 rename src/{backend/utils/misc => common}/guc-file.l (60%)
 create mode 100644 src/include/common/guc-file.h

diff --git a/src/backend/Makefile b/src/backend/Makefile
index 478a96db9b..721cb57e89 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -186,7 +186,7 @@ distprep:
 	$(MAKE) -C replication	repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
 	$(MAKE) -C storage/lmgr	lwlocknames.h lwlocknames.c
 	$(MAKE) -C utils	distprep
-	$(MAKE) -C utils/misc	guc-file.c
+	$(MAKE) -C common	guc-file.c
 	$(MAKE) -C utils/sort	qsort_tuple.c
 
 
@@ -307,7 +307,7 @@ maintainer-clean: distclean
 	      replication/syncrep_scanner.c \
 	      storage/lmgr/lwlocknames.c \
 	      storage/lmgr/lwlocknames.h \
-	      utils/misc/guc-file.c \
+	      common/guc-file.c \
 	      utils/sort/qsort_tuple.c
 
 
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index daf3f51636..195eb8a821 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -50,6 +50,7 @@
 #include "commands/defrem.h"
 #include "commands/extension.h"
 #include "commands/schemacmds.h"
+#include "common/guc-file.h"
 #include "funcapi.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
diff --git a/src/backend/utils/misc/.gitignore b/src/backend/utils/misc/.gitignore
deleted file mode 100644
index 495b1aec76..0000000000
--- a/src/backend/utils/misc/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/guc-file.c
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index ec7ec131e5..2205956a9f 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -25,11 +25,3 @@ override CPPFLAGS += -DPG_KRB_SRVTAB='"$(krb_srvtab)"'
 endif
 
 include $(top_srcdir)/src/backend/common.mk
-
-# guc-file is compiled as part of guc
-guc.o: guc-file.c
-
-# Note: guc-file.c is not deleted by 'make clean',
-# since we want to ship it in distribution tarballs.
-clean:
-	@rm -f lex.yy.c
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 156d147c85..a784738e57 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -41,6 +41,7 @@
 #include "commands/vacuum.h"
 #include "commands/variable.h"
 #include "commands/trigger.h"
+#include "common/guc-file.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "jit/jit.h"
@@ -213,7 +214,6 @@ static void assign_recovery_target_lsn(const char *newval, void *extra);
 static bool check_primary_slot_name(char **newval, void **extra, GucSource source);
 static bool check_default_with_oids(bool *newval, void **extra, GucSource source);
 
-/* Private functions in guc-file.l that need to be called from guc.c */
 static ConfigVariable *ProcessConfigFileInternal(GucContext context,
 						  bool applySettings, int elevel);
 
@@ -4492,7 +4492,6 @@ static int	GUCNestLevel = 0;	/* 1 when in main transaction */
 
 
 static int	guc_var_compare(const void *a, const void *b);
-static int	guc_name_compare(const char *namea, const char *nameb);
 static void InitializeGUCOptionsFromEnvironment(void);
 static void InitializeOneGUCOption(struct config_generic *gconf);
 static void push_old_value(struct config_generic *gconf, GucAction action);
@@ -4969,37 +4968,6 @@ guc_var_compare(const void *a, const void *b)
 	return guc_name_compare(confa->name, confb->name);
 }
 
-/*
- * the bare comparison function for GUC names
- */
-static int
-guc_name_compare(const char *namea, const char *nameb)
-{
-	/*
-	 * The temptation to use strcasecmp() here must be resisted, because the
-	 * array ordering has to remain stable across setlocale() calls. So, build
-	 * our own with a simple ASCII-only downcasing.
-	 */
-	while (*namea && *nameb)
-	{
-		char		cha = *namea++;
-		char		chb = *nameb++;
-
-		if (cha >= 'A' && cha <= 'Z')
-			cha += 'a' - 'A';
-		if (chb >= 'A' && chb <= 'Z')
-			chb += 'a' - 'A';
-		if (cha != chb)
-			return cha - chb;
-	}
-	if (*namea)
-		return 1;				/* a is longer */
-	if (*nameb)
-		return -1;				/* b is longer */
-	return 0;
-}
-
-
 /*
  * Initialize GUC options during program startup.
  *
@@ -11384,4 +11352,402 @@ check_default_with_oids(bool *newval, void **extra, GucSource source)
 	return true;
 }
 
-#include "guc-file.c"
+/*
+ * Exported function to read and process the configuration file. The
+ * parameter indicates in what context the file is being read --- either
+ * postmaster startup (including standalone-backend startup) or SIGHUP.
+ * All options mentioned in the configuration file are set to new values.
+ * If a hard error occurs, no values will be changed.  (There can also be
+ * errors that prevent just one value from being changed.)
+ */
+void
+ProcessConfigFile(GucContext context)
+{
+	int			elevel;
+	MemoryContext config_cxt;
+	MemoryContext caller_cxt;
+
+	/*
+	 * Config files are processed on startup (by the postmaster only) and on
+	 * SIGHUP (by the postmaster and its children)
+	 */
+	Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) ||
+		   context == PGC_SIGHUP);
+
+	/*
+	 * To avoid cluttering the log, only the postmaster bleats loudly about
+	 * problems with the config file.
+	 */
+	elevel = IsUnderPostmaster ? DEBUG2 : LOG;
+
+	/*
+	 * This function is usually called within a process-lifespan memory
+	 * context.  To ensure that any memory leaked during GUC processing does
+	 * not accumulate across repeated SIGHUP cycles, do the work in a private
+	 * context that we can free at exit.
+	 */
+	config_cxt = AllocSetContextCreate(CurrentMemoryContext,
+									   "config file processing",
+									   ALLOCSET_DEFAULT_SIZES);
+	caller_cxt = MemoryContextSwitchTo(config_cxt);
+
+	/*
+	 * Read and apply the config file.  We don't need to examine the result.
+	 */
+	(void) ProcessConfigFileInternal(context, true, elevel);
+
+	/* Clean up */
+	MemoryContextSwitchTo(caller_cxt);
+	MemoryContextDelete(config_cxt);
+}
+
+/*
+ * This function handles both actual config file (re)loads and execution of
+ * show_all_file_settings() (i.e., the pg_file_settings view).  In the latter
+ * case we don't apply any of the settings, but we make all the usual validity
+ * checks, and we return the ConfigVariable list so that it can be printed out
+ * by show_all_file_settings().
+ */
+static ConfigVariable *
+ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
+{
+	bool		error = false;
+	bool		applying = false;
+	const char *ConfFileWithError;
+	ConfigVariable *item,
+			   *head,
+			   *tail;
+	int			i;
+
+	/* Parse the main config file into a list of option names and values */
+	ConfFileWithError = ConfigFileName;
+	head = tail = NULL;
+
+	if (!ParseConfigFile(ConfigFileName, true,
+						 NULL, 0, 0, elevel,
+						 &head, &tail))
+	{
+		/* Syntax error(s) detected in the file, so bail out */
+		error = true;
+		goto bail_out;
+	}
+
+	/*
+	 * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to
+	 * replace any parameters set by ALTER SYSTEM command.  Because this file
+	 * is in the data directory, we can't read it until the DataDir has been
+	 * set.
+	 */
+	if (DataDir)
+	{
+		if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false,
+							 NULL, 0, 0, elevel,
+							 &head, &tail))
+		{
+			/* Syntax error(s) detected in the file, so bail out */
+			error = true;
+			ConfFileWithError = PG_AUTOCONF_FILENAME;
+			goto bail_out;
+		}
+	}
+	else
+	{
+		/*
+		 * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be
+		 * read.  In this case, we don't want to accept any settings but
+		 * data_directory from postgresql.conf, because they might be
+		 * overwritten with settings in the PG_AUTOCONF_FILENAME file which
+		 * will be read later. OTOH, since data_directory isn't allowed in the
+		 * PG_AUTOCONF_FILENAME file, it will never be overwritten later.
+		 */
+		ConfigVariable *newlist = NULL;
+
+		/*
+		 * Prune all items except the last "data_directory" from the list.
+		 */
+		for (item = head; item; item = item->next)
+		{
+			if (!item->ignore &&
+				strcmp(item->name, "data_directory") == 0)
+				newlist = item;
+		}
+
+		if (newlist)
+			newlist->next = NULL;
+		head = tail = newlist;
+
+		/*
+		 * Quick exit if data_directory is not present in file.
+		 *
+		 * We need not do any further processing, in particular we don't set
+		 * PgReloadTime; that will be set soon by subsequent full loading of
+		 * the config file.
+		 */
+		if (head == NULL)
+			goto bail_out;
+	}
+
+	/*
+	 * Mark all extant GUC variables as not present in the config file. We
+	 * need this so that we can tell below which ones have been removed from
+	 * the file since we last processed it.
+	 */
+	for (i = 0; i < num_guc_variables; i++)
+	{
+		struct config_generic *gconf = guc_variables[i];
+
+		gconf->status &= ~GUC_IS_IN_FILE;
+	}
+
+	/*
+	 * Check if all the supplied option names are valid, as an additional
+	 * quasi-syntactic check on the validity of the config file.  It is
+	 * important that the postmaster and all backends agree on the results of
+	 * this phase, else we will have strange inconsistencies about which
+	 * processes accept a config file update and which don't.  Hence, unknown
+	 * custom variable names have to be accepted without complaint.  For the
+	 * same reason, we don't attempt to validate the options' values here.
+	 *
+	 * In addition, the GUC_IS_IN_FILE flag is set on each existing GUC
+	 * variable mentioned in the file; and we detect duplicate entries in the
+	 * file and mark the earlier occurrences as ignorable.
+	 */
+	for (item = head; item; item = item->next)
+	{
+		struct config_generic *record;
+
+		/* Ignore anything already marked as ignorable */
+		if (item->ignore)
+			continue;
+
+		/*
+		 * Try to find the variable; but do not create a custom placeholder if
+		 * it's not there already.
+		 */
+		record = find_option(item->name, false, elevel);
+
+		if (record)
+		{
+			/* If it's already marked, then this is a duplicate entry */
+			if (record->status & GUC_IS_IN_FILE)
+			{
+				/*
+				 * Mark the earlier occurrence(s) as dead/ignorable.  We could
+				 * avoid the O(N^2) behavior here with some additional state,
+				 * but it seems unlikely to be worth the trouble.
+				 */
+				ConfigVariable *pitem;
+
+				for (pitem = head; pitem != item; pitem = pitem->next)
+				{
+					if (!pitem->ignore &&
+						strcmp(pitem->name, item->name) == 0)
+						pitem->ignore = true;
+				}
+			}
+			/* Now mark it as present in file */
+			record->status |= GUC_IS_IN_FILE;
+		}
+		else if (strchr(item->name, GUC_QUALIFIER_SEPARATOR) == NULL)
+		{
+			/* Invalid non-custom variable, so complain */
+			ereport(elevel,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %u",
+							item->name,
+							item->filename, item->sourceline)));
+			item->errmsg = pstrdup("unrecognized configuration parameter");
+			error = true;
+			ConfFileWithError = item->filename;
+		}
+	}
+
+	/*
+	 * If we've detected any errors so far, we don't want to risk applying any
+	 * changes.
+	 */
+	if (error)
+		goto bail_out;
+
+	/* Otherwise, set flag that we're beginning to apply changes */
+	applying = true;
+
+	/*
+	 * Check for variables having been removed from the config file, and
+	 * revert their reset values (and perhaps also effective values) to the
+	 * boot-time defaults.  If such a variable can't be changed after startup,
+	 * report that and continue.
+	 */
+	for (i = 0; i < num_guc_variables; i++)
+	{
+		struct config_generic *gconf = guc_variables[i];
+		GucStack   *stack;
+
+		if (gconf->reset_source != PGC_S_FILE ||
+			(gconf->status & GUC_IS_IN_FILE))
+			continue;
+		if (gconf->context < PGC_SIGHUP)
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
+					 errmsg("parameter \"%s\" cannot be changed without restarting the server",
+							gconf->name)));
+			record_config_file_error(psprintf("parameter \"%s\" cannot be changed without restarting the server",
+											  gconf->name),
+									 NULL, 0,
+									 &head, &tail);
+			error = true;
+			continue;
+		}
+
+		/* No more to do if we're just doing show_all_file_settings() */
+		if (!applySettings)
+			continue;
+
+		/*
+		 * Reset any "file" sources to "default", else set_config_option will
+		 * not override those settings.
+		 */
+		if (gconf->reset_source == PGC_S_FILE)
+			gconf->reset_source = PGC_S_DEFAULT;
+		if (gconf->source == PGC_S_FILE)
+			gconf->source = PGC_S_DEFAULT;
+		for (stack = gconf->stack; stack; stack = stack->prev)
+		{
+			if (stack->source == PGC_S_FILE)
+				stack->source = PGC_S_DEFAULT;
+		}
+
+		/* Now we can re-apply the wired-in default (i.e., the boot_val) */
+		if (set_config_option(gconf->name, NULL,
+							  context, PGC_S_DEFAULT,
+							  GUC_ACTION_SET, true, 0, false) > 0)
+		{
+			/* Log the change if appropriate */
+			if (context == PGC_SIGHUP)
+				ereport(elevel,
+						(errmsg("parameter \"%s\" removed from configuration file, reset to default",
+								gconf->name)));
+		}
+	}
+
+	/*
+	 * Restore any variables determined by environment variables or
+	 * dynamically-computed defaults.  This is a no-op except in the case
+	 * where one of these had been in the config file and is now removed.
+	 *
+	 * In particular, we *must not* do this during the postmaster's initial
+	 * loading of the file, since the timezone functions in particular should
+	 * be run only after initialization is complete.
+	 *
+	 * XXX this is an unmaintainable crock, because we have to know how to set
+	 * (or at least what to call to set) every variable that could potentially
+	 * have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. However, there's no
+	 * time to redesign it for 9.1.
+	 */
+	if (context == PGC_SIGHUP && applySettings)
+	{
+		InitializeGUCOptionsFromEnvironment();
+		pg_timezone_abbrev_initialize();
+		/* this selects SQL_ASCII in processes not connected to a database */
+		SetConfigOption("client_encoding", GetDatabaseEncodingName(),
+						PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT);
+	}
+
+	/*
+	 * Now apply the values from the config file.
+	 */
+	for (item = head; item; item = item->next)
+	{
+		char	   *pre_value = NULL;
+		int			scres;
+
+		/* Ignore anything marked as ignorable */
+		if (item->ignore)
+			continue;
+
+		/* In SIGHUP cases in the postmaster, we want to report changes */
+		if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster)
+		{
+			const char *preval = GetConfigOption(item->name, true, false);
+
+			/* If option doesn't exist yet or is NULL, treat as empty string */
+			if (!preval)
+				preval = "";
+			/* must dup, else might have dangling pointer below */
+			pre_value = pstrdup(preval);
+		}
+
+		scres = set_config_option(item->name, item->value,
+								  context, PGC_S_FILE,
+								  GUC_ACTION_SET, applySettings, 0, false);
+		if (scres > 0)
+		{
+			/* variable was updated, so log the change if appropriate */
+			if (pre_value)
+			{
+				const char *post_value = GetConfigOption(item->name, true, false);
+
+				if (!post_value)
+					post_value = "";
+				if (strcmp(pre_value, post_value) != 0)
+					ereport(elevel,
+							(errmsg("parameter \"%s\" changed to \"%s\"",
+									item->name, item->value)));
+			}
+			item->applied = true;
+		}
+		else if (scres == 0)
+		{
+			error = true;
+			item->errmsg = pstrdup("setting could not be applied");
+			ConfFileWithError = item->filename;
+		}
+		else
+		{
+			/* no error, but variable's active value was not changed */
+			item->applied = true;
+		}
+
+		/*
+		 * We should update source location unless there was an error, since
+		 * even if the active value didn't change, the reset value might have.
+		 * (In the postmaster, there won't be a difference, but it does matter
+		 * in backends.)
+		 */
+		if (scres != 0 && applySettings)
+			set_config_sourcefile(item->name, item->filename,
+								  item->sourceline);
+
+		if (pre_value)
+			pfree(pre_value);
+	}
+
+	/* Remember when we last successfully loaded the config file. */
+	if (applySettings)
+		PgReloadTime = GetCurrentTimestamp();
+
+bail_out:
+	if (error && applySettings)
+	{
+		/* During postmaster startup, any error is fatal */
+		if (context == PGC_POSTMASTER)
+			ereport(ERROR,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("configuration file \"%s\" contains errors",
+							ConfFileWithError)));
+		else if (applying)
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("configuration file \"%s\" contains errors; unaffected changes were applied",
+							ConfFileWithError)));
+		else
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("configuration file \"%s\" contains errors; no changes were applied",
+							ConfFileWithError)));
+	}
+
+	/* Successful or otherwise, return the collected data list */
+	return head;
+}
diff --git a/src/common/.gitignore b/src/common/.gitignore
index ffa3284fbf..424beacbc9 100644
--- a/src/common/.gitignore
+++ b/src/common/.gitignore
@@ -1 +1,2 @@
 /kwlist_d.h
+/guc-file.c
diff --git a/src/common/Makefile b/src/common/Makefile
index d84c7b6e6a..706e832811 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -50,7 +50,7 @@ OBJS_COMMON = base64.o config_info.o controldata_utils.o d2s.o exec.o f2s.o \
 	file_perm.o ip.o keywords.o kwlookup.o link-canary.o md5.o \
 	pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
 	rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
-	username.o wait_error.o
+	username.o wait_error.o guc-file.o
 
 ifeq ($(with_openssl),yes)
 OBJS_COMMON += sha2_openssl.o
@@ -72,7 +72,7 @@ GEN_KEYWORDLIST_DEPS = $(TOOLSDIR)/gen_keywordlist.pl $(TOOLSDIR)/PerfectHash.pm
 
 all: libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
 
-distprep: kwlist_d.h
+distprep: kwlist_d.h guc-file.c
 
 # libpgcommon is needed by some contrib
 install: all installdirs
@@ -139,10 +139,13 @@ RYU_OBJS = $(RYU_FILES) $(RYU_FILES:%.o=%_shlib.o) $(RYU_FILES:%.o=%_srv.o)
 
 $(RYU_OBJS): CFLAGS += $(PERMIT_DECLARATION_AFTER_STATEMENT)
 
-# kwlist_d.h is in the distribution tarball, so it is not cleaned here.
+# Note: guc-file.c and kwlist_d.h are not deleted by 'make clean',
+# since we want to ship them in distribution tarballs.
 clean distclean:
 	rm -f libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
 	rm -f $(OBJS_FRONTEND) $(OBJS_SHLIB) $(OBJS_SRV)
+	@rm -f lex.yy.c
 
 maintainer-clean: distclean
 	rm -f kwlist_d.h
+	rm -f guc-file.c
diff --git a/src/backend/utils/misc/guc-file.l b/src/common/guc-file.l
similarity index 60%
rename from src/backend/utils/misc/guc-file.l
rename to src/common/guc-file.l
index 1c8b5f7d84..175685c9b5 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/common/guc-file.l
@@ -4,21 +4,31 @@
  *
  * Copyright (c) 2000-2019, PostgreSQL Global Development Group
  *
- * src/backend/utils/misc/guc-file.l
+ * src/common/guc-file.l
  */
 
 %{
 
+#ifndef FRONTEND
 #include "postgres.h"
 
-#include <ctype.h>
-#include <unistd.h>
-
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
 #include "utils/guc.h"
+#include "utils/elog.h"
+#else
+#include "postgres_fe.h"
+
+#include <dirent.h>
+#include <setjmp.h>
+#endif
+
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/stat.h>
 
+#include "common/guc-file.h"
 
 /*
  * flex emits a yy_fatal_error() function that it calls in response to
@@ -48,12 +58,6 @@ static sigjmp_buf *GUC_flex_fatal_jmp;
 
 static void FreeConfigVariable(ConfigVariable *item);
 
-static void record_config_file_error(const char *errmsg,
-						 const char *config_file,
-						 int lineno,
-						 ConfigVariable **head_p,
-						 ConfigVariable **tail_p);
-
 static int	GUC_flex_fatal(const char *msg);
 static char *GUC_scanstr(const char *s);
 
@@ -111,404 +115,35 @@ STRING			\'([^'\\\n]|\\.|\'\')*\'
 
 /* LCOV_EXCL_STOP */
 
-/*
- * Exported function to read and process the configuration file. The
- * parameter indicates in what context the file is being read --- either
- * postmaster startup (including standalone-backend startup) or SIGHUP.
- * All options mentioned in the configuration file are set to new values.
- * If a hard error occurs, no values will be changed.  (There can also be
- * errors that prevent just one value from being changed.)
- */
-void
-ProcessConfigFile(GucContext context)
-{
-	int			elevel;
-	MemoryContext config_cxt;
-	MemoryContext caller_cxt;
-
-	/*
-	 * Config files are processed on startup (by the postmaster only) and on
-	 * SIGHUP (by the postmaster and its children)
-	 */
-	Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) ||
-		   context == PGC_SIGHUP);
-
-	/*
-	 * To avoid cluttering the log, only the postmaster bleats loudly about
-	 * problems with the config file.
-	 */
-	elevel = IsUnderPostmaster ? DEBUG2 : LOG;
-
-	/*
-	 * This function is usually called within a process-lifespan memory
-	 * context.  To ensure that any memory leaked during GUC processing does
-	 * not accumulate across repeated SIGHUP cycles, do the work in a private
-	 * context that we can free at exit.
-	 */
-	config_cxt = AllocSetContextCreate(CurrentMemoryContext,
-									   "config file processing",
-									   ALLOCSET_DEFAULT_SIZES);
-	caller_cxt = MemoryContextSwitchTo(config_cxt);
-
-	/*
-	 * Read and apply the config file.  We don't need to examine the result.
-	 */
-	(void) ProcessConfigFileInternal(context, true, elevel);
-
-	/* Clean up */
-	MemoryContextSwitchTo(caller_cxt);
-	MemoryContextDelete(config_cxt);
-}
 
 /*
- * This function handles both actual config file (re)loads and execution of
- * show_all_file_settings() (i.e., the pg_file_settings view).  In the latter
- * case we don't apply any of the settings, but we make all the usual validity
- * checks, and we return the ConfigVariable list so that it can be printed out
- * by show_all_file_settings().
+ * The bare comparison function for GUC names.
  */
-static ConfigVariable *
-ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
+int
+guc_name_compare(const char *namea, const char *nameb)
 {
-	bool		error = false;
-	bool		applying = false;
-	const char *ConfFileWithError;
-	ConfigVariable *item,
-			   *head,
-			   *tail;
-	int			i;
-
-	/* Parse the main config file into a list of option names and values */
-	ConfFileWithError = ConfigFileName;
-	head = tail = NULL;
-
-	if (!ParseConfigFile(ConfigFileName, true,
-						 NULL, 0, 0, elevel,
-						 &head, &tail))
-	{
-		/* Syntax error(s) detected in the file, so bail out */
-		error = true;
-		goto bail_out;
-	}
-
-	/*
-	 * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to
-	 * replace any parameters set by ALTER SYSTEM command.  Because this file
-	 * is in the data directory, we can't read it until the DataDir has been
-	 * set.
-	 */
-	if (DataDir)
-	{
-		if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false,
-							 NULL, 0, 0, elevel,
-							 &head, &tail))
-		{
-			/* Syntax error(s) detected in the file, so bail out */
-			error = true;
-			ConfFileWithError = PG_AUTOCONF_FILENAME;
-			goto bail_out;
-		}
-	}
-	else
-	{
-		/*
-		 * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be
-		 * read.  In this case, we don't want to accept any settings but
-		 * data_directory from postgresql.conf, because they might be
-		 * overwritten with settings in the PG_AUTOCONF_FILENAME file which
-		 * will be read later. OTOH, since data_directory isn't allowed in the
-		 * PG_AUTOCONF_FILENAME file, it will never be overwritten later.
-		 */
-		ConfigVariable *newlist = NULL;
-
-		/*
-		 * Prune all items except the last "data_directory" from the list.
-		 */
-		for (item = head; item; item = item->next)
-		{
-			if (!item->ignore &&
-				strcmp(item->name, "data_directory") == 0)
-				newlist = item;
-		}
-
-		if (newlist)
-			newlist->next = NULL;
-		head = tail = newlist;
-
-		/*
-		 * Quick exit if data_directory is not present in file.
-		 *
-		 * We need not do any further processing, in particular we don't set
-		 * PgReloadTime; that will be set soon by subsequent full loading of
-		 * the config file.
-		 */
-		if (head == NULL)
-			goto bail_out;
-	}
-
 	/*
-	 * Mark all extant GUC variables as not present in the config file. We
-	 * need this so that we can tell below which ones have been removed from
-	 * the file since we last processed it.
+	 * The temptation to use strcasecmp() here must be resisted, because the
+	 * array ordering has to remain stable across setlocale() calls. So, build
+	 * our own with a simple ASCII-only downcasing.
 	 */
-	for (i = 0; i < num_guc_variables; i++)
+	while (*namea && *nameb)
 	{
-		struct config_generic *gconf = guc_variables[i];
-
-		gconf->status &= ~GUC_IS_IN_FILE;
-	}
-
-	/*
-	 * Check if all the supplied option names are valid, as an additional
-	 * quasi-syntactic check on the validity of the config file.  It is
-	 * important that the postmaster and all backends agree on the results of
-	 * this phase, else we will have strange inconsistencies about which
-	 * processes accept a config file update and which don't.  Hence, unknown
-	 * custom variable names have to be accepted without complaint.  For the
-	 * same reason, we don't attempt to validate the options' values here.
-	 *
-	 * In addition, the GUC_IS_IN_FILE flag is set on each existing GUC
-	 * variable mentioned in the file; and we detect duplicate entries in the
-	 * file and mark the earlier occurrences as ignorable.
-	 */
-	for (item = head; item; item = item->next)
-	{
-		struct config_generic *record;
-
-		/* Ignore anything already marked as ignorable */
-		if (item->ignore)
-			continue;
-
-		/*
-		 * Try to find the variable; but do not create a custom placeholder if
-		 * it's not there already.
-		 */
-		record = find_option(item->name, false, elevel);
-
-		if (record)
-		{
-			/* If it's already marked, then this is a duplicate entry */
-			if (record->status & GUC_IS_IN_FILE)
-			{
-				/*
-				 * Mark the earlier occurrence(s) as dead/ignorable.  We could
-				 * avoid the O(N^2) behavior here with some additional state,
-				 * but it seems unlikely to be worth the trouble.
-				 */
-				ConfigVariable *pitem;
-
-				for (pitem = head; pitem != item; pitem = pitem->next)
-				{
-					if (!pitem->ignore &&
-						strcmp(pitem->name, item->name) == 0)
-						pitem->ignore = true;
-				}
-			}
-			/* Now mark it as present in file */
-			record->status |= GUC_IS_IN_FILE;
-		}
-		else if (strchr(item->name, GUC_QUALIFIER_SEPARATOR) == NULL)
-		{
-			/* Invalid non-custom variable, so complain */
-			ereport(elevel,
-					(errcode(ERRCODE_UNDEFINED_OBJECT),
-					 errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %u",
-							item->name,
-							item->filename, item->sourceline)));
-			item->errmsg = pstrdup("unrecognized configuration parameter");
-			error = true;
-			ConfFileWithError = item->filename;
-		}
+		char		cha = *namea++;
+		char		chb = *nameb++;
+
+		if (cha >= 'A' && cha <= 'Z')
+			cha += 'a' - 'A';
+		if (chb >= 'A' && chb <= 'Z')
+			chb += 'a' - 'A';
+		if (cha != chb)
+			return cha - chb;
 	}
-
-	/*
-	 * If we've detected any errors so far, we don't want to risk applying any
-	 * changes.
-	 */
-	if (error)
-		goto bail_out;
-
-	/* Otherwise, set flag that we're beginning to apply changes */
-	applying = true;
-
-	/*
-	 * Check for variables having been removed from the config file, and
-	 * revert their reset values (and perhaps also effective values) to the
-	 * boot-time defaults.  If such a variable can't be changed after startup,
-	 * report that and continue.
-	 */
-	for (i = 0; i < num_guc_variables; i++)
-	{
-		struct config_generic *gconf = guc_variables[i];
-		GucStack   *stack;
-
-		if (gconf->reset_source != PGC_S_FILE ||
-			(gconf->status & GUC_IS_IN_FILE))
-			continue;
-		if (gconf->context < PGC_SIGHUP)
-		{
-			ereport(elevel,
-					(errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
-					 errmsg("parameter \"%s\" cannot be changed without restarting the server",
-							gconf->name)));
-			record_config_file_error(psprintf("parameter \"%s\" cannot be changed without restarting the server",
-											  gconf->name),
-									 NULL, 0,
-									 &head, &tail);
-			error = true;
-			continue;
-		}
-
-		/* No more to do if we're just doing show_all_file_settings() */
-		if (!applySettings)
-			continue;
-
-		/*
-		 * Reset any "file" sources to "default", else set_config_option will
-		 * not override those settings.
-		 */
-		if (gconf->reset_source == PGC_S_FILE)
-			gconf->reset_source = PGC_S_DEFAULT;
-		if (gconf->source == PGC_S_FILE)
-			gconf->source = PGC_S_DEFAULT;
-		for (stack = gconf->stack; stack; stack = stack->prev)
-		{
-			if (stack->source == PGC_S_FILE)
-				stack->source = PGC_S_DEFAULT;
-		}
-
-		/* Now we can re-apply the wired-in default (i.e., the boot_val) */
-		if (set_config_option(gconf->name, NULL,
-							  context, PGC_S_DEFAULT,
-							  GUC_ACTION_SET, true, 0, false) > 0)
-		{
-			/* Log the change if appropriate */
-			if (context == PGC_SIGHUP)
-				ereport(elevel,
-						(errmsg("parameter \"%s\" removed from configuration file, reset to default",
-								gconf->name)));
-		}
-	}
-
-	/*
-	 * Restore any variables determined by environment variables or
-	 * dynamically-computed defaults.  This is a no-op except in the case
-	 * where one of these had been in the config file and is now removed.
-	 *
-	 * In particular, we *must not* do this during the postmaster's initial
-	 * loading of the file, since the timezone functions in particular should
-	 * be run only after initialization is complete.
-	 *
-	 * XXX this is an unmaintainable crock, because we have to know how to set
-	 * (or at least what to call to set) every variable that could potentially
-	 * have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. However, there's no
-	 * time to redesign it for 9.1.
-	 */
-	if (context == PGC_SIGHUP && applySettings)
-	{
-		InitializeGUCOptionsFromEnvironment();
-		pg_timezone_abbrev_initialize();
-		/* this selects SQL_ASCII in processes not connected to a database */
-		SetConfigOption("client_encoding", GetDatabaseEncodingName(),
-						PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT);
-	}
-
-	/*
-	 * Now apply the values from the config file.
-	 */
-	for (item = head; item; item = item->next)
-	{
-		char	   *pre_value = NULL;
-		int			scres;
-
-		/* Ignore anything marked as ignorable */
-		if (item->ignore)
-			continue;
-
-		/* In SIGHUP cases in the postmaster, we want to report changes */
-		if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster)
-		{
-			const char *preval = GetConfigOption(item->name, true, false);
-
-			/* If option doesn't exist yet or is NULL, treat as empty string */
-			if (!preval)
-				preval = "";
-			/* must dup, else might have dangling pointer below */
-			pre_value = pstrdup(preval);
-		}
-
-		scres = set_config_option(item->name, item->value,
-								  context, PGC_S_FILE,
-								  GUC_ACTION_SET, applySettings, 0, false);
-		if (scres > 0)
-		{
-			/* variable was updated, so log the change if appropriate */
-			if (pre_value)
-			{
-				const char *post_value = GetConfigOption(item->name, true, false);
-
-				if (!post_value)
-					post_value = "";
-				if (strcmp(pre_value, post_value) != 0)
-					ereport(elevel,
-							(errmsg("parameter \"%s\" changed to \"%s\"",
-									item->name, item->value)));
-			}
-			item->applied = true;
-		}
-		else if (scres == 0)
-		{
-			error = true;
-			item->errmsg = pstrdup("setting could not be applied");
-			ConfFileWithError = item->filename;
-		}
-		else
-		{
-			/* no error, but variable's active value was not changed */
-			item->applied = true;
-		}
-
-		/*
-		 * We should update source location unless there was an error, since
-		 * even if the active value didn't change, the reset value might have.
-		 * (In the postmaster, there won't be a difference, but it does matter
-		 * in backends.)
-		 */
-		if (scres != 0 && applySettings)
-			set_config_sourcefile(item->name, item->filename,
-								  item->sourceline);
-
-		if (pre_value)
-			pfree(pre_value);
-	}
-
-	/* Remember when we last successfully loaded the config file. */
-	if (applySettings)
-		PgReloadTime = GetCurrentTimestamp();
-
-bail_out:
-	if (error && applySettings)
-	{
-		/* During postmaster startup, any error is fatal */
-		if (context == PGC_POSTMASTER)
-			ereport(ERROR,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("configuration file \"%s\" contains errors",
-							ConfFileWithError)));
-		else if (applying)
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("configuration file \"%s\" contains errors; unaffected changes were applied",
-							ConfFileWithError)));
-		else
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("configuration file \"%s\" contains errors; no changes were applied",
-							ConfFileWithError)));
-	}
-
-	/* Successful or otherwise, return the collected data list */
-	return head;
+	if (*namea)
+		return 1;				/* a is longer */
+	if (*nameb)
+		return -1;				/* b is longer */
+	return 0;
 }
 
 /*
@@ -525,6 +160,7 @@ AbsoluteConfigLocation(const char *location, const char *calling_file)
 		return pstrdup(location);
 	else
 	{
+		#ifndef FRONTEND
 		if (calling_file != NULL)
 		{
 			strlcpy(abs_path, calling_file, sizeof(abs_path));
@@ -538,6 +174,12 @@ AbsoluteConfigLocation(const char *location, const char *calling_file)
 			join_path_components(abs_path, DataDir, location);
 			canonicalize_path(abs_path);
 		}
+		#else
+		strlcpy(abs_path, calling_file, sizeof(abs_path));
+		get_parent_directory(abs_path);
+		join_path_components(abs_path, abs_path, location);
+		canonicalize_path(abs_path);
+		#endif
 		return pstrdup(abs_path);
 	}
 }
@@ -574,10 +216,15 @@ ParseConfigFile(const char *config_file, bool strict,
 	 */
 	if (depth > 10)
 	{
+		#ifndef FRONTEND
 		ereport(elevel,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("could not open configuration file \"%s\": maximum nesting depth exceeded",
 						config_file)));
+		#else
+		printf(_("could not open configuration file \"%s\": maximum nesting depth exceeded\n"),
+				config_file);
+		#endif
 		record_config_file_error("nesting depth exceeded",
 								 calling_file, calling_lineno,
 								 head_p, tail_p);
@@ -585,15 +232,23 @@ ParseConfigFile(const char *config_file, bool strict,
 	}
 
 	abs_path = AbsoluteConfigLocation(config_file, calling_file);
+	#ifndef FRONTEND
 	fp = AllocateFile(abs_path, "r");
+	#else
+	fp = fopen(abs_path, "r");
+	#endif
 	if (!fp)
 	{
 		if (strict)
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode_for_file_access(),
 					 errmsg("could not open configuration file \"%s\": %m",
 							abs_path)));
+			#else
+			printf(_("could not open configuration file \"%s\"\n"), abs_path);
+			#endif
 			record_config_file_error(psprintf("could not open file \"%s\"",
 											  abs_path),
 									 calling_file, calling_lineno,
@@ -602,9 +257,13 @@ ParseConfigFile(const char *config_file, bool strict,
 		}
 		else
 		{
+			#ifndef FRONTEND
 			ereport(LOG,
 					(errmsg("skipping missing configuration file \"%s\"",
 							abs_path)));
+			#else
+			printf(_("skipping missing configuration file \"%s\"\n"), abs_path);
+			#endif
 		}
 		goto cleanup;
 	}
@@ -613,7 +272,14 @@ ParseConfigFile(const char *config_file, bool strict,
 
 cleanup:
 	if (fp)
+	{
+		#ifndef FRONTEND
 		FreeFile(fp);
+		#else
+		fclose(fp);
+		#endif
+	}
+
 	pfree(abs_path);
 
 	return OK;
@@ -623,7 +289,7 @@ cleanup:
  * Capture an error message in the ConfigVariable list returned by
  * config file parsing.
  */
-static void
+void
 record_config_file_error(const char *errmsg,
 						 const char *config_file,
 						 int lineno,
@@ -715,8 +381,13 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
 		 * corrupted parser state.  Consequently, abandon the file, but trust
 		 * that the state remains sane enough for yy_delete_buffer().
 		 */
+		#ifndef FRONTEND
 		elog(elevel, "%s at file \"%s\" line %u",
 			 GUC_flex_fatal_errmsg, config_file, ConfigFileLineno);
+		#else
+		printf(_("%s at file \"%s\" line %u\n"),
+				GUC_flex_fatal_errmsg, config_file, ConfigFileLineno);
+		#endif
 		record_config_file_error(GUC_flex_fatal_errmsg,
 								 config_file, ConfigFileLineno,
 								 head_p, tail_p);
@@ -855,20 +526,30 @@ parse_error:
 		/* report the error */
 		if (token == GUC_EOL || token == 0)
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 			  errmsg("syntax error in file \"%s\" line %u, near end of line",
 					 config_file, ConfigFileLineno - 1)));
+			#else
+			printf(_("syntax error in file \"%s\" line %u, near end of line\n"),
+											config_file, ConfigFileLineno - 1);
+			#endif
 			record_config_file_error("syntax error",
 									 config_file, ConfigFileLineno - 1,
 									 head_p, tail_p);
 		}
 		else
 		{
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 			 errmsg("syntax error in file \"%s\" line %u, near token \"%s\"",
 					config_file, ConfigFileLineno, yytext)));
+			#else
+			printf(_("syntax error in file \"%s\" line %u, near token \"%s\"\n"),
+										config_file, ConfigFileLineno, yytext);
+			#endif
 			record_config_file_error("syntax error",
 									 config_file, ConfigFileLineno,
 									 head_p, tail_p);
@@ -883,14 +564,23 @@ parse_error:
 		 * as well give up immediately.  (This prevents postmaster children
 		 * from bloating the logs with duplicate complaints.)
 		 */
+		#ifndef FRONTEND
 		if (errorcount >= 100 || elevel <= DEBUG1)
 		{
 			ereport(elevel,
 					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-			   errmsg("too many syntax errors found, abandoning file \"%s\"",
-					  config_file)));
+					 errmsg("too many syntax errors found, abandoning file \"%s\"",
+							config_file)));
 			break;
 		}
+		#else
+		if (errorcount >= 100)
+		{
+			printf(_("too many syntax errors found, abandoning file \"%s\"\n"),
+					config_file);
+			break;
+		}
+		#endif
 
 		/* resync to next end-of-line or EOF */
 		while (token != GUC_EOL && token != 0)
@@ -934,13 +624,21 @@ ParseConfigDirectory(const char *includedir,
 	bool		status;
 
 	directory = AbsoluteConfigLocation(includedir, calling_file);
+	#ifndef FRONTEND
 	d = AllocateDir(directory);
+	#else
+	d = opendir(directory);
+	#endif
 	if (d == NULL)
 	{
+		#ifndef FRONTEND
 		ereport(elevel,
 				(errcode_for_file_access(),
 				 errmsg("could not open configuration directory \"%s\": %m",
 						directory)));
+		#else
+		printf(_("could not open configuration directory \"%s\"\n"), directory);
+		#endif
 		record_config_file_error(psprintf("could not open directory \"%s\"",
 										  directory),
 								 calling_file, calling_lineno,
@@ -957,7 +655,11 @@ ParseConfigDirectory(const char *includedir,
 	filenames = (char **) palloc(size_filenames * sizeof(char *));
 	num_filenames = 0;
 
+	#ifndef FRONTEND
 	while ((de = ReadDir(d, directory)) != NULL)
+	#else
+	while ((de = readdir(d)) != NULL)
+	#endif
 	{
 		struct stat st;
 		char		filename[MAXPGPATH];
@@ -998,10 +700,14 @@ ParseConfigDirectory(const char *includedir,
 			 * a file can't be accessed now is if it was removed between the
 			 * directory listing and now.
 			 */
+			#ifndef FRONTEND
 			ereport(elevel,
 					(errcode_for_file_access(),
 					 errmsg("could not stat file \"%s\": %m",
 							filename)));
+			#else
+			printf(_("could not stat file \"%s\"\n"), filename);
+			#endif
 			record_config_file_error(psprintf("could not stat file \"%s\"",
 											  filename),
 									 calling_file, calling_lineno,
@@ -1032,7 +738,13 @@ ParseConfigDirectory(const char *includedir,
 
 cleanup:
 	if (d)
+	{
+		#ifndef FRONTEND
 		FreeDir(d);
+		#else
+		closedir(d);
+		#endif
+	}
 	pfree(directory);
 	return status;
 }
diff --git a/src/include/common/guc-file.h b/src/include/common/guc-file.h
new file mode 100644
index 0000000000..ca969e2aed
--- /dev/null
+++ b/src/include/common/guc-file.h
@@ -0,0 +1,50 @@
+#ifndef GUC_FILE_H
+#define GUC_FILE_H
+
+#include "c.h"
+
+/*
+ * Parsing the configuration file(s) will return a list of name-value pairs
+ * with source location info.  We also abuse this data structure to carry
+ * error reports about the config files.  An entry reporting an error will
+ * have errmsg != NULL, and might have NULLs for name, value, and/or filename.
+ *
+ * If "ignore" is true, don't attempt to apply the item (it might be an error
+ * report, or an item we determined to be duplicate).  "applied" is set true
+ * if we successfully applied, or could have applied, the setting.
+ */
+typedef struct ConfigVariable
+{
+	char	   *name;
+	char	   *value;
+	char	   *errmsg;
+	char	   *filename;
+	int			sourceline;
+	bool		ignore;
+	bool		applied;
+	struct ConfigVariable *next;
+} ConfigVariable;
+
+extern bool ParseConfigFile(const char *config_file, bool strict,
+				const char *calling_file, int calling_lineno,
+				int depth, int elevel,
+				ConfigVariable **head_p, ConfigVariable **tail_p);
+extern bool ParseConfigFp(FILE *fp, const char *config_file,
+			  int depth, int elevel,
+			  ConfigVariable **head_p, ConfigVariable **tail_p);
+extern bool ParseConfigDirectory(const char *includedir,
+					 const char *calling_file, int calling_lineno,
+					 int depth, int elevel,
+					 ConfigVariable **head_p,
+					 ConfigVariable **tail_p);
+extern void FreeConfigVariables(ConfigVariable *list);
+
+extern int	guc_name_compare(const char *namea, const char *nameb);
+
+extern void record_config_file_error(const char *errmsg,
+						 const char *config_file,
+						 int lineno,
+						 ConfigVariable **head_p,
+						 ConfigVariable **tail_p);
+
+#endif							/* GUC_FILE_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index c07e7b945e..0b3391004c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -1,8 +1,7 @@
 /*--------------------------------------------------------------------
  * guc.h
  *
- * External declarations pertaining to backend/utils/misc/guc.c and
- * backend/utils/misc/guc-file.l
+ * External declarations pertaining to backend/utils/misc/guc.c
  *
  * Copyright (c) 2000-2019, PostgreSQL Global Development Group
  * Written by Peter Eisentraut <peter_e@gmx.net>.
@@ -120,42 +119,6 @@ typedef enum
 	PGC_S_SESSION				/* SET command */
 } GucSource;
 
-/*
- * Parsing the configuration file(s) will return a list of name-value pairs
- * with source location info.  We also abuse this data structure to carry
- * error reports about the config files.  An entry reporting an error will
- * have errmsg != NULL, and might have NULLs for name, value, and/or filename.
- *
- * If "ignore" is true, don't attempt to apply the item (it might be an error
- * report, or an item we determined to be duplicate).  "applied" is set true
- * if we successfully applied, or could have applied, the setting.
- */
-typedef struct ConfigVariable
-{
-	char	   *name;
-	char	   *value;
-	char	   *errmsg;
-	char	   *filename;
-	int			sourceline;
-	bool		ignore;
-	bool		applied;
-	struct ConfigVariable *next;
-} ConfigVariable;
-
-extern bool ParseConfigFile(const char *config_file, bool strict,
-				const char *calling_file, int calling_lineno,
-				int depth, int elevel,
-				ConfigVariable **head_p, ConfigVariable **tail_p);
-extern bool ParseConfigFp(FILE *fp, const char *config_file,
-			  int depth, int elevel,
-			  ConfigVariable **head_p, ConfigVariable **tail_p);
-extern bool ParseConfigDirectory(const char *includedir,
-					 const char *calling_file, int calling_lineno,
-					 int depth, int elevel,
-					 ConfigVariable **head_p,
-					 ConfigVariable **tail_p);
-extern void FreeConfigVariables(ConfigVariable *list);
-
 /*
  * The possible values of an enum variable are specified by an array of
  * name-value pairs.  The "hidden" flag means the value is accepted but
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f4225030fc..3ade1d51c0 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 exec.c f2s.c file_perm.c ip.c
-	  keywords.c kwlookup.c link-canary.c md5.c
+	  base64.c config_info.c controldata_utils.c d2s.c exec.c f2s.c file_perm.c
+	  guc-file.c ip.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 unicode_norm.c username.c
 	  wait_error.c);
@@ -149,6 +149,7 @@ sub mkvcbuild
 
 	$libpgcommon = $solution->AddProject('libpgcommon', 'lib', 'misc');
 	$libpgcommon->AddDefine('FRONTEND');
+	$libpgcommon->AddFiles('src/common', 'guc-file.l');
 	$libpgcommon->AddFiles('src/common', @pgcommonfrontendfiles);
 
 	$libpgfeutils = $solution->AddProject('libpgfeutils', 'lib', 'misc');
@@ -165,6 +166,7 @@ sub mkvcbuild
 	$postgres->ReplaceFile('src/backend/port/pg_shmem.c',
 		'src/backend/port/win32_shmem.c');
 	$postgres->AddFiles('src/port',   @pgportfiles);
+	$postgres->AddFiles('src/common', 'guc-file.l');
 	$postgres->AddFiles('src/common', @pgcommonbkndfiles);
 	$postgres->AddDir('src/timezone');
 
@@ -174,7 +176,6 @@ sub mkvcbuild
 	$postgres->AddFiles('src/backend/parser', 'scan.l', 'gram.y');
 	$postgres->AddFiles('src/backend/bootstrap', 'bootscanner.l',
 		'bootparse.y');
-	$postgres->AddFiles('src/backend/utils/misc', 'guc-file.l');
 	$postgres->AddFiles(
 		'src/backend/replication', 'repl_scanner.l',
 		'repl_gram.y',             'syncrep_scanner.l',
diff --git a/src/tools/msvc/clean.bat b/src/tools/msvc/clean.bat
index 069d6eb569..53d77aaf0a 100755
--- a/src/tools/msvc/clean.bat
+++ b/src/tools/msvc/clean.bat
@@ -80,7 +80,7 @@ if %DIST%==1 if exist src\backend\parser\scan.c del /q src\backend\parser\scan.c
 if %DIST%==1 if exist src\backend\parser\gram.c del /q src\backend\parser\gram.c
 if %DIST%==1 if exist src\backend\bootstrap\bootscanner.c del /q src\backend\bootstrap\bootscanner.c
 if %DIST%==1 if exist src\backend\bootstrap\bootparse.c del /q src\backend\bootstrap\bootparse.c
-if %DIST%==1 if exist src\backend\utils\misc\guc-file.c del /q src\backend\utils\misc\guc-file.c
+if %DIST%==1 if exist src\common\guc-file.c del /q src\common\guc-file.c
 if %DIST%==1 if exist src\backend\replication\repl_scanner.c del /q src\backend\replication\repl_scanner.c
 if %DIST%==1 if exist src\backend\replication\repl_gram.c del /q src\backend\replication\repl_gram.c
 if %DIST%==1 if exist src\backend\replication\syncrep_scanner.c del /q src\backend\replication\syncrep_scanner.c

base-commit: 050710b36964dee7e1b2bf6b5ef00041fd5d2787
-- 
2.17.1

#23Andres Freund
andres@anarazel.de
In reply to: Alexey Kondratov (#22)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi,

On 2019-02-18 16:26:55 +0300, Alexey Kondratov wrote:

Hi Andres,

Thank you for your feedback.

On 16.02.2019 6:41, Andres Freund wrote:

It sounds like a seriously bad idea to use a different parser for
pg_rewind. Why don't you just use postgres for it? As in
/path/to/postgres -D /path/to/datadir/ -C shared_buffers
?

Initially, when I started working on this patch, recovery options were not a
part of GUCs, so it was not possible. Now, recovery.conf is a part of
postgresql.conf and postgres -C only reads config files, initializes GUCs,
prints required parameter and shuts down. Thus, it seems like an acceptable
solution for me. Though I am still a little bit afraid to start up a server,
which is meant to be shut down during rewind process, even for such a short
period of time.

-C doesn't really start the server.

The only thing I am concerned most about is that pg_rewind always has been a
standalone utility, so you were able to simply rewind two separated data
directories one relatively another without any need for other postgres
binaries. If we rely on postgres -C this would be tricky in some cases:

We don't generally support that. I don't see why this is something we
ought to invest significant effort into. It's not like you meaningfully
can use pg_rewind on a server without postgres installed.

Anyway, currently I do not use a different parser for pg_rewind. A few
versions back I have made guc-file.l common for frontend/backend. So
technically speaking it is the same parser as postmaster use, only small
number of sophisticated error reporting is wrapped with IFDEF.

Meh, there's significant parsing related logic, including dealing with
multiple values, that you've excluded. And you've copied substantial
amounts of code to make your approach possible.

Isn't this entirely broken? restore_command could be set in a different
file no?

Maybe I got it wrong, but I do not think so. Since recovery options are now
a part of GUCs, restore_command may be only set inside postgresql.conf or
any files/subdirs which are included there to take an effect, isn't it?
Parser will walk postgresql.conf with all includes recursively and should
eventually find it, if it was set.

Note that e.g. .auto.conf is loaded explicitly:
/*
* Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to
* replace any parameters set by ALTER SYSTEM command. Because this file
* is in the data directory, we can't read it until the DataDir has been
* set.
*/
if (DataDir)
{
if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false,
NULL, 0, 0, elevel,
&head, &tail))
{
/* Syntax error(s) detected in the file, so bail out */
error = true;
ConfFileWithError = PG_AUTOCONF_FILENAME;
goto bail_out;
}
}

- Andres

#24Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Andres Freund (#23)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2019-Feb-18, Andres Freund wrote:

Hi,

On 2019-02-18 16:26:55 +0300, Alexey Kondratov wrote:

Hi Andres,

Thank you for your feedback.

On 16.02.2019 6:41, Andres Freund wrote:

It sounds like a seriously bad idea to use a different parser for
pg_rewind. Why don't you just use postgres for it? As in
/path/to/postgres -D /path/to/datadir/ -C shared_buffers
?

Eh, this is what I suggested in this thread four months ago, though I
didn't remember at the time that aaa6e1def292 had already introduced -C
in 2011. It's definitely the way to go ... all this messing about with
the parser is insane.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#25Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Alvaro Herrera (#24)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 18.02.2019 19:49, Alvaro Herrera wrote:

On 16.02.2019 6:41, Andres Freund wrote:

It sounds like a seriously bad idea to use a different parser for
pg_rewind. Why don't you just use postgres for it? As in
/path/to/postgres -D /path/to/datadir/ -C shared_buffers
?

Eh, this is what I suggested in this thread four months ago, though I
didn't remember at the time that aaa6e1def292 had already introduced -C
in 2011. It's definitely the way to go ... all this messing about with
the parser is insane.

Yes, but four months ago recovery options were not a part of GUCs.

OK, if you and Andres are surely negative about solution with parser,
then I will work out this one with postgres -C and come back till the
next commitfest. I found that something similar is already used in
pg_ctl and there is a mechanism for finding valid executables in exec.c.
So it does not seem to be a big deal at the first sight.

Thanks for replies!

Regards

--
Alexey Kondratov

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

#26Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Alexey Kondratov (#25)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi,

I will work out this one with postgres -C and come back till the next
commitfest. I found that something similar is already used in pg_ctl
and there is a mechanism for finding valid executables in exec.c. So
it does not seem to be a big deal at the first sight.

I have reworked the patch, please find new version attached. It is 3
times as smaller than the previous one and now touches pg_rewind's code
only. Tests are also slightly refactored in order to remove duplicated
code. Execution of postgres -C is used for restore_command retrieval (if
-r is passed) as being suggested. Otherwise everything works as before.

Andres, Alvaro does it make sense now?

Regards

--
Alexey Kondratov

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

Attachments:

v4-0001-pg_rewind-options-to-use-restore_command.patchtext/x-patch; name=v4-0001-pg_rewind-options-to-use-restore_command.patchDownload
From 4c8f5c228e089e7e72835ae5c409a5bc8425ab15 Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <kondratov.aleksey@gmail.com>
Date: Tue, 19 Feb 2019 19:14:53 +0300
Subject: [PATCH v4] pg_rewind: options to use restore_command from command
 line or cluster config

---
 doc/src/sgml/ref/pg_rewind.sgml       |  30 ++++-
 src/bin/pg_rewind/parsexlog.c         | 161 +++++++++++++++++++++++++-
 src/bin/pg_rewind/pg_rewind.c         |  98 +++++++++++++++-
 src/bin/pg_rewind/pg_rewind.h         |   7 +-
 src/bin/pg_rewind/t/001_basic.pl      |   4 +-
 src/bin/pg_rewind/t/002_databases.pl  |   4 +-
 src/bin/pg_rewind/t/003_extrafiles.pl |   4 +-
 src/bin/pg_rewind/t/RewindTest.pm     |  84 +++++++++++++-
 8 files changed, 372 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 53a64ee29e..0c2441afa7 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -67,8 +67,10 @@ PostgreSQL documentation
    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
+   files might no longer be present. In that case, they can be automatically
+   copied by <application>pg_rewind</application> from the WAL archive to the 
+   <filename>pg_wal</filename> directory if either <literal>-r</literal> or
+   <literal>-R</literal> option is specified, or
    fetched on startup by configuring <xref linkend="guc-primary-conninfo"/> or
    <xref linkend="guc-restore-command"/>.  The use of
    <application>pg_rewind</application> is not limited to failover, e.g.  a standby
@@ -200,6 +202,30 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-r</option></term>
+      <term><option>--use-postgresql-conf</option></term>
+      <listitem>
+       <para>
+        Use restore_command in the <filename>postgresql.conf</filename> to
+        retreive missing in the target <filename>pg_wal</filename> directory
+        WAL files from the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-R <replaceable class="parameter">restore_command</replaceable></option></term>
+      <term><option>--restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the restore_command to use for retrieval of the missing
+        in the target <filename>pg_wal</filename> directory WAL files from
+        the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index e19c265cbb..5978ec9b99 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "pg_rewind.h"
 #include "filemap.h"
@@ -45,6 +46,7 @@ static char xlogfpath[MAXPGPATH];
 typedef struct XLogPageReadPrivate
 {
 	const char *datadir;
+	const char *restoreCommand;
 	int			tliIndex;
 } XLogPageReadPrivate;
 
@@ -53,6 +55,9 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
 				   int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
 				   TimeLineID *pageTLI);
 
+static int RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -60,7 +65,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;
@@ -69,6 +74,7 @@ extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restore_command;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -156,7 +162,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;
@@ -181,6 +187,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -291,9 +298,30 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
 
 		if (xlogreadfd < 0)
 		{
-			printf(_("could not open file \"%s\": %s\n"), xlogfpath,
-				   strerror(errno));
-			return -1;
+			/*
+			 * If we have no restore_command to execute, then exit.
+			 */
+			if (private->restoreCommand == NULL)
+			{
+				printf(_("could not open file \"%s\": %s\n"), xlogfpath,
+					   strerror(errno));
+				return -1;
+			}
+
+			/*
+			 * Since we have restore_command to execute, then try to retreive
+			 * missing WAL file from the archive.
+			 */
+			xlogreadfd = RestoreArchivedWAL(private->datadir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand);
+
+			if (xlogreadfd < 0)
+				return -1;
+			else
+				pg_log(PG_DEBUG, "using restored from archive version of file \"%s\"\n",
+					   xlogfpath);
 		}
 	}
 
@@ -409,3 +437,126 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return a file descriptor of 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.
+ *
+ * This is a simplified and adapted to frontend version of
+ * RestoreArchivedFile function from transam/xlogarchive.c
+ */
+static int
+RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH],
+				xlogRestoreCmd[MAXPGPATH],
+			   *dp,
+			   *endp;
+	const char *sp;
+	int			rc,
+				xlogfd;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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 */
+					pg_fatal("restore_command with %%r cannot be used during rewind process.\n");
+					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';
+
+	/*
+	 * 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)
+			{
+				printf(_("archive file \"%s\" has wrong size: %lu instead of %lu, %s"),
+						xlogfname, (unsigned long) stat_buf.st_size,
+						(unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					printf(_("could not open restored from archive file \"%s\": %s\n"),
+							xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			printf(_("could not stat file \"%s\": %s"),
+					xlogpath, strerror(errno));
+		}
+	}
+
+	printf(_("could not restore file \"%s\" from archive: %s\n"),
+			xlogfname, strerror(errno));
+
+	return -1;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 7ccde5c87f..9da578ec42 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -30,6 +30,8 @@
 #include "getopt_long.h"
 #include "storage/bufpage.h"
 
+#define MAX_RESTORE_COMMAND 1024
+
 static void usage(const char *progname);
 
 static void createBackupLabel(XLogRecPtr startpoint, TimeLineID starttli,
@@ -52,11 +54,13 @@ int			WalSegSz;
 char	   *datadir_target = NULL;
 char	   *datadir_source = NULL;
 char	   *connstr_source = NULL;
+char	   *restore_command = NULL;
 
 bool		debug = false;
 bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
+bool		restore_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -75,6 +79,9 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"));
 	printf(_("                                 safely to disk\n"));
 	printf(_("  -P, --progress                 write progress messages\n"));
+	printf(_("  -r, --use-postgresql-conf      use restore_command in the postgresql.conf to\n"));
+	printf(_("                                 retreive WALs from archive\n"));
+	printf(_("  -R, --restore-command=COMMAND  restore_command\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
@@ -94,6 +101,8 @@ main(int argc, char **argv)
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
+		{"use-postgresql-conf", no_argument, NULL, 'r'},
+		{"restore-command", required_argument, NULL, 'R'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
@@ -129,7 +138,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "D:nNPR:r", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -141,6 +150,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'r':
+				restore_wals = true;
+				break;
+
 			case 'n':
 				dry_run = true;
 				break;
@@ -157,6 +170,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'R':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -223,6 +240,78 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	if (restore_command != NULL)
+	{
+		if (restore_wals)
+		{
+			fprintf(stderr, _("%s: conflicting options: both -r and -R are specified\n"),
+				progname);
+			fprintf(stderr, _("You must run %s with either -r/--use-postgresql-conf "
+							"or -R/--restore-command.\n"), progname);
+			exit(1);
+		}
+
+		pg_log(PG_DEBUG, "using command line restore_command=\'%s\'.\n", restore_command);
+	}
+	else if (restore_wals)
+	{
+		int   rc;
+		char  postgres_exec_path[MAXPGPATH],
+			  postgres_cmd[MAXPGPATH],
+			  cmd_output[MAX_RESTORE_COMMAND];
+		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)
+				fprintf(stderr,
+						_("the program \"postgres\" is needed by %s "
+						"but was not found in the\n"
+						"same directory as \"%s\".\n"
+						"Check your installation.\n"),
+						progname, full_path);
+			else
+				fprintf(stderr,
+						_("the program \"postgres\" was found by \"%s\"\n"
+						"but was not the same version as %s.\n"
+						"Check your installation.\n"),
+						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\n",
+					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\n");
+
+		restore_command = pg_strdup(cmd_output);
+
+		pg_log(PG_DEBUG, "using config variable restore_command=\'%s\'.\n", restore_command);
+	}
+
 	/* Connect to remote server */
 	if (connstr_source)
 		libpqConnect(connstr_source);
@@ -294,9 +383,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);
 	printf(_("rewinding from last common checkpoint at %X/%X on timeline %u\n"),
 		   (uint32) (chkptrec >> 32), (uint32) chkptrec,
 		   chkpttli);
@@ -319,7 +407,7 @@ main(int argc, char **argv)
 	 */
 	pg_log(PG_PROGRESS, "reading WAL in target\n");
 	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 83b2898b8b..08a753475c 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -32,11 +32,10 @@ extern int	targetNentries;
 
 /* 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);
+				   int tliIndex, XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
+				   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 115192170e..8a6fa33016 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 => 10;
+use Test::More tests => 20;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -106,5 +106,7 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 6dc05720a1..99ea821cfe 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 12;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -62,5 +62,7 @@ template1
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index c4040bd562..24cec256de 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 8;
 
 use File::Find;
 
@@ -90,5 +90,7 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 85cae7e47b..4560fe4430 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -39,7 +39,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catdir catfile);
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -197,6 +199,38 @@ sub promote_standby
 	return;
 }
 
+# Moves WAL files to the temporary location and returns restore_command
+# to get them back.
+sub move_wals
+{
+	my $tmp_dir = shift;
+	my $master_pgdata = shift;
+	my $wals_archive_dir = catdir($tmp_dir, "master_wals_archive");
+	my @wal_files = bsd_glob catfile($master_pgdata, "pg_wal", "0000000*");
+	my $restore_command;
+
+	remove_tree($wals_archive_dir);
+	make_path($wals_archive_dir) or die;
+
+	# Move all old master WAL files to the archive.
+	# Old master should be stopped at this point.
+	foreach my $wal_file (@wal_files)
+	{
+		move($wal_file, "$wals_archive_dir") or die;
+	}
+
+	if ($windows_os)
+	{
+		$restore_command = "copy $wals_archive_dir\\\%f \%p";
+	}
+	else
+	{
+		$restore_command = "cp $wals_archive_dir/\%f \%p";
+	}
+
+	return $restore_command;
+}
+
 sub run_pg_rewind
 {
 	my $test_mode       = shift;
@@ -249,6 +283,54 @@ sub run_pg_rewind
 			],
 			'pg_rewind remote');
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# 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",
+				"--restore-command=$restore_command"
+			],
+			'pg_rewind archive');
+	}
+	elsif ($test_mode eq "archive_conf")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Add restore_command to postgresql.conf of target cluster.
+		open(my $conf_fd, ">>", $master_conf_path) or die;
+		print $conf_fd "\nrestore_command='$restore_command'";
+		close $conf_fd;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"-r"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 

base-commit: 41531e42d34f4aca117d343b5e40f3f757dec5fe
-- 
2.17.1

#27Andrey Borodin
x4mmm@yandex-team.ru
In reply to: Alexey Kondratov (#26)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi!

20 февр. 2019 г., в 17:06, Alexey Kondratov <a.kondratov@postgrespro.ru> написал(а):

I will work out this one with postgres -C and come back till the next commitfest. I found that something similar is already used in pg_ctl and there is a mechanism for finding valid executables in exec.c. So it does not seem to be a big deal at the first sight.

I have reworked the patch, please find new version attached. It is 3 times as smaller than the previous one and now touches pg_rewind's code only. Tests are also slightly refactored in order to remove duplicated code. Execution of postgres -C is used for restore_command retrieval (if -r is passed) as being suggested. Otherwise everything works as before.

<v4-0001-pg_rewind-options-to-use-restore_command.patch>

The new patch is much smaller (less than 400 lines) and works as advertised.
There's a typo "retreive" there.

These lines look a little suspicious:
char postgres_exec_path[MAXPGPATH],
postgres_cmd[MAXPGPATH],
cmd_output[MAX_RESTORE_COMMAND];
Is it supposed to be any difference between MAXPGPATH and MAX_RESTORE_COMMAND?

Besides this, patch looks fine to me.

Best regards, Andrey Borodin.

#28David Steele
david@pgmasters.net
In reply to: Andrey Borodin (#27)
Re: Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 3/6/19 5:38 PM, Andrey Borodin wrote:

20 февр. 2019 г., в 17:06, Alexey Kondratov <a.kondratov@postgrespro.ru> написал(а):

The new patch is much smaller (less than 400 lines) and works as advertised.
There's a typo "retreive" there.

These lines look a little suspicious:
char postgres_exec_path[MAXPGPATH],
postgres_cmd[MAXPGPATH],
cmd_output[MAX_RESTORE_COMMAND];
Is it supposed to be any difference between MAXPGPATH and MAX_RESTORE_COMMAND?

Besides this, patch looks fine to me.

This patch appears to need attention from the author so I have marked it
Waiting on Author.

Regards,
--
-David
david@pgmasters.net

#29Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: David Steele (#28)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 07.03.2019 10:26, David Steele wrote:

On 3/6/19 5:38 PM, Andrey Borodin wrote:

The new patch is much smaller (less than 400 lines) and works as
advertised.
There's a typo "retreive" there.

Ough, corrected this in three different places. Not my word, definitely.
Thanks!

These lines look a little suspicious:
        char  postgres_exec_path[MAXPGPATH],
                  postgres_cmd[MAXPGPATH],
                  cmd_output[MAX_RESTORE_COMMAND];
Is it supposed to be any difference between MAXPGPATH and
MAX_RESTORE_COMMAND?

Yes, it was supposed to be, but after your message I have double checked
everything and figured out that we use MAXPGPATH for final
restore_command build (with all aliases replaced). Thus, there is no
need in a separated constant. I have replaced it with MAXPGPATH.

This patch appears to need attention from the author so I have marked
it Waiting on Author.

I hope I have addressed all issues in the new patch version which is
attached. Also, I have added more detailed explanation of new
functionality into the multi-line commit-message.

Regards,

--
Alexey Kondratov

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

Attachments:

v5-0001-pg_rewind-options-to-use-restore_command.patchtext/x-patch; name=v5-0001-pg_rewind-options-to-use-restore_command.patchDownload
From 9770cab4909a3cd98c2db2b8a9fa4af1fedd4614 Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <kondratov.aleksey@gmail.com>
Date: Tue, 19 Feb 2019 19:14:53 +0300
Subject: [PATCH v5] pg_rewind: options to use restore_command from command
 line or cluster config

Previously, when pg_rewind could not find required WAL files in the
target data directory the rewind process would fail. One had to
manually figure out which of required WAL files have already moved to
the archival storage and copy them back.

This patch adds possibility to specify restore_command via command
line option or use one specified inside postgresql.conf. Specified
restore_command will be used for automatic retrieval of missing WAL
files from archival storage.
---
 doc/src/sgml/ref/pg_rewind.sgml       |  30 ++++-
 src/bin/pg_rewind/parsexlog.c         | 161 +++++++++++++++++++++++++-
 src/bin/pg_rewind/pg_rewind.c         |  96 ++++++++++++++-
 src/bin/pg_rewind/pg_rewind.h         |   7 +-
 src/bin/pg_rewind/t/001_basic.pl      |   4 +-
 src/bin/pg_rewind/t/002_databases.pl  |   4 +-
 src/bin/pg_rewind/t/003_extrafiles.pl |   4 +-
 src/bin/pg_rewind/t/RewindTest.pm     |  84 +++++++++++++-
 8 files changed, 370 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 53a64ee29e..90e3f22f97 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -67,8 +67,10 @@ PostgreSQL documentation
    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
+   files might no longer be present. In that case, they can be automatically
+   copied by <application>pg_rewind</application> from the WAL archive to the 
+   <filename>pg_wal</filename> directory if either <literal>-r</literal> or
+   <literal>-R</literal> option is specified, or
    fetched on startup by configuring <xref linkend="guc-primary-conninfo"/> or
    <xref linkend="guc-restore-command"/>.  The use of
    <application>pg_rewind</application> is not limited to failover, e.g.  a standby
@@ -200,6 +202,30 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-r</option></term>
+      <term><option>--use-postgresql-conf</option></term>
+      <listitem>
+       <para>
+        Use restore_command in the <filename>postgresql.conf</filename> to
+        retrieve missing in the target <filename>pg_wal</filename> directory
+        WAL files from the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-R <replaceable class="parameter">restore_command</replaceable></option></term>
+      <term><option>--restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the restore_command to use for retrieval of the missing
+        in the target <filename>pg_wal</filename> directory WAL files from
+        the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index e19c265cbb..6be6dab7e0 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "pg_rewind.h"
 #include "filemap.h"
@@ -45,6 +46,7 @@ static char xlogfpath[MAXPGPATH];
 typedef struct XLogPageReadPrivate
 {
 	const char *datadir;
+	const char *restoreCommand;
 	int			tliIndex;
 } XLogPageReadPrivate;
 
@@ -53,6 +55,9 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
 				   int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
 				   TimeLineID *pageTLI);
 
+static int RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -60,7 +65,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;
@@ -69,6 +74,7 @@ extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restore_command;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -156,7 +162,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;
@@ -181,6 +187,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -291,9 +298,30 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
 
 		if (xlogreadfd < 0)
 		{
-			printf(_("could not open file \"%s\": %s\n"), xlogfpath,
-				   strerror(errno));
-			return -1;
+			/*
+			 * If we have no restore_command to execute, then exit.
+			 */
+			if (private->restoreCommand == NULL)
+			{
+				printf(_("could not open file \"%s\": %s\n"), xlogfpath,
+					   strerror(errno));
+				return -1;
+			}
+
+			/*
+			 * Since we have restore_command to execute, then try to retrieve
+			 * missing WAL file from the archive.
+			 */
+			xlogreadfd = RestoreArchivedWAL(private->datadir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand);
+
+			if (xlogreadfd < 0)
+				return -1;
+			else
+				pg_log(PG_DEBUG, "using restored from archive version of file \"%s\"\n",
+					   xlogfpath);
 		}
 	}
 
@@ -409,3 +437,126 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return a file descriptor of 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.
+ *
+ * This is a simplified and adapted to frontend version of
+ * RestoreArchivedFile function from transam/xlogarchive.c
+ */
+static int
+RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH],
+				xlogRestoreCmd[MAXPGPATH],
+			   *dp,
+			   *endp;
+	const char *sp;
+	int			rc,
+				xlogfd;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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 */
+					pg_fatal("restore_command with %%r cannot be used during rewind process.\n");
+					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';
+
+	/*
+	 * 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)
+			{
+				printf(_("archive file \"%s\" has wrong size: %lu instead of %lu, %s"),
+						xlogfname, (unsigned long) stat_buf.st_size,
+						(unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					printf(_("could not open restored from archive file \"%s\": %s\n"),
+							xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			printf(_("could not stat file \"%s\": %s"),
+					xlogpath, strerror(errno));
+		}
+	}
+
+	printf(_("could not restore file \"%s\" from archive: %s\n"),
+			xlogfname, strerror(errno));
+
+	return -1;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index aa753bb315..dd26dccb07 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -52,11 +52,13 @@ int			WalSegSz;
 char	   *datadir_target = NULL;
 char	   *datadir_source = NULL;
 char	   *connstr_source = NULL;
+char	   *restore_command = NULL;
 
 bool		debug = false;
 bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
+bool		restore_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -75,6 +77,9 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"));
 	printf(_("                                 safely to disk\n"));
 	printf(_("  -P, --progress                 write progress messages\n"));
+	printf(_("  -r, --use-postgresql-conf      use restore_command in the postgresql.conf to\n"));
+	printf(_("                                 retrieve WALs from archive\n"));
+	printf(_("  -R, --restore-command=COMMAND  restore_command\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
@@ -94,6 +99,8 @@ main(int argc, char **argv)
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
+		{"use-postgresql-conf", no_argument, NULL, 'r'},
+		{"restore-command", required_argument, NULL, 'R'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
@@ -129,7 +136,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "D:nNPR:r", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -141,6 +148,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'r':
+				restore_wals = true;
+				break;
+
 			case 'n':
 				dry_run = true;
 				break;
@@ -157,6 +168,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'R':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -223,6 +238,78 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	if (restore_command != NULL)
+	{
+		if (restore_wals)
+		{
+			fprintf(stderr, _("%s: conflicting options: both -r and -R are specified\n"),
+				progname);
+			fprintf(stderr, _("You must run %s with either -r/--use-postgresql-conf "
+							"or -R/--restore-command.\n"), progname);
+			exit(1);
+		}
+
+		pg_log(PG_DEBUG, "using command line restore_command=\'%s\'.\n", restore_command);
+	}
+	else if (restore_wals)
+	{
+		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)
+				fprintf(stderr,
+						_("the program \"postgres\" is needed by %s "
+						"but was not found in the\n"
+						"same directory as \"%s\".\n"
+						"Check your installation.\n"),
+						progname, full_path);
+			else
+				fprintf(stderr,
+						_("the program \"postgres\" was found by \"%s\"\n"
+						"but was not the same version as %s.\n"
+						"Check your installation.\n"),
+						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\n",
+					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\n");
+
+		restore_command = pg_strdup(cmd_output);
+
+		pg_log(PG_DEBUG, "using config variable restore_command=\'%s\'.\n", restore_command);
+	}
+
 	/* Connect to remote server */
 	if (connstr_source)
 		libpqConnect(connstr_source);
@@ -294,9 +381,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);
 	printf(_("rewinding from last common checkpoint at %X/%X on timeline %u\n"),
 		   (uint32) (chkptrec >> 32), (uint32) chkptrec,
 		   chkpttli);
@@ -319,7 +405,7 @@ main(int argc, char **argv)
 	 */
 	pg_log(PG_PROGRESS, "reading WAL in target\n");
 	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 83b2898b8b..08a753475c 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -32,11 +32,10 @@ extern int	targetNentries;
 
 /* 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);
+				   int tliIndex, XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
+				   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 115192170e..8a6fa33016 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 => 10;
+use Test::More tests => 20;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -106,5 +106,7 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 6dc05720a1..99ea821cfe 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 12;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -62,5 +62,7 @@ template1
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index c4040bd562..24cec256de 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 8;
 
 use File::Find;
 
@@ -90,5 +90,7 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 85cae7e47b..4560fe4430 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -39,7 +39,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catdir catfile);
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -197,6 +199,38 @@ sub promote_standby
 	return;
 }
 
+# Moves WAL files to the temporary location and returns restore_command
+# to get them back.
+sub move_wals
+{
+	my $tmp_dir = shift;
+	my $master_pgdata = shift;
+	my $wals_archive_dir = catdir($tmp_dir, "master_wals_archive");
+	my @wal_files = bsd_glob catfile($master_pgdata, "pg_wal", "0000000*");
+	my $restore_command;
+
+	remove_tree($wals_archive_dir);
+	make_path($wals_archive_dir) or die;
+
+	# Move all old master WAL files to the archive.
+	# Old master should be stopped at this point.
+	foreach my $wal_file (@wal_files)
+	{
+		move($wal_file, "$wals_archive_dir") or die;
+	}
+
+	if ($windows_os)
+	{
+		$restore_command = "copy $wals_archive_dir\\\%f \%p";
+	}
+	else
+	{
+		$restore_command = "cp $wals_archive_dir/\%f \%p";
+	}
+
+	return $restore_command;
+}
+
 sub run_pg_rewind
 {
 	my $test_mode       = shift;
@@ -249,6 +283,54 @@ sub run_pg_rewind
 			],
 			'pg_rewind remote');
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# 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",
+				"--restore-command=$restore_command"
+			],
+			'pg_rewind archive');
+	}
+	elsif ($test_mode eq "archive_conf")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Add restore_command to postgresql.conf of target cluster.
+		open(my $conf_fd, ">>", $master_conf_path) or die;
+		print $conf_fd "\nrestore_command='$restore_command'";
+		close $conf_fd;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"-r"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 

base-commit: 42210524cca3a6d3161bcef9d9e687c7c1f5f6c4
-- 
2.17.1

#30Andrey Borodin
x4mmm@yandex-team.ru
In reply to: Alexey Kondratov (#29)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi!

7 марта 2019 г., в 20:27, Alexey Kondratov <a.kondratov@postgrespro.ru> написал(а):

<v5-0001-pg_rewind-options-to-use-restore_command.patch>

I'm a bit confused by by console output routines. E.g. in pg_rewind's main() you call pg_fatal()s, and printf(), and pg_log() with various levels. Shouldn't we use all the pg_* functions?
But most of this printing usages were there before your patch.

I'm marking the patch as RFC, since I have no more notices and patch really looks good.

Best regards, Andrey Borodin.

#31Michael Paquier
michael@paquier.xyz
In reply to: Andrey Borodin (#30)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Wed, Mar 20, 2019 at 01:55:51PM +0800, Andrey Borodin wrote:

I'm a bit confused by by console output routines. E.g. in
pg_rewind's main() you call pg_fatal()s, and printf(), and pg_log()
with various levels. Shouldn't we use all the pg_* functions?

pg_fatal() would exit immediately, and sometimes the error code paths
want to have multiple lines, which is why printf() gets used.

But most of this printing usages were there before your patch.

I'm marking the patch as RFC, since I have no more notices and patch
really looks good.

That does not look fully baked yet, at least in my opinion.

+ * This is a simplified and adapted to frontend version of
+ * RestoreArchivedFile function from transam/xlogarchive.c
+ */
+static int
+RestoreArchivedWAL(const char *path, const char *xlogfname,
I don't think that we should have duplicates for that, so I would
recommend refactoring the code so as a unique code path is taken by
both, especially since the user can fetch the command from
postgresql.conf. 

Why two options? Wouldn't actually be enough use-postgresql-conf to
do the job? Note that "postgres" should always be installed if
pg_rewind is present because it is a backend-side utility, so while I
don't like adding a dependency to other binaries in one binary, having
an option to pass out a command directly via the command line of
pg_rewind stresses me more.

Don't we need to worry about signals interrupting the restore command?
It seems to me that some refactoring from the stuff in xlogarchive.c
would be in order.
--
Michael

#32Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#31)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 26.03.2019 11:19, Michael Paquier wrote:

+ * This is a simplified and adapted to frontend version of
+ * RestoreArchivedFile function from transam/xlogarchive.c
+ */
+static int
+RestoreArchivedWAL(const char *path, const char *xlogfname,
I don't think that we should have duplicates for that, so I would
recommend refactoring the code so as a unique code path is taken by
both, especially since the user can fetch the command from
postgresql.conf.

This comment is here since the beginning of my work on this patch and
now it is rather misleading.

Even if we does not take into account obvious differences like error
reporting, different log levels based on many conditions, cleanup
options, check for standby mode; restore_command execution at backend
recovery and during pg_rewind has a very important difference. If it
fails at backend, then as stated in the comment 'Remember, we
rollforward UNTIL the restore fails so failure here is just part of the
process' -- it is OK. In opposite, in pg_rewind if we failed to recover
some required WAL segment, then it definitely means the end of the
entire process, since we will fail at finding last common checkpoint or
extracting page map.

The only part we can share is constructing restore_command with aliases
replacement. However, even in this place the logic is slightly
different, since we do not need %r alias for pg_rewind. The only use
case of %r in restore_command I know is pg_standby, which seems to be as
not a case for pg_rewind. I have tried to move this part to the common,
but it becomes full of conditions and less concise.

Please, correct me if I am wrong, but it seems that there are enough
differences to keep this function separated, isn't it?

Why two options? Wouldn't actually be enough use-postgresql-conf to
do the job? Note that "postgres" should always be installed if
pg_rewind is present because it is a backend-side utility, so while I
don't like adding a dependency to other binaries in one binary, having
an option to pass out a command directly via the command line of
pg_rewind stresses me more.

I am not familiar enough with DBA scenarios, where -R option may be
useful, but I was asked a few times for that. I can only speculate that
for example someone may want to run freshly rewinded cluster as master,
not replica, so its config may differ from replica's one, where
restore_command is surely intended to be. Thus, it is easier to leave
master's config at the place and just specify restore_command as command
line argument.

Don't we need to worry about signals interrupting the restore command?
It seems to me that some refactoring from the stuff in xlogarchive.c
would be in order.

Thank you for pointing me to this place again. Previously, I thought
that we should not care about it, since if restore_command was not
successful due to any reason, then rewind failed, so we will stop and
exit at upper levels. However, if it was due to a signal, then some of
next messages may be misleading, if e.g. user manually interrupted it
for some reason. So that, I added a similar check here as well.

Updated version of patch is attached.

--
Alexey Kondratov

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

Attachments:

v6-0001-pg_rewind-options-to-use-restore_command.patchtext/x-patch; name=v6-0001-pg_rewind-options-to-use-restore_command.patchDownload
From 9e00f7a7696a88f350e1e328a9758ab85631c813 Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <kondratov.aleksey@gmail.com>
Date: Tue, 19 Feb 2019 19:14:53 +0300
Subject: [PATCH v6] pg_rewind: options to use restore_command from command
 line or cluster config

Previously, when pg_rewind could not find required WAL files in the
target data directory the rewind process would fail. One had to
manually figure out which of required WAL files have already moved to
the archival storage and copy them back.

This patch adds possibility to specify restore_command via command
line option or use one specified inside postgresql.conf. Specified
restore_command will be used for automatic retrieval of missing WAL
files from archival storage.
---
 doc/src/sgml/ref/pg_rewind.sgml       |  30 ++++-
 src/bin/pg_rewind/parsexlog.c         | 167 +++++++++++++++++++++++++-
 src/bin/pg_rewind/pg_rewind.c         |  96 ++++++++++++++-
 src/bin/pg_rewind/pg_rewind.h         |   7 +-
 src/bin/pg_rewind/t/001_basic.pl      |   4 +-
 src/bin/pg_rewind/t/002_databases.pl  |   4 +-
 src/bin/pg_rewind/t/003_extrafiles.pl |   4 +-
 src/bin/pg_rewind/t/RewindTest.pm     |  84 ++++++++++++-
 8 files changed, 376 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 53a64ee29e..90e3f22f97 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -67,8 +67,10 @@ PostgreSQL documentation
    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
+   files might no longer be present. In that case, they can be automatically
+   copied by <application>pg_rewind</application> from the WAL archive to the 
+   <filename>pg_wal</filename> directory if either <literal>-r</literal> or
+   <literal>-R</literal> option is specified, or
    fetched on startup by configuring <xref linkend="guc-primary-conninfo"/> or
    <xref linkend="guc-restore-command"/>.  The use of
    <application>pg_rewind</application> is not limited to failover, e.g.  a standby
@@ -200,6 +202,30 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-r</option></term>
+      <term><option>--use-postgresql-conf</option></term>
+      <listitem>
+       <para>
+        Use restore_command in the <filename>postgresql.conf</filename> to
+        retrieve missing in the target <filename>pg_wal</filename> directory
+        WAL files from the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-R <replaceable class="parameter">restore_command</replaceable></option></term>
+      <term><option>--restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the restore_command to use for retrieval of the missing
+        in the target <filename>pg_wal</filename> directory WAL files from
+        the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index e19c265cbb..3dc110be4e 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "pg_rewind.h"
 #include "filemap.h"
@@ -45,6 +46,7 @@ static char xlogfpath[MAXPGPATH];
 typedef struct XLogPageReadPrivate
 {
 	const char *datadir;
+	const char *restoreCommand;
 	int			tliIndex;
 } XLogPageReadPrivate;
 
@@ -53,6 +55,9 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
 				   int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
 				   TimeLineID *pageTLI);
 
+static int RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -60,7 +65,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;
@@ -69,6 +74,7 @@ extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restore_command;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -156,7 +162,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;
@@ -181,6 +187,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -291,9 +298,30 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
 
 		if (xlogreadfd < 0)
 		{
-			printf(_("could not open file \"%s\": %s\n"), xlogfpath,
-				   strerror(errno));
-			return -1;
+			/*
+			 * If we have no restore_command to execute, then exit.
+			 */
+			if (private->restoreCommand == NULL)
+			{
+				printf(_("could not open file \"%s\": %s\n"), xlogfpath,
+					   strerror(errno));
+				return -1;
+			}
+
+			/*
+			 * Since we have restore_command to execute, then try to retrieve
+			 * missing WAL file from the archive.
+			 */
+			xlogreadfd = RestoreArchivedWAL(private->datadir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand);
+
+			if (xlogreadfd < 0)
+				return -1;
+			else
+				pg_log(PG_DEBUG, "using restored from archive version of file \"%s\"\n",
+					   xlogfpath);
 		}
 	}
 
@@ -409,3 +437,132 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return a file descriptor of 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.
+ */
+static int
+RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH],
+				xlogRestoreCmd[MAXPGPATH],
+			   *dp,
+			   *endp;
+	const char *sp;
+	int			rc,
+				xlogfd;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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 */
+					pg_fatal("restore_command with %%r cannot be used with pg_rewind.\n");
+					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';
+
+	/*
+	 * 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)
+			{
+				printf(_("archive file \"%s\" has wrong size: %lu instead of %lu, %s"),
+						xlogfname, (unsigned long) stat_buf.st_size,
+						(unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					printf(_("could not open restored from archive file \"%s\": %s\n"),
+							xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			printf(_("could not stat file \"%s\": %s"),
+					xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s\n",
+				 wait_result_to_str(rc));
+
+	printf(_("could not restore file \"%s\" from archive\n"),
+			xlogfname);
+
+	return -1;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 3dcadb9b40..344b67f99b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -52,11 +52,13 @@ int			WalSegSz;
 char	   *datadir_target = NULL;
 char	   *datadir_source = NULL;
 char	   *connstr_source = NULL;
+char	   *restore_command = NULL;
 
 bool		debug = false;
 bool		showprogress = false;
 bool		dry_run = false;
 bool		do_sync = true;
+bool		restore_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -75,6 +77,9 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"));
 	printf(_("                                 safely to disk\n"));
 	printf(_("  -P, --progress                 write progress messages\n"));
+	printf(_("  -r, --use-postgresql-conf      use restore_command in the postgresql.conf to\n"));
+	printf(_("                                 retrieve WALs from archive\n"));
+	printf(_("  -R, --restore-command=COMMAND  restore_command\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
@@ -94,6 +99,8 @@ main(int argc, char **argv)
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
+		{"use-postgresql-conf", no_argument, NULL, 'r'},
+		{"restore-command", required_argument, NULL, 'R'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
@@ -129,7 +136,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "D:nNPR:r", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -141,6 +148,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'r':
+				restore_wals = true;
+				break;
+
 			case 'n':
 				dry_run = true;
 				break;
@@ -157,6 +168,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'R':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -223,6 +238,78 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	if (restore_command != NULL)
+	{
+		if (restore_wals)
+		{
+			fprintf(stderr, _("%s: conflicting options: both -r and -R are specified\n"),
+				progname);
+			fprintf(stderr, _("You must run %s with either -r/--use-postgresql-conf "
+							"or -R/--restore-command.\n"), progname);
+			exit(1);
+		}
+
+		pg_log(PG_DEBUG, "using command line restore_command=\'%s\'.\n", restore_command);
+	}
+	else if (restore_wals)
+	{
+		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)
+				fprintf(stderr,
+						_("the program \"postgres\" is needed by %s "
+						"but was not found in the\n"
+						"same directory as \"%s\".\n"
+						"Check your installation.\n"),
+						progname, full_path);
+			else
+				fprintf(stderr,
+						_("the program \"postgres\" was found by \"%s\"\n"
+						"but was not the same version as %s.\n"
+						"Check your installation.\n"),
+						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\n",
+					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\n");
+
+		restore_command = pg_strdup(cmd_output);
+
+		pg_log(PG_DEBUG, "using config variable restore_command=\'%s\'.\n", restore_command);
+	}
+
 	/* Connect to remote server */
 	if (connstr_source)
 		libpqConnect(connstr_source);
@@ -294,9 +381,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);
 	printf(_("rewinding from last common checkpoint at %X/%X on timeline %u\n"),
 		   (uint32) (chkptrec >> 32), (uint32) chkptrec,
 		   chkpttli);
@@ -319,7 +405,7 @@ main(int argc, char **argv)
 	 */
 	pg_log(PG_PROGRESS, "reading WAL in target\n");
 	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 83b2898b8b..08a753475c 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -32,11 +32,10 @@ extern int	targetNentries;
 
 /* 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);
+				   int tliIndex, XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
+				   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 115192170e..8a6fa33016 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 => 10;
+use Test::More tests => 20;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -106,5 +106,7 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 0562c21549..f42fb5a068 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 12;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -70,5 +70,7 @@ template1
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index c4040bd562..24cec256de 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 8;
 
 use File::Find;
 
@@ -90,5 +90,7 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 900d452d8b..3a58376347 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -39,7 +39,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catdir catfile);
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -199,6 +201,38 @@ sub promote_standby
 	return;
 }
 
+# Moves WAL files to the temporary location and returns restore_command
+# to get them back.
+sub move_wals
+{
+	my $tmp_dir = shift;
+	my $master_pgdata = shift;
+	my $wals_archive_dir = catdir($tmp_dir, "master_wals_archive");
+	my @wal_files = bsd_glob catfile($master_pgdata, "pg_wal", "0000000*");
+	my $restore_command;
+
+	remove_tree($wals_archive_dir);
+	make_path($wals_archive_dir) or die;
+
+	# Move all old master WAL files to the archive.
+	# Old master should be stopped at this point.
+	foreach my $wal_file (@wal_files)
+	{
+		move($wal_file, "$wals_archive_dir") or die;
+	}
+
+	if ($windows_os)
+	{
+		$restore_command = "copy $wals_archive_dir\\\%f \%p";
+	}
+	else
+	{
+		$restore_command = "cp $wals_archive_dir/\%f \%p";
+	}
+
+	return $restore_command;
+}
+
 sub run_pg_rewind
 {
 	my $test_mode       = shift;
@@ -251,6 +285,54 @@ sub run_pg_rewind
 			],
 			'pg_rewind remote');
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# 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",
+				"--restore-command=$restore_command"
+			],
+			'pg_rewind archive');
+	}
+	elsif ($test_mode eq "archive_conf")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Add restore_command to postgresql.conf of target cluster.
+		open(my $conf_fd, ">>", $master_conf_path) or die;
+		print $conf_fd "\nrestore_command='$restore_command'";
+		close $conf_fd;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"-r"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 

base-commit: c8c885b7a5c8c1175288de1d8aaec3b4ae9050e1
-- 
2.17.1

#33Thomas Munro
thomas.munro@gmail.com
In reply to: Alexey Kondratov (#32)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Thu, Mar 28, 2019 at 6:49 AM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:

Updated version of patch is attached.

Hi Alexey,

This no longer applies. Since the Commitfest is starting now, could
you please rebase it?

Thanks,

--
Thomas Munro
https://enterprisedb.com

#34Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Thomas Munro (#33)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi Thomas,

On 01.07.2019 15:02, Thomas Munro wrote:

Hi Alexey,

This no longer applies. Since the Commitfest is starting now, could
you please rebase it?

Thank you for a reminder. Rebased version of the patch is attached. I've
also modified my logging code in order to obey new unified logging
system for command-line programs commited by Peter (cc8d415117).

Regards

--
Alexey Kondratov

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

Attachments:

v7-0001-pg_rewind-options-to-use-restore_command.patchtext/x-patch; name=v7-0001-pg_rewind-options-to-use-restore_command.patchDownload
From f5f359274322020c2338b5b494f6327eaa61c0e1 Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <kondratov.aleksey@gmail.com>
Date: Tue, 19 Feb 2019 19:14:53 +0300
Subject: [PATCH v7] pg_rewind: options to use restore_command from command
 line or cluster config

Previously, when pg_rewind could not find required WAL files in the
target data directory the rewind process would fail. One had to
manually figure out which of required WAL files have already moved to
the archival storage and copy them back.

This patch adds possibility to specify restore_command via command
line option or use one specified inside postgresql.conf. Specified
restore_command will be used for automatic retrieval of missing WAL
files from archival storage.
---
 doc/src/sgml/ref/pg_rewind.sgml       |  30 ++++-
 src/bin/pg_rewind/parsexlog.c         | 164 +++++++++++++++++++++++++-
 src/bin/pg_rewind/pg_rewind.c         |  92 ++++++++++++++-
 src/bin/pg_rewind/pg_rewind.h         |   6 +-
 src/bin/pg_rewind/t/001_basic.pl      |   4 +-
 src/bin/pg_rewind/t/002_databases.pl  |   4 +-
 src/bin/pg_rewind/t/003_extrafiles.pl |   4 +-
 src/bin/pg_rewind/t/RewindTest.pm     |  84 ++++++++++++-
 8 files changed, 371 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 4d91eeb0ff..746c07e4df 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -67,8 +67,10 @@ PostgreSQL documentation
    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
+   files might no longer be present. In that case, they can be automatically
+   copied by <application>pg_rewind</application> from the WAL archive to the 
+   <filename>pg_wal</filename> directory if either <literal>-r</literal> or
+   <literal>-R</literal> option is specified, or
    fetched on startup by configuring <xref linkend="guc-primary-conninfo"/> or
    <xref linkend="guc-restore-command"/>.  The use of
    <application>pg_rewind</application> is not limited to failover, e.g.  a standby
@@ -202,6 +204,30 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-r</option></term>
+      <term><option>--use-postgresql-conf</option></term>
+      <listitem>
+       <para>
+        Use restore_command in the <filename>postgresql.conf</filename> to
+        retrieve missing in the target <filename>pg_wal</filename> directory
+        WAL files from the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-R <replaceable class="parameter">restore_command</replaceable></option></term>
+      <term><option>--restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the restore_command to use for retrieval of the missing
+        in the target <filename>pg_wal</filename> directory WAL files from
+        the WAL archive.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 287af60c4e..d1de08320c 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "pg_rewind.h"
 #include "filemap.h"
@@ -44,6 +45,7 @@ static char xlogfpath[MAXPGPATH];
 typedef struct XLogPageReadPrivate
 {
 	const char *datadir;
+	const char *restoreCommand;
 	int			tliIndex;
 } XLogPageReadPrivate;
 
@@ -52,6 +54,9 @@ static int	SimpleXLogPageRead(XLogReaderState *xlogreader,
 							   int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
 							   TimeLineID *pageTLI);
 
+static int RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -59,7 +64,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;
@@ -68,6 +73,7 @@ extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restore_command;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -155,7 +161,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;
@@ -180,6 +186,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -290,8 +297,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 to execute, then try to retrieve
+			 * missing WAL file from the archive.
+			 */
+			xlogreadfd = RestoreArchivedWAL(private->datadir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand);
+
+			if (xlogreadfd < 0)
+				return -1;
+			else
+				pg_log_debug("using restored from archive version of file \"%s\"",
+							 xlogfpath);
 		}
 	}
 
@@ -405,3 +433,131 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return a file descriptor of 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.
+ */
+static int
+RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH],
+				xlogRestoreCmd[MAXPGPATH],
+			   *dp,
+			   *endp;
+	const char *sp;
+	int			rc,
+				xlogfd;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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 */
+					pg_fatal("restore_command with %%r cannot be used with pg_rewind.");
+					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';
+
+	/*
+	 * 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("archive file \"%s\" has wrong size: %lu instead of %lu, %s",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open restored from archive file \"%s\": %s\n",
+								 xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\": %s",
+						 xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s",
+				 wait_result_to_str(rc));
+
+	pg_log_error("could not restore file \"%s\" from archive\n",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 6e77201be6..3f9bf0d8f5 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -51,11 +51,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_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -79,6 +81,9 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
 			 "                                 safely to disk\n"));
 	printf(_("  -P, --progress                 write progress messages\n"));
+	printf(_("  -r, --use-postgresql-conf      use restore_command in the postgresql.conf to\n"));
+	printf(_("                                 retrieve WALs from archive\n"));
+	printf(_("  -R, --restore-command=COMMAND  restore_command\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
@@ -98,6 +103,8 @@ main(int argc, char **argv)
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
+		{"use-postgresql-conf", no_argument, NULL, 'r'},
+		{"restore-command", required_argument, NULL, 'R'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
@@ -134,7 +141,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "D:nNPR:r", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -146,6 +153,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'r':
+				restore_wals = true;
+				break;
+
 			case 'n':
 				dry_run = true;
 				break;
@@ -163,6 +174,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'R':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -229,6 +244,74 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	if (restore_command != NULL)
+	{
+		if (restore_wals)
+		{
+			pg_log_error("conflicting options: both -r and -R are specified");
+			fprintf(stderr, _("You must run %s with either -r/--use-postgresql-conf "
+							"or -R/--restore-command.\n"), progname);
+			exit(1);
+		}
+
+		pg_log_debug("using command line restore_command=\'%s\'.", restore_command);
+	}
+	else if (restore_wals)
+	{
+		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);
+	}
+
 	/* Connect to remote server */
 	if (connstr_source)
 		libpqConnect(connstr_source);
@@ -300,9 +383,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);
@@ -328,7 +410,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 1125c7e60f..7dc972dc39 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -40,11 +40,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 115192170e..8a6fa33016 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 => 10;
+use Test::More tests => 20;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -106,5 +106,7 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index f1eb4fe1d2..69a3c69a85 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 12;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -70,5 +70,7 @@ template1
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index c4040bd562..24cec256de 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 8;
 
 use File::Find;
 
@@ -90,5 +90,7 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 68b6004e94..9f39224192 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -35,7 +35,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catdir catfile);
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -216,6 +218,38 @@ sub promote_standby
 	return;
 }
 
+# Moves WAL files to the temporary location and returns restore_command
+# to get them back.
+sub move_wals
+{
+	my $tmp_dir = shift;
+	my $master_pgdata = shift;
+	my $wals_archive_dir = catdir($tmp_dir, "master_wals_archive");
+	my @wal_files = bsd_glob catfile($master_pgdata, "pg_wal", "0000000*");
+	my $restore_command;
+
+	remove_tree($wals_archive_dir);
+	make_path($wals_archive_dir) or die;
+
+	# Move all old master WAL files to the archive.
+	# Old master should be stopped at this point.
+	foreach my $wal_file (@wal_files)
+	{
+		move($wal_file, "$wals_archive_dir") or die;
+	}
+
+	if ($windows_os)
+	{
+		$restore_command = "copy $wals_archive_dir\\\%f \%p";
+	}
+	else
+	{
+		$restore_command = "cp $wals_archive_dir/\%f \%p";
+	}
+
+	return $restore_command;
+}
+
 sub run_pg_rewind
 {
 	my $test_mode       = shift;
@@ -270,6 +304,54 @@ sub run_pg_rewind
 			],
 			'pg_rewind remote');
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# 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",
+				"--restore-command=$restore_command"
+			],
+			'pg_rewind archive');
+	}
+	elsif ($test_mode eq "archive_conf")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Add restore_command to postgresql.conf of target cluster.
+		open(my $conf_fd, ">>", $master_conf_path) or die;
+		print $conf_fd "\nrestore_command='$restore_command'";
+		close $conf_fd;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"-r"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 

base-commit: 95bbe5d82e428db342fa3ec60b95f1b9873741e5
-- 
2.17.1

#35Liudmila Mantrova
l.mantrova@postgrespro.ru
In reply to: Alexey Kondratov (#34)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 7/1/19 5:20 PM, Alexey Kondratov wrote:

Hi Thomas,

On 01.07.2019 15:02, Thomas Munro wrote:

Hi Alexey,

This no longer applies.  Since the Commitfest is starting now, could
you please rebase it?

Thank you for a reminder. Rebased version of the patch is attached.
I've also modified my logging code in order to obey new unified
logging system for command-line programs commited by Peter (cc8d415117).

Regards

Hi Alexey,

I would like to suggest a couple of changes to docs and comments, please
see the attachment.
The "...or fetched on startup" part also seems wrong here, but it's not
a part of your patch, so I'm going to ask about it on psql-docs separately.

It might also be useful to reword the following error messages:
- "using restored from archive version of file \"%s\""
- "could not open restored from archive file \"%s\"
We could probably say something like "could not open file \"%s\"
restored from WAL archive" instead.

On a more general note, I wonder if everyone is happy with the
--using-postgresql-conf option name, or we should continue searching for
a narrower term. Unfortunately, I don't have any better suggestions
right now, but I believe it should be clear that its purpose is to fetch
missing WAL files for target. What do you think?

--
Liudmila Mantrova
Technical writer at Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v8-0001-pg_rewind-options-to-use-restore_command.patchtext/x-patch; name=v8-0001-pg_rewind-options-to-use-restore_command.patchDownload
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 52a1caa..7e76fcc 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -66,9 +66,12 @@ 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
+   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>-r</literal> or
+   <literal>-R</literal> option to automatically retrieve them from the WAL
+   archive, or
    fetched on startup by configuring <xref linkend="guc-primary-conninfo"/> or
    <xref linkend="guc-restore-command"/>.  The use of
    <application>pg_rewind</application> is not limited to failover, e.g.  a standby
@@ -203,6 +206,39 @@ PostgreSQL documentation
      </varlistentry>
 
      <varlistentry>
+      <term><option>-r</option></term>
+      <term><option>--use-postgresql-conf</option></term>
+      <listitem>
+       <para>
+        Use the <varname>restore_command</varname> defined in
+        <filename>postgresql.conf</filename> to retrieve WAL files from
+        the WAL archive if these files are no longer available in the
+        <filename>pg_wal</filename> directory of the target cluster.
+       </para>
+       <para>
+        This option cannot be used together with <option>--restore-command</option>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-R <replaceable class="parameter">restore_command</replaceable></option></term>
+      <term><option>--restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the <varname>restore_command</varname> to use for retrieving
+        WAL files from the WAL archive if these files are no longer available
+        in the <filename>pg_wal</filename> directory of the target cluster.
+       </para>
+       <para>
+        If <varname>restore_command</varname> is already set in
+        <filename>postgresql.conf</filename>, you can provide the
+        <option>--use-postgresql-conf</option> option instead.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
        <para>
@@ -288,7 +324,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>-r</option> or <option>-R</option> option to search
+      for the missing files in the WAL archive.
      </para>
     </step>
     <step>
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 287af60..5a7f759 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "pg_rewind.h"
 #include "filemap.h"
@@ -44,6 +45,7 @@ static char xlogfpath[MAXPGPATH];
 typedef struct XLogPageReadPrivate
 {
 	const char *datadir;
+	const char *restoreCommand;
 	int			tliIndex;
 } XLogPageReadPrivate;
 
@@ -52,6 +54,9 @@ static int	SimpleXLogPageRead(XLogReaderState *xlogreader,
 							   int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
 							   TimeLineID *pageTLI);
 
+static int RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -59,7 +64,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;
@@ -68,6 +73,7 @@ extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restore_command;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -155,7 +161,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;
@@ -180,6 +186,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -290,8 +297,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 to execute, then try to retrieve
+			 * missing WAL file from the archive.
+			 */
+			xlogreadfd = RestoreArchivedWAL(private->datadir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand);
+
+			if (xlogreadfd < 0)
+				return -1;
+			else
+				pg_log_debug("using restored from archive version of file \"%s\"",
+							 xlogfpath);
 		}
 	}
 
@@ -405,3 +433,131 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from offline 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
+ * unknown, set expectedSize = 0.
+ */
+static int
+RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH],
+				xlogRestoreCmd[MAXPGPATH],
+			   *dp,
+			   *endp;
+	const char *sp;
+	int			rc,
+				xlogfd;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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 */
+					pg_fatal("restore_command with %%r cannot be used with pg_rewind.");
+					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';
+
+	/*
+	 * 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("archive file \"%s\" has wrong size: %lu instead of %lu, %s",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open restored from archive file \"%s\": %s\n",
+								 xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\": %s",
+						 xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s",
+				 wait_result_to_str(rc));
+
+	pg_log_error("could not restore file \"%s\" from archive\n",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index d378053..8e06343 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -51,11 +51,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_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -79,6 +81,10 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
 			 "                                 safely to disk\n"));
 	printf(_("  -P, --progress                 write progress messages\n"));
+	printf(_("  -r, --use-postgresql-conf      use restore_command set in postgresql.conf to\n"));
+	printf(_("                                 retrieve WAL files from archive\n"));
+	printf(_("  -R, --restore-command=COMMAND  restore_command to retrieve WAL files\n"));
+	printf(_("                                 from archive\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
@@ -98,6 +104,8 @@ main(int argc, char **argv)
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
+		{"use-postgresql-conf", no_argument, NULL, 'r'},
+		{"restore-command", required_argument, NULL, 'R'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
@@ -134,7 +142,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "D:nNPR:r", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -146,6 +154,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'r':
+				restore_wals = true;
+				break;
+
 			case 'n':
 				dry_run = true;
 				break;
@@ -163,6 +175,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'R':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -229,6 +245,74 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	if (restore_command != NULL)
+	{
+		if (restore_wals)
+		{
+			pg_log_error("conflicting options: both -r and -R are specified");
+			fprintf(stderr, _("You must run %s with either -r/--use-postgresql-conf "
+							"or -R/--restore-command.\n"), progname);
+			exit(1);
+		}
+
+		pg_log_debug("using command line restore_command=\'%s\'.", restore_command);
+	}
+	else if (restore_wals)
+	{
+		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 configuration variable restore_command=\'%s\'.", restore_command);
+	}
+
 	/* Connect to remote server */
 	if (connstr_source)
 		libpqConnect(connstr_source);
@@ -300,9 +384,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);
@@ -328,7 +411,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 1125c7e..7dc972d 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -40,11 +40,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 1151921..8a6fa33 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 => 10;
+use Test::More tests => 20;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -106,5 +106,7 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index f1eb4fe..69a3c69 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 12;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -70,5 +70,7 @@ template1
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index c4040bd..24cec25 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 8;
 
 use File::Find;
 
@@ -90,5 +90,7 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 68b6004..ff9be13 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -35,7 +35,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catdir catfile);
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -216,6 +218,38 @@ sub promote_standby
 	return;
 }
 
+# Moves WAL files to a temporary location and returns restore_command
+# to get them back.
+sub move_wals
+{
+	my $tmp_dir = shift;
+	my $master_pgdata = shift;
+	my $wals_archive_dir = catdir($tmp_dir, "master_wals_archive");
+	my @wal_files = bsd_glob catfile($master_pgdata, "pg_wal", "0000000*");
+	my $restore_command;
+
+	remove_tree($wals_archive_dir);
+	make_path($wals_archive_dir) or die;
+
+	# Move all old master WAL files to the archive.
+	# Old master should be stopped at this point.
+	foreach my $wal_file (@wal_files)
+	{
+		move($wal_file, "$wals_archive_dir") or die;
+	}
+
+	if ($windows_os)
+	{
+		$restore_command = "copy $wals_archive_dir\\\%f \%p";
+	}
+	else
+	{
+		$restore_command = "cp $wals_archive_dir/\%f \%p";
+	}
+
+	return $restore_command;
+}
+
 sub run_pg_rewind
 {
 	my $test_mode       = shift;
@@ -270,6 +304,54 @@ sub run_pg_rewind
 			],
 			'pg_rewind remote');
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# the specified directory with target WAL archive.
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# 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",
+				"--restore-command=$restore_command"
+			],
+			'pg_rewind archive');
+	}
+	elsif ($test_mode eq "archive_conf")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# the specified directory with target WAL archive.
+		my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Add restore_command to postgresql.conf of the target cluster.
+		open(my $conf_fd, ">>", $master_conf_path) or die;
+		print $conf_fd "\nrestore_command='$restore_command'";
+		close $conf_fd;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"-r"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 
#36Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Liudmila Mantrova (#35)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 26.07.2019 20:43, Liudmila Mantrova wrote:

I would like to suggest a couple of changes to docs and comments,
please see the attachment.
The "...or fetched on startup" part also seems wrong here, but it's
not a part of your patch, so I'm going to ask about it on psql-docs
separately.

Agreed, thank you a lot! Yes, "...or fetched on startup" looks a bit
confusing for me, since the whole paragraph is about target server
before running pg_rewind, but this statement is more about target server
started first time after running pg_rewind, which is discussed in the
next paragraph.

It might also be useful to reword the following error messages:
- "using restored from archive version of file \"%s\""
- "could not open restored from archive file \"%s\"
We could probably say something like "could not open file \"%s\"
restored from WAL archive" instead.

I have reworded these and some similar messages, thanks. New patch with
changed messages is attached.

On a more general note, I wonder if everyone is happy with the
--using-postgresql-conf option name, or we should continue searching
for a narrower term. Unfortunately, I don't have any better
suggestions right now, but I believe it should be clear that its
purpose is to fetch missing WAL files for target. What do you think?

I don't like it either, but this one was my best guess then. Maybe
--restore-target-wal instead of --using-postgresql-conf will be better?
And --target-restore-command instead of --restore-command if we want to
specify that this is restore_command for target server?

Regards

--
Alexey Kondratov

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

Attachments:

v9-0001-pg_rewind-options-to-use-restore_command-from-com.patchtext/x-patch; name=v9-0001-pg_rewind-options-to-use-restore_command-from-com.patchDownload
From 328ed78356e2b270ffe4c84baa462eb6b8e6befb Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <kondratov.aleksey@gmail.com>
Date: Tue, 19 Feb 2019 19:14:53 +0300
Subject: [PATCH v9] pg_rewind: options to use restore_command from command
 line or cluster config

Previously, when pg_rewind could not find required WAL files in the
target data directory the rewind process would fail. One had to
manually figure out which of required WAL files have already moved to
the archival storage and copy them back.

This patch adds possibility to specify restore_command via command
line option or use one specified inside postgresql.conf. Specified
restore_command will be used for automatic retrieval of missing WAL
files from archival storage.
---
 doc/src/sgml/ref/pg_rewind.sgml       |  49 +++++++-
 src/bin/pg_rewind/parsexlog.c         | 164 +++++++++++++++++++++++++-
 src/bin/pg_rewind/pg_rewind.c         |  92 ++++++++++++++-
 src/bin/pg_rewind/pg_rewind.h         |   6 +-
 src/bin/pg_rewind/t/001_basic.pl      |   4 +-
 src/bin/pg_rewind/t/002_databases.pl  |   4 +-
 src/bin/pg_rewind/t/003_extrafiles.pl |   4 +-
 src/bin/pg_rewind/t/RewindTest.pm     |  84 ++++++++++++-
 8 files changed, 386 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 52a1caa246..d5a14a2e08 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -66,11 +66,12 @@ 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>-r</literal> or
+   <literal>-R</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.
@@ -202,6 +203,39 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-r</option></term>
+      <term><option>--use-postgresql-conf</option></term>
+      <listitem>
+       <para>
+        Use the <varname>restore_command</varname> defined in
+        <filename>postgresql.conf</filename> to retrieve WAL files from
+        the WAL archive if these files are no longer available in the
+        <filename>pg_wal</filename> directory of the target cluster.
+       </para>
+       <para>
+        This option cannot be used together with <option>--restore-command</option>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-R <replaceable class="parameter">restore_command</replaceable></option></term>
+      <term><option>--restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the <varname>restore_command</varname> to use for retrieving
+        WAL files from the WAL archive if these files are no longer available
+        in the <filename>pg_wal</filename> directory of the target cluster.
+       </para>
+       <para>
+        If <varname>restore_command</varname> is already set in
+        <filename>postgresql.conf</filename>, you can provide the
+        <option>--use-postgresql-conf</option> option instead.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
@@ -288,7 +322,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>-r</option> or <option>-R</option> option to search
+      for the missing files in the WAL archive.
      </para>
     </step>
     <step>
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index f0439edce3..6326e9a899 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "pg_rewind.h"
 #include "filemap.h"
@@ -44,6 +45,7 @@ static char xlogfpath[MAXPGPATH];
 typedef struct XLogPageReadPrivate
 {
 	const char *datadir;
+	const char *restoreCommand;
 	int			tliIndex;
 } XLogPageReadPrivate;
 
@@ -52,6 +54,9 @@ static int	SimpleXLogPageRead(XLogReaderState *xlogreader,
 							   int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
 							   TimeLineID *pageTLI);
 
+static int RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -59,7 +64,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;
@@ -68,6 +73,7 @@ extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restore_command;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -154,7 +160,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;
@@ -179,6 +185,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 
 	private.datadir = datadir;
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -289,8 +296,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 to execute, then try to retrieve
+			 * missing WAL file from the archive.
+			 */
+			xlogreadfd = RestoreArchivedWAL(private->datadir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand);
+
+			if (xlogreadfd < 0)
+				return -1;
+			else
+				pg_log_debug("using file \"%s\" restored from archive",
+							 xlogfpath);
 		}
 	}
 
@@ -404,3 +432,131 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return a file descriptor of 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.
+ */
+static int
+RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH],
+				xlogRestoreCmd[MAXPGPATH],
+			   *dp,
+			   *endp;
+	const char *sp;
+	int			rc,
+				xlogfd;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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 */
+					pg_fatal("restore_command with %%r cannot be used with pg_rewind.");
+					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';
+
+	/*
+	 * 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("archive file \"%s\" has wrong size: %lu instead of %lu, %s",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open file \"%s\" restored from archive: %s\n",
+								 xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\" restored from archive: %s",
+						 xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s",
+				 wait_result_to_str(rc));
+
+	pg_log_error("could not restore file \"%s\" from archive\n",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index d378053de4..1633e0271d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -51,11 +51,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_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -79,6 +81,9 @@ usage(const char *progname)
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
 			 "                                 safely to disk\n"));
 	printf(_("  -P, --progress                 write progress messages\n"));
+	printf(_("  -r, --use-postgresql-conf      use restore_command in the postgresql.conf to\n"));
+	printf(_("                                 retrieve WALs from archive\n"));
+	printf(_("  -R, --restore-command=COMMAND  restore_command\n"));
 	printf(_("      --debug                    write a lot of debug messages\n"));
 	printf(_("  -V, --version                  output version information, then exit\n"));
 	printf(_("  -?, --help                     show this help, then exit\n"));
@@ -98,6 +103,8 @@ main(int argc, char **argv)
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
+		{"use-postgresql-conf", no_argument, NULL, 'r'},
+		{"restore-command", required_argument, NULL, 'R'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
@@ -134,7 +141,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "D:nNPR:r", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -146,6 +153,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'r':
+				restore_wals = true;
+				break;
+
 			case 'n':
 				dry_run = true;
 				break;
@@ -163,6 +174,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'R':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -229,6 +244,74 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	if (restore_command != NULL)
+	{
+		if (restore_wals)
+		{
+			pg_log_error("conflicting options: both -r and -R are specified");
+			fprintf(stderr, _("You must run %s with either -r/--use-postgresql-conf "
+							"or -R/--restore-command.\n"), progname);
+			exit(1);
+		}
+
+		pg_log_debug("using command line restore_command=\'%s\'.", restore_command);
+	}
+	else if (restore_wals)
+	{
+		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);
+	}
+
 	/* Connect to remote server */
 	if (connstr_source)
 		libpqConnect(connstr_source);
@@ -300,9 +383,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);
@@ -328,7 +410,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 1125c7e60f..7dc972dc39 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -40,11 +40,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 115192170e..8a6fa33016 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 => 10;
+use Test::More tests => 20;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -106,5 +106,7 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index f1eb4fe1d2..69a3c69a85 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 12;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -70,5 +70,7 @@ template1
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index c4040bd562..24cec256de 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 8;
 
 use File::Find;
 
@@ -90,5 +90,7 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 68b6004e94..9f39224192 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -35,7 +35,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catdir catfile);
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -216,6 +218,38 @@ sub promote_standby
 	return;
 }
 
+# Moves WAL files to the temporary location and returns restore_command
+# to get them back.
+sub move_wals
+{
+	my $tmp_dir = shift;
+	my $master_pgdata = shift;
+	my $wals_archive_dir = catdir($tmp_dir, "master_wals_archive");
+	my @wal_files = bsd_glob catfile($master_pgdata, "pg_wal", "0000000*");
+	my $restore_command;
+
+	remove_tree($wals_archive_dir);
+	make_path($wals_archive_dir) or die;
+
+	# Move all old master WAL files to the archive.
+	# Old master should be stopped at this point.
+	foreach my $wal_file (@wal_files)
+	{
+		move($wal_file, "$wals_archive_dir") or die;
+	}
+
+	if ($windows_os)
+	{
+		$restore_command = "copy $wals_archive_dir\\\%f \%p";
+	}
+	else
+	{
+		$restore_command = "cp $wals_archive_dir/\%f \%p";
+	}
+
+	return $restore_command;
+}
+
 sub run_pg_rewind
 {
 	my $test_mode       = shift;
@@ -270,6 +304,54 @@ sub run_pg_rewind
 			],
 			'pg_rewind remote');
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# 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",
+				"--restore-command=$restore_command"
+			],
+			'pg_rewind archive');
+	}
+	elsif ($test_mode eq "archive_conf")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Add restore_command to postgresql.conf of target cluster.
+		open(my $conf_fd, ">>", $master_conf_path) or die;
+		print $conf_fd "\nrestore_command='$restore_command'";
+		close $conf_fd;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"-r"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 

base-commit: da9456d22a7697ef2c5ba9dd1402d948b2ec7f09
-- 
2.17.1

#37Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Alexey Kondratov (#36)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 01.08.2019 19:53, Alexey Kondratov wrote:

On 26.07.2019 20:43, Liudmila Mantrova wrote:

On a more general note, I wonder if everyone is happy with the
--using-postgresql-conf option name, or we should continue searching
for a narrower term. Unfortunately, I don't have any better
suggestions right now, but I believe it should be clear that its
purpose is to fetch missing WAL files for target. What do you think?

I don't like it either, but this one was my best guess then. Maybe
--restore-target-wal instead of --using-postgresql-conf will be
better? And --target-restore-command instead of --restore-command if
we want to specify that this is restore_command for target server?

As Alvaro correctly pointed in the nearby thread [1]/messages/by-id/20190925174812.GA4916@alvherre.pgsql, we've got an
interference regarding -R command line argument. I agree that it's a
good idea to reserve -R for recovery configuration write to be
consistent with pg_basebackup, so I've updated my patch to use another
letters:

1. -c/--restore-target-wal --- to use restore_command from postgresql.conf
2. -C/--target-restore-command --- to pass restore_command as a command
line argument

Updated and rebased patch is attached. However, now I'm wondering, do we
actually need 1. as a separated option and not being enabled by default?
I cannot imagine a situation, when restore_command is set in the
postgresql.conf and someone prefer pg_rewind to fail instead of fetching
missed WALs automatically, but maybe there are some cases?

[1]: /messages/by-id/20190925174812.GA4916@alvherre.pgsql
/messages/by-id/20190925174812.GA4916@alvherre.pgsql

--
Alexey Kondratov

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

Attachments:

v10-0001-pg_rewind-options-to-use-restore_command-from-co.patchtext/x-patch; name=v10-0001-pg_rewind-options-to-use-restore_command-from-co.patchDownload
From d7e1041c756b79e6e4636be1b0337453db8a7457 Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <kondratov.aleksey@gmail.com>
Date: Tue, 19 Feb 2019 19:14:53 +0300
Subject: [PATCH v10] pg_rewind: options to use restore_command from command
 line or cluster config

Previously, when pg_rewind could not find required WAL files in the
target data directory the rewind process would fail. One had to
manually figure out which of required WAL files have already moved to
the archival storage and copy them back.

This patch adds possibility to specify restore_command via command
line option or use one specified inside postgresql.conf. Specified
restore_command will be used for automatic retrieval of missing WAL
files from archival storage.
---
 doc/src/sgml/ref/pg_rewind.sgml       |  49 +++++++-
 src/bin/pg_rewind/parsexlog.c         | 164 +++++++++++++++++++++++++-
 src/bin/pg_rewind/pg_rewind.c         | 112 +++++++++++++++---
 src/bin/pg_rewind/pg_rewind.h         |   6 +-
 src/bin/pg_rewind/t/001_basic.pl      |   4 +-
 src/bin/pg_rewind/t/002_databases.pl  |   4 +-
 src/bin/pg_rewind/t/003_extrafiles.pl |   4 +-
 src/bin/pg_rewind/t/RewindTest.pm     |  84 ++++++++++++-
 8 files changed, 396 insertions(+), 31 deletions(-)

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index ac142d22fc..27c662cc83 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -66,11 +66,12 @@ 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> or
+   <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.
@@ -202,6 +203,39 @@ 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
+        <filename>postgresql.conf</filename> to retrieve WAL files from
+        the WAL archive if these files are no longer available in the
+        <filename>pg_wal</filename> directory of the target cluster.
+       </para>
+       <para>
+        This option cannot be used together with <option>--target-restore-command</option>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-C <replaceable class="parameter">restore_command</replaceable></option></term>
+      <term><option>--target-restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the <varname>restore_command</varname> to use for retrieving
+        WAL files from the WAL archive if these files are no longer available
+        in the <filename>pg_wal</filename> directory of the target cluster.
+       </para>
+       <para>
+        If <varname>restore_command</varname> is already set in
+        <filename>postgresql.conf</filename>, you can provide the
+        <option>--restore-target-wal</option> option instead.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
@@ -288,7 +322,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> or <option>-C</option> option to search
+      for the missing files in the WAL archive.
      </para>
     </step>
     <step>
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 264a8f4db5..3452bf5c79 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "pg_rewind.h"
 #include "filemap.h"
@@ -43,6 +44,7 @@ static char xlogfpath[MAXPGPATH];
 
 typedef struct XLogPageReadPrivate
 {
+	const char *restoreCommand;
 	int			tliIndex;
 } XLogPageReadPrivate;
 
@@ -50,6 +52,9 @@ static int	SimpleXLogPageRead(XLogReaderState *xlogreader,
 							   XLogRecPtr targetPagePtr,
 							   int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
 
+static int RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -57,7 +62,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;
@@ -65,6 +70,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)
@@ -150,7 +156,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;
@@ -174,6 +180,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 	}
 
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	xlogreader = XLogReaderAllocate(WalSegSz, datadir, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -284,8 +291,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 to execute, then try to retrieve
+			 * missing WAL file from the archive.
+			 */
+			xlogreadfd = RestoreArchivedWAL(xlogreader->segcxt.ws_dir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand);
+
+			if (xlogreadfd < 0)
+				return -1;
+			else
+				pg_log_debug("using file \"%s\" restored from archive",
+							 xlogfpath);
 		}
 	}
 
@@ -399,3 +427,131 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return a file descriptor of 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.
+ */
+static int
+RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH],
+				xlogRestoreCmd[MAXPGPATH],
+			   *dp,
+			   *endp;
+	const char *sp;
+	int			rc,
+				xlogfd;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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 */
+					pg_fatal("restore_command with %%r cannot be used with pg_rewind.");
+					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';
+
+	/*
+	 * 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("archive file \"%s\" has wrong size: %lu instead of %lu, %s",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open file \"%s\" restored from archive: %s\n",
+								 xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\" restored from archive: %s",
+						 xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s",
+				 wait_result_to_str(rc));
+
+	pg_log_error("could not restore file \"%s\" from archive\n",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 15e3eab550..28b3d6bcab 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -51,11 +51,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_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -72,16 +74,19 @@ 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(_("  -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"));
-	printf(_("  -n, --dry-run                  stop before modifying anything\n"));
-	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
-			 "                                 safely to disk\n"));
-	printf(_("  -P, --progress                 write progress messages\n"));
-	printf(_("      --debug                    write a lot of debug messages\n"));
-	printf(_("  -V, --version                  output version information, then exit\n"));
-	printf(_("  -?, --help                     show this help, then exit\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"));
+	printf(_("  -n, --dry-run                         stop before modifying anything\n"));
+	printf(_("  -N, --no-sync                         do not wait for changes to be written\n"
+			 "                                        safely to disk\n"));
+	printf(_("  -P, --progress                        write progress messages\n"));
+	printf(_("  -c, --restore-target-wal              use restore_command in the postgresql.conf\n"));
+	printf(_("                                        to retrieve WALs from archive\n"));
+	printf(_("  -C, --target-restore-command=COMMAND  target WAL restore_command\n"));
+	printf(_("      --debug                           write a lot of debug messages\n"));
+	printf(_("  -V, --version                         output version information, then exit\n"));
+	printf(_("  -?, --help                            show this help, then exit\n"));
 	printf(_("\nReport bugs to <pgsql-bugs@lists.postgresql.org>.\n"));
 }
 
@@ -98,6 +103,8 @@ 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'},
+		{"target-restore-command", required_argument, NULL, 'C'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
@@ -134,7 +141,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "D:nNPC:c", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -146,6 +153,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'c':
+				restore_wals = true;
+				break;
+
 			case 'n':
 				dry_run = true;
 				break;
@@ -163,6 +174,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'C':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -229,6 +244,74 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	if (restore_command != NULL)
+	{
+		if (restore_wals)
+		{
+			pg_log_error("conflicting options: both -c and -C are specified");
+			fprintf(stderr, _("You must run %s with either -c/--restore-target-wal "
+							"or -C/--target-restore-command.\n"), progname);
+			exit(1);
+		}
+
+		pg_log_debug("using command line restore_command=\'%s\'.", restore_command);
+	}
+	else if (restore_wals)
+	{
+		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);
+	}
+
 	/* Connect to remote server */
 	if (connstr_source)
 		libpqConnect(connstr_source);
@@ -300,9 +383,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);
@@ -328,7 +410,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 1125c7e60f..7dc972dc39 100644
--- a/src/bin/pg_rewind/pg_rewind.h
+++ b/src/bin/pg_rewind/pg_rewind.h
@@ -40,11 +40,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 115192170e..8a6fa33016 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 => 10;
+use Test::More tests => 20;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -106,5 +106,7 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index f1eb4fe1d2..69a3c69a85 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 12;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -70,5 +70,7 @@ template1
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index c4040bd562..24cec256de 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 8;
 
 use File::Find;
 
@@ -90,5 +90,7 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 68b6004e94..260a58b3fa 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -35,7 +35,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catdir catfile);
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -216,6 +218,38 @@ sub promote_standby
 	return;
 }
 
+# Moves WAL files to the temporary location and returns restore_command
+# to get them back.
+sub move_wals
+{
+	my $tmp_dir = shift;
+	my $master_pgdata = shift;
+	my $wals_archive_dir = catdir($tmp_dir, "master_wals_archive");
+	my @wal_files = bsd_glob catfile($master_pgdata, "pg_wal", "0000000*");
+	my $restore_command;
+
+	remove_tree($wals_archive_dir);
+	make_path($wals_archive_dir) or die;
+
+	# Move all old master WAL files to the archive.
+	# Old master should be stopped at this point.
+	foreach my $wal_file (@wal_files)
+	{
+		move($wal_file, "$wals_archive_dir") or die;
+	}
+
+	if ($windows_os)
+	{
+		$restore_command = "copy $wals_archive_dir\\\%f \%p";
+	}
+	else
+	{
+		$restore_command = "cp $wals_archive_dir/\%f \%p";
+	}
+
+	return $restore_command;
+}
+
 sub run_pg_rewind
 {
 	my $test_mode       = shift;
@@ -270,6 +304,54 @@ sub run_pg_rewind
 			],
 			'pg_rewind remote');
 	}
+	elsif ($test_mode eq "archive")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# 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",
+				"--target-restore-command=$restore_command"
+			],
+			'pg_rewind archive');
+	}
+	elsif ($test_mode eq "archive_conf")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Add restore_command to postgresql.conf of target cluster.
+		open(my $conf_fd, ">>", $master_conf_path) or die;
+		print $conf_fd "\nrestore_command='$restore_command'";
+		close $conf_fd;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"-c"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 

base-commit: a083657896c739909a25190ebd0032c01f6c8109
-- 
2.17.1

#38Michael Paquier
michael@paquier.xyz
In reply to: Alexey Kondratov (#37)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Thu, Sep 26, 2019 at 03:08:22PM +0300, Alexey Kondratov wrote:

As Alvaro correctly pointed in the nearby thread [1], we've got an
interference regarding -R command line argument. I agree that it's a good
idea to reserve -R for recovery configuration write to be consistent with
pg_basebackup, so I've updated my patch to use another letters:

The patch has rotten and does not apply anymore. Could you please
send a rebased version? I have moved the patch to next CF, waiting on
author for now.
--
Michael

#39Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#38)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 01.12.2019 5:57, Michael Paquier wrote:

On Thu, Sep 26, 2019 at 03:08:22PM +0300, Alexey Kondratov wrote:

As Alvaro correctly pointed in the nearby thread [1], we've got an
interference regarding -R command line argument. I agree that it's a good
idea to reserve -R for recovery configuration write to be consistent with
pg_basebackup, so I've updated my patch to use another letters:

The patch has rotten and does not apply anymore. Could you please
send a rebased version? I have moved the patch to next CF, waiting on
author for now.

Rebased and updated patch is attached.

There was a problem with testing new restore_command options altogether
with recent ensureCleanShutdown. My test simply moves all WAL from
pg_wal and generates restore_command for a new options testing, but this
prevents startup recovery required by ensureCleanShutdown. To test both
options in the same we have to leave some recent WAL segments in the
pg_wal and be sure that they are enough for startup recovery, but not
enough for successful pg_rewind run. I have manually figured out that
required amount of inserted records (and generated WAL) to achieve this.
However, I think that this approach is not good for test, since tests
may be modified in the future (amount of writes to DB changed) or even
volume of WAL written by Postgres will change. It will lead to falsely
always failed or passed tests.

Moreover, testing both ensureCleanShutdown and new options in the same
time doesn't hit new code paths, so I decided to test new options with
--no-ensure-shutdown for simplicity and stability of tests.

Regards

--
Alexey Kondratov

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

Attachments:

v11-0001-pg_rewind-options-to-use-restore_command-from-co.patchtext/x-patch; name=v11-0001-pg_rewind-options-to-use-restore_command-from-co.patchDownload
From a05c3343e0bd6fe339c944f6b0cde64ceb46a0b3 Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <kondratov.aleksey@gmail.com>
Date: Tue, 19 Feb 2019 19:14:53 +0300
Subject: [PATCH v11] pg_rewind: options to use restore_command from command
 line or cluster config

Previously, when pg_rewind could not find required WAL files in the
target data directory the rewind process would fail. One had to
manually figure out which of required WAL files have already moved to
the archival storage and copy them back.

This patch adds possibility to specify restore_command via command
line option or use one specified inside postgresql.conf. Specified
restore_command will be used for automatic retrieval of missing WAL
files from archival storage.
---
 doc/src/sgml/ref/pg_rewind.sgml       |  49 +++++++-
 src/bin/pg_rewind/parsexlog.c         | 164 +++++++++++++++++++++++++-
 src/bin/pg_rewind/pg_rewind.c         | 118 +++++++++++++++---
 src/bin/pg_rewind/pg_rewind.h         |   6 +-
 src/bin/pg_rewind/t/001_basic.pl      |   4 +-
 src/bin/pg_rewind/t/002_databases.pl  |   4 +-
 src/bin/pg_rewind/t/003_extrafiles.pl |   4 +-
 src/bin/pg_rewind/t/RewindTest.pm     | 105 ++++++++++++++++-
 8 files changed, 416 insertions(+), 38 deletions(-)

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 42d29edd4e..b601a5c7e4 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -66,11 +66,12 @@ 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> or
+   <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 +233,39 @@ 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
+        <filename>postgresql.conf</filename> to retrieve WAL files from
+        the WAL archive if these files are no longer available in the
+        <filename>pg_wal</filename> directory of the target cluster.
+       </para>
+       <para>
+        This option cannot be used together with <option>--target-restore-command</option>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-C <replaceable class="parameter">restore_command</replaceable></option></term>
+      <term><option>--target-restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the <varname>restore_command</varname> to use for retrieving
+        WAL files from the WAL archive if these files are no longer available
+        in the <filename>pg_wal</filename> directory of the target cluster.
+       </para>
+       <para>
+        If <varname>restore_command</varname> is already set in
+        <filename>postgresql.conf</filename>, you can provide the
+        <option>--restore-target-wal</option> option instead.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
@@ -318,7 +352,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> or <option>-C</option> option to search
+      for the missing files in the WAL archive.
      </para>
     </step>
     <step>
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 1d03375a3e..fd67790ac3 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "access/rmgr.h"
 #include "access/xlog_internal.h"
@@ -41,6 +42,7 @@ static char xlogfpath[MAXPGPATH];
 
 typedef struct XLogPageReadPrivate
 {
+	const char *restoreCommand;
 	int			tliIndex;
 } XLogPageReadPrivate;
 
@@ -48,6 +50,9 @@ static int	SimpleXLogPageRead(XLogReaderState *xlogreader,
 							   XLogRecPtr targetPagePtr,
 							   int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
 
+static int RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -55,7 +60,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 +68,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)
@@ -148,7 +154,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;
@@ -172,6 +178,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 	}
 
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	xlogreader = XLogReaderAllocate(WalSegSz, datadir, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -282,8 +289,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 to execute, then try to retrieve
+			 * missing WAL file from the archive.
+			 */
+			xlogreadfd = RestoreArchivedWAL(xlogreader->segcxt.ws_dir,
+											xlogfname,
+											WalSegSz,
+											private->restoreCommand);
+
+			if (xlogreadfd < 0)
+				return -1;
+			else
+				pg_log_debug("using file \"%s\" restored from archive",
+							 xlogfpath);
 		}
 	}
 
@@ -397,3 +425,131 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful return a file descriptor of 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.
+ */
+static int
+RestoreArchivedWAL(const char *path, const char *xlogfname,
+				   off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH],
+				xlogRestoreCmd[MAXPGPATH],
+			   *dp,
+			   *endp;
+	const char *sp;
+	int			rc,
+				xlogfd;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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 */
+					pg_fatal("restore_command with %%r cannot be used with pg_rewind.");
+					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';
+
+	/*
+	 * 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("archive file \"%s\" has wrong size: %lu instead of %lu, %s",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open file \"%s\" restored from archive: %s\n",
+								 xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\" restored from archive: %s",
+						 xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s",
+				 wait_result_to_str(rc));
+
+	pg_log_error("could not restore file \"%s\" from archive\n",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index e75c7eed1c..72e903912e 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_wals = false;
 
 /* Target history */
 TimeLineHistoryEntry *targetHistory;
@@ -74,19 +76,22 @@ 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(_("  -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"));
-	printf(_("  -R, --write-recovery-conf      write configuration for replication\n"
-			 "                                 (requires --source-server)\n"));
-	printf(_("  -n, --dry-run                  stop before modifying anything\n"));
-	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
-			 "                                 safely to disk\n"));
-	printf(_("  -P, --progress                 write progress messages\n"));
-	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
-	printf(_("      --debug                    write a lot of debug messages\n"));
-	printf(_("  -V, --version                  output version information, then exit\n"));
-	printf(_("  -?, --help                     show this help, then exit\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"));
+	printf(_("  -R, --write-recovery-conf             write configuration for replication\n"
+			 "                                        (requires --source-server)\n"));
+	printf(_("  -c, --restore-target-wal              use restore_command in the postgresql.conf\n"));
+	printf(_("                                        to retrieve WALs from archive\n"));
+	printf(_("  -C, --target-restore-command=COMMAND  target WAL restore_command\n"));
+	printf(_("  -n, --dry-run                         stop before modifying anything\n"));
+	printf(_("  -N, --no-sync                         do not wait for changes to be written\n"
+			 "                                        safely to disk\n"));
+	printf(_("  -P, --progress                        write progress messages\n"));
+	printf(_("      --no-ensure-shutdown              do not automatically fix unclean shutdown\n"));
+	printf(_("      --debug                           write a lot of debug messages\n"));
+	printf(_("  -V, --version                         output version information, then exit\n"));
+	printf(_("  -?, --help                            show this help, then exit\n"));
 	printf(_("\nReport bugs to <pgsql-bugs@lists.postgresql.org>.\n"));
 }
 
@@ -105,6 +110,8 @@ 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'},
+		{"target-restore-command", required_argument, NULL, 'C'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
@@ -143,7 +150,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, "D:nNPRC:c", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -155,6 +162,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'c':
+				restore_wals = true;
+				break;
+
 			case 'n':
 				dry_run = true;
 				break;
@@ -176,6 +187,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'C':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -252,6 +267,74 @@ main(int argc, char **argv)
 		exit(1);
 	}
 
+	if (restore_command != NULL)
+	{
+		if (restore_wals)
+		{
+			pg_log_error("conflicting options: both -c and -C are specified");
+			fprintf(stderr, _("You must run %s with either -c/--restore-target-wal "
+							"or -C/--target-restore-command.\n"), progname);
+			exit(1);
+		}
+
+		pg_log_debug("using command line restore_command=\'%s\'.", restore_command);
+	}
+	else if (restore_wals)
+	{
+		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 +432,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 +459,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 360b2b3da8..3abe83b38c 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..73967d818d 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 => 25;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -171,5 +171,7 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 1db534c0dc..d046a755e5 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 7;
+use Test::More tests => 13;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -70,5 +70,7 @@ template1
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index f4710440fc..ab1fbbc125 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 5;
+use Test::More tests => 9;
 
 use File::Find;
 
@@ -90,5 +90,7 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 82fa220ac8..58055bb494 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -35,7 +35,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catdir catfile);
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -216,6 +218,38 @@ sub promote_standby
 	return;
 }
 
+# Moves WAL files to the temporary location and returns restore_command
+# to get them back.
+sub move_wals
+{
+	my $tmp_dir = shift;
+	my $master_pgdata = shift;
+	my $wals_archive_dir = catdir($tmp_dir, "master_wals_archive");
+	my @wal_files = bsd_glob catfile($master_pgdata, "pg_wal", "0000000*");
+	my $restore_command;
+
+	remove_tree($wals_archive_dir);
+	make_path($wals_archive_dir) or die;
+
+	# Move all old master WAL files to the archive.
+	# Old master should be stopped at this point.
+	foreach my $wal_file (@wal_files)
+	{
+		move($wal_file, "$wals_archive_dir") or die;
+	}
+
+	if ($windows_os)
+	{
+		$restore_command = "copy $wals_archive_dir\\\%f \%p";
+	}
+	else
+	{
+		$restore_command = "cp $wals_archive_dir/\%f \%p";
+	}
+
+	return $restore_command;
+}
+
 sub run_pg_rewind
 {
 	my $test_mode       = shift;
@@ -227,10 +261,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' || $test_mode eq 'archive_conf')
+	{
+		# We test pg_rewind with restore_command by simply moving all
+		# WAL 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 +331,54 @@ 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 WALs archive.
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# 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",
+				"--target-restore-command=$restore_command"
+			],
+			'pg_rewind archive');
+	}
+	elsif ($test_mode eq "archive_conf")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WALs archive.
+		my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');
+		my $restore_command = move_wals($tmp_folder, $master_pgdata);
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Add restore_command to postgresql.conf of target cluster.
+		open(my $conf_fd, ">>", $master_conf_path) or die;
+		print $conf_fd "\nrestore_command='$restore_command'";
+		close $conf_fd;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync", "--no-ensure-shutdown",
+				"-c"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 

base-commit: 88d45ac752ae49dbfafeb163b07381d3b8a6b601
-- 
2.17.1

#40Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexey Kondratov (#39)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi!

On Tue, Dec 3, 2019 at 12:41 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:

On 01.12.2019 5:57, Michael Paquier wrote:

On Thu, Sep 26, 2019 at 03:08:22PM +0300, Alexey Kondratov wrote:

As Alvaro correctly pointed in the nearby thread [1], we've got an
interference regarding -R command line argument. I agree that it's a good
idea to reserve -R for recovery configuration write to be consistent with
pg_basebackup, so I've updated my patch to use another letters:

The patch has rotten and does not apply anymore. Could you please
send a rebased version? I have moved the patch to next CF, waiting on
author for now.

Rebased and updated patch is attached.

There was a problem with testing new restore_command options altogether
with recent ensureCleanShutdown. My test simply moves all WAL from
pg_wal and generates restore_command for a new options testing, but this
prevents startup recovery required by ensureCleanShutdown. To test both
options in the same we have to leave some recent WAL segments in the
pg_wal and be sure that they are enough for startup recovery, but not
enough for successful pg_rewind run. I have manually figured out that
required amount of inserted records (and generated WAL) to achieve this.
However, I think that this approach is not good for test, since tests
may be modified in the future (amount of writes to DB changed) or even
volume of WAL written by Postgres will change. It will lead to falsely
always failed or passed tests.

Moreover, testing both ensureCleanShutdown and new options in the same
time doesn't hit new code paths, so I decided to test new options with
--no-ensure-shutdown for simplicity and stability of tests.

I think this patch is now in a good shape and already got enough of review.

I made some minor cleanup. In particular, I've to fix usage of terms
"WAL" and "WALs". This patch sometimes use term "WAL" to specify
single WAL file and term "WALs" to specify multiple WAL files. But
WAL stands for Write Ahead Log. So, "WALs" literally stands to
multiple logs. And we don't use term "WALs" to describe multiple WAL
files anywhere else. Usage of term "WAL" to describe single file is
not clear as well.

The revision of patch is attached. I'm going to push it if no objections.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v12-0001-pg_rewind-options-to-use-restore_command-from-co.patchapplication/octet-stream; name=v12-0001-pg_rewind-options-to-use-restore_command-from-co.patchDownload
commit fc40af46a4f42ef648fac3cb5662a4a31b562759
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sat Jan 18 18:11:49 2020 +0300

    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 two new pg_rewind options, which allow pg_rewind to
    automatically retrieve missing WAL files from archival storage.  The first
    option let pg_rewind read restore_command from postgresql.conf, while the
    second one specifies restore_command directly.
    
    Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1%40postgrespro.ru
    Author: Alexey Kondratov
    Reviewed-by: Andrey Borodin, Alvaro Herrera, Michael Paquier
    Reviewed-by: Andres Freund, Alexander Korotkov

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 42d29edd4e9..b601a5c7e44 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -66,11 +66,12 @@ 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> or
+   <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 +233,39 @@ 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
+        <filename>postgresql.conf</filename> to retrieve WAL files from
+        the WAL archive if these files are no longer available in the
+        <filename>pg_wal</filename> directory of the target cluster.
+       </para>
+       <para>
+        This option cannot be used together with <option>--target-restore-command</option>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-C <replaceable class="parameter">restore_command</replaceable></option></term>
+      <term><option>--target-restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the <varname>restore_command</varname> to use for retrieving
+        WAL files from the WAL archive if these files are no longer available
+        in the <filename>pg_wal</filename> directory of the target cluster.
+       </para>
+       <para>
+        If <varname>restore_command</varname> is already set in
+        <filename>postgresql.conf</filename>, you can provide the
+        <option>--restore-target-wal</option> option instead.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
@@ -318,7 +352,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> or <option>-C</option> option to search
+      for the missing files in the WAL archive.
      </para>
     </step>
     <step>
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index b6429827cfb..7db7aa60e1a 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "access/rmgr.h"
 #include "access/xlog_internal.h"
@@ -41,6 +42,7 @@ static char xlogfpath[MAXPGPATH];
 
 typedef struct XLogPageReadPrivate
 {
+	const char *restoreCommand;
 	int			tliIndex;
 } XLogPageReadPrivate;
 
@@ -48,6 +50,9 @@ static int	SimpleXLogPageRead(XLogReaderState *xlogreader,
 							   XLogRecPtr targetPagePtr,
 							   int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
 
+static int	RestoreArchivedWALFile(const char *path, const char *xlogfname,
+								   off_t expectedSize, const char *restoreCommand);
+
 /*
  * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
  * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -55,7 +60,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 +68,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)
@@ -148,7 +154,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;
@@ -172,6 +178,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 	}
 
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	xlogreader = XLogReaderAllocate(WalSegSz, datadir, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -282,8 +289,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 to execute, 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);
 		}
 	}
 
@@ -397,3 +425,131 @@ extractPageInfo(XLogReaderState *record)
 		process_block_change(forknum, rnode, blkno);
 	}
 }
+
+/*
+ * 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.
+ */
+static int
+RestoreArchivedWALFile(const char *path, const char *xlogfname,
+					   off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH],
+				xlogRestoreCmd[MAXPGPATH],
+			   *dp,
+			   *endp;
+	const char *sp;
+	int			rc,
+				xlogfd;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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 */
+					pg_fatal("restore_command with %%r cannot be used with pg_rewind.");
+					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';
+
+	/*
+	 * 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("archive file \"%s\" has wrong size: %lu instead of %lu, %s",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open file \"%s\" restored from archive: %s\n",
+								 xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\" restored from archive: %s",
+						 xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s",
+				 wait_result_to_str(rc));
+
+	pg_log_error("could not restore file \"%s\" from archive\n",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c6d00bb0ab7..fed70222cc6 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,19 +76,22 @@ 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(_("  -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"));
-	printf(_("  -R, --write-recovery-conf      write configuration for replication\n"
-			 "                                 (requires --source-server)\n"));
-	printf(_("  -n, --dry-run                  stop before modifying anything\n"));
-	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
-			 "                                 safely to disk\n"));
-	printf(_("  -P, --progress                 write progress messages\n"));
-	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
-	printf(_("      --debug                    write a lot of debug messages\n"));
-	printf(_("  -V, --version                  output version information, then exit\n"));
-	printf(_("  -?, --help                     show this help, then exit\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"));
+	printf(_("  -R, --write-recovery-conf             write configuration for replication\n"
+			 "                                        (requires --source-server)\n"));
+	printf(_("  -c, --restore-target-wal              use restore_command in the postgresql.conf\n"));
+	printf(_("                                        to retrieve WAL files from archive\n"));
+	printf(_("  -C, --target-restore-command=COMMAND  target WAL restore_command\n"));
+	printf(_("  -n, --dry-run                         stop before modifying anything\n"));
+	printf(_("  -N, --no-sync                         do not wait for changes to be written\n"
+			 "                                        safely to disk\n"));
+	printf(_("  -P, --progress                        write progress messages\n"));
+	printf(_("      --no-ensure-shutdown              do not automatically fix unclean shutdown\n"));
+	printf(_("      --debug                           write a lot of debug messages\n"));
+	printf(_("  -V, --version                         output version information, then exit\n"));
+	printf(_("  -?, --help                            show this help, then exit\n"));
 	printf(_("\nReport bugs to <pgsql-bugs@lists.postgresql.org>.\n"));
 }
 
@@ -105,6 +110,8 @@ 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'},
+		{"target-restore-command", required_argument, NULL, 'C'},
 		{"debug", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
@@ -143,7 +150,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, "D:nNPRC:c", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -155,6 +162,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'c':
+				restore_wal = true;
+				break;
+
 			case 'n':
 				dry_run = true;
 				break;
@@ -176,6 +187,10 @@ main(int argc, char **argv)
 				datadir_target = pg_strdup(optarg);
 				break;
 
+			case 'C':
+				restore_command = pg_strdup(optarg);
+				break;
+
 			case 1:				/* --source-pgdata */
 				datadir_source = pg_strdup(optarg);
 				break;
@@ -252,6 +267,77 @@ main(int argc, char **argv)
 		exit(1);
 	}
 
+	if (restore_command != NULL)
+	{
+		if (restore_wal)
+		{
+			pg_log_error("conflicting options: both -c and -C are specified");
+			fprintf(stderr, _("You must run %s with either -c/--restore-target-wal "
+							  "or -C/--target-restore-command.\n"), progname);
+			exit(1);
+		}
+
+		pg_log_debug("using command line restore_command=\'%s\'.", restore_command);
+	}
+	else 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 +435,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 +462,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 e4e8d23c32d..b122ae43e5a 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 95d8ccfced1..73967d818d0 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 => 25;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -171,5 +171,7 @@ in master, before promotion
 # Run the test in both modes
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 1db534c0dc0..d046a755e5f 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 7;
+use Test::More tests => 13;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -70,5 +70,7 @@ template1
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index f4710440fc3..ab1fbbc1256 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 5;
+use Test::More tests => 9;
 
 use File::Find;
 
@@ -90,5 +90,7 @@ sub run_test
 # Run the test in both modes.
 run_test('local');
 run_test('remote');
+run_test('archive');
+run_test('archive_conf');
 
 exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 82fa220ac86..873239b9d95 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -35,7 +35,9 @@ use Carp;
 use Config;
 use Exporter 'import';
 use File::Copy;
-use File::Path qw(rmtree);
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catdir catfile);
 use IPC::Run qw(run);
 use PostgresNode;
 use TestLib;
@@ -216,6 +218,38 @@ sub promote_standby
 	return;
 }
 
+# Moves WAL files to the temporary location and returns restore_command
+# to get them back.
+sub move_wal
+{
+	my $tmp_dir = shift;
+	my $master_pgdata = shift;
+	my $wal_archive_dir = catdir($tmp_dir, "master_wal_archive");
+	my @wal_files = bsd_glob catfile($master_pgdata, "pg_wal", "0000000*");
+	my $restore_command;
+
+	remove_tree($wal_archive_dir);
+	make_path($wal_archive_dir) or die;
+
+	# Move all old master WAL files to the archive.
+	# Old master should be stopped at this point.
+	foreach my $wal_file (@wal_files)
+	{
+		move($wal_file, "$wal_archive_dir") or die;
+	}
+
+	if ($windows_os)
+	{
+		$restore_command = "copy $wal_archive_dir\\\%f \%p";
+	}
+	else
+	{
+		$restore_command = "cp $wal_archive_dir/\%f \%p";
+	}
+
+	return $restore_command;
+}
+
 sub run_pg_rewind
 {
 	my $test_mode       = shift;
@@ -227,10 +261,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' || $test_mode eq 'archive_conf')
+	{
+		# 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 +331,54 @@ 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.
+		my $restore_command = move_wal($tmp_folder, $master_pgdata);
+
+		# 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",
+				"--target-restore-command=$restore_command"
+			],
+			'pg_rewind archive');
+	}
+	elsif ($test_mode eq "archive_conf")
+	{
+
+		# Do rewind using a local pgdata as source and
+		# specified directory with target WAL archive.
+		my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');
+		my $restore_command = move_wal($tmp_folder, $master_pgdata);
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Add restore_command to postgresql.conf of target cluster.
+		open(my $conf_fd, ">>", $master_conf_path) or die;
+		print $conf_fd "\nrestore_command='$restore_command'";
+		close $conf_fd;
+
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync", "--no-ensure-shutdown",
+				"-c"
+			],
+			'pg_rewind archive_conf');
+	}
 	else
 	{
 
#41Michael Paquier
michael@paquier.xyz
In reply to: Alexander Korotkov (#40)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Sat, Jan 18, 2020 at 06:21:22PM +0300, Alexander Korotkov wrote:

I made some minor cleanup. In particular, I've to fix usage of terms
"WAL" and "WALs". This patch sometimes use term "WAL" to specify
single WAL file and term "WALs" to specify multiple WAL files. But
WAL stands for Write Ahead Log. So, "WALs" literally stands to
multiple logs. And we don't use term "WALs" to describe multiple WAL
files anywhere else. Usage of term "WAL" to describe single file is
not clear as well.

WAL is a method to ensure data integrity, as the docs mention:
https://www.postgresql.org/docs/11/wal-intro.html

So using WAL to tell about a WAL segment file is wrong, WALs is not a
term that actually exists. So, in my opinion, it is fine to use "WAL
file", "WAL segment" or even "WAL segment file".

I have read through the patch, while on it..

+static int
+RestoreArchivedWALFile(const char *path, const char *xlogfname,
+                      off_t expectedSize, const char *restoreCommand)
Not a fan of putting that to pg_rewind/parsexlog.c.  It has nothing to
do with WAL parsing, and it seems to me that we could have an argument
for making this available as a frontend-only API in src/common/.

Do we actually need --target-restore-command at all? It seems to me
that we have all we need with --restore-target-wal, and that's not
really instinctive to pass down a command via another command..

+   printf(_("  -c, --restore-target-wal              use restore_command in the postgresql.conf\n"));
+   printf(_("                                        to retrieve WAL
files from archive\n"));
The command could be part of a different configuration file, included
by postgresql.conf.
+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catdir catfile);
Is this compatible with our minimum perl requirements for the TAP
tests?
+       # Add restore_command to postgresql.conf of target cluster.
+       open(my $conf_fd, ">>", $master_conf_path) or die;
+       print $conf_fd "\nrestore_command='$restore_command'";
+       close $conf_fd;
We have append_conf() for that.
--
Michael
#42Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#41)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-Jan-19, Michael Paquier wrote:

So using WAL to tell about a WAL segment file is wrong, WALs is not a
term that actually exists.

I agree.

So, in my opinion, it is fine to use "WAL file", "WAL segment" or even
"WAL segment file".

Agreed with these three terms -- "WAL file" seems to be the most common,
but the other two terms you suggest are just as good.

+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catdir catfile);
Is this compatible with our minimum perl requirements for the TAP
tests?

By and large, we just join elements with a slash "foo/bar" to create
path names; no need for catdir or catfile, ISTM.

I *think* :bsd_glob should be available in all the Perl versions we
support, but I'm not sure that we really need it. We seem to do just
fine with regular glob elsewhere.

PostgresNode already uses File::Path's rmtree. Looking at its manual,
maybe the safest bet is to change all that to remove_tree.

Not sure about make_path.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#43Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#42)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

On 2020-Jan-19, Michael Paquier wrote:

+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catdir catfile);
Is this compatible with our minimum perl requirements for the TAP
tests?

I *think* :bsd_glob should be available in all the Perl versions we
support, but I'm not sure that we really need it. We seem to do just
fine with regular glob elsewhere.

We had some previous discussion about that, and Andrew seemed to
think that it wouldn't necessarily be a net win:

/messages/by-id/fde787ee-9c2d-46da-3ece-f7ae64a70b33@2ndQuadrant.com

In any case, I'd say -1 to having just one place use that.

regards, tom lane

#44Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Michael Paquier (#41)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi, Michael!

Thank you for your review!
The revised patch is attached.

On Sun, Jan 19, 2020 at 1:24 PM Michael Paquier <michael@paquier.xyz> wrote:

On Sat, Jan 18, 2020 at 06:21:22PM +0300, Alexander Korotkov wrote:

I made some minor cleanup. In particular, I've to fix usage of terms
"WAL" and "WALs". This patch sometimes use term "WAL" to specify
single WAL file and term "WALs" to specify multiple WAL files. But
WAL stands for Write Ahead Log. So, "WALs" literally stands to
multiple logs. And we don't use term "WALs" to describe multiple WAL
files anywhere else. Usage of term "WAL" to describe single file is
not clear as well.

WAL is a method to ensure data integrity, as the docs mention:
https://www.postgresql.org/docs/11/wal-intro.html

So using WAL to tell about a WAL segment file is wrong, WALs is not a
term that actually exists. So, in my opinion, it is fine to use "WAL
file", "WAL segment" or even "WAL segment file".

I have read through the patch, while on it..

+static int
+RestoreArchivedWALFile(const char *path, const char *xlogfname,
+                      off_t expectedSize, const char *restoreCommand)
Not a fan of putting that to pg_rewind/parsexlog.c.  It has nothing to
do with WAL parsing, and it seems to me that we could have an argument
for making this available as a frontend-only API in src/common/.

I've put it into src/common/fe_archive.c.

Do we actually need --target-restore-command at all? It seems to me
that we have all we need with --restore-target-wal, and that's not
really instinctive to pass down a command via another command..

I was going to argue that --restore-target-wal is useful. But I
didn't find convincing example for that. Naturally, if one uses
restore_command during pg_rewind then why the same restore_command
can't be used in new standby. So, I've removed --restore-target-wal
argument. If we will find convincing use case we can return it any
moment.

+   printf(_("  -c, --restore-target-wal              use restore_command in the postgresql.conf\n"));
+   printf(_("                                        to retrieve WAL
files from archive\n"));
The command could be part of a different configuration file, included
by postgresql.conf.

I've rephrased the comment.

+use File::Glob ':bsd_glob';
+use File::Path qw(remove_tree make_path);
+use File::Spec::Functions qw(catdir catfile);
Is this compatible with our minimum perl requirements for the TAP
tests?

All extra dependencies have been removed.

+       # Add restore_command to postgresql.conf of target cluster.
+       open(my $conf_fd, ">>", $master_conf_path) or die;
+       print $conf_fd "\nrestore_command='$restore_command'";
+       close $conf_fd;
We have append_conf() for that.

Sure, thank you for pointing!

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v13-0001-pg_rewind-options-to-use-restore_command-from-co.patchapplication/octet-stream; name=v13-0001-pg_rewind-options-to-use-restore_command-from-co.patchDownload
commit b2d59c9532f3beb3a17b8c03341aa4557809d038
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sat Jan 18 18:11:49 2020 +0300

    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 two new pg_rewind options, which allow pg_rewind to
    automatically retrieve missing WAL files from archival storage.  The first
    option let pg_rewind read restore_command from postgresql.conf, while the
    second one specifies restore_command directly.
    
    Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1%40postgrespro.ru
    Author: Alexey Kondratov
    Reviewed-by: Andrey Borodin, Alvaro Herrera, Michael Paquier
    Reviewed-by: Andres Freund, Alexander Korotkov

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 42d29edd4e9..b601a5c7e44 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -66,11 +66,12 @@ 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> or
+   <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 +233,39 @@ 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
+        <filename>postgresql.conf</filename> to retrieve WAL files from
+        the WAL archive if these files are no longer available in the
+        <filename>pg_wal</filename> directory of the target cluster.
+       </para>
+       <para>
+        This option cannot be used together with <option>--target-restore-command</option>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-C <replaceable class="parameter">restore_command</replaceable></option></term>
+      <term><option>--target-restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the <varname>restore_command</varname> to use for retrieving
+        WAL files from the WAL archive if these files are no longer available
+        in the <filename>pg_wal</filename> directory of the target cluster.
+       </para>
+       <para>
+        If <varname>restore_command</varname> is already set in
+        <filename>postgresql.conf</filename>, you can provide the
+        <option>--restore-target-wal</option> option instead.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--debug</option></term>
       <listitem>
@@ -318,7 +352,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> or <option>-C</option> option to search
+      for the missing files in the WAL archive.
      </para>
     </step>
     <step>
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index b6429827cfb..26f41581c00 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -12,6 +12,7 @@
 #include "postgres_fe.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "access/rmgr.h"
 #include "access/xlog_internal.h"
@@ -19,6 +20,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 +43,7 @@ static char xlogfpath[MAXPGPATH];
 
 typedef struct XLogPageReadPrivate
 {
+	const char *restoreCommand;
 	int			tliIndex;
 } XLogPageReadPrivate;
 
@@ -55,7 +58,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 +66,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)
@@ -148,7 +152,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;
@@ -172,6 +176,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
 	}
 
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	xlogreader = XLogReaderAllocate(WalSegSz, datadir, &SimpleXLogPageRead,
 									&private);
 	if (xlogreader == NULL)
@@ -282,8 +287,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 to execute, 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 c6d00bb0ab7..33d6aada4e5 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,19 +76,21 @@ 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(_("  -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"));
-	printf(_("  -R, --write-recovery-conf      write configuration for replication\n"
-			 "                                 (requires --source-server)\n"));
-	printf(_("  -n, --dry-run                  stop before modifying anything\n"));
-	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
-			 "                                 safely to disk\n"));
-	printf(_("  -P, --progress                 write progress messages\n"));
-	printf(_("      --no-ensure-shutdown       do not automatically fix unclean shutdown\n"));
-	printf(_("      --debug                    write a lot of debug messages\n"));
-	printf(_("  -V, --version                  output version information, then exit\n"));
-	printf(_("  -?, --help                     show this help, then exit\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"));
+	printf(_("  -R, --write-recovery-conf             write configuration for replication\n"
+			 "                                        (requires --source-server)\n"));
+	printf(_("  -c, --restore-target-wal              use restore_command in target config\n"));
+	printf(_("                                        to retrieve WAL files from archive\n"));
+	printf(_("  -n, --dry-run                         stop before modifying anything\n"));
+	printf(_("  -N, --no-sync                         do not wait for changes to be written\n"
+			 "                                        safely to disk\n"));
+	printf(_("  -P, --progress                        write progress messages\n"));
+	printf(_("      --no-ensure-shutdown              do not automatically fix unclean shutdown\n"));
+	printf(_("      --debug                           write a lot of debug messages\n"));
+	printf(_("  -V, --version                         output version information, then exit\n"));
+	printf(_("  -?, --help                            show this help, then exit\n"));
 	printf(_("\nReport bugs to <pgsql-bugs@lists.postgresql.org>.\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, "D:nNPRC:c", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -155,6 +160,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'c':
+				restore_wal = true;
+				break;
+
 			case 'n':
 				dry_run = 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 e4e8d23c32d..b122ae43e5a 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 95d8ccfced1..d97e4377419 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/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl
index 1db534c0dc0..3027e09e0f4 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 7;
+use Test::More tests => 10;
 
 use FindBin;
 use lib $FindBin::RealBin;
@@ -70,5 +70,6 @@ template1
 # 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/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl
index f4710440fc3..bbad5ce9c94 100644
--- a/src/bin/pg_rewind/t/003_extrafiles.pl
+++ b/src/bin/pg_rewind/t/003_extrafiles.pl
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 5;
+use Test::More tests => 7;
 
 use File::Find;
 
@@ -90,5 +90,6 @@ sub run_test
 # 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 82fa220ac86..14cefe4517c 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -216,6 +216,46 @@ sub promote_standby
 	return;
 }
 
+# Moves WAL files to the temporary location and returns restore_command
+# to get them back.
+sub move_wal
+{
+	my ($tmp_dir, $master_pgdata) = @_;
+	my $wal_archive_path = "$tmp_dir/master_wal_archive";
+	my $wal_path = "$master_pgdata/pg_wal";
+	my $wal_dir;
+	my $restore_command;
+
+	rmtree($wal_archive_path);
+	mkdir($wal_archive_path) or
+	  die "mkdir($wal_archive_path) failed: $!";
+
+	# Move all old master WAL files to the archive.
+	# Old master should be stopped at this point.
+	opendir($wal_dir, $wal_path) or
+	  die "opendir($wal_path) failed: $!";;
+	while (my $wal_file = readdir($wal_dir))
+	{
+		if ($wal_file =~ /^[0-9A-F]{24}$/)
+		{
+			move("$wal_path/$wal_file", "$wal_archive_path") or
+			  die "move $wal_path/$wal_file -> $wal_archive_path failed: $!";
+		}
+	}
+	closedir($wal_dir);
+
+	if ($windows_os)
+	{
+		$restore_command = "copy $wal_archive_path\\\%f \%p";
+	}
+	else
+	{
+		$restore_command = "cp $wal_archive_path/\%f \%p";
+	}
+
+	return $restore_command;
+}
+
 sub run_pg_rewind
 {
 	my $test_mode       = shift;
@@ -227,10 +267,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' || $test_mode eq 'archive_conf')
+	{
+		# 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 +337,31 @@ 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.
+		my $restore_command = move_wal($tmp_folder, $master_pgdata);
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Add restore_command to postgresql.conf of target cluster.
+		$node_master->append_conf("postgresql.conf",
+								  "restore_command='$restore_command'");
+
+		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 44ca68fa6c3..b92c03c6331 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -86,6 +86,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/fe_archive.c b/src/common/fe_archive.c
new file mode 100644
index 00000000000..34d2be86954
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,154 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.c
+ *	  Routines to access WAL archive from frontend
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/common/fe_archive.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "access/xlog_internal.h"
+#include "common/fe_archive.h"
+#include "common/logging.h"
+
+
+/* logging support */
+#define pg_fatal(...) do { pg_log_fatal(__VA_ARGS__); exit(1); } while(0)
+
+/*
+ * 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],
+			   *dp,
+			   *endp;
+	const char *sp;
+	int			rc,
+				xlogfd;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	/*
+	 * Construct the command to be executed.
+	 */
+	dp = xlogRestoreCmd;
+	endp = xlogRestoreCmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = restoreCommand; *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 */
+					pg_fatal("restore_command with %%r cannot be used with pg_rewind.");
+					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';
+
+	/*
+	 * 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("archive file \"%s\" has wrong size: %lu instead of %lu, %s",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open file \"%s\" restored from archive: %s\n",
+								 xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\" restored from archive: %s",
+						 xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s",
+				 wait_result_to_str(rc));
+
+	pg_log_error("could not restore file \"%s\" from archive\n",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/include/common/fe_archive.h b/src/include/common/fe_archive.h
new file mode 100644
index 00000000000..7e9d140ea8f
--- /dev/null
+++ b/src/include/common/fe_archive.h
@@ -0,0 +1,18 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.h
+ *	  Routines to access WAL archive from frontend
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/fe_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ARCHIVE_H
+#define ARCHIVE_H
+
+extern int RestoreArchivedWALFile(const char *path, const char *xlogfname,
+								  off_t expectedSize, const char *restoreCommand);
+
+#endif							/* ARCHIVE_H */
#45Michael Paquier
michael@paquier.xyz
In reply to: Alexander Korotkov (#44)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Wed, Jan 22, 2020 at 12:55:30AM +0300, Alexander Korotkov wrote:

Thank you for your review!
The revised patch is attached.

Thanks for the new version.

On Sun, Jan 19, 2020 at 1:24 PM Michael Paquier <michael@paquier.xyz> wrote:

+static int
+RestoreArchivedWALFile(const char *path, const char *xlogfname,
+                      off_t expectedSize, const char *restoreCommand)
Not a fan of putting that to pg_rewind/parsexlog.c.  It has nothing to
do with WAL parsing, and it seems to me that we could have an argument
for making this available as a frontend-only API in src/common/.

I've put it into src/common/fe_archive.c.

This split makes sense. You have forgotten to update
@pgcommonfrontendfiles in Mkvcbuild.pm for the MSVC build. Still, I
can see that we have a lot of duplication between the code of the
frontend and the backend, which is annoying.. The execution part is
tricky to split because the backend has pre- and post- callbacks, the
interruption handling is not the same and there is the problem of
elog() calls, with elevel that can be changed depending on the backend
configuration. However, I can see one portion of the code which is
fully duplicated and could be refactored out: the construction of the
command to execute, by having in input the restore_command string and
the arguments that we expect to replace in the '%' markers which are
part of the command. If NULL is specified for one of those values,
then the construction routine returns NULL, synonym of failure. And
the result of the routine is the built command. I think that you
would need an extra argument to give space for the error message
generated in the event of an error when building the command.

Do we actually need --target-restore-command at all? It seems to me
that we have all we need with --restore-target-wal, and that's not
really instinctive to pass down a command via another command..

I was going to argue that --restore-target-wal is useful. But I
didn't find convincing example for that. Naturally, if one uses
restore_command during pg_rewind then why the same restore_command
can't be used in new standby. So, I've removed --restore-target-wal
argument. If we will find convincing use case we can return it any
moment.

Okay. Let's consider it if it makes sense. You can always go through
this restriction by first setting restore_command in the target
cluster, and then run pg_rewind. And I would think that someone is
going to use the same restore_command even after restarting the
rewound target cluster.

+       # Add restore_command to postgresql.conf of target cluster.
+       open(my $conf_fd, ">>", $master_conf_path) or die;
+       print $conf_fd "\nrestore_command='$restore_command'";
+       close $conf_fd;
We have append_conf() for that.

Sure, thank you for pointing!

+++ b/src/include/common/fe_archive.h
+#ifndef ARCHIVE_H
+#define ARCHIVE_H
This should be FE_ARCHIVE_H.
-   while ((c = getopt_long(argc, argv, "D:nNPR", long_options,
    &option_index)) != -1)
+   while ((c = getopt_long(argc, argv, "D:nNPRC:c", long_options,
    &option_index)) != -1)
This still includes 'C', but that should not be the case.
+   <application>pg_rewind</application> with the
<literal>-c</literal> or
+   <literal>-C</literal> option to automatically retrieve them from
the WAL
[...]
+      <term><option>-C <replaceable
class="parameter">restore_command</replaceable></option></term>
+      <term><option>--target-restore-command=<replaceable
class="parameter">restore_command</replaceable></option></term>
[...]
+      available, try re-running <application>pg_rewind</application> with
+      the <option>-c</option> or <option>-C</option> option to search
+      for the missing files in the WAL archive.
Three places in the docs need to be cleaned up.

Do we really need to test the new "archive" mode as well for
003_extrafiles and 002_databases? I would imagine that only 001_basic
is enough.

+# Moves WAL files to the temporary location and returns restore_command
+# to get them back.
+sub move_wal
+{
+   my ($tmp_dir, $master_pgdata) = @_;
+   my $wal_archive_path = "$tmp_dir/master_wal_archive";
+   my $wal_path = "$master_pgdata/pg_wal";
+   my $wal_dir;
+   my $restore_command;
I think that this routine is wrongly designed.  First, in order to
copy the contents from one path to another, we have
RecursiveCopy::copy_path, and there is a long history about making 
it safe for the TAP tests.  Second, there is in
PostgresNode::enable_restoring already a code path doing the
decision-making on how building restore_command.  We should keep this
code path unique.
--
Michael
#46Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#45)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-01-24 08:50, Michael Paquier wrote:

On Wed, Jan 22, 2020 at 12:55:30AM +0300, Alexander Korotkov wrote:

On Sun, Jan 19, 2020 at 1:24 PM Michael Paquier <michael@paquier.xyz>
wrote:

+static int
+RestoreArchivedWALFile(const char *path, const char *xlogfname,
+                      off_t expectedSize, const char 
*restoreCommand)
Not a fan of putting that to pg_rewind/parsexlog.c.  It has nothing 
to
do with WAL parsing, and it seems to me that we could have an 
argument
for making this available as a frontend-only API in src/common/.

I've put it into src/common/fe_archive.c.

This split makes sense. You have forgotten to update
@pgcommonfrontendfiles in Mkvcbuild.pm for the MSVC build. Still, I
can see that we have a lot of duplication between the code of the
frontend and the backend, which is annoying.. The execution part is
tricky to split because the backend has pre- and post- callbacks, the
interruption handling is not the same and there is the problem of
elog() calls, with elevel that can be changed depending on the backend
configuration. However, I can see one portion of the code which is
fully duplicated and could be refactored out: the construction of the
command to execute, by having in input the restore_command string and
the arguments that we expect to replace in the '%' markers which are
part of the command.

OK, I agree that duplication of this part directly is annoying. I did a
refactoring and moved this restore_command building code into
common/archive. Separate file was needed, since fe_archive is
frontend-only, so I cannot put universal code there. I do not know is
there any better place for it.

If NULL is specified for one of those values,
then the construction routine returns NULL, synonym of failure. And
the result of the routine is the built command. I think that you
would need an extra argument to give space for the error message
generated in the event of an error when building the command.

I did it in a bit different way, but the overall logic is exactly as you
proposed, I hope.
Also I have checked win/linux build in the similar to cfbot CI setup and
now everything runs well.

+++ b/src/include/common/fe_archive.h
+#ifndef ARCHIVE_H
+#define ARCHIVE_H
This should be FE_ARCHIVE_H.

Changed.

-   while ((c = getopt_long(argc, argv, "D:nNPR", long_options,
&option_index)) != -1)
+   while ((c = getopt_long(argc, argv, "D:nNPRC:c", long_options,
&option_index)) != -1)
This still includes 'C', but that should not be the case.
+   <application>pg_rewind</application> with the
<literal>-c</literal> or
+   <literal>-C</literal> option to automatically retrieve them from
the WAL
[...]
+      <term><option>-C <replaceable
class="parameter">restore_command</replaceable></option></term>
+      <term><option>--target-restore-command=<replaceable
class="parameter">restore_command</replaceable></option></term>
[...]
+      available, try re-running <application>pg_rewind</application> 
with
+      the <option>-c</option> or <option>-C</option> option to search
+      for the missing files in the WAL archive.
Three places in the docs need to be cleaned up.

I have fixed all the above, thanks.

Do we really need to test the new "archive" mode as well for
003_extrafiles and 002_databases? I would imagine that only 001_basic
is enough.

We do not check any unique code paths with it, so I do it only for
001_basic now. I have left it as a test mode, since it will be easy to
turn this on for any other test in the future if needed and it fits well
RewindTest.pm module.

+# Moves WAL files to the temporary location and returns 
restore_command
+# to get them back.
+sub move_wal
+{
+   my ($tmp_dir, $master_pgdata) = @_;
+   my $wal_archive_path = "$tmp_dir/master_wal_archive";
+   my $wal_path = "$master_pgdata/pg_wal";
+   my $wal_dir;
+   my $restore_command;
I think that this routine is wrongly designed.  First, in order to
copy the contents from one path to another, we have
RecursiveCopy::copy_path, and there is a long history about making
it safe for the TAP tests.  Second, there is in
PostgresNode::enable_restoring already a code path doing the
decision-making on how building restore_command.  We should keep this
code path unique.

Yeah, thank you for pointing me to the RecursiveCopy::copypath and
especially PostgresNode::enable_restoring, I have modified test to use
them instead. The copypath does not work with existing destination
directories and does not preserve initial permissions, so I had to do
some dirty work to achieve what we need in the test and keep it simple
in the same time. However, I think that using these unified routines is
much better for a future support.

New version is attached. Do you have any other comments or objections?

Regards
--
Alexey

Attachments:

v14-0001-pg_rewind-Add-options-to-restore-WAL-files.patchtext/x-diff; name=v14-0001-pg_rewind-Add-options-to-restore-WAL-files.patchDownload
From 7af0b3642f6218c764eee361e013f24bfb43ffbe Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <kondratov.aleksey@gmail.com>
Date: Fri, 31 Jan 2020 20:08:13 +0300
Subject: [PATCH v14] 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 two new pg_rewind options, which allow pg_rewind to
    automatically retrieve missing WAL files from archival storage.  The first
    option let pg_rewind read restore_command from postgresql.conf, while the
    second one specifies restore_command directly.

    Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1%40postgrespro.ru
    Author: Alexey Kondratov
    Reviewed-by: Andrey Borodin, Alvaro Herrera, Michael Paquier
    Reviewed-by: Andres Freund, Alexander Korotkov
---
 doc/src/sgml/ref/pg_rewind.sgml          |  28 ++++--
 src/backend/access/transam/xlogarchive.c |  58 +------------
 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        |  64 +++++++++++++-
 src/common/Makefile                      |   2 +
 src/common/archive.c                     |  97 +++++++++++++++++++++
 src/common/fe_archive.c                  | 106 +++++++++++++++++++++++
 src/include/common/archive.h             |  21 +++++
 src/include/common/fe_archive.h          |  18 ++++
 src/tools/msvc/Mkvcbuild.pm              |   8 +-
 13 files changed, 440 insertions(+), 81 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..8fa0d5aad8 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
+        <filename>postgresql.conf</filename> to retrieve WAL files from
+        the WAL archive if these files are no longer available in the
+        <filename>pg_wal</filename> directory of the target cluster.
+       </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..e71aa2edb8 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,8 @@ 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';
-
-	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';
+	(void) ConstructRestoreCommand(recoveryRestoreCommand, xlogpath, xlogfname,
+								   lastRestartPointFname, xlogRestoreCmd);
 
 	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..08a7fc3560 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 to execute, 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..f078cd694a 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;
@@ -79,6 +81,8 @@ usage(const char *progname)
 	printf(_("      --source-server=CONNSTR    source server to synchronize with\n"));
 	printf(_("  -R, --write-recovery-conf      write configuration for replication\n"
 			 "                                 (requires --source-server)\n"));
+	printf(_("  -c, --restore-target-wal       use restore_command in target config\n"));
+	printf(_("                                 to retrieve WAL files from archive\n"));
 	printf(_("  -n, --dry-run                  stop before modifying anything\n"));
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
 			 "                                 safely to disk\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, "D:nNPRc", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -155,6 +160,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'c':
+				restore_wal = true;
+				break;
+
 			case 'n':
 				dry_run = 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..fd81d5684f 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,48 @@ 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.
+		rmdir($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
+		);
+		chmod(0700, $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");
+		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 e757fb7399..43702dc309 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -47,6 +47,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..d57768a949
--- /dev/null
+++ b/src/common/archive.c
@@ -0,0 +1,97 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive.c
+ *	  Common WAL archive routines
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/common/archive.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#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
+ConstructRestoreCommand(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..389d389d0d
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,106 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.c
+ *	  Routines to access WAL archive from frontend
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/common/fe_archive.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#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"
+
+
+/* logging support */
+#define pg_fatal(...) do { pg_log_fatal(__VA_ARGS__); exit(1); } while(0)
+
+/*
+ * 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 = ConstructRestoreCommand(restoreCommand, xlogpath, xlogfname, NULL, xlogRestoreCmd);
+
+	if (rc < 0)
+		pg_fatal("restore_command with %%r alias cannot be used.");
+
+	/*
+	 * 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("archive file \"%s\" has wrong size: %lu instead of %lu, %s",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open file \"%s\" restored from archive: %s\n",
+								 xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\" restored from archive: %s",
+						 xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s",
+				 wait_result_to_str(rc));
+
+	pg_log_error("could not restore file \"%s\" from archive\n",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/include/common/archive.h b/src/include/common/archive.h
new file mode 100644
index 0000000000..032761425b
--- /dev/null
+++ b/src/include/common/archive.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive.h
+ *	  Common WAL archive routines
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ARCHIVE_H
+#define ARCHIVE_H
+
+extern int ConstructRestoreCommand(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..a24e9f383b
--- /dev/null
+++ b/src/include/common/fe_archive.h
@@ -0,0 +1,18 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.h
+ *	  Routines to access WAL archive from frontend
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * 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 a43e31c60e..0e298d3fdc 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -120,8 +120,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 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 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
@@ -138,8 +138,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;
 
-- 
2.19.1

#47Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexey Kondratov (#46)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Sat, Feb 1, 2020 at 2:16 AM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:

New version is attached. Do you have any other comments or objections?

Now patch looks much better. Thanks to Michael Paquier for review.
I've just revised commit message reflecting we've removed one of the
new options.

But I have following question.

+       # Move all old master WAL files to the archive.
+       RecursiveCopy::copypath(
+           $node_master->data_dir . "/pg_wal",
+           $node_master->archive_dir
+       );
+       chmod(0700, $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");
+       chmod(0700, $node_master->data_dir . "/pg_wal");

I think usage of chmod() deserves comment. As I get default
permissions are sufficient for work, but we need to set them to
satisfy 'check PGDATA permissions' test.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v15-0001-pg_rewind-Add-options-to-restore-WAL-files.patchapplication/octet-stream; name=v15-0001-pg_rewind-Add-options-to-restore-WAL-files.patchDownload
commit 9d1b38885662d40dcbcfef88e06eda7cc54225d0
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Tue Feb 25 02:22:45 2020 +0300

    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: Andrey Borodin, Alvaro Herrera, Michael Paquier
    Reviewed-by: Andres Freund, Alexander Korotkov

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 42d29edd4e9..8fa0d5aad88 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
+        <filename>postgresql.conf</filename> to retrieve WAL files from
+        the WAL archive if these files are no longer available in the
+        <filename>pg_wal</filename> directory of the target cluster.
+       </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 188b73e7526..e71aa2edb8d 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,8 @@ 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';
-
-	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';
+	(void) ConstructRestoreCommand(recoveryRestoreCommand, xlogpath, xlogfname,
+								   lastRestartPointFname, xlogRestoreCmd);
 
 	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 eb61cb8803f..08a7fc35608 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 to execute, 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 c6d00bb0ab7..f078cd694a4 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;
@@ -79,6 +81,8 @@ usage(const char *progname)
 	printf(_("      --source-server=CONNSTR    source server to synchronize with\n"));
 	printf(_("  -R, --write-recovery-conf      write configuration for replication\n"
 			 "                                 (requires --source-server)\n"));
+	printf(_("  -c, --restore-target-wal       use restore_command in target config\n"));
+	printf(_("                                 to retrieve WAL files from archive\n"));
 	printf(_("  -n, --dry-run                  stop before modifying anything\n"));
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
 			 "                                 safely to disk\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, "D:nNPRc", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -155,6 +160,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'c':
+				restore_wal = true;
+				break;
+
 			case 'n':
 				dry_run = 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 e4e8d23c32d..b122ae43e5a 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 95d8ccfced1..d97e4377419 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 82fa220ac86..fd81d5684f0 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,48 @@ 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.
+		rmdir($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
+		);
+		chmod(0700, $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");
+		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 ab98f4faafb..f0631da5e41 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 \
@@ -86,6 +87,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 00000000000..d57768a9498
--- /dev/null
+++ b/src/common/archive.c
@@ -0,0 +1,97 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive.c
+ *	  Common WAL archive routines
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/common/archive.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#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
+ConstructRestoreCommand(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 00000000000..389d389d0dc
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,106 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.c
+ *	  Routines to access WAL archive from frontend
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/common/fe_archive.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#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"
+
+
+/* logging support */
+#define pg_fatal(...) do { pg_log_fatal(__VA_ARGS__); exit(1); } while(0)
+
+/*
+ * 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 = ConstructRestoreCommand(restoreCommand, xlogpath, xlogfname, NULL, xlogRestoreCmd);
+
+	if (rc < 0)
+		pg_fatal("restore_command with %%r alias cannot be used.");
+
+	/*
+	 * 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("archive file \"%s\" has wrong size: %lu instead of %lu, %s",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open file \"%s\" restored from archive: %s\n",
+								 xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\" restored from archive: %s",
+						 xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s",
+				 wait_result_to_str(rc));
+
+	pg_log_error("could not restore file \"%s\" from archive\n",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/include/common/archive.h b/src/include/common/archive.h
new file mode 100644
index 00000000000..032761425bb
--- /dev/null
+++ b/src/include/common/archive.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive.h
+ *	  Common WAL archive routines
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ARCHIVE_H
+#define ARCHIVE_H
+
+extern int ConstructRestoreCommand(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 00000000000..a24e9f383b2
--- /dev/null
+++ b/src/include/common/fe_archive.h
@@ -0,0 +1,18 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.h
+ *	  Routines to access WAL archive from frontend
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * 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 727a8fdec9b..915a2945ef0 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 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 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;
 
#48Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#47)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Tue, Feb 25, 2020 at 1:48 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I think usage of chmod() deserves comment. As I get default
permissions are sufficient for work, but we need to set them to
satisfy 'check PGDATA permissions' test.

I've added this comment myself. I've also fixes some indentation.
Patch now looks good to me. I'm going to push it if no objections.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v16-0001-pg_rewind-Add-options-to-restore-WAL-files.patchapplication/octet-stream; name=v16-0001-pg_rewind-Add-options-to-restore-WAL-files.patchDownload
commit 40f801f9b48f3cda5f8c1007439d5d90fcc0f7a1
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Tue Feb 25 02:22:45 2020 +0300

    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

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 42d29edd4e9..8fa0d5aad88 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
+        <filename>postgresql.conf</filename> to retrieve WAL files from
+        the WAL archive if these files are no longer available in the
+        <filename>pg_wal</filename> directory of the target cluster.
+       </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 188b73e7526..52281e87ee6 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;
@@ -65,8 +63,8 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 	TimeLineID	restartTli;
 
 	/*
-	 * Ignore restore_command when not in archive recovery (meaning
-	 * we are in crash recovery).
+	 * Ignore restore_command when not in archive recovery (meaning we are in
+	 * crash recovery).
 	 */
 	if (!ArchiveRecoveryRequested)
 		goto not_available;
@@ -149,58 +147,8 @@ 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';
-
-	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';
+	(void) ConstructRestoreCommand(recoveryRestoreCommand, xlogpath, xlogfname,
+								   lastRestartPointFname, xlogRestoreCmd);
 
 	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 eb61cb8803f..08a7fc35608 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 to execute, 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 c6d00bb0ab7..f078cd694a4 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;
@@ -79,6 +81,8 @@ usage(const char *progname)
 	printf(_("      --source-server=CONNSTR    source server to synchronize with\n"));
 	printf(_("  -R, --write-recovery-conf      write configuration for replication\n"
 			 "                                 (requires --source-server)\n"));
+	printf(_("  -c, --restore-target-wal       use restore_command in target config\n"));
+	printf(_("                                 to retrieve WAL files from archive\n"));
 	printf(_("  -n, --dry-run                  stop before modifying anything\n"));
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
 			 "                                 safely to disk\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, "D:nNPRc", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -155,6 +160,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'c':
+				restore_wal = true;
+				break;
+
 			case 'n':
 				dry_run = 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 e4e8d23c32d..b122ae43e5a 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 95d8ccfced1..d97e4377419 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 82fa220ac86..8962edd99e6 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,51 @@ 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.
+		rmdir($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 ab98f4faafb..f0631da5e41 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 \
@@ -86,6 +87,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 00000000000..d57768a9498
--- /dev/null
+++ b/src/common/archive.c
@@ -0,0 +1,97 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive.c
+ *	  Common WAL archive routines
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/common/archive.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#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
+ConstructRestoreCommand(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 00000000000..389d389d0dc
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,106 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.c
+ *	  Routines to access WAL archive from frontend
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/common/fe_archive.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#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"
+
+
+/* logging support */
+#define pg_fatal(...) do { pg_log_fatal(__VA_ARGS__); exit(1); } while(0)
+
+/*
+ * 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 = ConstructRestoreCommand(restoreCommand, xlogpath, xlogfname, NULL, xlogRestoreCmd);
+
+	if (rc < 0)
+		pg_fatal("restore_command with %%r alias cannot be used.");
+
+	/*
+	 * 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("archive file \"%s\" has wrong size: %lu instead of %lu, %s",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open file \"%s\" restored from archive: %s\n",
+								 xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\" restored from archive: %s",
+						 xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s",
+				 wait_result_to_str(rc));
+
+	pg_log_error("could not restore file \"%s\" from archive\n",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/include/common/archive.h b/src/include/common/archive.h
new file mode 100644
index 00000000000..74aa43cbaca
--- /dev/null
+++ b/src/include/common/archive.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive.h
+ *	  Common WAL archive routines
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ARCHIVE_H
+#define ARCHIVE_H
+
+extern int	ConstructRestoreCommand(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 00000000000..028e97a1dff
--- /dev/null
+++ b/src/include/common/fe_archive.h
@@ -0,0 +1,18 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.h
+ *	  Routines to access WAL archive from frontend
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * 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 727a8fdec9b..915a2945ef0 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 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 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;
 
#49Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Alexander Korotkov (#48)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-02-26 22:03, Alexander Korotkov wrote:

On Tue, Feb 25, 2020 at 1:48 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I think usage of chmod() deserves comment. As I get default
permissions are sufficient for work, but we need to set them to
satisfy 'check PGDATA permissions' test.

I've added this comment myself.

Thanks for doing it yourself, I was going to answer tonight, but it
would be obviously too late.

I've also fixes some indentation.
Patch now looks good to me. I'm going to push it if no objections.

I think that docs should be corrected. Previously Michael was against
the phrase 'restore_command defined in the postgresql.conf', since it
also could be defined in any config file included there. We corrected it
in the pg_rewind --help output, but now docs say:

+        Use the <varname>restore_command</varname> defined in
+        <filename>postgresql.conf</filename> to retrieve WAL files from
+        the WAL archive if these files are no longer available in the
+        <filename>pg_wal</filename> directory of the target cluster.

Probably it should be something like:

+        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.

Here the only text split changed:

-	 * Ignore restore_command when not in archive recovery (meaning
-	 * we are in crash recovery).
+	 * Ignore restore_command when not in archive recovery (meaning we are 
in
+	 * crash recovery).

Should we do so in this patch?

I think that this extra dot at the end is not necessary here:

+ pg_log_debug("using config variable restore_command=\'%s\'.",
restore_command);

If you agree then attached is a patch with all the corrections above. It
is made with default git format-patch format, but yours were in a
slightly different format, so I only was able to apply them with git am
--patch-format=stgit.

--
Alexey Kondratov

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

Attachments:

v17-0001-pg_rewind-Add-options-to-restore-WAL-files.patchtext/x-diff; name=v17-0001-pg_rewind-Add-options-to-restore-WAL-files.patchDownload
From fa2fc359dd9852afc608663fa32733e800652ffa Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Tue, 25 Feb 2020 02:22:45 +0300
Subject: [PATCH v17] 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 |  58 +------------
 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        |  67 +++++++++++++-
 src/common/Makefile                      |   2 +
 src/common/archive.c                     |  97 +++++++++++++++++++++
 src/common/fe_archive.c                  | 106 +++++++++++++++++++++++
 src/include/common/archive.h             |  21 +++++
 src/include/common/fe_archive.h          |  18 ++++
 src/tools/msvc/Mkvcbuild.pm              |   8 +-
 13 files changed, 443 insertions(+), 81 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..e71aa2edb8 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,8 @@ 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';
-
-	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';
+	(void) ConstructRestoreCommand(recoveryRestoreCommand, xlogpath, xlogfname,
+								   lastRestartPointFname, xlogRestoreCmd);
 
 	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..08a7fc3560 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 to execute, 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..149c0f1dc4 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;
@@ -79,6 +81,8 @@ usage(const char *progname)
 	printf(_("      --source-server=CONNSTR    source server to synchronize with\n"));
 	printf(_("  -R, --write-recovery-conf      write configuration for replication\n"
 			 "                                 (requires --source-server)\n"));
+	printf(_("  -c, --restore-target-wal       use restore_command in target config\n"));
+	printf(_("                                 to retrieve WAL files from archive\n"));
 	printf(_("  -n, --dry-run                  stop before modifying anything\n"));
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
 			 "                                 safely to disk\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, "D:nNPRc", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -155,6 +160,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'c':
+				restore_wal = true;
+				break;
+
 			case 'n':
 				dry_run = 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..bc761596cf 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,51 @@ 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.
+		rmdir($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 ab98f4faaf..f0631da5e4 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 \
@@ -86,6 +87,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..d57768a949
--- /dev/null
+++ b/src/common/archive.c
@@ -0,0 +1,97 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive.c
+ *	  Common WAL archive routines
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/common/archive.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#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
+ConstructRestoreCommand(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..0b40fde302
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,106 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.c
+ *	  Routines to access WAL archive from frontend
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/common/fe_archive.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#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"
+
+
+/* logging support */
+#define pg_fatal(...) do { pg_log_fatal(__VA_ARGS__); exit(1); } while(0)
+
+/*
+ * 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 = ConstructRestoreCommand(restoreCommand, xlogpath, xlogfname, NULL, xlogRestoreCmd);
+
+	if (rc < 0)
+		pg_fatal("restore_command with %%r alias cannot be used.");
+
+	/*
+	 * 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("archive file \"%s\" has wrong size: %lu instead of %lu, %s",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open file \"%s\" restored from archive: %s\n",
+								 xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\" restored from archive: %s",
+						 xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s",
+				 wait_result_to_str(rc));
+
+	pg_log_error("could not restore file \"%s\" from archive\n",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/include/common/archive.h b/src/include/common/archive.h
new file mode 100644
index 0000000000..74aa43cbac
--- /dev/null
+++ b/src/include/common/archive.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive.h
+ *	  Common WAL archive routines
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ARCHIVE_H
+#define ARCHIVE_H
+
+extern int	ConstructRestoreCommand(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..028e97a1df
--- /dev/null
+++ b/src/include/common/fe_archive.h
@@ -0,0 +1,18 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.h
+ *	  Routines to access WAL archive from frontend
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * 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 727a8fdec9..915a2945ef 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 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 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: 612a1ab76724aa1514b6509269342649f8cab375
-- 
2.19.1

#50Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexey Kondratov (#49)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Wed, Feb 26, 2020 at 11:45 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:

On 2020-02-26 22:03, Alexander Korotkov wrote:

On Tue, Feb 25, 2020 at 1:48 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I think usage of chmod() deserves comment. As I get default
permissions are sufficient for work, but we need to set them to
satisfy 'check PGDATA permissions' test.

I've added this comment myself.

Thanks for doing it yourself, I was going to answer tonight, but it
would be obviously too late.

I've also fixes some indentation.
Patch now looks good to me. I'm going to push it if no objections.

I think that docs should be corrected. Previously Michael was against
the phrase 'restore_command defined in the postgresql.conf', since it
also could be defined in any config file included there. We corrected it
in the pg_rewind --help output, but now docs say:

+        Use the <varname>restore_command</varname> defined in
+        <filename>postgresql.conf</filename> to retrieve WAL files from
+        the WAL archive if these files are no longer available in the
+        <filename>pg_wal</filename> directory of the target cluster.

Probably it should be something like:

+        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.

Here the only text split changed:

-        * Ignore restore_command when not in archive recovery (meaning
-        * we are in crash recovery).
+        * Ignore restore_command when not in archive recovery (meaning we are
in
+        * crash recovery).

Should we do so in this patch?

I think that this extra dot at the end is not necessary here:

+ pg_log_debug("using config variable restore_command=\'%s\'.",
restore_command);

If you agree then attached is a patch with all the corrections above.

Thank you! I'll push the v17 version of patch.

Regarding text split change, it was made by pgindent. I didn't notice
it belongs to unchanged part of code. Sure, we shouldn't include this
into the patch.

It is made with default git format-patch format, but yours were in a
slightly different format, so I only was able to apply them with git am
--patch-format=stgit.

I made this patch using 'git show'. Yes, it would be better if I use
'git format-patch' instead. Thank you for noticing.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#51Michael Paquier
michael@paquier.xyz
In reply to: Alexander Korotkov (#50)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Thu, Feb 27, 2020 at 12:43:55AM +0300, Alexander Korotkov wrote:

Regarding text split change, it was made by pgindent. I didn't notice
it belongs to unchanged part of code. Sure, we shouldn't include this
into the patch.

I have read through v17 (not tested, sorry), and spotted a couple of
issues that need to be addressed.

+                               "--source-pgdata=$standby_pgdata",
+                               "--target-pgdata=$master_pgdata",
+                               "--no-sync", "--no-ensure-shutdown",
FWIW, I think that perl indenting would reshape this part.  I would
recommend to run src/tools/pgindent/pgperltidy and
./src/tools/perlcheck/pgperlcritic before commit.

+ * Copyright (c) 2020, PostgreSQL Global Development Group
Wouldn't it be better to just use the full copyright here? I mean the
following:
Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
Portions Copyright (c) 1994, The Regents of the University of California

+++ b/src/common/archive.c
[...]
+#include "postgres.h"
+
+#include "common/archive.h"
This is incorrect.  All files shared between the backend and the
frontend in src/common/ have to include the following set of headers:
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif
+++ b/src/common/fe_archive.c
[...]
+#include "postgres_fe.h"
This is incomplete.  The following piece should be added:
#ifndef FRONTEND
#error "This file is not expected to be compiled for backend code"
#endif
+               snprintf(postgres_cmd, sizeof(postgres_cmd), "%s -D %s
-C restore_command",
+                                postgres_exec_path, datadir_target);
+
I think that this is missing proper quoting.

I would rename ConstructRestoreCommand() to BuildRestoreCommand()
while on it..

I think that it would be saner to check the return status of
ConstructRestoreCommand() in xlogarchive.c as a sanity check, with an
elog(ERROR) if not 0, as that should never happen.
--
Michael

#52Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#51)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-02-27 04:52, Michael Paquier wrote:

On Thu, Feb 27, 2020 at 12:43:55AM +0300, Alexander Korotkov wrote:

Regarding text split change, it was made by pgindent. I didn't notice
it belongs to unchanged part of code. Sure, we shouldn't include this
into the patch.

I have read through v17 (not tested, sorry), and spotted a couple of
issues that need to be addressed.

+                               "--source-pgdata=$standby_pgdata",
+                               "--target-pgdata=$master_pgdata",
+                               "--no-sync", "--no-ensure-shutdown",
FWIW, I think that perl indenting would reshape this part.  I would
recommend to run src/tools/pgindent/pgperltidy and
./src/tools/perlcheck/pgperlcritic before commit.

Thanks, formatted this part with perltidy. It also has modified
RecursiveCopy's indents. Pgperlcritic has no complains about this file.
BTW, being executed on the whole project pgperltidy modifies dozens of
perl files an even pgindent itself.

+ * Copyright (c) 2020, PostgreSQL Global Development Group
Wouldn't it be better to just use the full copyright here? I mean the
following:
Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
Portions Copyright (c) 1994, The Regents of the University of
California

I think so, it contains some older code parts, so it is better to use
unified copyrights.

+++ b/src/common/archive.c
[...]
+#include "postgres.h"
+
+#include "common/archive.h"
This is incorrect.  All files shared between the backend and the
frontend in src/common/ have to include the following set of headers:
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif
+++ b/src/common/fe_archive.c
[...]
+#include "postgres_fe.h"
This is incomplete.  The following piece should be added:
#ifndef FRONTEND
#error "This file is not expected to be compiled for backend code"
#endif

Fixed both.

+               snprintf(postgres_cmd, sizeof(postgres_cmd), "%s -D %s
-C restore_command",
+                                postgres_exec_path, datadir_target);
+
I think that this is missing proper quoting.

Yep, added the same quoting as in pg_upgrade/options.

I would rename ConstructRestoreCommand() to BuildRestoreCommand()
while on it..

OK, shorter is better.

I think that it would be saner to check the return status of
ConstructRestoreCommand() in xlogarchive.c as a sanity check, with an
elog(ERROR) if not 0, as that should never happen.

Added.

New version of the patch is attached. Thanks again for your review.

Regards
--
Alexey Kondratov

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

Attachments:

v18-0001-pg_rewind-Add-options-to-restore-WAL-files.patchtext/x-diff; name=v18-0001-pg_rewind-Add-options-to-restore-WAL-files.patchDownload
From c775a2e40e405474f6ecef35843d276d43fb462f Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Tue, 25 Feb 2020 02:22:45 +0300
Subject: [PATCH v18] 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                  | 111 +++++++++++++++++++++++
 src/include/common/archive.h             |  22 +++++
 src/include/common/fe_archive.h          |  19 ++++
 src/tools/msvc/Mkvcbuild.pm              |   8 +-
 13 files changed, 457 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..08a7fc3560 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 to execute, 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..db43aada05 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;
@@ -79,6 +81,8 @@ usage(const char *progname)
 	printf(_("      --source-server=CONNSTR    source server to synchronize with\n"));
 	printf(_("  -R, --write-recovery-conf      write configuration for replication\n"
 			 "                                 (requires --source-server)\n"));
+	printf(_("  -c, --restore-target-wal       use restore_command in target config\n"));
+	printf(_("                                 to retrieve WAL files from archive\n"));
 	printf(_("  -n, --dry-run                  stop before modifying anything\n"));
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
 			 "                                 safely to disk\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, "D:nNPRc", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -155,6 +160,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'c':
+				restore_wal = true;
+				break;
+
 			case 'n':
 				dry_run = 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..7af8b9b347 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.
+		rmdir($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 ab98f4faaf..f0631da5e4 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 \
@@ -86,6 +87,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..7309983ed4
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,111 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+
+/* logging support */
+#define pg_fatal(...) do { pg_log_fatal(__VA_ARGS__); exit(1); } while(0)
+
+/*
+ * 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_fatal("restore_command with %%r alias cannot be used.");
+
+	/*
+	 * 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("archive file \"%s\" has wrong size: %lu instead of %lu, %s",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open file \"%s\" restored from archive: %s\n",
+								 xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\" restored from archive: %s",
+						 xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s",
+				 wait_result_to_str(rc));
+
+	pg_log_error("could not restore file \"%s\" from archive\n",
+				 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 727a8fdec9..915a2945ef 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 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 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: 612a1ab76724aa1514b6509269342649f8cab375
-- 
2.19.1

#53Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Alexey Kondratov (#52)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

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.

--
Alexey Kondratov

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

Attachments:

v19-0001-pg_rewind-Add-options-to-restore-WAL-files.patchtext/x-diff; name=v19-0001-pg_rewind-Add-options-to-restore-WAL-files.patchDownload
From ea93b52b298d80aac547735c5917386b37667595 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Tue, 25 Feb 2020 02:22:45 +0300
Subject: [PATCH v19] 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                  | 111 +++++++++++++++++++++++
 src/include/common/archive.h             |  22 +++++
 src/include/common/fe_archive.h          |  19 ++++
 src/tools/msvc/Mkvcbuild.pm              |   8 +-
 13 files changed, 457 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..08a7fc3560 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 to execute, 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..db43aada05 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;
@@ -79,6 +81,8 @@ usage(const char *progname)
 	printf(_("      --source-server=CONNSTR    source server to synchronize with\n"));
 	printf(_("  -R, --write-recovery-conf      write configuration for replication\n"
 			 "                                 (requires --source-server)\n"));
+	printf(_("  -c, --restore-target-wal       use restore_command in target config\n"));
+	printf(_("                                 to retrieve WAL files from archive\n"));
 	printf(_("  -n, --dry-run                  stop before modifying anything\n"));
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
 			 "                                 safely to disk\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, "D:nNPRc", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -155,6 +160,10 @@ main(int argc, char **argv)
 				showprogress = true;
 				break;
 
+			case 'c':
+				restore_wal = true;
+				break;
+
 			case 'n':
 				dry_run = 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..7af8b9b347 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.
+		rmdir($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..7309983ed4
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,111 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+
+/* logging support */
+#define pg_fatal(...) do { pg_log_fatal(__VA_ARGS__); exit(1); } while(0)
+
+/*
+ * 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_fatal("restore_command with %%r alias cannot be used.");
+
+	/*
+	 * 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("archive file \"%s\" has wrong size: %lu instead of %lu, %s",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize, strerror(errno));
+			}
+			else
+			{
+				xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+					pg_log_error("could not open file \"%s\" restored from archive: %s\n",
+								 xlogpath, strerror(errno));
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			/* Stat failed */
+			pg_log_error("could not stat file \"%s\" restored from archive: %s",
+						 xlogpath, strerror(errno));
+		}
+	}
+
+	/*
+	 * 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_fatal("restore_command failed due to the signal: %s",
+				 wait_result_to_str(rc));
+
+	pg_log_error("could not restore file \"%s\" from archive\n",
+				 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: c4b0edb07ed53063ea4c86cd7918ad6ea01d8979
-- 
2.19.1

#54Michael Paquier
michael@paquier.xyz
In reply to: Alexey Kondratov (#53)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

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.
+   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.
+/* 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.
-   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.

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

+               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.

--
Michael

#55Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#54)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

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

Attachments:

v20-0001-pg_rewind-Add-options-to-restore-WAL-files.patchtext/x-diff; name=v20-0001-pg_rewind-Add-options-to-restore-WAL-files.patchDownload
From ba20808ffddf3fe2eefe96d3385697fb6583ce9a Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@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

#56Michael Paquier
michael@paquier.xyz
In reply to: Alexey Kondratov (#55)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Fri, Feb 28, 2020 at 03:37:47PM +0300, Alexey Kondratov wrote:

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.

elog() is called in the event of errors which should never happen by
definition, so your comment is just a duplication of this state. I'd
still remove that.

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.

archive.c should not depend on anything from src/bin/.

+ * 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)

Actually, expectedSize is IMO a bad idea, because any caller of this
routine passing down zero could be trapped with an incorrect file
size. So let's remove the behavior where it is possible to bypass
this sanity check. We don't need it in pg_rewind either.

+		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));

Still missing one %m here.

+		/* Remove trailing newline */
+		if (strchr(cmd_output, '\n') != NULL)
+			*strchr(cmd_output, '\n') = '\0';

It seems to me that what you are looking here is to use
pg_strip_crlf(). Thinking harder, we have pipe_read_line() in
src/common/exec.c which does the exact same job..

- /*
- * construct the command to be executed
- */

Perhaps you meant "build" here.

+	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);

For readability, I would move that into its own separate routine.
--
Michael

#57Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#56)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-03-02 07:53, Michael Paquier wrote:

+ * 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)

Actually, expectedSize is IMO a bad idea, because any caller of this
routine passing down zero could be trapped with an incorrect file
size. So let's remove the behavior where it is possible to bypass
this sanity check. We don't need it in pg_rewind either.

OK, sounds reasonable, but just to be clear. I will remove only a
possibility to bypass this sanity check (with 0), but leave expectedSize
argument intact. We still need it, since pg_rewind takes WalSegSz from
ControlFile and should pass it further, am I right?

+		/* Remove trailing newline */
+		if (strchr(cmd_output, '\n') != NULL)
+			*strchr(cmd_output, '\n') = '\0';

It seems to me that what you are looking here is to use
pg_strip_crlf(). Thinking harder, we have pipe_read_line() in
src/common/exec.c which does the exact same job..

pg_strip_crlf fits well, but would you mind if I also make
pipe_read_line external in this patch?

- /*
- * construct the command to be executed
- */

Perhaps you meant "build" here.

Actually, the verb 'construct' is used historically applied to
archive/restore commands (see also xlogarchive.c and pgarch.c), but it
should be 'build' in (fe_)archive.c, since we have BuildRestoreCommand
there now.

All other remarks look clear for me, so I fix them in the next patch
version, thanks.

Regards
--
Alexey Kondratov

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

#58Michael Paquier
michael@paquier.xyz
In reply to: Alexey Kondratov (#57)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Mon, Mar 02, 2020 at 08:59:49PM +0300, Alexey Kondratov wrote:

OK, sounds reasonable, but just to be clear. I will remove only a
possibility to bypass this sanity check (with 0), but leave expectedSize
argument intact. We still need it, since pg_rewind takes WalSegSz from
ControlFile and should pass it further, am I right?

We need to keep around the argument, but I think that we give any user
of this routine a favor by making mandatory a file size.

pg_strip_crlf fits well, but would you mind if I also make pipe_read_line
external in this patch?

I got to look at that more, and pipe_read_line() is a nice fit.

Actually, the verb 'construct' is used historically applied to
archive/restore commands (see also xlogarchive.c and pgarch.c), but it
should be 'build' in (fe_)archive.c, since we have BuildRestoreCommand there
now.

All other remarks look clear for me, so I fix them in the next patch
version, thanks.

Already done as per the attached, with a new routine named
getRestoreCommand() and more done. There were a couple of extra
things I noticed on the way:
- Found some typos.
- The translation of pg_rewind --help was incorrect, with a sentence
cut in the middle (you used twice gettext).
- I did not actually get why you don't check for a missing command
when using wait_result_is_any_signal. In this case I'd think that it
is better to exit immediately as follow-up calls would just fail.
- The code was rather careless about error handling and
RestoreArchivedWALFile(), and it seemed to me that it is rather
pointless to report an extra message "could not restore file \"%s\"
from archive" on top of the other error.
- I could not resist to add more documentation for the new routines of
src/common/..
- Used long options in the tests for readability, reworked the
comments.

On top of that, I have spent some time testing the reliability of the
test, and tested the whole on Windows and Linux. This is in a rather
committable shape now.
--
Michael

Attachments:

v21-0001-pg_rewind-Add-options-to-restore-WAL-files.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/include/common/archive.h b/src/include/common/archive.h
new file mode 100644
index 0000000000..e0260dd453
--- /dev/null
+++ b/src/include/common/archive.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+/*
+ * Routine to build a restore command to retrieve WAL segments from a WAL
+ * archive.  This uses the arguments given by the caller to replace the
+ * corresponding alias fields as defined in the GUC parameter
+ * restore_command.
+ */
+extern int	BuildRestoreCommand(const char *restoreCommand,
+								const char *xlogpath,	/* alias %p */
+								const char *xlogfname,	/* alias %f */
+								const char *lastRestartPointFname,	/* alias %r */
+								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/include/port.h b/src/include/port.h
index 3be994b43c..0685ce3abc 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -102,10 +102,11 @@ extern void pgfnames_cleanup(char **filenames);
 /* Portable locale initialization (in exec.c) */
 extern void set_pglocale_pgservice(const char *argv0, const char *app);
 
-/* Portable way to find binaries (in exec.c) */
+/* Portable way to find and execute binaries (in exec.c) */
 extern int	find_my_exec(const char *argv0, char *retpath);
 extern int	find_other_exec(const char *argv0, const char *target,
 							const char *versionstr, char *retpath);
+extern char *pipe_read_line(char *cmd, char *line, int maxsize);
 
 /* Doesn't belong here, but this is used with find_other_exec(), so... */
 #define PG_BACKEND_VERSIONSTR "postgres (PostgreSQL) " PG_VERSION "\n"
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 188b73e752..b90dbd44d8 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;
@@ -65,8 +63,8 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 	TimeLineID	restartTli;
 
 	/*
-	 * Ignore restore_command when not in archive recovery (meaning
-	 * we are in crash recovery).
+	 * Ignore restore_command when not in archive recovery (meaning we are in
+	 * crash recovery).
 	 */
 	if (!ArchiveRecoveryRequested)
 		goto not_available;
@@ -149,58 +147,13 @@ 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';
+	/* Build the command to execute */
+	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)
+		elog(ERROR, "failed to build restore command \"%s\" due to incorrect parameters",
+			 xlogRestoreCmd);
 
 	ereport(DEBUG3,
 			(errmsg_internal("executing restore command \"%s\"",
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..7152b96983
--- /dev/null
+++ b/src/common/archive.c
@@ -0,0 +1,106 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*
+ * BuildRestoreCommand
+ *
+ * Builds a restore command to retrieve a WAL segment from WAL archives,
+ * replacing the supported aliases with values supplied by the caller:
+ * xlogpath for %p, xlogfname for %f and lastRestartPointFname for %r.
+ * Returns 0 if restore_command was successfully built.
+ *
+ * If any of the required arguments is NULL and that the corresponding alias
+ * is found in the command given by the caller, then -1 is returned.
+ */
+int
+BuildRestoreCommand(const char *restoreCommand,
+					const char *xlogpath,
+					const char *xlogfname,
+					const char *lastRestartPointFname,
+					char *result)
+{
+	char	   *dp,
+			   *endp;
+	const char *sp;
+
+	/*
+	 * Build 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/exec.c b/src/common/exec.c
index 88400daa47..5321509b00 100644
--- a/src/common/exec.c
+++ b/src/common/exec.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * exec.c
- *		Functions for finding and validating executable files
+ *		Functions for finding and validating from executables files
  *
  *
  * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
@@ -51,7 +51,6 @@
 
 static int	validate_exec(const char *path);
 static int	resolve_symlinks(char *path);
-static char *pipe_read_line(char *cmd, char *line, int maxsize);
 
 #ifdef WIN32
 static BOOL GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser);
@@ -356,7 +355,7 @@ find_other_exec(const char *argv0, const char *target,
 /*
  * Execute a command in a pipe and read the first line from it.
  */
-static char *
+char *
 pipe_read_line(char *cmd, char *line, int maxsize)
 {
 	FILE	   *pgver;
diff --git a/src/common/fe_archive.c b/src/common/fe_archive.c
new file mode 100644
index 0000000000..3c688c4731
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,124 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+
+/*
+ * RestoreArchivedWALFile
+ *
+ * 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.
+ *
+ * The caller has to pass the expected size of the file restored from
+ * the archives via "expectedSize".
+ */
+int
+RestoreArchivedWALFile(const char *path, const char *xlogfname,
+					   off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH],
+				xlogRestoreCmd[MAXPGPATH];
+	int			rc;
+	struct stat stat_buf;
+
+	Assert(expectedSize > 0);
+
+	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 (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);
+				return -1;
+			}
+			else
+			{
+				int			xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+				{
+					pg_log_error("could not open file \"%s\" restored from archive: %m",
+								 xlogpath);
+					return -1;
+				}
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			pg_log_error("could not stat file \"%s\": %m",
+						 xlogpath);
+			return -1;
+		}
+	}
+
+	/*
+	 * If the failure was due to a signal, then it would be misleading to
+	 * return with a failure at restoring the WAL file.  So just bail out and
+	 * exit.
+	 */
+	if (wait_result_is_any_signal(rc, true))
+	{
+		pg_log_fatal("restore_command failed due to the signal: %s",
+					 wait_result_to_str(rc));
+		exit(1);
+	}
+
+	/*
+	 * The WAL file is not available, so just let the caller decide what to do
+	 * next.
+	 */
+	pg_log_error("could not restore file \"%s\" from archive",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index eb61cb8803..d28096397c 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 bf2d2983e7..aa5522e870 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/restricted_token.h"
+#include "common/string.h"
 #include "fe_utils/recovery_gen.h"
 #include "fetch.h"
 #include "file_ops.h"
@@ -38,6 +39,7 @@ static void createBackupLabel(XLogRecPtr startpoint, TimeLineID starttli,
 static void digestControlFile(ControlFileData *ControlFile, char *source,
 							  size_t size);
 static void syncTargetDirectory(void);
+static void getRestoreCommand(const char *argv0);
 static void sanityChecks(void);
 static void findCommonAncestorTimeline(XLogRecPtr *recptr, int *tliIndex);
 static void ensureCleanShutdown(const char *argv0);
@@ -53,11 +55,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 +78,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"
+			 "                                 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"));
@@ -103,6 +109,7 @@ main(int argc, char **argv)
 		{"source-server", required_argument, NULL, 2},
 		{"no-ensure-shutdown", no_argument, NULL, 4},
 		{"version", no_argument, NULL, 'V'},
+		{"restore-target-wal", no_argument, NULL, 'c'},
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
@@ -144,7 +151,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)
 		{
@@ -152,6 +159,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;
@@ -255,6 +266,8 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	getRestoreCommand(argv[0]);
+
 	atexit(disconnect_atexit);
 
 	/* Connect to remote server */
@@ -350,9 +363,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);
@@ -378,7 +390,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)
@@ -804,6 +816,67 @@ syncTargetDirectory(void)
 	fsync_pgdata(datadir_target, PG_VERSION_NUM);
 }
 
+/*
+ */
+static void
+getRestoreCommand(const char *argv0)
+{
+	int			rc;
+	char		postgres_exec_path[MAXPGPATH],
+				postgres_cmd[MAXPGPATH],
+				cmd_output[MAXPGPATH];
+
+	if (!restore_wal)
+		return;
+
+	/* find postgres executable */
+	rc = find_other_exec(argv0, "postgres",
+						 PG_BACKEND_VERSIONSTR,
+						 postgres_exec_path);
+
+	if (rc < 0)
+	{
+		char		full_path[MAXPGPATH];
+
+		if (find_my_exec(argv0, 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 able to retrieve the value of GUC parameter
+	 * restore_command, if set.
+	 */
+	snprintf(postgres_cmd, sizeof(postgres_cmd),
+			 "\"%s\" -D \"%s\" -C restore_command",
+			 postgres_exec_path, datadir_target);
+
+	if (!pipe_read_line(postgres_cmd, cmd_output, sizeof(cmd_output)))
+		exit(1);
+
+	(void) pg_strip_crlf(cmd_output);
+
+	if (strcmp(cmd_output, "") == 0)
+		pg_fatal("restore_command is not set on the target cluster");
+
+	restore_command = pg_strdup(cmd_output);
+
+	pg_log_debug("using for rewind restore_command = \'%s\'",
+				 restore_command);
+}
+
+
 /*
  * Ensure clean shutdown of target instance by launching single-user mode
  * postgres to do crash recovery.
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..577a4294f3 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,51 @@ 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.  The old master has
+		# to be stopped at this point.
+
+		# First, remove all the content in the archive directory,
+		# as RecursiveCopy::copypath does not support copying to
+		# existing directories.
+		rmtree($node_master->archive_dir);
+
+		# Move all WAL segments from the old master to the archives.
+		# These will be used by pg_rewind.
+		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");
+
+		# Make sure that directories have the right umask as this is
+		# required by a follow-up check on permissions, and better
+		# safe than sorry.
+		chmod(0700, $node_master->archive_dir);
+		chmod(0700, $node_master->data_dir . "/pg_wal");
+
+		# Add appropriate restore_command to the target cluster
+		$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",
+				"--restore-target-wal"
+			],
+			'pg_rewind archive');
+	}
 	else
 	{
 
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 42d29edd4e..180b05ddb6 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 <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/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;
 
#59Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#58)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 04.03.2020 10:45, Michael Paquier wrote:

On Mon, Mar 02, 2020 at 08:59:49PM +0300, Alexey Kondratov wrote:

All other remarks look clear for me, so I fix them in the next patch
version, thanks.

Already done as per the attached, with a new routine named
getRestoreCommand() and more done.

Many thanks for doing that. I went through the diff between v21 and v20.
Most of the changes look good to me.

- *��� ��� Functions for finding and validating executable files
+ *��� ��� Functions for finding and validating from executables files

There is probably something missing here. Finding and validating what?
And 'executables files' does not seem to be correct as well.

+��� ��� # First, remove all the content in the archive directory,
+��� ��� # as RecursiveCopy::copypath does not support copying to
+��� ��� # existing directories.

I think that 'remove all the content' is not completely correct in this
case. We are simply removing archive directory. There is no content
there yet, so 'First, remove archive directory...' should be fine.

- I did not actually get why you don't check for a missing command
when using wait_result_is_any_signal. In this case I'd think that it
is better to exit immediately as follow-up calls would just fail.

Believe me or not, but I put 'false' there intentionally. The idea was
that if the reason is a signal, then maybe user tired of waiting and
killed that restore_command process theirself or something like that, so
it is better to exit immediately. If it was a missing command, then
there is no hurry, so we can go further and complain that attempt of
recovering WAL segment has failed.

Actually, I guess that there is no big difference if we include missing
command here or not. There is no complicated logic further compared to
real recovery process in Postgres, where we cannot simply return false
in that case.

- The code was rather careless about error handling and
RestoreArchivedWALFile(), and it seemed to me that it is rather
pointless to report an extra message "could not restore file \"%s\"
from archive" on top of the other error.

Probably you mean several pg_log_error calls not followed by 'return
-1;'. Yes, I did it to fall down to the function end and show this extra
message, but I agree that there is no much sense in doing so.

Regards

--
Alexey Kondratov

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

#60Michael Paquier
michael@paquier.xyz
In reply to: Alexey Kondratov (#59)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Wed, Mar 04, 2020 at 08:14:20PM +0300, Alexey Kondratov wrote:

On 04.03.2020 10:45, Michael Paquier wrote:
- *        Functions for finding and validating executable files
+ *        Functions for finding and validating from executables files

There is probably something missing here. Finding and validating what? And
'executables files' does not seem to be correct as well.

Oops. I was tweaking this part at some point of my review, but
discarded the change afterwards.

+        # First, remove all the content in the archive directory,
+        # as RecursiveCopy::copypath does not support copying to
+        # existing directories.

I think that 'remove all the content' is not completely correct in this
case. We are simply removing archive directory. There is no content there
yet, so 'First, remove archive directory...' should be fine.

Indeed, this can be improved. I still need to do an extra pass on
this patch set.

- I did not actually get why you don't check for a missing command
when using wait_result_is_any_signal. In this case I'd think that it
is better to exit immediately as follow-up calls would just fail.

Believe me or not, but I put 'false' there intentionally. The idea was that
if the reason is a signal, then maybe user tired of waiting and killed that
restore_command process theirself or something like that, so it is better to
exit immediately. If it was a missing command, then there is no hurry, so we
can go further and complain that attempt of recovering WAL segment has
failed.

Actually, I guess that there is no big difference if we include missing
command here or not. There is no complicated logic further compared to real
recovery process in Postgres, where we cannot simply return false in that
case.

On the contrary, it seems to me that the difference is very important.
Imagine for example a frontend tool which calls RestoreArchivedWALFile
in a loop, and that this one fails because the command called is
missing. This tool would keep looping for nothing. So checking for a
missing command and leaving immediately would be more helpful for the
user. Can you think about scenarios where it would make sense to be
able to loop in this case instead of failing?
--
Michael

#61Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#60)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 05.03.2020 09:24, Michael Paquier wrote:

On Wed, Mar 04, 2020 at 08:14:20PM +0300, Alexey Kondratov wrote:

- I did not actually get why you don't check for a missing command
when using wait_result_is_any_signal. In this case I'd think that it
is better to exit immediately as follow-up calls would just fail.

Believe me or not, but I put 'false' there intentionally. The idea was that
if the reason is a signal, then maybe user tired of waiting and killed that
restore_command process theirself or something like that, so it is better to
exit immediately. If it was a missing command, then there is no hurry, so we
can go further and complain that attempt of recovering WAL segment has
failed.

Actually, I guess that there is no big difference if we include missing
command here or not. There is no complicated logic further compared to real
recovery process in Postgres, where we cannot simply return false in that
case.

On the contrary, it seems to me that the difference is very important.
Imagine for example a frontend tool which calls RestoreArchivedWALFile
in a loop, and that this one fails because the command called is
missing. This tool would keep looping for nothing. So checking for a
missing command and leaving immediately would be more helpful for the
user. Can you think about scenarios where it would make sense to be
able to loop in this case instead of failing?

OK, I was still having in mind pg_rewind as the only one user of this
routine. Now it is a part of the common and I could imagine a
hypothetical tool that is polling the archive and waiting for a specific
WAL segment to become available. In this case 'command not found' is
definitely the end of game, while the absence of segment is expected
error, so we can continue looping.

Regards

--
Alexey Kondratov

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

#62Michael Paquier
michael@paquier.xyz
In reply to: Alexey Kondratov (#61)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Thu, Mar 05, 2020 at 07:52:24PM +0300, Alexey Kondratov wrote:

OK, I was still having in mind pg_rewind as the only one user of this
routine. Now it is a part of the common and I could imagine a hypothetical
tool that is polling the archive and waiting for a specific WAL segment to
become available. In this case 'command not found' is definitely the end of
game, while the absence of segment is expected error, so we can continue
looping.

Depending on the needs, we could also add in the future an option in
this API to not exit if the command is missing. Who knows if a case
can be made..

By the way, as a matter of transparency, I have discussed this patch
with Alexander offlist and he has pinged me that I may be in a better
place to commit it per the amount of review I have done, so I'll try
to handle it. I was also thinking to split the patch into two pieces:
- Introduction of common/archive.c and common/fe_archive.c (the former
is used by xlogarchive.c and the latter only by pg_rewind). The
latter is dead code without the second patch, but this would validate
the MSVC build with the buildfarm.
- The pg_rewind pieces with the new option.
--
Michael

#63Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#62)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-Mar-06, Michael Paquier wrote:

I was also thinking to split the patch into two pieces:
- Introduction of common/archive.c and common/fe_archive.c (the former
is used by xlogarchive.c and the latter only by pg_rewind). The
latter is dead code without the second patch, but this would validate
the MSVC build with the buildfarm.
- The pg_rewind pieces with the new option.

Hmm, doesn't the CF bot already validate the MSVC build?

Splitting in two seems all right, but I think one commit that introduces
dead code is not great. It may make more sense to have one commit for
common/archive.c, and a second commit that does fe_archive plus
pg_rewind changes ... If that doesn't work for whatever reason, then
doing a single commit may be preferrable.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#64Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#63)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Thu, Mar 05, 2020 at 11:09:06PM -0300, Alvaro Herrera wrote:

Hmm, doesn't the CF bot already validate the MSVC build?

Splitting in two seems all right, but I think one commit that introduces
dead code is not great. It may make more sense to have one commit for
common/archive.c, and a second commit that does fe_archive plus
pg_rewind changes ... If that doesn't work for whatever reason, then
doing a single commit may be preferrable.

Thanks for the suggestion. Avoiding dead code makes sense as well
here. I'll think about this stuff a bit more first.
--
Michael

#65Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#64)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Fri, Mar 06, 2020 at 05:22:16PM +0900, Michael Paquier wrote:

Thanks for the suggestion. Avoiding dead code makes sense as well
here. I'll think about this stuff a bit more first.

Okay, after pondering more on that, here is a first cut with a patch
refactoring restore_command build to src/common/. One thing I have
changed compared to the other versions is that BuildRestoreCommand()
now returns a boolean to tell if the command was successfully built or
not.

A second thing. As of now the interface of BuildRestoreCommand()
assumes that the resulting buffer has an allocation of MAXPGPATH.
This should be fine because that's an assumption we rely on a lot in
the code, be it frontend or backend. However, it could also be a trap
for any caller of this routine if they allocate something smaller.
Wouldn't it be cleaner to pass down the size of the result buffer
directly to BuildRestoreCommand() and use the length given by the
caller of the routine as a sanity check?

Any thoughts?
--
Michael

Attachments:

0001-Move-routine-generating-restore_command-to-src-commo.patchtext/x-diff; charset=us-asciiDownload
From bad2c04b0eb3a146f0c2719ed8360b3b255c3c47 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 9 Mar 2020 17:15:00 +0900
Subject: [PATCH] Move routine generating restore_command to src/common/

restore_command has only been used until now by the backend, but there
is a pending patch for pg_rewind to make use of that in the frontend.

Author: Alexey Kondratov
Reviewed-by: Andrey Borodin, Andres Freund, Alvaro Herrera, Alexander
Korotkov, Michael Paquier
Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1@postgrespro.ru
---
 src/include/common/archive.h             |  28 ++++++
 src/backend/access/transam/xlogarchive.c |  61 ++-----------
 src/common/Makefile                      |   1 +
 src/common/archive.c                     | 109 +++++++++++++++++++++++
 src/tools/msvc/Mkvcbuild.pm              |   4 +-
 5 files changed, 146 insertions(+), 57 deletions(-)
 create mode 100644 src/include/common/archive.h
 create mode 100644 src/common/archive.c

diff --git a/src/include/common/archive.h b/src/include/common/archive.h
new file mode 100644
index 0000000000..bdcc49ffd3
--- /dev/null
+++ b/src/include/common/archive.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+/*
+ * Routine to build a restore command to retrieve WAL segments from a WAL
+ * archive.  This uses the arguments given by the caller to replace the
+ * corresponding alias fields as defined in the GUC parameter
+ * restore_command.
+ */
+extern bool BuildRestoreCommand(const char *restoreCommand,
+								const char *xlogpath,	/* %p */
+								const char *xlogfname,	/* %f */
+								const char *lastRestartPointFname,	/* %r */
+								char *result);
+
+#endif							/* ARCHIVE_H */
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 188b73e752..6e8cd88a5e 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,11 @@ 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';
-
-	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';
+	/* Build the restore command to execute */
+	if (!BuildRestoreCommand(recoveryRestoreCommand, xlogpath, xlogfname,
+							 lastRestartPointFname, xlogRestoreCmd))
+		elog(ERROR, "could not build restore command \"%s\"",
+			 xlogRestoreCmd);
 
 	ereport(DEBUG3,
 			(errmsg_internal("executing restore command \"%s\"",
diff --git a/src/common/Makefile b/src/common/Makefile
index ce01df68b9..6939b9d087 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 \
diff --git a/src/common/archive.c b/src/common/archive.c
new file mode 100644
index 0000000000..574d56410d
--- /dev/null
+++ b/src/common/archive.c
@@ -0,0 +1,109 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*
+ * BuildRestoreCommand
+ *
+ * Builds a restore command to retrieve a WAL segment from WAL archives,
+ * replacing the supported aliases with values supplied by the caller:
+ * xlogpath for %p, xlogfname for %f and lastRestartPointFname for %r.
+ * Returns true if restore_command was successfully built.
+ *
+ * If any of the required arguments is NULL and that the corresponding alias
+ * is found in the command given by the caller, then false is returned.
+ *
+ * The resulting restore command is stored in "result", which has to be
+ * a pre-allocated buffer of size MAXPGPATH.
+ */
+bool
+BuildRestoreCommand(const char *restoreCommand,
+					const char *xlogpath,
+					const char *xlogfname,
+					const char *lastRestartPointFname,
+					char *result)
+{
+	char	   *dp,
+			   *endp;
+	const char *sp;
+
+	/*
+	 * Build 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 false;
+					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 false;
+					sp++;
+					StrNCpy(dp, xlogfname, endp - dp);
+					dp += strlen(dp);
+					break;
+				case 'r':
+					/* %r: filename of last restartpoint */
+					if (lastRestartPointFname == NULL)
+						return false;
+					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 true;
+}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f89a8a4fdb..c648078e20 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -120,8 +120,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
-- 
2.25.1

#66Kuntal Ghosh
kuntalghosh.2007@gmail.com
In reply to: Michael Paquier (#65)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Mon, Mar 9, 2020 at 1:46 PM Michael Paquier <michael@paquier.xyz> wrote:

On Fri, Mar 06, 2020 at 05:22:16PM +0900, Michael Paquier wrote:

Thanks for the suggestion. Avoiding dead code makes sense as well
here. I'll think about this stuff a bit more first.

Okay, after pondering more on that, here is a first cut with a patch
refactoring restore_command build to src/common/. One thing I have
changed compared to the other versions is that BuildRestoreCommand()
now returns a boolean to tell if the command was successfully built or
not.

Yeah. If we're returning only -1 and 0, it's better to use a boolean.
If we're planning to provide some information about which parameter is
missing, a few negative integer values might be useful. But, I feel
that's unnecessary in this case.

A second thing. As of now the interface of BuildRestoreCommand()
assumes that the resulting buffer has an allocation of MAXPGPATH.
This should be fine because that's an assumption we rely on a lot in
the code, be it frontend or backend. However, it could also be a trap
for any caller of this routine if they allocate something smaller.
Wouldn't it be cleaner to pass down the size of the result buffer
directly to BuildRestoreCommand() and use the length given by the
caller of the routine as a sanity check?

That's a good suggestion. But, it's unlikely that a caller would pass
something longer than MAXPGPATH and we indeed use that value a lot in
the code. IMHO, it looks okay to me to have that assumption here as
well.

--
Thanks & Regards,
Kuntal Ghosh
EnterpriseDB: http://www.enterprisedb.com

#67Michael Paquier
michael@paquier.xyz
In reply to: Kuntal Ghosh (#66)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Mon, Mar 09, 2020 at 03:38:29PM +0530, Kuntal Ghosh wrote:

That's a good suggestion. But, it's unlikely that a caller would pass
something longer than MAXPGPATH and we indeed use that value a lot in
the code. IMHO, it looks okay to me to have that assumption here as
well.

Well, a more serious problem would be to allocate something smaller
than MAXPGPATH. This reminds me a bit of 09ec55b9 where we did not
correctly design from the start the base64 encode and decode routines
for SCRAM, so I'd rather design this one correctly from the start as
per the attached. Alexey, Alexander, what do you think?
--
Michael

Attachments:

v2-0001-Move-routine-generating-restore_command-to-src-co.patchtext/x-diff; charset=us-asciiDownload
From 4e2b5b60e003ec67a4854f2625cc610f01428fe2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 9 Mar 2020 17:15:00 +0900
Subject: [PATCH v2] Move routine generating restore_command to src/common/

restore_command has only been used until now by the backend, but there
is a pending patch for pg_rewind to make use of that in the frontend.

Author: Alexey Kondratov
Reviewed-by: Andrey Borodin, Andres Freund, Alvaro Herrera, Alexander
Korotkov, Michael Paquier
Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1@postgrespro.ru
---
 src/include/common/archive.h             |  29 ++++++
 src/backend/access/transam/xlogarchive.c |  62 ++-----------
 src/common/Makefile                      |   1 +
 src/common/archive.c                     | 110 +++++++++++++++++++++++
 src/tools/msvc/Mkvcbuild.pm              |   4 +-
 5 files changed, 149 insertions(+), 57 deletions(-)
 create mode 100644 src/include/common/archive.h
 create mode 100644 src/common/archive.c

diff --git a/src/include/common/archive.h b/src/include/common/archive.h
new file mode 100644
index 0000000000..805930490b
--- /dev/null
+++ b/src/include/common/archive.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+/*
+ * Routine to build a restore command to retrieve WAL segments from a WAL
+ * archive.  This uses the arguments given by the caller to replace the
+ * corresponding alias fields as defined in the GUC parameter
+ * restore_command.
+ */
+extern bool BuildRestoreCommand(const char *restoreCommand,
+								const char *xlogpath,	/* %p */
+								const char *xlogfname,	/* %f */
+								const char *lastRestartPointFname,	/* %r */
+								char *commandResult,
+								int commandResultLen);
+
+#endif							/* ARCHIVE_H */
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 188b73e752..e1d6df89e1 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';
-
-	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';
+	/* Build the restore command to execute */
+	if (!BuildRestoreCommand(recoveryRestoreCommand, xlogpath, xlogfname,
+							 lastRestartPointFname, xlogRestoreCmd,
+							 MAXPGPATH))
+		elog(ERROR, "could not build restore command \"%s\"",
+			 xlogRestoreCmd);
 
 	ereport(DEBUG3,
 			(errmsg_internal("executing restore command \"%s\"",
diff --git a/src/common/Makefile b/src/common/Makefile
index ce01df68b9..6939b9d087 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 \
diff --git a/src/common/archive.c b/src/common/archive.c
new file mode 100644
index 0000000000..74c71e0f4c
--- /dev/null
+++ b/src/common/archive.c
@@ -0,0 +1,110 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*
+ * BuildRestoreCommand
+ *
+ * Builds a restore command to retrieve a WAL segment from WAL archives,
+ * replacing the supported aliases with values supplied by the caller:
+ * xlogpath for %p, xlogfname for %f and lastRestartPointFname for %r.
+ * Returns true if restore_command was successfully built.
+ *
+ * If any of the required arguments is NULL and that the corresponding alias
+ * is found in the command given by the caller, then false is returned.
+ *
+ * The resulting restore command is stored in "commandResult", pre-allocated
+ * buffer of size "restoreCommandLen".
+ */
+bool
+BuildRestoreCommand(const char *restoreCommand,
+					const char *xlogpath,
+					const char *xlogfname,
+					const char *lastRestartPointFname,
+					char *commandResult,
+					int commandResultLen)
+{
+	char	   *dp,
+			   *endp;
+	const char *sp;
+
+	/*
+	 * Build the command to be executed.
+	 */
+	dp = commandResult;
+	endp = commandResult + commandResultLen - 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 false;
+					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 false;
+					sp++;
+					StrNCpy(dp, xlogfname, endp - dp);
+					dp += strlen(dp);
+					break;
+				case 'r':
+					/* %r: filename of last restartpoint */
+					if (lastRestartPointFname == NULL)
+						return false;
+					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 true;
+}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f89a8a4fdb..c648078e20 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -120,8 +120,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
-- 
2.25.1

#68Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Michael Paquier (#67)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Tue, Mar 10, 2020 at 7:28 AM Michael Paquier <michael@paquier.xyz> wrote:

On Mon, Mar 09, 2020 at 03:38:29PM +0530, Kuntal Ghosh wrote:

That's a good suggestion. But, it's unlikely that a caller would pass
something longer than MAXPGPATH and we indeed use that value a lot in
the code. IMHO, it looks okay to me to have that assumption here as
well.

Well, a more serious problem would be to allocate something smaller
than MAXPGPATH. This reminds me a bit of 09ec55b9 where we did not
correctly design from the start the base64 encode and decode routines
for SCRAM, so I'd rather design this one correctly from the start as
per the attached. Alexey, Alexander, what do you think?

Two options seem reasonable to me in this case. The first is to pass
length as additional argument as you did. The second option is to
make argument a pointer to fixed-size array as following.

extern bool BuildRestoreCommand(const char *restoreCommand,
const char *xlogpath, /* %p */
const char *xlogfname, /* %f */
const char *lastRestartPointFname, /* %r */
char (*commandResult)[MAXPGPATH]);

Passing pointer to array of different size would cause an error. The
downside of this approach is that passing palloc'd chunk of memory as
commandResult would be less convenient.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#69Michael Paquier
michael@paquier.xyz
In reply to: Alexander Korotkov (#68)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Tue, Mar 10, 2020 at 01:05:40PM +0300, Alexander Korotkov wrote:

Two options seem reasonable to me in this case. The first is to pass
length as additional argument as you did. The second option is to
make argument a pointer to fixed-size array as following.

extern bool BuildRestoreCommand(const char *restoreCommand,
const char *xlogpath, /* %p */
const char *xlogfname, /* %f */
const char *lastRestartPointFname, /* %r */
char (*commandResult)[MAXPGPATH]);

Passing pointer to array of different size would cause an error. The
downside of this approach is that passing palloc'd chunk of memory as
commandResult would be less convenient.

Thanks Alexander for the input. Another argument is that what you are
suggesting here is not the usual project style, so I would still stick
with two arguments to be consistent.
--
Michael

#70Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Michael Paquier (#69)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Tue, Mar 10, 2020 at 3:44 PM Michael Paquier <michael@paquier.xyz> wrote:

On Tue, Mar 10, 2020 at 01:05:40PM +0300, Alexander Korotkov wrote:

Two options seem reasonable to me in this case. The first is to pass
length as additional argument as you did. The second option is to
make argument a pointer to fixed-size array as following.

extern bool BuildRestoreCommand(const char *restoreCommand,
const char *xlogpath, /* %p */
const char *xlogfname, /* %f */
const char *lastRestartPointFname, /* %r */
char (*commandResult)[MAXPGPATH]);

Passing pointer to array of different size would cause an error. The
downside of this approach is that passing palloc'd chunk of memory as
commandResult would be less convenient.

Thanks Alexander for the input. Another argument is that what you are
suggesting here is not the usual project style, so I would still stick
with two arguments to be consistent.

Yes, another argument is valid as well. I'm OK with current solution.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#71Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#69)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-Mar-10, Michael Paquier wrote:

On Tue, Mar 10, 2020 at 01:05:40PM +0300, Alexander Korotkov wrote:

Two options seem reasonable to me in this case. The first is to pass
length as additional argument as you did. The second option is to
make argument a pointer to fixed-size array as following.

Another option is to return the command as a palloc'ed string (per
psprintf), instead of using a caller-stack-allocated variable. Passing
the buffer len is widely used, but more error prone (and I think getting
this one wrong might be more catastrophic than a mistake elsewhere.)
This is not a performance-critical path enough that we *need* the
optimization that avoids the palloc is important. (Failure can be
reported by returning NULL.) Also, I think the function comment could
stand some more detailing.

Also, I think Msvcbuild.pm could follow Makefile's ideas of one line per
file. Maybe no need to fix all of that in this patch, but let's start
by adding the new file it its own line rather than reflowing two
adjacent lines (oh wait ... does perltidy put it that way? if so,
nevermind.)

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#72Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#71)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Tue, Mar 10, 2020 at 12:39:53PM -0300, Alvaro Herrera wrote:

Another option is to return the command as a palloc'ed string (per
psprintf), instead of using a caller-stack-allocated variable. Passing
the buffer len is widely used, but more error prone (and I think getting
this one wrong might be more catastrophic than a mistake elsewhere.)
This is not a performance-critical path enough that we *need* the
optimization that avoids the palloc is important. (Failure can be
reported by returning NULL.)

That's a better approach here.

Also, I think the function comment could stand some more detailing.

What kind of additional information would you like to add on top of
what the updated version attached does?

Also, I think Msvcbuild.pm could follow Makefile's ideas of one line per
file. Maybe no need to fix all of that in this patch, but let's start
by adding the new file it its own line rather than reflowing two
adjacent lines (oh wait ... does perltidy put it that way? if so,
nevermind.)

Good idea. It happens that perltidy does not care about that, but I
would rather keep that stuff for a separate patch/thread.
--
Michael

Attachments:

v3-0001-Move-routine-generating-restore_command-to-src-co.patchtext/x-diff; charset=us-asciiDownload
From 33d9a6837f2435966006947dad18c21e40e0c271 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 11 Mar 2020 12:25:18 +0900
Subject: [PATCH v3] Move routine generating restore_command to src/common/

restore_command has only been used until now by the backend, but there
is a pending patch for pg_rewind to make use of that in the frontend.

Author: Alexey Kondratov
Reviewed-by: Andrey Borodin, Andres Freund, Alvaro Herrera, Alexander
Korotkov, Michael Paquier
Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1@postgrespro.ru
---
 src/include/common/archive.h             |  27 +++++
 src/backend/access/transam/xlogarchive.c |  66 ++-----------
 src/common/Makefile                      |   1 +
 src/common/archive.c                     | 119 +++++++++++++++++++++++
 src/tools/msvc/Mkvcbuild.pm              |   4 +-
 5 files changed, 159 insertions(+), 58 deletions(-)
 create mode 100644 src/include/common/archive.h
 create mode 100644 src/common/archive.c

diff --git a/src/include/common/archive.h b/src/include/common/archive.h
new file mode 100644
index 0000000000..8af0b4566f
--- /dev/null
+++ b/src/include/common/archive.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+/*
+ * Routine to build a restore command to retrieve WAL segments from a WAL
+ * archive.  This uses the arguments given by the caller to replace the
+ * corresponding alias fields as defined in the GUC parameter
+ * restore_command.
+ */
+extern char *BuildRestoreCommand(const char *restoreCommand,
+								 const char *xlogpath,	/* %p */
+								 const char *xlogfname, /* %f */
+								 const char *lastRestartPointFname);	/* %r */
+
+#endif							/* ARCHIVE_H */
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 188b73e752..914ad340ea 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"
@@ -53,11 +54,8 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 					bool cleanupEnabled)
 {
 	char		xlogpath[MAXPGPATH];
-	char		xlogRestoreCmd[MAXPGPATH];
+	char	   *xlogRestoreCmd;
 	char		lastRestartPointFname[MAXPGPATH];
-	char	   *dp;
-	char	   *endp;
-	const char *sp;
 	int			rc;
 	struct stat stat_buf;
 	XLogSegNo	restartSegNo;
@@ -149,58 +147,13 @@ 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';
-
-	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';
+	/* Build the restore command to execute */
+	xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
+										 xlogpath, xlogfname,
+										 lastRestartPointFname);
+	if (xlogRestoreCmd == NULL)
+		elog(ERROR, "could not build restore command \"%s\"",
+			 recoveryRestoreCommand);
 
 	ereport(DEBUG3,
 			(errmsg_internal("executing restore command \"%s\"",
@@ -217,6 +170,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 	rc = system(xlogRestoreCmd);
 
 	PostRestoreCommand();
+	pfree(xlogRestoreCmd);
 
 	if (rc == 0)
 	{
diff --git a/src/common/Makefile b/src/common/Makefile
index ce01df68b9..6939b9d087 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 \
diff --git a/src/common/archive.c b/src/common/archive.c
new file mode 100644
index 0000000000..ef0ce532ed
--- /dev/null
+++ b/src/common/archive.c
@@ -0,0 +1,119 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*
+ * BuildRestoreCommand
+ *
+ * Builds a restore command to retrieve a WAL segment from WAL archives,
+ * replacing the supported aliases with values supplied by the caller as
+ * defined by the GUC parameter restore_command: xlogpath for %p,
+ * xlogfname for %f and lastRestartPointFname for %r.
+ *
+ * The result is a palloc'd string for the restore command built.  The
+ * caller is responsible for freeing it.  If any of the required arguments
+ * is NULL and that the corresponding alias is found in the command given
+ * by the caller, then NULL is returned.
+ */
+char *
+BuildRestoreCommand(const char *restoreCommand,
+					const char *xlogpath,
+					const char *xlogfname,
+					const char *lastRestartPointFname)
+{
+	char	   *dp;
+	char	   *endp;
+	char	   *result;
+	const char *sp;
+
+	result = palloc0(MAXPGPATH);
+
+	/*
+	 * Build 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)
+					{
+						pfree(result);
+						return NULL;
+					}
+					sp++;
+					StrNCpy(dp, xlogpath, endp - dp);
+					make_native_path(dp);
+					dp += strlen(dp);
+					break;
+				case 'f':
+					/* %f: filename of desired file */
+					if (xlogfname == NULL)
+					{
+						pfree(result);
+						return NULL;
+					}
+					sp++;
+					StrNCpy(dp, xlogfname, endp - dp);
+					dp += strlen(dp);
+					break;
+				case 'r':
+					/* %r: filename of last restartpoint */
+					if (lastRestartPointFname == NULL)
+					{
+						pfree(result);
+						return NULL;
+					}
+					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 result;
+}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f89a8a4fdb..c648078e20 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -120,8 +120,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
-- 
2.25.1

#73Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#72)
2 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Wed, Mar 11, 2020 at 12:27:03PM +0900, Michael Paquier wrote:

Also, I think the function comment could stand some more detailing.

What kind of additional information would you like to add on top of
what the updated version attached does?

I have been working more on that, and attached is an updated version
of the main feature based on 0001, attached as 0002. I have applied
the following changes, fixing some actual bugs while on it:
- Added back expectedSize, so as frontends can also fetch things other
than WAL segments from the archives, including history files.
pg_rewind needs only WAL segments, still other tools may want more.
- Fixed an issue with ENOENT when doing stat() on a segment after
running the restore command. Like the backend, there is an argument
for looping here.
- Switched the several failures to issue exit() for the several
failure types possible instead of looping. If a file has an incorrect
expected size or that stat() fails for a non-ENOENT, things are more
likely going to repeat if the frontend tool loops, so exiting
immediately is safer.

I think that we need a better name for RestoreArchivedFile() in
fe_archive.c. The backend uses RestoreArchivedFile(), and the
previous versions of the patch switched to RestoreArchivedWALFile()
which is incorrect if working on something else than a WAL segment.
The version attached uses FrontendRestoreArchivedFile(), still we
could do better.

I'd like to commit the refactoring piece in 0001 tomorrow, then let's
move on with the rest as of 0002. If more comments and docs are
needed for archive.c, let's continue discussing that.
--
Michael

Attachments:

0001-Move-routine-generating-restore_command-to-src-commo.patchtext/x-diff; charset=us-asciiDownload
From 43ac17434f5fba9effd77c1962d46589ba693874 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 12 Mar 2020 13:22:34 +0900
Subject: [PATCH 1/2] Move routine generating restore_command to src/common/

restore_command has only been used until now by the backend, but there
is a pending patch for pg_rewind to make use of that in the frontend.

Author: Alexey Kondratov
Reviewed-by: Andrey Borodin, Andres Freund, Alvaro Herrera, Alexander
Korotkov, Michael Paquier
Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1@postgrespro.ru
---
 src/include/common/archive.h             |  27 +++++
 src/backend/access/transam/xlogarchive.c |  66 ++-----------
 src/common/Makefile                      |   1 +
 src/common/archive.c                     | 119 +++++++++++++++++++++++
 src/tools/msvc/Mkvcbuild.pm              |   4 +-
 5 files changed, 159 insertions(+), 58 deletions(-)
 create mode 100644 src/include/common/archive.h
 create mode 100644 src/common/archive.c

diff --git a/src/include/common/archive.h b/src/include/common/archive.h
new file mode 100644
index 0000000000..8af0b4566f
--- /dev/null
+++ b/src/include/common/archive.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+/*
+ * Routine to build a restore command to retrieve WAL segments from a WAL
+ * archive.  This uses the arguments given by the caller to replace the
+ * corresponding alias fields as defined in the GUC parameter
+ * restore_command.
+ */
+extern char *BuildRestoreCommand(const char *restoreCommand,
+								 const char *xlogpath,	/* %p */
+								 const char *xlogfname, /* %f */
+								 const char *lastRestartPointFname);	/* %r */
+
+#endif							/* ARCHIVE_H */
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 188b73e752..914ad340ea 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"
@@ -53,11 +54,8 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 					bool cleanupEnabled)
 {
 	char		xlogpath[MAXPGPATH];
-	char		xlogRestoreCmd[MAXPGPATH];
+	char	   *xlogRestoreCmd;
 	char		lastRestartPointFname[MAXPGPATH];
-	char	   *dp;
-	char	   *endp;
-	const char *sp;
 	int			rc;
 	struct stat stat_buf;
 	XLogSegNo	restartSegNo;
@@ -149,58 +147,13 @@ 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';
-
-	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';
+	/* Build the restore command to execute */
+	xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
+										 xlogpath, xlogfname,
+										 lastRestartPointFname);
+	if (xlogRestoreCmd == NULL)
+		elog(ERROR, "could not build restore command \"%s\"",
+			 recoveryRestoreCommand);
 
 	ereport(DEBUG3,
 			(errmsg_internal("executing restore command \"%s\"",
@@ -217,6 +170,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 	rc = system(xlogRestoreCmd);
 
 	PostRestoreCommand();
+	pfree(xlogRestoreCmd);
 
 	if (rc == 0)
 	{
diff --git a/src/common/Makefile b/src/common/Makefile
index ce01df68b9..6939b9d087 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 \
diff --git a/src/common/archive.c b/src/common/archive.c
new file mode 100644
index 0000000000..783fc3d752
--- /dev/null
+++ b/src/common/archive.c
@@ -0,0 +1,119 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*
+ * BuildRestoreCommand
+ *
+ * Builds a restore command to retrieve a file from WAL archives, replacing
+ * the supported aliases with values supplied by the caller as defined by
+ * the GUC parameter restore_command: xlogpath for %p, xlogfname for %f and
+ * lastRestartPointFname for %r.
+ *
+ * The result is a palloc'd string for the restore command built.  The
+ * caller is responsible for freeing it.  If any of the required arguments
+ * is NULL and that the corresponding alias is found in the command given
+ * by the caller, then NULL is returned.
+ */
+char *
+BuildRestoreCommand(const char *restoreCommand,
+					const char *xlogpath,
+					const char *xlogfname,
+					const char *lastRestartPointFname)
+{
+	char	   *dp;
+	char	   *endp;
+	char	   *result;
+	const char *sp;
+
+	result = palloc0(MAXPGPATH);
+
+	/*
+	 * Build 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)
+					{
+						pfree(result);
+						return NULL;
+					}
+					sp++;
+					StrNCpy(dp, xlogpath, endp - dp);
+					make_native_path(dp);
+					dp += strlen(dp);
+					break;
+				case 'f':
+					/* %f: filename of desired file */
+					if (xlogfname == NULL)
+					{
+						pfree(result);
+						return NULL;
+					}
+					sp++;
+					StrNCpy(dp, xlogfname, endp - dp);
+					dp += strlen(dp);
+					break;
+				case 'r':
+					/* %r: filename of last restartpoint */
+					if (lastRestartPointFname == NULL)
+					{
+						pfree(result);
+						return NULL;
+					}
+					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 result;
+}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f89a8a4fdb..c648078e20 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -120,8 +120,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
-- 
2.25.1

0002-Add-c-restore-target-wal-to-pg_rewind.patchtext/x-diff; charset=us-asciiDownload
From ccf64c26b425ef4d36bb409a9e6f2e70c64513e8 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 12 Mar 2020 13:03:23 +0900
Subject: [PATCH 2/2] Add -c/--restore-target-wal to pg_rewind

pg_rewind needs to copy from the source server to the target a set of
relation blocks changed from the previous checkpoint where WAL forked
between both up to the end of WAL on the target.  This may require WAL
segments that are not present anymore on the target's pg_wal, causing
pg_rewind to fail.  It is possible to fix that by copying manually the
WAL segments needed but this may lead to extra and useless work.

Instead, this commit introduces a new option allowing pg_rewind to use a
restore_command when running by grabbing it from the target
configuration.  This allows the rewind operation to be more reliable,
and only the necessary segments are fetched from the archives in this
case.

In order to do that, a new routine is added to src/common/ to allow
frontend tools to retrieve a WAL segment using an already-built restore
command.  This version is much more simple than the backend equivalent.

Author: Alexey Kondratov
Reviewed-by: Andrey Borodin, Andres Freund, Alvaro Herrera, Alexander
Korotkov, Michael Paquier
Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1@postgrespro.ru
---
 src/include/common/fe_archive.h   |  21 +++++
 src/include/port.h                |   3 +-
 src/common/Makefile               |   1 +
 src/common/exec.c                 |   3 +-
 src/common/fe_archive.c           | 129 ++++++++++++++++++++++++++++++
 src/bin/pg_rewind/parsexlog.c     |  33 +++++++-
 src/bin/pg_rewind/pg_rewind.c     |  87 ++++++++++++++++++--
 src/bin/pg_rewind/pg_rewind.h     |   6 +-
 src/bin/pg_rewind/t/001_basic.pl  |   3 +-
 src/bin/pg_rewind/t/RewindTest.pm |  64 ++++++++++++++-
 doc/src/sgml/ref/pg_rewind.sgml   |  28 +++++--
 src/tools/msvc/Mkvcbuild.pm       |   4 +-
 12 files changed, 355 insertions(+), 27 deletions(-)
 create mode 100644 src/include/common/fe_archive.h
 create mode 100644 src/common/fe_archive.c

diff --git a/src/include/common/fe_archive.h b/src/include/common/fe_archive.h
new file mode 100644
index 0000000000..13d19d11e7
--- /dev/null
+++ b/src/include/common/fe_archive.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.h
+ *	  Routines to access WAL archives 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	FrontendRestoreArchivedFile(const char *path,
+										const char *xlogfname,
+										off_t expectedSize,
+										const char *restoreCommand);
+
+#endif							/* FE_ARCHIVE_H */
diff --git a/src/include/port.h b/src/include/port.h
index 29f3e39e5b..20a5de1b7a 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -102,10 +102,11 @@ extern void pgfnames_cleanup(char **filenames);
 /* Portable locale initialization (in exec.c) */
 extern void set_pglocale_pgservice(const char *argv0, const char *app);
 
-/* Portable way to find binaries (in exec.c) */
+/* Portable way to find and execute binaries (in exec.c) */
 extern int	find_my_exec(const char *argv0, char *retpath);
 extern int	find_other_exec(const char *argv0, const char *target,
 							const char *versionstr, char *retpath);
+extern char *pipe_read_line(char *cmd, char *line, int maxsize);
 
 /* Doesn't belong here, but this is used with find_other_exec(), so... */
 #define PG_BACKEND_VERSIONSTR "postgres (PostgreSQL) " PG_VERSION "\n"
diff --git a/src/common/Makefile b/src/common/Makefile
index 6939b9d087..a97c723fbd 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -88,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/exec.c b/src/common/exec.c
index 88400daa47..f39b0a294b 100644
--- a/src/common/exec.c
+++ b/src/common/exec.c
@@ -51,7 +51,6 @@
 
 static int	validate_exec(const char *path);
 static int	resolve_symlinks(char *path);
-static char *pipe_read_line(char *cmd, char *line, int maxsize);
 
 #ifdef WIN32
 static BOOL GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser);
@@ -356,7 +355,7 @@ find_other_exec(const char *argv0, const char *target,
 /*
  * Execute a command in a pipe and read the first line from it.
  */
-static char *
+char *
 pipe_read_line(char *cmd, char *line, int maxsize)
 {
 	FILE	   *pgver;
diff --git a/src/common/fe_archive.c b/src/common/fe_archive.c
new file mode 100644
index 0000000000..1716e754ee
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,129 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.c
+ *	  Routines to access WAL archives 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"
+
+
+/*
+ * FrontendRestoreArchivedFile
+ *
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful, return a file descriptor of the restored 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
+FrontendRestoreArchivedFile(const char *path, const char *xlogfname,
+							off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH];
+	char	   *xlogRestoreCmd;
+	int			rc;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	xlogRestoreCmd = BuildRestoreCommand(restoreCommand, xlogpath,
+										 xlogfname, NULL);
+	if (xlogRestoreCmd == NULL)
+	{
+		pg_log_fatal("could not use restore_command with %%r alias");
+		exit(1);
+	}
+
+	/*
+	 * Execute restore_command, which should copy the missing file from
+	 * archival storage.
+	 */
+	rc = system(xlogRestoreCmd);
+	pfree(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_fatal("unexpected file size for \"%s\": %lu instead of %lu",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize);
+				exit(1);
+			}
+			else
+			{
+				int			xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+				{
+					pg_log_fatal("could not open file \"%s\" restored from archive: %m",
+								 xlogpath);
+					exit(1);
+				}
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			if (errno != ENOENT)
+			{
+				pg_log_fatal("could not stat file \"%s\": %m",
+							 xlogpath);
+				exit(1);
+			}
+			return -1;
+		}
+	}
+
+	/*
+	 * If the failure was due to a signal, then it would be misleading to
+	 * return with a failure at restoring the file.  So just bail out and
+	 * exit.  Hard shell errors such as "command not found" are treated as
+	 * fatal too.
+	 */
+	if (wait_result_is_any_signal(rc, true))
+	{
+		pg_log_fatal("restore_command failed due to the signal: %s",
+					 wait_result_to_str(rc));
+		exit(1);
+	}
+
+	/*
+	 * The file is not available, so just let the caller decide what to do
+	 * next.
+	 */
+	pg_log_error("could not restore file \"%s\" from archive",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index eb61cb8803..7c8bc1dfe2 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 = FrontendRestoreArchivedFile(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 bf2d2983e7..44eb15b269 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/restricted_token.h"
+#include "common/string.h"
 #include "fe_utils/recovery_gen.h"
 #include "fetch.h"
 #include "file_ops.h"
@@ -38,6 +39,7 @@ static void createBackupLabel(XLogRecPtr startpoint, TimeLineID starttli,
 static void digestControlFile(ControlFileData *ControlFile, char *source,
 							  size_t size);
 static void syncTargetDirectory(void);
+static void getRestoreCommand(const char *argv0);
 static void sanityChecks(void);
 static void findCommonAncestorTimeline(XLogRecPtr *recptr, int *tliIndex);
 static void ensureCleanShutdown(const char *argv0);
@@ -53,11 +55,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 +78,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"
+			 "                                 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"));
@@ -103,6 +109,7 @@ main(int argc, char **argv)
 		{"source-server", required_argument, NULL, 2},
 		{"no-ensure-shutdown", no_argument, NULL, 4},
 		{"version", no_argument, NULL, 'V'},
+		{"restore-target-wal", no_argument, NULL, 'c'},
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
@@ -144,7 +151,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)
 		{
@@ -152,6 +159,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;
@@ -255,6 +266,8 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	getRestoreCommand(argv[0]);
+
 	atexit(disconnect_atexit);
 
 	/* Connect to remote server */
@@ -350,9 +363,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);
@@ -378,7 +390,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)
@@ -804,6 +816,71 @@ syncTargetDirectory(void)
 	fsync_pgdata(datadir_target, PG_VERSION_NUM);
 }
 
+/*
+ * Get value of GUC parameter restore_command from the target cluster.
+ *
+ * This uses a logic based on "postgres -C" to get the value from
+ * from the cluster.
+ */
+static void
+getRestoreCommand(const char *argv0)
+{
+	int			rc;
+	char		postgres_exec_path[MAXPGPATH],
+				postgres_cmd[MAXPGPATH],
+				cmd_output[MAXPGPATH];
+
+	if (!restore_wal)
+		return;
+
+	/* find postgres executable */
+	rc = find_other_exec(argv0, "postgres",
+						 PG_BACKEND_VERSIONSTR,
+						 postgres_exec_path);
+
+	if (rc < 0)
+	{
+		char		full_path[MAXPGPATH];
+
+		if (find_my_exec(argv0, 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 able to retrieve the value of GUC parameter
+	 * restore_command, if set.
+	 */
+	snprintf(postgres_cmd, sizeof(postgres_cmd),
+			 "\"%s\" -D \"%s\" -C restore_command",
+			 postgres_exec_path, datadir_target);
+
+	if (!pipe_read_line(postgres_cmd, cmd_output, sizeof(cmd_output)))
+		exit(1);
+
+	(void) pg_strip_crlf(cmd_output);
+
+	if (strcmp(cmd_output, "") == 0)
+		pg_fatal("restore_command is not set on the target cluster");
+
+	restore_command = pg_strdup(cmd_output);
+
+	pg_log_debug("using for rewind restore_command = \'%s\'",
+				 restore_command);
+}
+
+
 /*
  * Ensure clean shutdown of target instance by launching single-user mode
  * postgres to do crash recovery.
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..349d2fa0b2 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,48 @@ 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.  The old master has
+		# to be stopped at this point.
+
+		# Remove the existing archive directory and move all WAL
+		# segments from the old master to the archives.  These
+		# will be used by pg_rewind.
+		rmtree($node_master->archive_dir);
+		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");
+
+		# Make sure that directories have the right umask as this is
+		# required by a follow-up check on permissions, and better
+		# safe than sorry.
+		chmod(0700, $node_master->archive_dir);
+		chmod(0700, $node_master->data_dir . "/pg_wal");
+
+		# Add appropriate restore_command to the target cluster
+		$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",
+				"--restore-target-wal"
+			],
+			'pg_rewind archive');
+	}
 	else
 	{
 
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index f64d659522..07c49e4719 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 <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/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index c648078e20..92df33a7e9 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -138,8 +138,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;
 
-- 
2.25.1

#74Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#73)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 12.03.2020 07:39, Michael Paquier wrote:

I'd like to commit the refactoring piece in 0001 tomorrow, then let's
move on with the rest as of 0002. If more comments and docs are
needed for archive.c, let's continue discussing that.

I just went through the both patches and realized that I cannot get into
semantics of splitting frontend code between common and fe_utils. This
applies only to 0002, where we introduce fe_archive.c. Should it be
placed into fe_utils alongside of the recent recovery_gen.c also used by
pg_rewind? This is a frontend-only code intended to be used by frontend
applications, so fe_utils feels like the right place, doesn't it? Just
tried to do so and everything went fine, so it seems that there is no
obstacles from the build system.

BTW, most of 'common' is a really common code with only four exceptions
like logging.c, which is frontend-only. Is it there for historical
reasons only or something else?

Regards

--
Alexey Kondratov

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

#75Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#72)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-Mar-11, Michael Paquier wrote:

On Tue, Mar 10, 2020 at 12:39:53PM -0300, Alvaro Herrera wrote:

Another option is to return the command as a palloc'ed string (per
psprintf), instead of using a caller-stack-allocated variable. Passing
the buffer len is widely used, but more error prone (and I think getting
this one wrong might be more catastrophic than a mistake elsewhere.)
This is not a performance-critical path enough that we *need* the
optimization that avoids the palloc is important. (Failure can be
reported by returning NULL.)

That's a better approach here.

Thanks, looks good. I don't think we *need* the MAXPGPATH restriction
really -- I was thinking in a StringInfo kind of approach where you just
append the stuff you need without having to think about the buffer
length.

Also, I think Msvcbuild.pm could follow Makefile's ideas of one line per
file. Maybe no need to fix all of that in this patch, but let's start
by adding the new file it its own line rather than reflowing two
adjacent lines (oh wait ... does perltidy put it that way? if so,
nevermind.)

Good idea. It happens that perltidy does not care about that, but I
would rather keep that stuff for a separate patch/thread.

Aha, good. I would still put the new "archive.c" entry on its own line,
and just keep the other two lines unchanged. (That preserves the
perhaps non-obvious property that all entries that start with the same
letter are in the same line.)

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#76Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#75)
2 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Thu, Mar 12, 2020 at 12:50:17PM -0300, Alvaro Herrera wrote:

Thanks, looks good. I don't think we *need* the MAXPGPATH restriction
really -- I was thinking in a StringInfo kind of approach where you just
append the stuff you need without having to think about the buffer
length.

Oh, OK. I missed your point then. No problem with that by doing it
as the attached. There is one small trick because of
make_native_path() though.

Aha, good. I would still put the new "archive.c" entry on its own line,
and just keep the other two lines unchanged. (That preserves the
perhaps non-obvious property that all entries that start with the same
letter are in the same line.)

No issues with that either. Are you fine with the updated version
attached for 0001?
--
Michael

Attachments:

v3-0001-Move-routine-generating-restore_command-to-src-co.patchtext/x-diff; charset=us-asciiDownload
From 53ef0a122eedd661a31813815563a09178dd56da Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 13 Mar 2020 09:16:44 +0900
Subject: [PATCH v3 1/2] Move routine generating restore_command to src/common/

restore_command has only been used until now by the backend, but there
is a pending patch for pg_rewind to make use of that in the frontend.

Author: Alexey Kondratov
Reviewed-by: Andrey Borodin, Andres Freund, Alvaro Herrera, Alexander
Korotkov, Michael Paquier
Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1@postgrespro.ru
---
 src/include/common/archive.h             |  27 +++++
 src/backend/access/transam/xlogarchive.c |  66 ++-----------
 src/common/Makefile                      |   1 +
 src/common/archive.c                     | 121 +++++++++++++++++++++++
 src/tools/msvc/Mkvcbuild.pm              |   1 +
 5 files changed, 160 insertions(+), 56 deletions(-)
 create mode 100644 src/include/common/archive.h
 create mode 100644 src/common/archive.c

diff --git a/src/include/common/archive.h b/src/include/common/archive.h
new file mode 100644
index 0000000000..8af0b4566f
--- /dev/null
+++ b/src/include/common/archive.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+/*
+ * Routine to build a restore command to retrieve WAL segments from a WAL
+ * archive.  This uses the arguments given by the caller to replace the
+ * corresponding alias fields as defined in the GUC parameter
+ * restore_command.
+ */
+extern char *BuildRestoreCommand(const char *restoreCommand,
+								 const char *xlogpath,	/* %p */
+								 const char *xlogfname, /* %f */
+								 const char *lastRestartPointFname);	/* %r */
+
+#endif							/* ARCHIVE_H */
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 188b73e752..914ad340ea 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"
@@ -53,11 +54,8 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 					bool cleanupEnabled)
 {
 	char		xlogpath[MAXPGPATH];
-	char		xlogRestoreCmd[MAXPGPATH];
+	char	   *xlogRestoreCmd;
 	char		lastRestartPointFname[MAXPGPATH];
-	char	   *dp;
-	char	   *endp;
-	const char *sp;
 	int			rc;
 	struct stat stat_buf;
 	XLogSegNo	restartSegNo;
@@ -149,58 +147,13 @@ 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';
-
-	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';
+	/* Build the restore command to execute */
+	xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
+										 xlogpath, xlogfname,
+										 lastRestartPointFname);
+	if (xlogRestoreCmd == NULL)
+		elog(ERROR, "could not build restore command \"%s\"",
+			 recoveryRestoreCommand);
 
 	ereport(DEBUG3,
 			(errmsg_internal("executing restore command \"%s\"",
@@ -217,6 +170,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 	rc = system(xlogRestoreCmd);
 
 	PostRestoreCommand();
+	pfree(xlogRestoreCmd);
 
 	if (rc == 0)
 	{
diff --git a/src/common/Makefile b/src/common/Makefile
index ce01df68b9..6939b9d087 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 \
diff --git a/src/common/archive.c b/src/common/archive.c
new file mode 100644
index 0000000000..a94e4d0a5e
--- /dev/null
+++ b/src/common/archive.c
@@ -0,0 +1,121 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+#include "lib/stringinfo.h"
+
+/*
+ * BuildRestoreCommand
+ *
+ * Builds a restore command to retrieve a file from WAL archives, replacing
+ * the supported aliases with values supplied by the caller as defined by
+ * the GUC parameter restore_command: xlogpath for %p, xlogfname for %f and
+ * lastRestartPointFname for %r.
+ *
+ * The result is a palloc'd string for the restore command built.  The
+ * caller is responsible for freeing it.  If any of the required arguments
+ * is NULL and that the corresponding alias is found in the command given
+ * by the caller, then NULL is returned.
+ */
+char *
+BuildRestoreCommand(const char *restoreCommand,
+					const char *xlogpath,
+					const char *xlogfname,
+					const char *lastRestartPointFname)
+{
+	StringInfoData result;
+	const char *sp;
+
+	/*
+	 * Build the command to be executed.
+	 */
+	initStringInfo(&result);
+
+	for (sp = restoreCommand; *sp; sp++)
+	{
+		if (*sp == '%')
+		{
+			switch (sp[1])
+			{
+				case 'p':
+					{
+						char	   *nativePath;
+
+						/* %p: relative path of target file */
+						if (xlogpath == NULL)
+						{
+							pfree(result.data);
+							return NULL;
+						}
+						sp++;
+
+						/*
+						 * This needs to use a placeholder to not modify the
+						 * input with the conversion done via
+						 * make_native_path().
+						 */
+						nativePath = pstrdup(xlogpath);
+						make_native_path(nativePath);
+						appendStringInfoString(&result,
+											   nativePath);
+						pfree(nativePath);
+						break;
+					}
+				case 'f':
+					/* %f: filename of desired file */
+					if (xlogfname == NULL)
+					{
+						pfree(result.data);
+						return NULL;
+					}
+					sp++;
+					appendStringInfoString(&result, xlogfname);
+					break;
+				case 'r':
+					/* %r: filename of last restartpoint */
+					if (lastRestartPointFname == NULL)
+					{
+						pfree(result.data);
+						return NULL;
+					}
+					sp++;
+					appendStringInfoString(&result,
+										   lastRestartPointFname);
+					break;
+				case '%':
+					/* convert %% to a single % */
+					sp++;
+					appendStringInfoChar(&result, *sp);
+					break;
+				default:
+					/* otherwise treat the % as not special */
+					appendStringInfoChar(&result, *sp);
+					break;
+			}
+		}
+		else
+		{
+			appendStringInfoChar(&result, *sp);
+		}
+	}
+
+	return result.data;
+}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f89a8a4fdb..636428b044 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -120,6 +120,7 @@ sub mkvcbuild
 	}
 
 	our @pgcommonallfiles = qw(
+	  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
-- 
2.25.1

v3-0002-Add-c-restore-target-wal-to-pg_rewind.patchtext/x-diff; charset=us-asciiDownload
From 947411524c6aea85edf76e0d04ac97ddd4300a36 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 13 Mar 2020 09:17:21 +0900
Subject: [PATCH v3 2/2] Add -c/--restore-target-wal to pg_rewind

pg_rewind needs to copy from the source server to the target a set of
relation blocks changed from the previous checkpoint where WAL forked
between both up to the end of WAL on the target.  This may require WAL
segments that are not present anymore on the target's pg_wal, causing
pg_rewind to fail.  It is possible to fix that by copying manually the
WAL segments needed but this may lead to extra and useless work.

Instead, this commit introduces a new option allowing pg_rewind to use a
restore_command when running by grabbing it from the target
configuration.  This allows the rewind operation to be more reliable,
and only the necessary segments are fetched from the archives in this
case.

In order to do that, a new routine is added to src/common/ to allow
frontend tools to retrieve a WAL segment using an already-built restore
command.  This version is much more simple than the backend equivalent.

Author: Alexey Kondratov
Reviewed-by: Andrey Borodin, Andres Freund, Alvaro Herrera, Alexander
Korotkov, Michael Paquier
Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1@postgrespro.ru
---
 src/include/common/fe_archive.h   |  21 +++++
 src/include/port.h                |   3 +-
 src/common/Makefile               |   1 +
 src/common/exec.c                 |   3 +-
 src/common/fe_archive.c           | 129 ++++++++++++++++++++++++++++++
 src/bin/pg_rewind/parsexlog.c     |  33 +++++++-
 src/bin/pg_rewind/pg_rewind.c     |  87 ++++++++++++++++++--
 src/bin/pg_rewind/pg_rewind.h     |   6 +-
 src/bin/pg_rewind/t/001_basic.pl  |   3 +-
 src/bin/pg_rewind/t/RewindTest.pm |  64 ++++++++++++++-
 doc/src/sgml/ref/pg_rewind.sgml   |  28 +++++--
 src/tools/msvc/Mkvcbuild.pm       |   4 +-
 12 files changed, 355 insertions(+), 27 deletions(-)
 create mode 100644 src/include/common/fe_archive.h
 create mode 100644 src/common/fe_archive.c

diff --git a/src/include/common/fe_archive.h b/src/include/common/fe_archive.h
new file mode 100644
index 0000000000..13d19d11e7
--- /dev/null
+++ b/src/include/common/fe_archive.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.h
+ *	  Routines to access WAL archives 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	FrontendRestoreArchivedFile(const char *path,
+										const char *xlogfname,
+										off_t expectedSize,
+										const char *restoreCommand);
+
+#endif							/* FE_ARCHIVE_H */
diff --git a/src/include/port.h b/src/include/port.h
index 29f3e39e5b..20a5de1b7a 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -102,10 +102,11 @@ extern void pgfnames_cleanup(char **filenames);
 /* Portable locale initialization (in exec.c) */
 extern void set_pglocale_pgservice(const char *argv0, const char *app);
 
-/* Portable way to find binaries (in exec.c) */
+/* Portable way to find and execute binaries (in exec.c) */
 extern int	find_my_exec(const char *argv0, char *retpath);
 extern int	find_other_exec(const char *argv0, const char *target,
 							const char *versionstr, char *retpath);
+extern char *pipe_read_line(char *cmd, char *line, int maxsize);
 
 /* Doesn't belong here, but this is used with find_other_exec(), so... */
 #define PG_BACKEND_VERSIONSTR "postgres (PostgreSQL) " PG_VERSION "\n"
diff --git a/src/common/Makefile b/src/common/Makefile
index 6939b9d087..a97c723fbd 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -88,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/exec.c b/src/common/exec.c
index 88400daa47..f39b0a294b 100644
--- a/src/common/exec.c
+++ b/src/common/exec.c
@@ -51,7 +51,6 @@
 
 static int	validate_exec(const char *path);
 static int	resolve_symlinks(char *path);
-static char *pipe_read_line(char *cmd, char *line, int maxsize);
 
 #ifdef WIN32
 static BOOL GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser);
@@ -356,7 +355,7 @@ find_other_exec(const char *argv0, const char *target,
 /*
  * Execute a command in a pipe and read the first line from it.
  */
-static char *
+char *
 pipe_read_line(char *cmd, char *line, int maxsize)
 {
 	FILE	   *pgver;
diff --git a/src/common/fe_archive.c b/src/common/fe_archive.c
new file mode 100644
index 0000000000..1716e754ee
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,129 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.c
+ *	  Routines to access WAL archives 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"
+
+
+/*
+ * FrontendRestoreArchivedFile
+ *
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful, return a file descriptor of the restored 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
+FrontendRestoreArchivedFile(const char *path, const char *xlogfname,
+							off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH];
+	char	   *xlogRestoreCmd;
+	int			rc;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	xlogRestoreCmd = BuildRestoreCommand(restoreCommand, xlogpath,
+										 xlogfname, NULL);
+	if (xlogRestoreCmd == NULL)
+	{
+		pg_log_fatal("could not use restore_command with %%r alias");
+		exit(1);
+	}
+
+	/*
+	 * Execute restore_command, which should copy the missing file from
+	 * archival storage.
+	 */
+	rc = system(xlogRestoreCmd);
+	pfree(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_fatal("unexpected file size for \"%s\": %lu instead of %lu",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize);
+				exit(1);
+			}
+			else
+			{
+				int			xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+				{
+					pg_log_fatal("could not open file \"%s\" restored from archive: %m",
+								 xlogpath);
+					exit(1);
+				}
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			if (errno != ENOENT)
+			{
+				pg_log_fatal("could not stat file \"%s\": %m",
+							 xlogpath);
+				exit(1);
+			}
+			return -1;
+		}
+	}
+
+	/*
+	 * If the failure was due to a signal, then it would be misleading to
+	 * return with a failure at restoring the file.  So just bail out and
+	 * exit.  Hard shell errors such as "command not found" are treated as
+	 * fatal too.
+	 */
+	if (wait_result_is_any_signal(rc, true))
+	{
+		pg_log_fatal("restore_command failed due to the signal: %s",
+					 wait_result_to_str(rc));
+		exit(1);
+	}
+
+	/*
+	 * The file is not available, so just let the caller decide what to do
+	 * next.
+	 */
+	pg_log_error("could not restore file \"%s\" from archive",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index eb61cb8803..7c8bc1dfe2 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 = FrontendRestoreArchivedFile(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 bf2d2983e7..44eb15b269 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/restricted_token.h"
+#include "common/string.h"
 #include "fe_utils/recovery_gen.h"
 #include "fetch.h"
 #include "file_ops.h"
@@ -38,6 +39,7 @@ static void createBackupLabel(XLogRecPtr startpoint, TimeLineID starttli,
 static void digestControlFile(ControlFileData *ControlFile, char *source,
 							  size_t size);
 static void syncTargetDirectory(void);
+static void getRestoreCommand(const char *argv0);
 static void sanityChecks(void);
 static void findCommonAncestorTimeline(XLogRecPtr *recptr, int *tliIndex);
 static void ensureCleanShutdown(const char *argv0);
@@ -53,11 +55,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 +78,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"
+			 "                                 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"));
@@ -103,6 +109,7 @@ main(int argc, char **argv)
 		{"source-server", required_argument, NULL, 2},
 		{"no-ensure-shutdown", no_argument, NULL, 4},
 		{"version", no_argument, NULL, 'V'},
+		{"restore-target-wal", no_argument, NULL, 'c'},
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
@@ -144,7 +151,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)
 		{
@@ -152,6 +159,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;
@@ -255,6 +266,8 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	getRestoreCommand(argv[0]);
+
 	atexit(disconnect_atexit);
 
 	/* Connect to remote server */
@@ -350,9 +363,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);
@@ -378,7 +390,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)
@@ -804,6 +816,71 @@ syncTargetDirectory(void)
 	fsync_pgdata(datadir_target, PG_VERSION_NUM);
 }
 
+/*
+ * Get value of GUC parameter restore_command from the target cluster.
+ *
+ * This uses a logic based on "postgres -C" to get the value from
+ * from the cluster.
+ */
+static void
+getRestoreCommand(const char *argv0)
+{
+	int			rc;
+	char		postgres_exec_path[MAXPGPATH],
+				postgres_cmd[MAXPGPATH],
+				cmd_output[MAXPGPATH];
+
+	if (!restore_wal)
+		return;
+
+	/* find postgres executable */
+	rc = find_other_exec(argv0, "postgres",
+						 PG_BACKEND_VERSIONSTR,
+						 postgres_exec_path);
+
+	if (rc < 0)
+	{
+		char		full_path[MAXPGPATH];
+
+		if (find_my_exec(argv0, 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 able to retrieve the value of GUC parameter
+	 * restore_command, if set.
+	 */
+	snprintf(postgres_cmd, sizeof(postgres_cmd),
+			 "\"%s\" -D \"%s\" -C restore_command",
+			 postgres_exec_path, datadir_target);
+
+	if (!pipe_read_line(postgres_cmd, cmd_output, sizeof(cmd_output)))
+		exit(1);
+
+	(void) pg_strip_crlf(cmd_output);
+
+	if (strcmp(cmd_output, "") == 0)
+		pg_fatal("restore_command is not set on the target cluster");
+
+	restore_command = pg_strdup(cmd_output);
+
+	pg_log_debug("using for rewind restore_command = \'%s\'",
+				 restore_command);
+}
+
+
 /*
  * Ensure clean shutdown of target instance by launching single-user mode
  * postgres to do crash recovery.
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..349d2fa0b2 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,48 @@ 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.  The old master has
+		# to be stopped at this point.
+
+		# Remove the existing archive directory and move all WAL
+		# segments from the old master to the archives.  These
+		# will be used by pg_rewind.
+		rmtree($node_master->archive_dir);
+		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");
+
+		# Make sure that directories have the right umask as this is
+		# required by a follow-up check on permissions, and better
+		# safe than sorry.
+		chmod(0700, $node_master->archive_dir);
+		chmod(0700, $node_master->data_dir . "/pg_wal");
+
+		# Add appropriate restore_command to the target cluster
+		$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",
+				"--restore-target-wal"
+			],
+			'pg_rewind archive');
+	}
 	else
 	{
 
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index f64d659522..07c49e4719 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 <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/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 636428b044..4b68a5c640 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -139,8 +139,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;
 
-- 
2.25.1

#77Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Michael Paquier (#76)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Dear Alvaro,

On Fri, Mar 13, 2020 at 3:18 AM Michael Paquier <michael@paquier.xyz> wrote:

No issues with that either. Are you fine with the updated version
attached for 0001?

I think patchset is almost ready for commit. Michael is waiting for
your answer on whether you're fine with current shape of 0001. Could
you please provide a feedback?

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#78Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alexander Korotkov (#77)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-Mar-23, Alexander Korotkov wrote:

Dear Alvaro,

On Fri, Mar 13, 2020 at 3:18 AM Michael Paquier <michael@paquier.xyz> wrote:

No issues with that either. Are you fine with the updated version
attached for 0001?

I think patchset is almost ready for commit. Michael is waiting for
your answer on whether you're fine with current shape of 0001. Could
you please provide a feedback?

Hi, I didn't realize that this was waiting on me. It looks good to me
-- I'd just remove the comment on the function prototype in archiver.h,
which is not customary (we only put these comments in the .c file).
Let's get it pushed.

Thanks for everybody's work on this.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#79Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alvaro Herrera (#78)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Mon, Mar 23, 2020 at 6:16 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

On 2020-Mar-23, Alexander Korotkov wrote:

On Fri, Mar 13, 2020 at 3:18 AM Michael Paquier <michael@paquier.xyz> wrote:

No issues with that either. Are you fine with the updated version
attached for 0001?

I think patchset is almost ready for commit. Michael is waiting for
your answer on whether you're fine with current shape of 0001. Could
you please provide a feedback?

Hi, I didn't realize that this was waiting on me. It looks good to me
-- I'd just remove the comment on the function prototype in archiver.h,
which is not customary (we only put these comments in the .c file).
Let's get it pushed.

Thanks for everybody's work on this.

Great, thank you!

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#80Michael Paquier
michael@paquier.xyz
In reply to: Alexander Korotkov (#79)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Mon, Mar 23, 2020 at 07:17:13PM +0300, Alexander Korotkov wrote:

On Mon, Mar 23, 2020 at 6:16 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Hi, I didn't realize that this was waiting on me. It looks good to me
-- I'd just remove the comment on the function prototype in archiver.h,
which is not customary (we only put these comments in the .c file).
Let's get it pushed.

Thanks. I have removed the comment in archive.h.

Thanks for everybody's work on this.

Great, thank you!

Thanks Alvaro and Alexander. 0001 has been applied as of e09ad07.
Now for 0002, let's see about it later. Attached is a rebased
version, with no actual changes.
--
Michael

Attachments:

v4-0001-Add-c-restore-target-wal-to-pg_rewind.patchtext/x-diff; charset=us-asciiDownload
From ecb2b3d62cce7ef56aa4fdb5343c54d9ee94100b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 13 Mar 2020 09:17:21 +0900
Subject: [PATCH v4] Add -c/--restore-target-wal to pg_rewind

pg_rewind needs to copy from the source server to the target a set of
relation blocks changed from the previous checkpoint where WAL forked
between both up to the end of WAL on the target.  This may require WAL
segments that are not present anymore on the target's pg_wal, causing
pg_rewind to fail.  It is possible to fix that by copying manually the
WAL segments needed but this may lead to extra and useless work.

Instead, this commit introduces a new option allowing pg_rewind to use a
restore_command when running by grabbing it from the target
configuration.  This allows the rewind operation to be more reliable,
and only the necessary segments are fetched from the archives in this
case.

In order to do that, a new routine is added to src/common/ to allow
frontend tools to retrieve a WAL segment using an already-built restore
command.  This version is much more simple than the backend equivalent.

Author: Alexey Kondratov
Reviewed-by: Andrey Borodin, Andres Freund, Alvaro Herrera, Alexander
Korotkov, Michael Paquier
Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1@postgrespro.ru
---
 src/include/common/fe_archive.h   |  21 +++++
 src/include/port.h                |   3 +-
 src/common/Makefile               |   1 +
 src/common/exec.c                 |   3 +-
 src/common/fe_archive.c           | 129 ++++++++++++++++++++++++++++++
 src/bin/pg_rewind/parsexlog.c     |  33 +++++++-
 src/bin/pg_rewind/pg_rewind.c     |  87 ++++++++++++++++++--
 src/bin/pg_rewind/pg_rewind.h     |   6 +-
 src/bin/pg_rewind/t/001_basic.pl  |   3 +-
 src/bin/pg_rewind/t/RewindTest.pm |  64 ++++++++++++++-
 doc/src/sgml/ref/pg_rewind.sgml   |  28 +++++--
 src/tools/msvc/Mkvcbuild.pm       |   4 +-
 12 files changed, 355 insertions(+), 27 deletions(-)
 create mode 100644 src/include/common/fe_archive.h
 create mode 100644 src/common/fe_archive.c

diff --git a/src/include/common/fe_archive.h b/src/include/common/fe_archive.h
new file mode 100644
index 0000000000..13d19d11e7
--- /dev/null
+++ b/src/include/common/fe_archive.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.h
+ *	  Routines to access WAL archives 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	FrontendRestoreArchivedFile(const char *path,
+										const char *xlogfname,
+										off_t expectedSize,
+										const char *restoreCommand);
+
+#endif							/* FE_ARCHIVE_H */
diff --git a/src/include/port.h b/src/include/port.h
index 29f3e39e5b..20a5de1b7a 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -102,10 +102,11 @@ extern void pgfnames_cleanup(char **filenames);
 /* Portable locale initialization (in exec.c) */
 extern void set_pglocale_pgservice(const char *argv0, const char *app);
 
-/* Portable way to find binaries (in exec.c) */
+/* Portable way to find and execute binaries (in exec.c) */
 extern int	find_my_exec(const char *argv0, char *retpath);
 extern int	find_other_exec(const char *argv0, const char *target,
 							const char *versionstr, char *retpath);
+extern char *pipe_read_line(char *cmd, char *line, int maxsize);
 
 /* Doesn't belong here, but this is used with find_other_exec(), so... */
 #define PG_BACKEND_VERSIONSTR "postgres (PostgreSQL) " PG_VERSION "\n"
diff --git a/src/common/Makefile b/src/common/Makefile
index 6939b9d087..a97c723fbd 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -88,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/exec.c b/src/common/exec.c
index 88400daa47..f39b0a294b 100644
--- a/src/common/exec.c
+++ b/src/common/exec.c
@@ -51,7 +51,6 @@
 
 static int	validate_exec(const char *path);
 static int	resolve_symlinks(char *path);
-static char *pipe_read_line(char *cmd, char *line, int maxsize);
 
 #ifdef WIN32
 static BOOL GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser);
@@ -356,7 +355,7 @@ find_other_exec(const char *argv0, const char *target,
 /*
  * Execute a command in a pipe and read the first line from it.
  */
-static char *
+char *
 pipe_read_line(char *cmd, char *line, int maxsize)
 {
 	FILE	   *pgver;
diff --git a/src/common/fe_archive.c b/src/common/fe_archive.c
new file mode 100644
index 0000000000..1716e754ee
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,129 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.c
+ *	  Routines to access WAL archives 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"
+
+
+/*
+ * FrontendRestoreArchivedFile
+ *
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful, return a file descriptor of the restored 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
+FrontendRestoreArchivedFile(const char *path, const char *xlogfname,
+							off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH];
+	char	   *xlogRestoreCmd;
+	int			rc;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	xlogRestoreCmd = BuildRestoreCommand(restoreCommand, xlogpath,
+										 xlogfname, NULL);
+	if (xlogRestoreCmd == NULL)
+	{
+		pg_log_fatal("could not use restore_command with %%r alias");
+		exit(1);
+	}
+
+	/*
+	 * Execute restore_command, which should copy the missing file from
+	 * archival storage.
+	 */
+	rc = system(xlogRestoreCmd);
+	pfree(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_fatal("unexpected file size for \"%s\": %lu instead of %lu",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize);
+				exit(1);
+			}
+			else
+			{
+				int			xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+				{
+					pg_log_fatal("could not open file \"%s\" restored from archive: %m",
+								 xlogpath);
+					exit(1);
+				}
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			if (errno != ENOENT)
+			{
+				pg_log_fatal("could not stat file \"%s\": %m",
+							 xlogpath);
+				exit(1);
+			}
+			return -1;
+		}
+	}
+
+	/*
+	 * If the failure was due to a signal, then it would be misleading to
+	 * return with a failure at restoring the file.  So just bail out and
+	 * exit.  Hard shell errors such as "command not found" are treated as
+	 * fatal too.
+	 */
+	if (wait_result_is_any_signal(rc, true))
+	{
+		pg_log_fatal("restore_command failed due to the signal: %s",
+					 wait_result_to_str(rc));
+		exit(1);
+	}
+
+	/*
+	 * The file is not available, so just let the caller decide what to do
+	 * next.
+	 */
+	pg_log_error("could not restore file \"%s\" from archive",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index eb61cb8803..7c8bc1dfe2 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 = FrontendRestoreArchivedFile(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 bf2d2983e7..44eb15b269 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/restricted_token.h"
+#include "common/string.h"
 #include "fe_utils/recovery_gen.h"
 #include "fetch.h"
 #include "file_ops.h"
@@ -38,6 +39,7 @@ static void createBackupLabel(XLogRecPtr startpoint, TimeLineID starttli,
 static void digestControlFile(ControlFileData *ControlFile, char *source,
 							  size_t size);
 static void syncTargetDirectory(void);
+static void getRestoreCommand(const char *argv0);
 static void sanityChecks(void);
 static void findCommonAncestorTimeline(XLogRecPtr *recptr, int *tliIndex);
 static void ensureCleanShutdown(const char *argv0);
@@ -53,11 +55,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 +78,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"
+			 "                                 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"));
@@ -103,6 +109,7 @@ main(int argc, char **argv)
 		{"source-server", required_argument, NULL, 2},
 		{"no-ensure-shutdown", no_argument, NULL, 4},
 		{"version", no_argument, NULL, 'V'},
+		{"restore-target-wal", no_argument, NULL, 'c'},
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
@@ -144,7 +151,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)
 		{
@@ -152,6 +159,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;
@@ -255,6 +266,8 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	getRestoreCommand(argv[0]);
+
 	atexit(disconnect_atexit);
 
 	/* Connect to remote server */
@@ -350,9 +363,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);
@@ -378,7 +390,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)
@@ -804,6 +816,71 @@ syncTargetDirectory(void)
 	fsync_pgdata(datadir_target, PG_VERSION_NUM);
 }
 
+/*
+ * Get value of GUC parameter restore_command from the target cluster.
+ *
+ * This uses a logic based on "postgres -C" to get the value from
+ * from the cluster.
+ */
+static void
+getRestoreCommand(const char *argv0)
+{
+	int			rc;
+	char		postgres_exec_path[MAXPGPATH],
+				postgres_cmd[MAXPGPATH],
+				cmd_output[MAXPGPATH];
+
+	if (!restore_wal)
+		return;
+
+	/* find postgres executable */
+	rc = find_other_exec(argv0, "postgres",
+						 PG_BACKEND_VERSIONSTR,
+						 postgres_exec_path);
+
+	if (rc < 0)
+	{
+		char		full_path[MAXPGPATH];
+
+		if (find_my_exec(argv0, 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 able to retrieve the value of GUC parameter
+	 * restore_command, if set.
+	 */
+	snprintf(postgres_cmd, sizeof(postgres_cmd),
+			 "\"%s\" -D \"%s\" -C restore_command",
+			 postgres_exec_path, datadir_target);
+
+	if (!pipe_read_line(postgres_cmd, cmd_output, sizeof(cmd_output)))
+		exit(1);
+
+	(void) pg_strip_crlf(cmd_output);
+
+	if (strcmp(cmd_output, "") == 0)
+		pg_fatal("restore_command is not set on the target cluster");
+
+	restore_command = pg_strdup(cmd_output);
+
+	pg_log_debug("using for rewind restore_command = \'%s\'",
+				 restore_command);
+}
+
+
 /*
  * Ensure clean shutdown of target instance by launching single-user mode
  * postgres to do crash recovery.
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..349d2fa0b2 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,48 @@ 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.  The old master has
+		# to be stopped at this point.
+
+		# Remove the existing archive directory and move all WAL
+		# segments from the old master to the archives.  These
+		# will be used by pg_rewind.
+		rmtree($node_master->archive_dir);
+		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");
+
+		# Make sure that directories have the right umask as this is
+		# required by a follow-up check on permissions, and better
+		# safe than sorry.
+		chmod(0700, $node_master->archive_dir);
+		chmod(0700, $node_master->data_dir . "/pg_wal");
+
+		# Add appropriate restore_command to the target cluster
+		$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",
+				"--restore-target-wal"
+			],
+			'pg_rewind archive');
+	}
 	else
 	{
 
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index f64d659522..07c49e4719 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 <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/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 636428b044..4b68a5c640 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -139,8 +139,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;
 
-- 
2.25.1

#81Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#80)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Tue, Mar 24, 2020 at 12:22:16PM +0900, Michael Paquier wrote:

Thanks Alvaro and Alexander. 0001 has been applied as of e09ad07.
Now for 0002, let's see about it later. Attached is a rebased
version, with no actual changes.

I was looking at this patch again today and I am rather fine with the
existing semantics. Still I don't like much to name the frontend-side
routine FrontendRestoreArchivedFile() and use a different name than
the backend counterpart because we have to include xlog_internal.h in
fe_archive.c to be able to grab XLOGDIR.

So here is an idea: let's move the declaration of the routines part of
xlogarchive.c to a new header, called xlogarchive.h, and then let's
use the same routine name for the frontend and the backend in this
second patch. We include xlog_internal.h already in many frontend
tools, so that would clean up things a bit. Two extra things are the
routines for the checkpointer as well as the variables like
ArchiveRecoveryRequested. It may be nice to move those while on it,
but I am not sure where and that's not actually required for this
patch set so that could be addressed later if need be.

Any thoughts?
--
Michael

#82Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#81)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-03-26 10:34, Michael Paquier wrote:

On Tue, Mar 24, 2020 at 12:22:16PM +0900, Michael Paquier wrote:

Thanks Alvaro and Alexander. 0001 has been applied as of e09ad07.
Now for 0002, let's see about it later. Attached is a rebased
version, with no actual changes.

I was looking at this patch again today and I am rather fine with the
existing semantics. Still I don't like much to name the frontend-side
routine FrontendRestoreArchivedFile() and use a different name than
the backend counterpart because we have to include xlog_internal.h in
fe_archive.c to be able to grab XLOGDIR.

So here is an idea: let's move the declaration of the routines part of
xlogarchive.c to a new header, called xlogarchive.h, and then let's
use the same routine name for the frontend and the backend in this
second patch. We include xlog_internal.h already in many frontend
tools, so that would clean up things a bit. Two extra things are the
routines for the checkpointer as well as the variables like
ArchiveRecoveryRequested. It may be nice to move those while on it,
but I am not sure where and that's not actually required for this
patch set so that could be addressed later if need be.

Any thoughts?

The block of function declarations for xlogarchive.c inside
xlog_internal.h looks a bit dangling already, since all other functions
and variables defined/initialized in xlog.c. That way, it looks good to
me to move it outside.

The only one concern about using the same name I have is that later
someone may introduce a new variable or typedef inside xlogarchive.h. So
someone else would be required to include both fe_archive.h and
xlogarchive.h at once. But probably there should be a warning in the
header comment section against doing so.

Anyway, I have tried to do what you proposed and attached is a small
patch, that introduces xlogarchive.h.

Regards
--
Alexey Kondratov

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

Attachments:

introduce_xlogarchive_h.difftext/x-diff; name=introduce_xlogarchive_h.diffDownload
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 860a996414..d683af377f 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -36,6 +36,7 @@
 
 #include "access/timeline.h"
 #include "access/xlog.h"
+#include "access/xlogarchive.h"
 #include "access/xlog_internal.h"
 #include "access/xlogdefs.h"
 #include "pgstat.h"
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 7621fc05e2..242427f815 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -32,6 +32,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xact.h"
+#include "access/xlogarchive.h"
 #include "access/xlog_internal.h"
 #include "access/xloginsert.h"
 #include "access/xlogreader.h"
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 914ad340ea..04104b55ea 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -20,6 +20,7 @@
 #include <unistd.h>
 
 #include "access/xlog.h"
+#include "access/xlogarchive.h"
 #include "access/xlog_internal.h"
 #include "common/archive.h"
 #include "miscadmin.h"
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 25e0333c9e..cc91954ac1 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -48,6 +48,7 @@
 #include "access/htup_details.h"
 #include "access/timeline.h"
 #include "access/transam.h"
+#include "access/xlogarchive.h"
 #include "access/xlog_internal.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 27ded593ab..8e3cfcf83e 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -320,22 +320,4 @@ extern bool InArchiveRecovery;
 extern bool StandbyMode;
 extern char *recoveryRestoreCommand;
 
-/*
- * Prototypes for functions in xlogarchive.c
- */
-extern bool RestoreArchivedFile(char *path, const char *xlogfname,
-								const char *recovername, off_t expectedSize,
-								bool cleanupEnabled);
-extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
-								   bool failOnSignal);
-extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
-extern void XLogArchiveNotify(const char *xlog);
-extern void XLogArchiveNotifySeg(XLogSegNo segno);
-extern void XLogArchiveForceDone(const char *xlog);
-extern bool XLogArchiveCheckDone(const char *xlog);
-extern bool XLogArchiveIsBusy(const char *xlog);
-extern bool XLogArchiveIsReady(const char *xlog);
-extern bool XLogArchiveIsReadyOrDone(const char *xlog);
-extern void XLogArchiveCleanup(const char *xlog);
-
 #endif							/* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
new file mode 100644
index 0000000000..3406fb2800
--- /dev/null
+++ b/src/include/access/xlogarchive.h
@@ -0,0 +1,31 @@
+/*
+ * xlogarchive.h
+ *		Prototypes of functions for archiving WAL files and restoring
+ *		from the archive.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/xlogarchive.h
+ */
+#ifndef XLOG_ARCHIVE_H
+#define XLOG_ARCHIVE_H
+
+#include "access/xlogdefs.h"
+
+extern bool RestoreArchivedFile(char *path, const char *xlogfname,
+								const char *recovername, off_t expectedSize,
+								bool cleanupEnabled);
+extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
+								   bool failOnSignal);
+extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
+extern void XLogArchiveNotify(const char *xlog);
+extern void XLogArchiveNotifySeg(XLogSegNo segno);
+extern void XLogArchiveForceDone(const char *xlog);
+extern bool XLogArchiveCheckDone(const char *xlog);
+extern bool XLogArchiveIsBusy(const char *xlog);
+extern bool XLogArchiveIsReady(const char *xlog);
+extern bool XLogArchiveIsReadyOrDone(const char *xlog);
+extern void XLogArchiveCleanup(const char *xlog);
+
+#endif							/* XLOG_ARCHIVE_H */
#83Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#81)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-Mar-26, Michael Paquier wrote:

I was looking at this patch again today and I am rather fine with the
existing semantics. Still I don't like much to name the frontend-side
routine FrontendRestoreArchivedFile() and use a different name than
the backend counterpart because we have to include xlog_internal.h in
fe_archive.c to be able to grab XLOGDIR.

Uh, is XLOGDIR the only reason to include xlog_internal.h? Maybe it
would be easier to have a routine in xlog.c that returns the path?
There's a few functions in xlog.c that could use it, as well.

Altough ... looking at xlog.h, that one is even worse, so I'm not sure
*where* you'd put the prototype for the new function I'm proposing.

So here is an idea: let's move the declaration of the routines part of
xlogarchive.c to a new header, called xlogarchive.h, and then let's
use the same routine name for the frontend and the backend in this
second patch. We include xlog_internal.h already in many frontend
tools, so that would clean up things a bit.

The patch downthread looks decent cleanup, but I'm not sure how it helps
further the objective.

(A really good cleanup could be a situation where frontend files don't
need xlog_internal.h -- for example, maybe a new file xlogpage.h could
contain struct defs that relate to page and segment headers and the
like, as well as useful macros. I don't know if this can be made to
work -- but xlog_internal.h contains stuff like xl_parameter_change etc
as well as RmgrData which surely are of no interest to readers of wal
files ... or, say, RequestXLogSwitch.)

I don't think any such cleanup should hamper the patch at hand anyway.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#84Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#83)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Thu, Mar 26, 2020 at 06:56:36PM -0300, Alvaro Herrera wrote:

Uh, is XLOGDIR the only reason to include xlog_internal.h? Maybe it
would be easier to have a routine in xlog.c that returns the path?
There's a few functions in xlog.c that could use it, as well.

Yep, that's all. We could also just hardcode the path as we did when
we worked on the exclusion filter lists for pg_rewind and basebackup.c,
though I'd prefer avoid that if possible.

The patch downthread looks decent cleanup, but I'm not sure how it helps
further the objective.

I actually think it does, because you get out of the way the conflicts
caused by RestoreArchivedFile() in the backend, as we are targetting
for fe_archive.c to be a frontend-only file, though I agree that it is
not the full of it.

(A really good cleanup could be a situation where frontend files don't
need xlog_internal.h -- for example, maybe a new file xlogpage.h could
contain struct defs that relate to page and segment headers and the
like, as well as useful macros. I don't know if this can be made to
work -- but xlog_internal.h contains stuff like xl_parameter_change etc
as well as RmgrData which surely are of no interest to readers of wal
files ... or, say, RequestXLogSwitch.)

A second header that could be created is xlogpaths.h (or xlogfiles.h?)
that includes all the routines and variables we use to build WAL
segment names and such, with XLogFileNameById, IsTLHistoryFileName,
etc. I agree that splitting the record-related parts may make sense,
say xlogrecovery.h?

I don't think any such cleanup should hamper the patch at hand anyway.

I don't think either, so I would do the split for the archive routines
at least. It still feels strange to me to have a different routine
name for the frontend-side of RestoreArchivedFile(). That's not
really consistent with the current practice we have palloc() & co, as
well as the sync routines.
--
Michael

#85Michael Paquier
michael@paquier.xyz
In reply to: Alexey Kondratov (#82)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Fri, Mar 27, 2020 at 12:24:19AM +0300, Alexey Kondratov wrote:

The block of function declarations for xlogarchive.c inside xlog_internal.h
looks a bit dangling already, since all other functions and variables
defined/initialized in xlog.c. That way, it looks good to me to move it
outside.

Yep, exactly my point of view.

The only one concern about using the same name I have is that later someone
may introduce a new variable or typedef inside xlogarchive.h. So someone
else would be required to include both fe_archive.h and xlogarchive.h at
once. But probably there should be a warning in the header comment section
against doing so.

Anyway, I have tried to do what you proposed and attached is a small patch,
that introduces xlogarchive.h.

Thanks for sending a patch, that's the split I would have done.

+#include "access/xlogdefs.h"
Oh, I see. You need that in xlogarchive.h for XLogSegNo. Makes sense.

+ * xlogarchive.h
+ *     Prototypes of functions for archiving WAL files and restoring
+ *     from the archive.
The only tweak I would have done here is to reword that as "Utilities
for interacting with WAL archives in the backend."

Alvaro, Alexander, do you like this split? FWIW, I do as it is
simple.
--
Michael

#86Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#84)
2 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Fri, Mar 27, 2020 at 07:47:29AM +0900, Michael Paquier wrote:

On Thu, Mar 26, 2020 at 06:56:36PM -0300, Alvaro Herrera wrote:

I don't think any such cleanup should hamper the patch at hand anyway.

I don't think either, so I would do the split for the archive routines
at least. It still feels strange to me to have a different routine
name for the frontend-side of RestoreArchivedFile(). That's not
really consistent with the current practice we have palloc() & co, as
well as the sync routines.

Okay. Hearing nothing, I have rebased the patch set this morning,
improving some comments and the code format while on it. I would like
to commit both 0001 (creation of xlogarchiver.h) and 0002 attached in
the next couple of days. If you have an objection, please feel free.
--
Michael

Attachments:

v5-0001-Move-routines-of-xlogarchive.c-into-their-own-hea.patchtext/x-diff; charset=us-asciiDownload
From 71302a709a54a88985563c38830deb784728e682 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 30 Mar 2020 10:34:17 +0900
Subject: [PATCH v5 1/2] Move routines of xlogarchive.c into their own header

The definitions of those routines have been part of xlog_internal.h,
which is included by several frontend tools.  More could be done in this
area, but that's already a nice and simple cut.
---
 src/include/access/xlog_internal.h       | 18 ------------
 src/include/access/xlogarchive.h         | 35 ++++++++++++++++++++++++
 src/backend/access/transam/timeline.c    |  1 +
 src/backend/access/transam/xlog.c        |  1 +
 src/backend/access/transam/xlogarchive.c |  1 +
 src/backend/replication/walreceiver.c    |  1 +
 6 files changed, 39 insertions(+), 18 deletions(-)
 create mode 100644 src/include/access/xlogarchive.h

diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 27ded593ab..8e3cfcf83e 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -320,22 +320,4 @@ extern bool InArchiveRecovery;
 extern bool StandbyMode;
 extern char *recoveryRestoreCommand;
 
-/*
- * Prototypes for functions in xlogarchive.c
- */
-extern bool RestoreArchivedFile(char *path, const char *xlogfname,
-								const char *recovername, off_t expectedSize,
-								bool cleanupEnabled);
-extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
-								   bool failOnSignal);
-extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
-extern void XLogArchiveNotify(const char *xlog);
-extern void XLogArchiveNotifySeg(XLogSegNo segno);
-extern void XLogArchiveForceDone(const char *xlog);
-extern bool XLogArchiveCheckDone(const char *xlog);
-extern bool XLogArchiveIsBusy(const char *xlog);
-extern bool XLogArchiveIsReady(const char *xlog);
-extern bool XLogArchiveIsReadyOrDone(const char *xlog);
-extern void XLogArchiveCleanup(const char *xlog);
-
 #endif							/* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
new file mode 100644
index 0000000000..1c67de2ede
--- /dev/null
+++ b/src/include/access/xlogarchive.h
@@ -0,0 +1,35 @@
+/*------------------------------------------------------------------------
+ *
+ * xlogarchive.h
+ *		Prototypes for WAL archives in the backend
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		src/include/access/xlogarchive.h
+ *
+ *------------------------------------------------------------------------
+ */
+
+#ifndef XLOG_ARCHIVE_H
+#define XLOG_ARCHIVE_H
+
+#include "access/xlogdefs.h"
+
+extern bool RestoreArchivedFile(char *path, const char *xlogfname,
+								const char *recovername, off_t expectedSize,
+								bool cleanupEnabled);
+extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
+								   bool failOnSignal);
+extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
+extern void XLogArchiveNotify(const char *xlog);
+extern void XLogArchiveNotifySeg(XLogSegNo segno);
+extern void XLogArchiveForceDone(const char *xlog);
+extern bool XLogArchiveCheckDone(const char *xlog);
+extern bool XLogArchiveIsBusy(const char *xlog);
+extern bool XLogArchiveIsReady(const char *xlog);
+extern bool XLogArchiveIsReadyOrDone(const char *xlog);
+extern void XLogArchiveCleanup(const char *xlog);
+
+#endif							/* XLOG_ARCHIVE_H */
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 860a996414..d683af377f 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -36,6 +36,7 @@
 
 #include "access/timeline.h"
 #include "access/xlog.h"
+#include "access/xlogarchive.h"
 #include "access/xlog_internal.h"
 #include "access/xlogdefs.h"
 #include "pgstat.h"
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8fe92962b0..03c81c0375 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -32,6 +32,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xact.h"
+#include "access/xlogarchive.h"
 #include "access/xlog_internal.h"
 #include "access/xloginsert.h"
 #include "access/xlogreader.h"
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 914ad340ea..04104b55ea 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -20,6 +20,7 @@
 #include <unistd.h>
 
 #include "access/xlog.h"
+#include "access/xlogarchive.h"
 #include "access/xlog_internal.h"
 #include "common/archive.h"
 #include "miscadmin.h"
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 760e3c7ab0..9ecbfe336e 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -54,6 +54,7 @@
 #include "access/htup_details.h"
 #include "access/timeline.h"
 #include "access/transam.h"
+#include "access/xlogarchive.h"
 #include "access/xlog_internal.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
-- 
2.26.0

v5-0002-Add-c-restore-target-wal-to-pg_rewind.patchtext/x-diff; charset=us-asciiDownload
From fb4470376ee308463648c31354942169553e0582 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 30 Mar 2020 10:51:51 +0900
Subject: [PATCH v5 2/2] Add -c/--restore-target-wal to pg_rewind

pg_rewind needs to copy from the source server to the target a set of
relation blocks changed from the previous checkpoint where WAL forked
between both up to the end of WAL on the target.  This may require WAL
segments that are not present anymore on the target's pg_wal, causing
pg_rewind to fail.  It is possible to fix that by copying manually the
WAL segments needed but this may lead to extra and useless work.

Instead, this commit introduces a new option allowing pg_rewind to use a
restore_command when running by grabbing it from the target
configuration.  This allows the rewind operation to be more reliable,
and only the necessary segments are fetched from the archives in this
case.

In order to do that, a new routine is added to src/common/ to allow
frontend tools to retrieve a WAL segment using an already-built restore
command.  This version is much more simple than the backend equivalent.

Author: Alexey Kondratov
Reviewed-by: Andrey Borodin, Andres Freund, Alvaro Herrera, Alexander
Korotkov, Michael Paquier
Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1@postgrespro.ru
---
 src/include/common/fe_archive.h   |  21 +++++
 src/include/port.h                |   3 +-
 src/common/Makefile               |   1 +
 src/common/exec.c                 |   3 +-
 src/common/fe_archive.c           | 128 ++++++++++++++++++++++++++++++
 src/bin/pg_rewind/parsexlog.c     |  33 +++++++-
 src/bin/pg_rewind/pg_rewind.c     |  87 ++++++++++++++++++--
 src/bin/pg_rewind/pg_rewind.h     |   6 +-
 src/bin/pg_rewind/t/001_basic.pl  |   3 +-
 src/bin/pg_rewind/t/RewindTest.pm |  70 +++++++++++++++-
 doc/src/sgml/ref/pg_rewind.sgml   |  28 +++++--
 src/tools/msvc/Mkvcbuild.pm       |   4 +-
 12 files changed, 360 insertions(+), 27 deletions(-)
 create mode 100644 src/include/common/fe_archive.h
 create mode 100644 src/common/fe_archive.c

diff --git a/src/include/common/fe_archive.h b/src/include/common/fe_archive.h
new file mode 100644
index 0000000000..96506baefb
--- /dev/null
+++ b/src/include/common/fe_archive.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.h
+ *	  Routines to access WAL archives 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	RestoreArchivedFile(const char *path,
+										const char *xlogfname,
+										off_t expectedSize,
+										const char *restoreCommand);
+
+#endif							/* FE_ARCHIVE_H */
diff --git a/src/include/port.h b/src/include/port.h
index 29f3e39e5b..20a5de1b7a 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -102,10 +102,11 @@ extern void pgfnames_cleanup(char **filenames);
 /* Portable locale initialization (in exec.c) */
 extern void set_pglocale_pgservice(const char *argv0, const char *app);
 
-/* Portable way to find binaries (in exec.c) */
+/* Portable way to find and execute binaries (in exec.c) */
 extern int	find_my_exec(const char *argv0, char *retpath);
 extern int	find_other_exec(const char *argv0, const char *target,
 							const char *versionstr, char *retpath);
+extern char *pipe_read_line(char *cmd, char *line, int maxsize);
 
 /* Doesn't belong here, but this is used with find_other_exec(), so... */
 #define PG_BACKEND_VERSIONSTR "postgres (PostgreSQL) " PG_VERSION "\n"
diff --git a/src/common/Makefile b/src/common/Makefile
index 6939b9d087..a97c723fbd 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -88,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/exec.c b/src/common/exec.c
index 88400daa47..f39b0a294b 100644
--- a/src/common/exec.c
+++ b/src/common/exec.c
@@ -51,7 +51,6 @@
 
 static int	validate_exec(const char *path);
 static int	resolve_symlinks(char *path);
-static char *pipe_read_line(char *cmd, char *line, int maxsize);
 
 #ifdef WIN32
 static BOOL GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser);
@@ -356,7 +355,7 @@ find_other_exec(const char *argv0, const char *target,
 /*
  * Execute a command in a pipe and read the first line from it.
  */
-static char *
+char *
 pipe_read_line(char *cmd, char *line, int maxsize)
 {
 	FILE	   *pgver;
diff --git a/src/common/fe_archive.c b/src/common/fe_archive.c
new file mode 100644
index 0000000000..146bc4e048
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,128 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.c
+ *	  Routines to access WAL archives 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"
+
+
+/*
+ * RestoreArchivedFile
+ *
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful, return a file descriptor of the restored 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
+RestoreArchivedFile(const char *path, const char *xlogfname,
+							off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH];
+	char	   *xlogRestoreCmd;
+	int			rc;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	xlogRestoreCmd = BuildRestoreCommand(restoreCommand, xlogpath,
+										 xlogfname, NULL);
+	if (xlogRestoreCmd == NULL)
+	{
+		pg_log_fatal("could not use restore_command with %%r alias");
+		exit(1);
+	}
+
+	/*
+	 * Execute restore_command, which should copy the missing file from
+	 * archival storage.
+	 */
+	rc = system(xlogRestoreCmd);
+	pfree(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_fatal("unexpected file size for \"%s\": %lu instead of %lu",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize);
+				exit(1);
+			}
+			else
+			{
+				int			xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+				{
+					pg_log_fatal("could not open file \"%s\" restored from archive: %m",
+								 xlogpath);
+					exit(1);
+				}
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			if (errno != ENOENT)
+			{
+				pg_log_fatal("could not stat file \"%s\": %m",
+							 xlogpath);
+				exit(1);
+			}
+		}
+	}
+
+	/*
+	 * If the failure was due to a signal, then it would be misleading to
+	 * return with a failure at restoring the file.  So just bail out and
+	 * exit.  Hard shell errors such as "command not found" are treated as
+	 * fatal too.
+	 */
+	if (wait_result_is_any_signal(rc, true))
+	{
+		pg_log_fatal("restore_command failed due to the signal: %s",
+					 wait_result_to_str(rc));
+		exit(1);
+	}
+
+	/*
+	 * The file is not available, so just let the caller decide what to do
+	 * next.
+	 */
+	pg_log_error("could not restore file \"%s\" from archive",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index eb61cb8803..b147b7212f 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 = RestoreArchivedFile(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 bf2d2983e7..44eb15b269 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/restricted_token.h"
+#include "common/string.h"
 #include "fe_utils/recovery_gen.h"
 #include "fetch.h"
 #include "file_ops.h"
@@ -38,6 +39,7 @@ static void createBackupLabel(XLogRecPtr startpoint, TimeLineID starttli,
 static void digestControlFile(ControlFileData *ControlFile, char *source,
 							  size_t size);
 static void syncTargetDirectory(void);
+static void getRestoreCommand(const char *argv0);
 static void sanityChecks(void);
 static void findCommonAncestorTimeline(XLogRecPtr *recptr, int *tliIndex);
 static void ensureCleanShutdown(const char *argv0);
@@ -53,11 +55,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 +78,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"
+			 "                                 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"));
@@ -103,6 +109,7 @@ main(int argc, char **argv)
 		{"source-server", required_argument, NULL, 2},
 		{"no-ensure-shutdown", no_argument, NULL, 4},
 		{"version", no_argument, NULL, 'V'},
+		{"restore-target-wal", no_argument, NULL, 'c'},
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
@@ -144,7 +151,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)
 		{
@@ -152,6 +159,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;
@@ -255,6 +266,8 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	getRestoreCommand(argv[0]);
+
 	atexit(disconnect_atexit);
 
 	/* Connect to remote server */
@@ -350,9 +363,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);
@@ -378,7 +390,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)
@@ -804,6 +816,71 @@ syncTargetDirectory(void)
 	fsync_pgdata(datadir_target, PG_VERSION_NUM);
 }
 
+/*
+ * Get value of GUC parameter restore_command from the target cluster.
+ *
+ * This uses a logic based on "postgres -C" to get the value from
+ * from the cluster.
+ */
+static void
+getRestoreCommand(const char *argv0)
+{
+	int			rc;
+	char		postgres_exec_path[MAXPGPATH],
+				postgres_cmd[MAXPGPATH],
+				cmd_output[MAXPGPATH];
+
+	if (!restore_wal)
+		return;
+
+	/* find postgres executable */
+	rc = find_other_exec(argv0, "postgres",
+						 PG_BACKEND_VERSIONSTR,
+						 postgres_exec_path);
+
+	if (rc < 0)
+	{
+		char		full_path[MAXPGPATH];
+
+		if (find_my_exec(argv0, 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 able to retrieve the value of GUC parameter
+	 * restore_command, if set.
+	 */
+	snprintf(postgres_cmd, sizeof(postgres_cmd),
+			 "\"%s\" -D \"%s\" -C restore_command",
+			 postgres_exec_path, datadir_target);
+
+	if (!pipe_read_line(postgres_cmd, cmd_output, sizeof(cmd_output)))
+		exit(1);
+
+	(void) pg_strip_crlf(cmd_output);
+
+	if (strcmp(cmd_output, "") == 0)
+		pg_fatal("restore_command is not set on the target cluster");
+
+	restore_command = pg_strdup(cmd_output);
+
+	pg_log_debug("using for rewind restore_command = \'%s\'",
+				 restore_command);
+}
+
+
 /*
  * Ensure clean shutdown of target instance by launching single-user mode
  * postgres to do crash recovery.
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..7dabf395e1 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,26 @@ 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')
+	{
+		# pg_rewind is tested with --restore-target-wal by moving all
+		# WAL files to a secondary location.  Note that this leads to
+		# a failure in ensureCleanShutdown(), forcing to the use of
+		# --no-ensure-shutdown in this mode as the initial set of WAL
+		# files needed to ensure a clean restart is gone.  This could
+		# be improved by keeping around only a minimum set of WAL
+		# segments but that would just make the test more costly,
+		# without improving the coverage.  Hence, instead, stop
+		# gracefully the primary here.
+		$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 +301,51 @@ 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.  The old master has
+		# to be stopped at this point.
+
+		# Remove the existing archive directory and move all WAL
+		# segments from the old master to the archives.  These
+		# will be used by pg_rewind.
+		rmtree($node_master->archive_dir);
+		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");
+
+		# Make sure that directories have the right umask as this is
+		# required by a follow-up check on permissions, and better
+		# safe than sorry.
+		chmod(0700, $node_master->archive_dir);
+		chmod(0700, $node_master->data_dir . "/pg_wal");
+
+		# Add appropriate restore_command to the target cluster
+		$node_master->enable_restoring($node_master, 0);
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Note the use of --no-ensure-shutdown here.  WAL files are
+		# gone in this mode and the primary has been stopped
+		# gracefully already.
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"--no-ensure-shutdown",
+				"--restore-target-wal"
+			],
+			'pg_rewind archive');
+	}
 	else
 	{
 
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index f64d659522..07c49e4719 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 <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/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 39709f20e6..5c88825f49 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -139,8 +139,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;
 
-- 
2.26.0

#87Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Michael Paquier (#86)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Mon, Mar 30, 2020 at 4:57 AM Michael Paquier <michael@paquier.xyz> wrote:

On Fri, Mar 27, 2020 at 07:47:29AM +0900, Michael Paquier wrote:

On Thu, Mar 26, 2020 at 06:56:36PM -0300, Alvaro Herrera wrote:

I don't think any such cleanup should hamper the patch at hand anyway.

I don't think either, so I would do the split for the archive routines
at least. It still feels strange to me to have a different routine
name for the frontend-side of RestoreArchivedFile(). That's not
really consistent with the current practice we have palloc() & co, as
well as the sync routines.

Okay. Hearing nothing, I have rebased the patch set this morning,
improving some comments and the code format while on it. I would like
to commit both 0001 (creation of xlogarchiver.h) and 0002 attached in
the next couple of days. If you have an objection, please feel free.

I'm fine with patchset attached. Sorry for late reply.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#88Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#86)
2 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-03-30 04:56, Michael Paquier wrote:

On Fri, Mar 27, 2020 at 07:47:29AM +0900, Michael Paquier wrote:

On Thu, Mar 26, 2020 at 06:56:36PM -0300, Alvaro Herrera wrote:

I don't think any such cleanup should hamper the patch at hand
anyway.

I don't think either, so I would do the split for the archive routines
at least. It still feels strange to me to have a different routine
name for the frontend-side of RestoreArchivedFile(). That's not
really consistent with the current practice we have palloc() & co, as
well as the sync routines.

Okay. Hearing nothing, I have rebased the patch set this morning,
improving some comments and the code format while on it. I would like
to commit both 0001 (creation of xlogarchiver.h) and 0002 attached in
the next couple of days. If you have an objection, please feel free.

0001:

What do think about adding following sanity check into xlogarchive.c?

+#ifdef FRONTEND
+#error "This file is not expected to be compiled for frontend code"
+#endif

It would prevent someone from adding typedefs and any other common
definitions into xlogarchive.h in the future, leading to the accidental
inclusion of both xlogarchive.h and fe_archive.h in the same time.

0002:

+ */
+int
+RestoreArchivedFile(const char *path, const char *xlogfname,
+							off_t expectedSize, const char *restoreCommand)
+{

There is a couple of extra tabs IMO.

+extern int	RestoreArchivedFile(const char *path,
+						const char *xlogfname,
+						off_t expectedSize,
+						const char *restoreCommand);

And the same here.

+ * This uses a logic based on "postgres -C" to get the value from
+ * from the cluster.

Repetitive 'from'.

  extractPageMap(const char *datadir, XLogRecPtr startpoint, int 
tliIndex,
-			   XLogRecPtr endpoint)
+			   XLogRecPtr endpoint, const char *restore_command)

Let us use camel case 'restoreCommand' here as in the header for
tidiness.

I have left 0001 intact, but fixed all these small remarks in the 0002.
Please, find it attached.

Regards
--
Alexey Kondratov

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

Attachments:

v6-0002-Add-c-restore-target-wal-to-pg_rewind.patchtext/x-diff; name=v6-0002-Add-c-restore-target-wal-to-pg_rewind.patchDownload
From f94990da84844841a35e80ca30e2b3d8c3bdaded Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 30 Mar 2020 10:51:51 +0900
Subject: [PATCH v6 2/2] Add -c/--restore-target-wal to pg_rewind

pg_rewind needs to copy from the source server to the target a set of
relation blocks changed from the previous checkpoint where WAL forked
between both up to the end of WAL on the target.  This may require WAL
segments that are not present anymore on the target's pg_wal, causing
pg_rewind to fail.  It is possible to fix that by copying manually the
WAL segments needed but this may lead to extra and useless work.

Instead, this commit introduces a new option allowing pg_rewind to use a
restore_command when running by grabbing it from the target
configuration.  This allows the rewind operation to be more reliable,
and only the necessary segments are fetched from the archives in this
case.

In order to do that, a new routine is added to src/common/ to allow
frontend tools to retrieve a WAL segment using an already-built restore
command.  This version is much more simple than the backend equivalent.

Author: Alexey Kondratov
Reviewed-by: Andrey Borodin, Andres Freund, Alvaro Herrera, Alexander
Korotkov, Michael Paquier
Discussion: https://postgr.es/m/a3acff50-5a0d-9a2c-b3b2-ee36168955c1@postgrespro.ru
---
 doc/src/sgml/ref/pg_rewind.sgml   |  28 +++++--
 src/bin/pg_rewind/parsexlog.c     |  33 +++++++-
 src/bin/pg_rewind/pg_rewind.c     |  87 ++++++++++++++++++--
 src/bin/pg_rewind/pg_rewind.h     |   6 +-
 src/bin/pg_rewind/t/001_basic.pl  |   3 +-
 src/bin/pg_rewind/t/RewindTest.pm |  70 +++++++++++++++-
 src/common/Makefile               |   1 +
 src/common/exec.c                 |   3 +-
 src/common/fe_archive.c           | 128 ++++++++++++++++++++++++++++++
 src/include/common/fe_archive.h   |  21 +++++
 src/include/port.h                |   3 +-
 src/tools/msvc/Mkvcbuild.pm       |   4 +-
 12 files changed, 360 insertions(+), 27 deletions(-)
 create mode 100644 src/common/fe_archive.c
 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 f64d659522..07c49e4719 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 <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/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index eb61cb8803..b7629fcc6b 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 *restoreCommand)
 {
 	XLogRecord *record;
 	XLogReaderState *xlogreader;
@@ -63,6 +65,7 @@ extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
 	XLogPageReadPrivate private;
 
 	private.tliIndex = tliIndex;
+	private.restoreCommand = restoreCommand;
 	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 = RestoreArchivedFile(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 bf2d2983e7..db849aa209 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -22,6 +22,7 @@
 #include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/restricted_token.h"
+#include "common/string.h"
 #include "fe_utils/recovery_gen.h"
 #include "fetch.h"
 #include "file_ops.h"
@@ -38,6 +39,7 @@ static void createBackupLabel(XLogRecPtr startpoint, TimeLineID starttli,
 static void digestControlFile(ControlFileData *ControlFile, char *source,
 							  size_t size);
 static void syncTargetDirectory(void);
+static void getRestoreCommand(const char *argv0);
 static void sanityChecks(void);
 static void findCommonAncestorTimeline(XLogRecPtr *recptr, int *tliIndex);
 static void ensureCleanShutdown(const char *argv0);
@@ -53,11 +55,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 +78,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"
+			 "                                 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"));
@@ -103,6 +109,7 @@ main(int argc, char **argv)
 		{"source-server", required_argument, NULL, 2},
 		{"no-ensure-shutdown", no_argument, NULL, 4},
 		{"version", no_argument, NULL, 'V'},
+		{"restore-target-wal", no_argument, NULL, 'c'},
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
@@ -144,7 +151,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)
 		{
@@ -152,6 +159,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;
@@ -255,6 +266,8 @@ main(int argc, char **argv)
 
 	umask(pg_mode_mask);
 
+	getRestoreCommand(argv[0]);
+
 	atexit(disconnect_atexit);
 
 	/* Connect to remote server */
@@ -350,9 +363,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);
@@ -378,7 +390,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)
@@ -804,6 +816,71 @@ syncTargetDirectory(void)
 	fsync_pgdata(datadir_target, PG_VERSION_NUM);
 }
 
+/*
+ * Get value of GUC parameter restore_command from the target cluster.
+ *
+ * This uses a logic based on "postgres -C" to get the value
+ * from the cluster.
+ */
+static void
+getRestoreCommand(const char *argv0)
+{
+	int			rc;
+	char		postgres_exec_path[MAXPGPATH],
+				postgres_cmd[MAXPGPATH],
+				cmd_output[MAXPGPATH];
+
+	if (!restore_wal)
+		return;
+
+	/* find postgres executable */
+	rc = find_other_exec(argv0, "postgres",
+						 PG_BACKEND_VERSIONSTR,
+						 postgres_exec_path);
+
+	if (rc < 0)
+	{
+		char		full_path[MAXPGPATH];
+
+		if (find_my_exec(argv0, 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 able to retrieve the value of GUC parameter
+	 * restore_command, if set.
+	 */
+	snprintf(postgres_cmd, sizeof(postgres_cmd),
+			 "\"%s\" -D \"%s\" -C restore_command",
+			 postgres_exec_path, datadir_target);
+
+	if (!pipe_read_line(postgres_cmd, cmd_output, sizeof(cmd_output)))
+		exit(1);
+
+	(void) pg_strip_crlf(cmd_output);
+
+	if (strcmp(cmd_output, "") == 0)
+		pg_fatal("restore_command is not set on the target cluster");
+
+	restore_command = pg_strdup(cmd_output);
+
+	pg_log_debug("using for rewind restore_command = \'%s\'",
+				 restore_command);
+}
+
+
 /*
  * Ensure clean shutdown of target instance by launching single-user mode
  * postgres to do crash recovery.
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..7dabf395e1 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,26 @@ 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')
+	{
+		# pg_rewind is tested with --restore-target-wal by moving all
+		# WAL files to a secondary location.  Note that this leads to
+		# a failure in ensureCleanShutdown(), forcing to the use of
+		# --no-ensure-shutdown in this mode as the initial set of WAL
+		# files needed to ensure a clean restart is gone.  This could
+		# be improved by keeping around only a minimum set of WAL
+		# segments but that would just make the test more costly,
+		# without improving the coverage.  Hence, instead, stop
+		# gracefully the primary here.
+		$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 +301,51 @@ 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.  The old master has
+		# to be stopped at this point.
+
+		# Remove the existing archive directory and move all WAL
+		# segments from the old master to the archives.  These
+		# will be used by pg_rewind.
+		rmtree($node_master->archive_dir);
+		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");
+
+		# Make sure that directories have the right umask as this is
+		# required by a follow-up check on permissions, and better
+		# safe than sorry.
+		chmod(0700, $node_master->archive_dir);
+		chmod(0700, $node_master->data_dir . "/pg_wal");
+
+		# Add appropriate restore_command to the target cluster
+		$node_master->enable_restoring($node_master, 0);
+
+		# Stop the new master and be ready to perform the rewind.
+		$node_standby->stop;
+
+		# Note the use of --no-ensure-shutdown here.  WAL files are
+		# gone in this mode and the primary has been stopped
+		# gracefully already.
+		command_ok(
+			[
+				'pg_rewind',
+				"--debug",
+				"--source-pgdata=$standby_pgdata",
+				"--target-pgdata=$master_pgdata",
+				"--no-sync",
+				"--no-ensure-shutdown",
+				"--restore-target-wal"
+			],
+			'pg_rewind archive');
+	}
 	else
 	{
 
diff --git a/src/common/Makefile b/src/common/Makefile
index 6939b9d087..a97c723fbd 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -88,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/exec.c b/src/common/exec.c
index 88400daa47..f39b0a294b 100644
--- a/src/common/exec.c
+++ b/src/common/exec.c
@@ -51,7 +51,6 @@
 
 static int	validate_exec(const char *path);
 static int	resolve_symlinks(char *path);
-static char *pipe_read_line(char *cmd, char *line, int maxsize);
 
 #ifdef WIN32
 static BOOL GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser);
@@ -356,7 +355,7 @@ find_other_exec(const char *argv0, const char *target,
 /*
  * Execute a command in a pipe and read the first line from it.
  */
-static char *
+char *
 pipe_read_line(char *cmd, char *line, int maxsize)
 {
 	FILE	   *pgver;
diff --git a/src/common/fe_archive.c b/src/common/fe_archive.c
new file mode 100644
index 0000000000..b0d68870db
--- /dev/null
+++ b/src/common/fe_archive.c
@@ -0,0 +1,128 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.c
+ *	  Routines to access WAL archives 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"
+
+
+/*
+ * RestoreArchivedFile
+ *
+ * Attempt to retrieve the specified file from off-line archival storage.
+ * If successful, return a file descriptor of the restored 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
+RestoreArchivedFile(const char *path, const char *xlogfname,
+					off_t expectedSize, const char *restoreCommand)
+{
+	char		xlogpath[MAXPGPATH];
+	char	   *xlogRestoreCmd;
+	int			rc;
+	struct stat stat_buf;
+
+	snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
+
+	xlogRestoreCmd = BuildRestoreCommand(restoreCommand, xlogpath,
+										 xlogfname, NULL);
+	if (xlogRestoreCmd == NULL)
+	{
+		pg_log_fatal("could not use restore_command with %%r alias");
+		exit(1);
+	}
+
+	/*
+	 * Execute restore_command, which should copy the missing file from
+	 * archival storage.
+	 */
+	rc = system(xlogRestoreCmd);
+	pfree(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_fatal("unexpected file size for \"%s\": %lu instead of %lu",
+							 xlogfname, (unsigned long) stat_buf.st_size,
+							 (unsigned long) expectedSize);
+				exit(1);
+			}
+			else
+			{
+				int			xlogfd = open(xlogpath, O_RDONLY | PG_BINARY, 0);
+
+				if (xlogfd < 0)
+				{
+					pg_log_fatal("could not open file \"%s\" restored from archive: %m",
+								 xlogpath);
+					exit(1);
+				}
+				else
+					return xlogfd;
+			}
+		}
+		else
+		{
+			if (errno != ENOENT)
+			{
+				pg_log_fatal("could not stat file \"%s\": %m",
+							 xlogpath);
+				exit(1);
+			}
+		}
+	}
+
+	/*
+	 * If the failure was due to a signal, then it would be misleading to
+	 * return with a failure at restoring the file.  So just bail out and
+	 * exit.  Hard shell errors such as "command not found" are treated as
+	 * fatal too.
+	 */
+	if (wait_result_is_any_signal(rc, true))
+	{
+		pg_log_fatal("restore_command failed due to the signal: %s",
+					 wait_result_to_str(rc));
+		exit(1);
+	}
+
+	/*
+	 * The file is not available, so just let the caller decide what to do
+	 * next.
+	 */
+	pg_log_error("could not restore file \"%s\" from archive",
+				 xlogfname);
+	return -1;
+}
diff --git a/src/include/common/fe_archive.h b/src/include/common/fe_archive.h
new file mode 100644
index 0000000000..495b560d24
--- /dev/null
+++ b/src/include/common/fe_archive.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe_archive.h
+ *	  Routines to access WAL archives 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	RestoreArchivedFile(const char *path,
+								const char *xlogfname,
+								off_t expectedSize,
+								const char *restoreCommand);
+
+#endif							/* FE_ARCHIVE_H */
diff --git a/src/include/port.h b/src/include/port.h
index 29f3e39e5b..20a5de1b7a 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -102,10 +102,11 @@ extern void pgfnames_cleanup(char **filenames);
 /* Portable locale initialization (in exec.c) */
 extern void set_pglocale_pgservice(const char *argv0, const char *app);
 
-/* Portable way to find binaries (in exec.c) */
+/* Portable way to find and execute binaries (in exec.c) */
 extern int	find_my_exec(const char *argv0, char *retpath);
 extern int	find_other_exec(const char *argv0, const char *target,
 							const char *versionstr, char *retpath);
+extern char *pipe_read_line(char *cmd, char *line, int maxsize);
 
 /* Doesn't belong here, but this is used with find_other_exec(), so... */
 #define PG_BACKEND_VERSIONSTR "postgres (PostgreSQL) " PG_VERSION "\n"
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 39709f20e6..5c88825f49 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -139,8 +139,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;
 
-- 
2.19.1

v6-0001-Move-routines-of-xlogarchive.c-into-their-own-hea.patchtext/x-diff; name=v6-0001-Move-routines-of-xlogarchive.c-into-their-own-hea.patchDownload
From 4736dc52ad71f4eb9801856d0968eed9f89379ec Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 30 Mar 2020 10:34:17 +0900
Subject: [PATCH v6 1/2] Move routines of xlogarchive.c into their own header

The definitions of those routines have been part of xlog_internal.h,
which is included by several frontend tools.  More could be done in this
area, but that's already a nice and simple cut.
---
 src/backend/access/transam/timeline.c    |  1 +
 src/backend/access/transam/xlog.c        |  1 +
 src/backend/access/transam/xlogarchive.c |  1 +
 src/backend/replication/walreceiver.c    |  1 +
 src/include/access/xlog_internal.h       | 18 ------------
 src/include/access/xlogarchive.h         | 35 ++++++++++++++++++++++++
 6 files changed, 39 insertions(+), 18 deletions(-)
 create mode 100644 src/include/access/xlogarchive.h

diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 860a996414..d683af377f 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -36,6 +36,7 @@
 
 #include "access/timeline.h"
 #include "access/xlog.h"
+#include "access/xlogarchive.h"
 #include "access/xlog_internal.h"
 #include "access/xlogdefs.h"
 #include "pgstat.h"
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 1951103b26..39fe801c7e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -32,6 +32,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xact.h"
+#include "access/xlogarchive.h"
 #include "access/xlog_internal.h"
 #include "access/xloginsert.h"
 #include "access/xlogreader.h"
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 914ad340ea..04104b55ea 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -20,6 +20,7 @@
 #include <unistd.h>
 
 #include "access/xlog.h"
+#include "access/xlogarchive.h"
 #include "access/xlog_internal.h"
 #include "common/archive.h"
 #include "miscadmin.h"
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 760e3c7ab0..9ecbfe336e 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -54,6 +54,7 @@
 #include "access/htup_details.h"
 #include "access/timeline.h"
 #include "access/transam.h"
+#include "access/xlogarchive.h"
 #include "access/xlog_internal.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 27ded593ab..8e3cfcf83e 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -320,22 +320,4 @@ extern bool InArchiveRecovery;
 extern bool StandbyMode;
 extern char *recoveryRestoreCommand;
 
-/*
- * Prototypes for functions in xlogarchive.c
- */
-extern bool RestoreArchivedFile(char *path, const char *xlogfname,
-								const char *recovername, off_t expectedSize,
-								bool cleanupEnabled);
-extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
-								   bool failOnSignal);
-extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
-extern void XLogArchiveNotify(const char *xlog);
-extern void XLogArchiveNotifySeg(XLogSegNo segno);
-extern void XLogArchiveForceDone(const char *xlog);
-extern bool XLogArchiveCheckDone(const char *xlog);
-extern bool XLogArchiveIsBusy(const char *xlog);
-extern bool XLogArchiveIsReady(const char *xlog);
-extern bool XLogArchiveIsReadyOrDone(const char *xlog);
-extern void XLogArchiveCleanup(const char *xlog);
-
 #endif							/* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
new file mode 100644
index 0000000000..1c67de2ede
--- /dev/null
+++ b/src/include/access/xlogarchive.h
@@ -0,0 +1,35 @@
+/*------------------------------------------------------------------------
+ *
+ * xlogarchive.h
+ *		Prototypes for WAL archives in the backend
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		src/include/access/xlogarchive.h
+ *
+ *------------------------------------------------------------------------
+ */
+
+#ifndef XLOG_ARCHIVE_H
+#define XLOG_ARCHIVE_H
+
+#include "access/xlogdefs.h"
+
+extern bool RestoreArchivedFile(char *path, const char *xlogfname,
+								const char *recovername, off_t expectedSize,
+								bool cleanupEnabled);
+extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
+								   bool failOnSignal);
+extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
+extern void XLogArchiveNotify(const char *xlog);
+extern void XLogArchiveNotifySeg(XLogSegNo segno);
+extern void XLogArchiveForceDone(const char *xlog);
+extern bool XLogArchiveCheckDone(const char *xlog);
+extern bool XLogArchiveIsBusy(const char *xlog);
+extern bool XLogArchiveIsReady(const char *xlog);
+extern bool XLogArchiveIsReadyOrDone(const char *xlog);
+extern void XLogArchiveCleanup(const char *xlog);
+
+#endif							/* XLOG_ARCHIVE_H */

base-commit: 24566b359d095c3800c2a326d88a595722813f58
-- 
2.19.1

#89Michael Paquier
michael@paquier.xyz
In reply to: Alexey Kondratov (#88)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Mon, Mar 30, 2020 at 05:00:01PM +0300, Alexey Kondratov wrote:

What do think about adding following sanity check into xlogarchive.c?

+#ifdef FRONTEND
+#error "This file is not expected to be compiled for frontend code"
+#endif

It would prevent someone from adding typedefs and any other common
definitions into xlogarchive.h in the future, leading to the accidental
inclusion of both xlogarchive.h and fe_archive.h in the same time.

I don't see much the point as this would fail to compile anyway, and
that's not project-style. Note that we have already a clear
separation here between the backend and the frontend code here as
xlogarchive.h is backend-only and fe_archive.h is frontend-only.

0002:

[format comments]

Let us use camel case 'restoreCommand' here as in the header for tidiness.

All this makes sense, and also note the same formatting issue in
parsexlog.c for RestoreArchivedFile(). A run of pgindent was
missing.

I have left 0001 intact, but fixed all these small remarks in the 0002.
Please, find it attached.

Thanks, committed 0001 after fixing the order of the headers. One
patch left.
--
Michael

#90Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#89)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Tue, Mar 31, 2020 at 03:48:21PM +0900, Michael Paquier wrote:

Thanks, committed 0001 after fixing the order of the headers. One
patch left.

And committed now 0002, meaning that we are officially done. Thanks
Alexey for your patience.
--
Michael

#91Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#90)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-04-01 05:19, Michael Paquier wrote:

On Tue, Mar 31, 2020 at 03:48:21PM +0900, Michael Paquier wrote:

Thanks, committed 0001 after fixing the order of the headers. One
patch left.

And committed now 0002, meaning that we are officially done. Thanks
Alexey for your patience.

Thanks for doing that! And to all others who participated.

--
Alexey Kondratov

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

#92Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Michael Paquier (#89)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-03-31 08:48, Michael Paquier wrote:

On Mon, Mar 30, 2020 at 05:00:01PM +0300, Alexey Kondratov wrote:

What do think about adding following sanity check into xlogarchive.c?

+#ifdef FRONTEND
+#error "This file is not expected to be compiled for frontend code"
+#endif

It would prevent someone from adding typedefs and any other common
definitions into xlogarchive.h in the future, leading to the accidental
inclusion of both xlogarchive.h and fe_archive.h in the same time.

I don't see much the point as this would fail to compile anyway, and
that's not project-style. Note that we have already a clear
separation here between the backend and the frontend code here as
xlogarchive.h is backend-only and fe_archive.h is frontend-only.

Why is fe_archive.c in src/common/ and not in src/fe_utils/? It is not
"common" to frontend and backend.

It actually defines functions that clash with functions in the backend,
so this seems doubly wrong.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#93Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Peter Eisentraut (#92)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Hi!

On Sat, Jun 6, 2020 at 8:49 PM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 2020-03-31 08:48, Michael Paquier wrote:

On Mon, Mar 30, 2020 at 05:00:01PM +0300, Alexey Kondratov wrote:

What do think about adding following sanity check into xlogarchive.c?

+#ifdef FRONTEND
+#error "This file is not expected to be compiled for frontend code"
+#endif

It would prevent someone from adding typedefs and any other common
definitions into xlogarchive.h in the future, leading to the accidental
inclusion of both xlogarchive.h and fe_archive.h in the same time.

I don't see much the point as this would fail to compile anyway, and
that's not project-style. Note that we have already a clear
separation here between the backend and the frontend code here as
xlogarchive.h is backend-only and fe_archive.h is frontend-only.

Why is fe_archive.c in src/common/ and not in src/fe_utils/? It is not
"common" to frontend and backend.

Yep, this seems wrong to me. I can propose following file rename.

src/common/fe_archive.c => src/fe_utils/archive.c
include/common/fe_archive.h => include/fe_utils/archive.h

It actually defines functions that clash with functions in the backend,
so this seems doubly wrong.

Let's have frontend version of RestoreArchivedFile() renamed as well.
What about RestoreArchivedFileFrontend()?

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#94Michael Paquier
michael@paquier.xyz
In reply to: Alexander Korotkov (#93)
1 attachment(s)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Sun, Jun 07, 2020 at 10:05:03PM +0300, Alexander Korotkov wrote:

On Sat, Jun 6, 2020 at 8:49 PM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

Why is fe_archive.c in src/common/ and not in src/fe_utils/? It is not
"common" to frontend and backend.

Yep, this seems wrong to me. I can propose following file rename.

src/common/fe_archive.c => src/fe_utils/archive.c
include/common/fe_archive.h => include/fe_utils/archive.h

Is it technically a problem though? fe_archive.c is listed as a
frontend-only file with OBJS_FRONTEND in src/common/Makefile. Anyway,
for this one I would not mind to do the move so please see the
attached, but I am not completely sure either why src/fe_utils/ had
better be chosen than src/common/.

It actually defines functions that clash with functions in the backend,
so this seems doubly wrong.

Let's have frontend version of RestoreArchivedFile() renamed as well.
What about RestoreArchivedFileFrontend()?

-1 from me for the renaming, which was intentional to ease grepping
with the backend counterpart. We have other cases like that, say
palloc(), fsync_fname(), etc.

Perhaps we have more things that are frontend-only in src/common/ that
could be moved to src/fe_utils/? I am looking at restricted_token.c,
fe_memutils.c, logging.c and file_utils.c here, but that would mean
breaking a couple of declarations, something never free for plugin
developers.
--
Michael

Attachments:

0001-Move-frontend-side-archive-APIs-to-src-fe_utils.patchtext/x-diff; charset=us-asciiDownload
From ca75b91498c71b6558bd6befa3ad1510e00cf5fa Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 8 Jun 2020 15:06:02 +0900
Subject: [PATCH] Move frontend-side archive APIs to src/fe_utils/

---
 .../{common/fe_archive.h => fe_utils/archive.h}        |  4 ++--
 src/common/Makefile                                    |  1 -
 src/fe_utils/Makefile                                  |  1 +
 src/{common/fe_archive.c => fe_utils/archive.c}        | 10 +++-------
 src/bin/pg_rewind/parsexlog.c                          |  2 +-
 src/tools/msvc/Mkvcbuild.pm                            |  8 ++++----
 6 files changed, 11 insertions(+), 15 deletions(-)
 rename src/include/{common/fe_archive.h => fe_utils/archive.h} (91%)
 rename src/{common/fe_archive.c => fe_utils/archive.c} (94%)

diff --git a/src/include/common/fe_archive.h b/src/include/fe_utils/archive.h
similarity index 91%
rename from src/include/common/fe_archive.h
rename to src/include/fe_utils/archive.h
index 495b560d24..a6beaf04ea 100644
--- a/src/include/common/fe_archive.h
+++ b/src/include/fe_utils/archive.h
@@ -1,12 +1,12 @@
 /*-------------------------------------------------------------------------
  *
- * fe_archive.h
+ * archive.h
  *	  Routines to access WAL archives 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
+ * src/include/fe_utils/archive.h
  *
  *-------------------------------------------------------------------------
  */
diff --git a/src/common/Makefile b/src/common/Makefile
index d0be882cca..16619e4ba8 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -89,7 +89,6 @@ 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/fe_utils/Makefile b/src/fe_utils/Makefile
index 9eb4417690..dd20663604 100644
--- a/src/fe_utils/Makefile
+++ b/src/fe_utils/Makefile
@@ -20,6 +20,7 @@ include $(top_builddir)/src/Makefile.global
 override CPPFLAGS := -DFRONTEND -I$(libpq_srcdir) $(CPPFLAGS)
 
 OBJS = \
+	archive.o \
 	cancel.o \
 	conditional.o \
 	mbprint.o \
diff --git a/src/common/fe_archive.c b/src/fe_utils/archive.c
similarity index 94%
rename from src/common/fe_archive.c
rename to src/fe_utils/archive.c
index b0d68870db..c4cb213198 100644
--- a/src/common/fe_archive.c
+++ b/src/fe_utils/archive.c
@@ -1,6 +1,6 @@
 /*-------------------------------------------------------------------------
  *
- * fe_archive.c
+ * archive.c
  *	  Routines to access WAL archives from frontend
  *
  * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
@@ -8,15 +8,11 @@
  *
  *
  * IDENTIFICATION
- *	  src/common/fe_archive.c
+ *	  src/fe_utils/archive.c
  *
  *-------------------------------------------------------------------------
  */
 
-#ifndef FRONTEND
-#error "This file is not expected to be compiled for backend code"
-#endif
-
 #include "postgres_fe.h"
 
 #include <unistd.h>
@@ -24,8 +20,8 @@
 
 #include "access/xlog_internal.h"
 #include "common/archive.h"
-#include "common/fe_archive.h"
 #include "common/logging.h"
+#include "fe_utils/archive.h"
 
 
 /*
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index d637f5eb77..bc6f976994 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -19,7 +19,7 @@
 #include "catalog/pg_control.h"
 #include "catalog/storage_xlog.h"
 #include "commands/dbcommands_xlog.h"
-#include "common/fe_archive.h"
+#include "fe_utils/archive.h"
 #include "filemap.h"
 #include "pg_rewind.h"
 
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index c21c94dc1f..82d7d2c0e8 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -138,14 +138,14 @@ sub mkvcbuild
 	}
 
 	our @pgcommonfrontendfiles = (
-		@pgcommonallfiles, qw(fe_archive.c fe_memutils.c
-		  file_utils.c logging.c restricted_token.c));
+		@pgcommonallfiles, qw(fe_memutils.c file_utils.c
+			logging.c restricted_token.c));
 
 	our @pgcommonbkndfiles = @pgcommonallfiles;
 
 	our @pgfeutilsfiles = qw(
-	  cancel.c conditional.c mbprint.c print.c psqlscan.l psqlscan.c
-	  simple_list.c string_utils.c recovery_gen.c);
+	  archive.c cancel.c conditional.c mbprint.c print.c psqlscan.l
+	  psqlscan.c simple_list.c string_utils.c recovery_gen.c);
 
 	$libpgport = $solution->AddProject('libpgport', 'lib', 'misc');
 	$libpgport->AddDefine('FRONTEND');
-- 
2.27.0.rc0

#95Alexey Kondratov
a.kondratov@postgrespro.ru
In reply to: Michael Paquier (#94)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On 2020-06-08 09:14, Michael Paquier wrote:

On Sun, Jun 07, 2020 at 10:05:03PM +0300, Alexander Korotkov wrote:

On Sat, Jun 6, 2020 at 8:49 PM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

Why is fe_archive.c in src/common/ and not in src/fe_utils/? It is
not
"common" to frontend and backend.

Yep, this seems wrong to me. I can propose following file rename.

src/common/fe_archive.c => src/fe_utils/archive.c
include/common/fe_archive.h => include/fe_utils/archive.h

Is it technically a problem though? fe_archive.c is listed as a
frontend-only file with OBJS_FRONTEND in src/common/Makefile. Anyway,
for this one I would not mind to do the move so please see the
attached, but I am not completely sure either why src/fe_utils/ had
better be chosen than src/common/.

Perhaps we have more things that are frontend-only in src/common/ that
could be moved to src/fe_utils/? I am looking at restricted_token.c,
fe_memutils.c, logging.c and file_utils.c here, but that would mean
breaking a couple of declarations, something never free for plugin
developers.

I noticed this before in the thread [1]/messages/by-id/784fa7dc-414b-9dc9-daae-138033db298c@postgrespro.ru, but it has been left unnoticed
I guess:

"I just went through the both patches and realized that I cannot get
into
semantics of splitting frontend code between common and fe_utils. This
applies only to 0002, where we introduce fe_archive.c. Should it be
placed into fe_utils alongside of the recent recovery_gen.c also used by
pg_rewind? This is a frontend-only code intended to be used by frontend
applications, so fe_utils feels like the right place, doesn't it? Just
tried to do so and everything went fine, so it seems that there is no
obstacles from the build system.

BTW, most of 'common' is a really common code with only four exceptions
like logging.c, which is frontend-only. Is it there for historical
reasons only or something else?"

Personally, I would prefer that everything in the 'common' was actually
common. I also do not sure about moving an older code, because of
possible backward compatibility breakage, but doing so for a newer one
seems to be a good idea.

It actually defines functions that clash with functions in the
backend,
so this seems doubly wrong.

Let's have frontend version of RestoreArchivedFile() renamed as well.
What about RestoreArchivedFileFrontend()?

-1 from me for the renaming, which was intentional to ease grepping
with the backend counterpart. We have other cases like that, say
palloc(), fsync_fname(), etc.

Do not like it either. Having the same naming and a guarantee that this
code is always compiled separately looks more convenient for me.

[1]: /messages/by-id/784fa7dc-414b-9dc9-daae-138033db298c@postgrespro.ru
/messages/by-id/784fa7dc-414b-9dc9-daae-138033db298c@postgrespro.ru

Regards
--
Alexey Kondratov

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

#96Michael Paquier
michael@paquier.xyz
In reply to: Alexey Kondratov (#95)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Mon, Jun 08, 2020 at 02:16:32PM +0300, Alexey Kondratov wrote:

BTW, most of 'common' is a really common code with only four exceptions
like logging.c, which is frontend-only. Is it there for historical
reasons only or something else?"

Personally, I would prefer that everything in the 'common' was actually
common. I also do not sure about moving an older code, because of possible
backward compatibility breakage, but doing so for a newer one seems to be a
good idea.

src/fe_utils/ has been created much after src/common/ (588d963 vs
8396447). I got to wonder if we should add a note to src/common/'s
Makefile to not add more stuff to OBJS_FRONTEND and just tell to use
src/fe_utils/. Moving things from common/ to fe_utils/ would mean
breakages, which may be hard to justify just for the sake of being
clean and more consistent, and src/common/ APIs are usually quite
popular with external plugins.
--
Michael

#97Michael Paquier
michael@paquier.xyz
In reply to: Alexey Kondratov (#95)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Mon, Jun 08, 2020 at 02:16:32PM +0300, Alexey Kondratov wrote:

Do not like it either. Having the same naming and a guarantee that this code
is always compiled separately looks more convenient for me.

[1] /messages/by-id/784fa7dc-414b-9dc9-daae-138033db298c@postgrespro.ru

Okay. After sleeping on it, it looks like would be better to move
this new fe_archive.c to src/fe_utils/. I'll try to do that tomorrow,
and added an open item so as we don't forget about it.
--
Michael

#98Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#97)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Wed, Jun 10, 2020 at 01:23:37PM +0900, Michael Paquier wrote:

Okay. After sleeping on it, it looks like would be better to move
this new fe_archive.c to src/fe_utils/. I'll try to do that tomorrow,
and added an open item so as we don't forget about it.

Applied this part now as of a3b2bf1.
--
Michael

#99Stephen Frost
sfrost@snowman.net
In reply to: Michael Paquier (#98)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

Greetings,

* Michael Paquier (michael@paquier.xyz) wrote:

On Wed, Jun 10, 2020 at 01:23:37PM +0900, Michael Paquier wrote:

Okay. After sleeping on it, it looks like would be better to move
this new fe_archive.c to src/fe_utils/. I'll try to do that tomorrow,
and added an open item so as we don't forget about it.

Applied this part now as of a3b2bf1.

Uh, doesn't this pull these functions out of libpgcommon, where they
might have been being used by utilities outside of our client tools?

Thanks,

Stephen

#100Michael Paquier
michael@paquier.xyz
In reply to: Stephen Frost (#99)
Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line

On Thu, Jun 11, 2020 at 10:16:50AM -0400, Stephen Frost wrote:

Uh, doesn't this pull these functions out of libpgcommon, where they
might have been being used by utilities outside of our client tools?

This stuff is new to 13, and we are still in beta so it is fine in my
opinion to still change things how we think is best. Suggesting to
change things once we are officially GA would not be possible.
--
Michael