From 63c0567b47fb59617c76c7aa989c89668bdd46be Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Mon, 18 Oct 2021 22:56:58 +0000
Subject: [PATCH v2 1/1] Replace archive_command with a hook.

---
 src/backend/access/transam/xlog.c                |   9 +-
 src/backend/postmaster/pgarch.c                  | 136 +++------------
 src/backend/utils/misc/guc.c                     |  22 +--
 src/backend/utils/misc/postgresql.conf.sample    |   4 -
 src/include/access/xlog.h                        |   2 -
 src/include/postmaster/pgarch.h                  |   3 +
 src/test/modules/Makefile                        |   1 +
 src/test/modules/shell_archive/Makefile          |  15 ++
 src/test/modules/shell_archive/shell_archive.c   | 207 +++++++++++++++++++++++
 src/test/perl/PostgresNode.pm                    |   3 +-
 src/test/recovery/t/020_archive_status.pl        |   6 +-
 src/test/recovery/t/025_stuck_on_old_timeline.pl |   2 +-
 12 files changed, 261 insertions(+), 149 deletions(-)
 create mode 100644 src/test/modules/shell_archive/Makefile
 create mode 100644 src/test/modules/shell_archive/shell_archive.c

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 62862255fc..7a8b8eff20 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -94,7 +94,6 @@ int			wal_keep_size_mb = 0;
 int			XLOGbuffers = -1;
 int			XLogArchiveTimeout = 0;
 int			XLogArchiveMode = ARCHIVE_MODE_OFF;
-char	   *XLogArchiveCommand = NULL;
 bool		EnableHotStandby = false;
 bool		fullPageWrites = true;
 bool		wal_log_hints = false;
@@ -7898,7 +7897,7 @@ StartupXLOG(void)
 	 * assign it a unique new ID.  Even if we ran to the end, modifying the
 	 * current last segment is problematic because it may result in trying to
 	 * overwrite an already-archived copy of that segment, and we encourage
-	 * DBAs to make their archive_commands reject that.  We can dodge the
+	 * DBAs to make their archive_hooks reject that.  We can dodge the
 	 * problem by making the new active segment have a new timeline ID.
 	 *
 	 * In a normal crash recovery, we can just extend the timeline we were in.
@@ -8777,7 +8776,7 @@ ShutdownXLOG(int code, Datum arg)
 		 * process one more time at the end of shutdown). The checkpoint
 		 * record will go to the next XLOG file and won't be archived (yet).
 		 */
-		if (XLogArchivingActive() && XLogArchiveCommandSet())
+		if (XLogArchivingActive() && archive_hook != NULL)
 			RequestXLogSwitch(false);
 
 		CreateCheckPoint(CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_IMMEDIATE);
@@ -11791,7 +11790,7 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p)
 	 * property of the WAL files ensures any earlier WAL files are safely
 	 * archived as well.
 	 *
-	 * We wait forever, since archive_command is supposed to work and we
+	 * We wait forever, since archive_hook is supposed to work and we
 	 * assume the admin wanted his backup to work completely. If you don't
 	 * wish to wait, then either waitforarchive should be passed in as false,
 	 * or you can set statement_timeout.  Also, some notices are issued to
@@ -11836,7 +11835,7 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p)
 				ereport(WARNING,
 						(errmsg("still waiting for all required WAL segments to be archived (%d seconds elapsed)",
 								waits),
-						 errhint("Check that your archive_command is executing properly.  "
+						 errhint("Check that your archive_hook is executing properly.  "
 								 "You can safely cancel this backup, "
 								 "but the database backup will not be usable without all the WAL segments.")));
 			}
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 74a7d7c4d0..4041bcf9d5 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -36,7 +36,6 @@
 #include "access/xlog.h"
 #include "access/xlog_internal.h"
 #include "libpq/pqsignal.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/interrupt.h"
 #include "postmaster/pgarch.h"
@@ -78,6 +77,7 @@ typedef struct PgArchData
 	int			pgprocno;		/* pgprocno of archiver process */
 } PgArchData;
 
+archive_hook_type archive_hook = NULL;
 
 /* ----------
  * Local data
@@ -358,11 +358,11 @@ pgarch_ArchiverCopyLoop(void)
 			 */
 			HandlePgArchInterrupts();
 
-			/* can't do anything if no command ... */
-			if (!XLogArchiveCommandSet())
+			/* can't do anything if no hook ... */
+			if (archive_hook == NULL)
 			{
 				ereport(WARNING,
-						(errmsg("archive_mode enabled, yet archive_command is not set")));
+						(errmsg("archive_mode enabled, yet archive_hook is not set")));
 				return;
 			}
 
@@ -443,136 +443,48 @@ pgarch_ArchiverCopyLoop(void)
 /*
  * pgarch_archiveXlog
  *
- * Invokes system(3) to copy one archive file to wherever it should go
+ * Invokes archive_hook to copy one archive file to wherever it should go
  *
  * Returns true if successful
  */
 static bool
 pgarch_archiveXlog(char *xlog)
 {
-	char		xlogarchcmd[MAXPGPATH];
 	char		pathname[MAXPGPATH];
 	char		activitymsg[MAXFNAMELEN + 16];
-	char	   *dp;
-	char	   *endp;
-	const char *sp;
-	int			rc;
+	bool		success = false;
 
 	snprintf(pathname, MAXPGPATH, XLOGDIR "/%s", xlog);
 
-	/*
-	 * construct the command to be executed
-	 */
-	dp = xlogarchcmd;
-	endp = xlogarchcmd + MAXPGPATH - 1;
-	*endp = '\0';
-
-	for (sp = XLogArchiveCommand; *sp; sp++)
-	{
-		if (*sp == '%')
-		{
-			switch (sp[1])
-			{
-				case 'p':
-					/* %p: relative path of source file */
-					sp++;
-					strlcpy(dp, pathname, endp - dp);
-					make_native_path(dp);
-					dp += strlen(dp);
-					break;
-				case 'f':
-					/* %f: filename of source file */
-					sp++;
-					strlcpy(dp, xlog, 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';
-
 	ereport(DEBUG3,
-			(errmsg_internal("executing archive command \"%s\"",
-							 xlogarchcmd)));
+			(errmsg_internal("executing archive_hook for \"%s\"",
+							 xlog)));
 
 	/* Report archive activity in PS display */
 	snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
 	set_ps_display(activitymsg);
 
-	rc = system(xlogarchcmd);
-	if (rc != 0)
-	{
-		/*
-		 * If either the shell itself, or a called command, died on a signal,
-		 * abort the archiver.  We do this because system() ignores SIGINT and
-		 * SIGQUIT while waiting; so a signal is very likely something that
-		 * should have interrupted us too.  Also die if the shell got a hard
-		 * "command not found" type of error.  If we overreact it's no big
-		 * deal, the postmaster will just start the archiver again.
-		 */
-		int			lev = wait_result_is_any_signal(rc, true) ? FATAL : LOG;
-
-		if (WIFEXITED(rc))
-		{
-			ereport(lev,
-					(errmsg("archive command failed with exit code %d",
-							WEXITSTATUS(rc)),
-					 errdetail("The failed archive command was: %s",
-							   xlogarchcmd)));
-		}
-		else if (WIFSIGNALED(rc))
-		{
-#if defined(WIN32)
-			ereport(lev,
-					(errmsg("archive command was terminated by exception 0x%X",
-							WTERMSIG(rc)),
-					 errhint("See C include file \"ntstatus.h\" for a description of the hexadecimal value."),
-					 errdetail("The failed archive command was: %s",
-							   xlogarchcmd)));
-#else
-			ereport(lev,
-					(errmsg("archive command was terminated by signal %d: %s",
-							WTERMSIG(rc), pg_strsignal(WTERMSIG(rc))),
-					 errdetail("The failed archive command was: %s",
-							   xlogarchcmd)));
-#endif
-		}
-		else
-		{
-			ereport(lev,
-					(errmsg("archive command exited with unrecognized status %d",
-							rc),
-					 errdetail("The failed archive command was: %s",
-							   xlogarchcmd)));
-		}
+	/* Call the archiving hook */
+	(*archive_hook) (xlog, pathname, &success);
 
+	/* Report whether the hook succeeded and update activity in PS display */
+	if (success)
+	{
+		ereport(DEBUG1,
+				(errmsg("archived write-ahead log file \"%s\"",
+						xlog)));
+		snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
+	}
+	else
+	{
+		ereport(LOG,
+				(errmsg("failed to archive write-ahead log file \"%s\"",
+						xlog)));
 		snprintf(activitymsg, sizeof(activitymsg), "failed on %s", xlog);
-		set_ps_display(activitymsg);
-
-		return false;
 	}
-	elog(DEBUG1, "archived write-ahead log file \"%s\"", xlog);
-
-	snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
 	set_ps_display(activitymsg);
 
-	return true;
+	return success;
 }
 
 /*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a8450..a1744a94bc 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -192,7 +192,6 @@ static bool check_canonical_path(char **newval, void **extra, GucSource source);
 static bool check_timezone_abbreviations(char **newval, void **extra, GucSource source);
 static void assign_timezone_abbreviations(const char *newval, void *extra);
 static void pg_timezone_abbrev_initialize(void);
-static const char *show_archive_command(void);
 static void assign_tcp_keepalives_idle(int newval, void *extra);
 static void assign_tcp_keepalives_interval(int newval, void *extra);
 static void assign_tcp_keepalives_count(int newval, void *extra);
@@ -3848,16 +3847,6 @@ static struct config_real ConfigureNamesReal[] =
 
 static struct config_string ConfigureNamesString[] =
 {
-	{
-		{"archive_command", PGC_SIGHUP, WAL_ARCHIVING,
-			gettext_noop("Sets the shell command that will be called to archive a WAL file."),
-			NULL
-		},
-		&XLogArchiveCommand,
-		"",
-		NULL, NULL, show_archive_command
-	},
-
 	{
 		{"restore_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
 			gettext_noop("Sets the shell command that will be called to retrieve an archived WAL file."),
@@ -4802,7 +4791,7 @@ static struct config_enum ConfigureNamesEnum[] =
 
 	{
 		{"archive_mode", PGC_POSTMASTER, WAL_ARCHIVING,
-			gettext_noop("Allows archiving of WAL files using archive_command."),
+			gettext_noop("Allows archiving of WAL files."),
 			NULL
 		},
 		&XLogArchiveMode,
@@ -11914,15 +11903,6 @@ pg_timezone_abbrev_initialize(void)
 					PGC_POSTMASTER, PGC_S_DYNAMIC_DEFAULT);
 }
 
-static const char *
-show_archive_command(void)
-{
-	if (XLogArchivingActive())
-		return XLogArchiveCommand;
-	else
-		return "(disabled)";
-}
-
 static void
 assign_tcp_keepalives_idle(int newval, void *extra)
 {
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 3fe9a53cb3..a466eedf9b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -245,10 +245,6 @@
 
 #archive_mode = off		# enables archiving; off, on, or always
 				# (change requires restart)
-#archive_command = ''		# command to use to archive a logfile segment
-				# placeholders: %p = path of file to archive
-				#               %f = file name only
-				# e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'
 #archive_timeout = 0		# force a logfile segment switch after this
 				# number of seconds; 0 disables
 
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 5e2c94a05f..3724d8cd83 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -70,7 +70,6 @@ extern int	max_slot_wal_keep_size_mb;
 extern int	XLOGbuffers;
 extern int	XLogArchiveTimeout;
 extern int	wal_retrieve_retry_interval;
-extern char *XLogArchiveCommand;
 extern bool EnableHotStandby;
 extern bool fullPageWrites;
 extern bool wal_log_hints;
@@ -157,7 +156,6 @@ extern PGDLLIMPORT int wal_level;
 /* Is WAL archiving enabled always (even during recovery)? */
 #define XLogArchivingAlways() \
 	(AssertMacro(XLogArchiveMode == ARCHIVE_MODE_OFF || wal_level >= WAL_LEVEL_REPLICA), XLogArchiveMode == ARCHIVE_MODE_ALWAYS)
-#define XLogArchiveCommandSet() (XLogArchiveCommand[0] != '\0')
 
 /*
  * Is WAL-logging necessary for archival or log-shipping, or can we skip
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index 1e47a143e1..753628ed7f 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -32,4 +32,7 @@ extern bool PgArchCanRestart(void);
 extern void PgArchiverMain(void) pg_attribute_noreturn();
 extern void PgArchWakeup(void);
 
+typedef void (*archive_hook_type) (const char *file, const char *path, bool *success);
+extern PGDLLIMPORT archive_hook_type archive_hook;
+
 #endif							/* _PGARCH_H */
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index dffc79b2d9..e924c9c7ac 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -12,6 +12,7 @@ SUBDIRS = \
 		  dummy_seclabel \
 		  libpq_pipeline \
 		  plsample \
+		  shell_archive \
 		  snapshot_too_old \
 		  spgist_name_ops \
 		  test_bloomfilter \
diff --git a/src/test/modules/shell_archive/Makefile b/src/test/modules/shell_archive/Makefile
new file mode 100644
index 0000000000..a48d227c9f
--- /dev/null
+++ b/src/test/modules/shell_archive/Makefile
@@ -0,0 +1,15 @@
+# src/test/modules/shell_archive/Makefile
+
+MODULES = shell_archive
+PGFILEDESC = "shell_archive - archive module example"
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/shell_archive
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/shell_archive/shell_archive.c b/src/test/modules/shell_archive/shell_archive.c
new file mode 100644
index 0000000000..ba8c63b5f4
--- /dev/null
+++ b/src/test/modules/shell_archive/shell_archive.c
@@ -0,0 +1,207 @@
+/* -------------------------------------------------------------------------
+ *
+ * shell_archive.c
+ *		Sample archive module code that uses a user-specified shell command
+ *		to copy write-ahead log files.  This strategy has many shortcomings
+ *		and should ideally not be used in production settings.  However,
+ *		prior to v15, the functionality provided in this module was the only
+ *		archiving mechanism available in core, so it is provided here for
+ *		backward compatibility.
+ *
+ * Copyright (c) 2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/shell_archive/shell_archive.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xlog.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "postmaster/pgarch.h"
+#include "utils/guc.h"
+
+PG_MODULE_MAGIC;
+
+void		_PG_init(void);
+void		_PG_fini(void);
+
+static const char *show_archive_command(void);
+static void shell_archive_hook(const char *file, const char *path,
+							   bool *success);
+
+static char *ArchiveCommand = NULL;
+
+void
+_PG_init(void)
+{
+	if (!process_shared_preload_libraries_in_progress)
+		return;
+
+	DefineCustomStringVariable("shell_archive.archive_command",
+							   "Sets the shell command that will be called to archive a WAL file.",
+							   NULL,
+							   &ArchiveCommand,
+							   "",
+							   PGC_SIGHUP,
+							   0,
+							   NULL, NULL, show_archive_command);
+
+	EmitWarningsOnPlaceholders("shell_archive");
+
+	if (archive_hook != NULL)
+		ereport(ERROR,
+				(errmsg("archive_hook already set"),
+				 errdetail("Only one archive_hook can be loaded via "
+						   "shared_preload_libraries.")));
+
+	archive_hook = shell_archive_hook;
+}
+
+void
+_PG_fini(void)
+{
+	archive_hook = NULL;
+}
+
+static const char *
+show_archive_command(void)
+{
+	if (XLogArchivingActive())
+		return ArchiveCommand;
+	else
+		return "(disabled)";
+}
+
+/*
+ * shell_archive_hook
+ *
+ * Invokes system(3) to copy one archive file to wherever it should go
+ */
+static void
+shell_archive_hook(const char *file, const char *path, bool *success)
+{
+	char		xlogarchcmd[MAXPGPATH];
+	char	   *dp;
+	char	   *endp;
+	const char *sp;
+	int			rc;
+
+	Assert(file != NULL);
+	Assert(path != NULL);
+	Assert(success != NULL);
+
+	if (ArchiveCommand == NULL || ArchiveCommand[0] == '\0')
+	{
+		ereport(WARNING,
+				(errmsg("\"shell_archive.archive_command\" is not set")));
+		*success = false;
+		return;
+	}
+
+	/*
+	 * construct the command to be executed
+	 */
+	dp = xlogarchcmd;
+	endp = xlogarchcmd + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (sp = ArchiveCommand; *sp; sp++)
+	{
+		if (*sp == '%')
+		{
+			switch (sp[1])
+			{
+				case 'p':
+					/* %p: relative path of source file */
+					sp++;
+					strlcpy(dp, path, endp - dp);
+					make_native_path(dp);
+					dp += strlen(dp);
+					break;
+				case 'f':
+					/* %f: filename of source file */
+					sp++;
+					strlcpy(dp, file, 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';
+
+	ereport(DEBUG3,
+			(errmsg_internal("executing archive command \"%s\"",
+							 xlogarchcmd)));
+
+	rc = system(xlogarchcmd);
+	if (rc != 0)
+	{
+		/*
+		 * If either the shell itself, or a called command, died on a signal,
+		 * abort the archiver.  We do this because system() ignores SIGINT and
+		 * SIGQUIT while waiting; so a signal is very likely something that
+		 * should have interrupted us too.  Also die if the shell got a hard
+		 * "command not found" type of error.  If we overreact it's no big
+		 * deal, the postmaster will just start the archiver again.
+		 */
+		int			lev = wait_result_is_any_signal(rc, true) ? FATAL : LOG;
+
+		if (WIFEXITED(rc))
+		{
+			ereport(lev,
+					(errmsg("archive command failed with exit code %d",
+							WEXITSTATUS(rc)),
+					 errdetail("The failed archive command was: %s",
+							   xlogarchcmd)));
+		}
+		else if (WIFSIGNALED(rc))
+		{
+#if defined(WIN32)
+			ereport(lev,
+					(errmsg("archive command was terminated by exception 0x%X",
+							WTERMSIG(rc)),
+					 errhint("See C include file \"ntstatus.h\" for a description of the hexadecimal value."),
+					 errdetail("The failed archive command was: %s",
+							   xlogarchcmd)));
+#else
+			ereport(lev,
+					(errmsg("archive command was terminated by signal %d: %s",
+							WTERMSIG(rc), pg_strsignal(WTERMSIG(rc))),
+					 errdetail("The failed archive command was: %s",
+							   xlogarchcmd)));
+#endif
+		}
+		else
+		{
+			ereport(lev,
+					(errmsg("archive command exited with unrecognized status %d",
+							rc),
+					 errdetail("The failed archive command was: %s",
+							   xlogarchcmd)));
+		}
+
+		*success = false;
+		return;
+	}
+
+	*success = true;
+}
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index ba80baf091..7d3ed412d0 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -1107,7 +1107,8 @@ sub enable_archiving
 	$self->append_conf(
 		'postgresql.conf', qq(
 archive_mode = on
-archive_command = '$copy_command'
+shared_preload_libraries = 'shell_archive'
+shell_archive.archive_command = '$copy_command'
 ));
 	return;
 }
diff --git a/src/test/recovery/t/020_archive_status.pl b/src/test/recovery/t/020_archive_status.pl
index cea65735a3..4dfeae3a66 100644
--- a/src/test/recovery/t/020_archive_status.pl
+++ b/src/test/recovery/t/020_archive_status.pl
@@ -32,7 +32,7 @@ my $incorrect_command =
   : qq{cp "%p_does_not_exist" "%f_does_not_exist"};
 $primary->safe_psql(
 	'postgres', qq{
-    ALTER SYSTEM SET archive_command TO '$incorrect_command';
+    ALTER SYSTEM SET shell_archive.archive_command TO '$incorrect_command';
     SELECT pg_reload_conf();
 });
 
@@ -90,7 +90,7 @@ ok( -f "$primary_data/$segment_path_1_ready",
 # Allow WAL archiving again and wait for a success.
 $primary->safe_psql(
 	'postgres', q{
-	ALTER SYSTEM RESET archive_command;
+	ALTER SYSTEM RESET shell_archive.archive_command;
 	SELECT pg_reload_conf();
 });
 
@@ -212,7 +212,7 @@ ok( -f "$standby2_data/$segment_path_1_ready",
 # Allow WAL archiving again, and wait for the segments to be archived.
 $standby2->safe_psql(
 	'postgres', q{
-	ALTER SYSTEM RESET archive_command;
+	ALTER SYSTEM RESET shell_archive.archive_command;
 	SELECT pg_reload_conf();
 });
 $standby2->poll_query_until('postgres',
diff --git a/src/test/recovery/t/025_stuck_on_old_timeline.pl b/src/test/recovery/t/025_stuck_on_old_timeline.pl
index 00ee9fcaed..819995bcac 100644
--- a/src/test/recovery/t/025_stuck_on_old_timeline.pl
+++ b/src/test/recovery/t/025_stuck_on_old_timeline.pl
@@ -34,7 +34,7 @@ my $archivedir_primary = $node_primary->archive_dir;
 $archivedir_primary =~ s!\\!/!g if $TestLib::windows_os;
 $node_primary->append_conf(
 	'postgresql.conf', qq(
-archive_command = '"$perlbin" "$FindBin::RealBin/cp_history_files" "%p" "$archivedir_primary/%f"'
+shell_archive.archive_command = '"$perlbin" "$FindBin::RealBin/cp_history_files" "%p" "$archivedir_primary/%f"'
 wal_keep_size=128MB
 ));
 # Make sure that Msys perl doesn't complain about difficulty in setting locale
-- 
2.16.6

