From 0d6cb5561aedd2c355cb3a5f04804824b5ca7e55 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Sat, 23 Oct 2021 21:23:22 +0000
Subject: [PATCH v5 1/2] Introduce archive module framework.

---
 src/backend/access/transam/xlog.c      |   2 +-
 src/backend/postmaster/Makefile        |   1 +
 src/backend/postmaster/pgarch.c        | 135 +++--------------------------
 src/backend/postmaster/postmaster.c    |  10 +++
 src/backend/postmaster/shell_archive.c | 154 +++++++++++++++++++++++++++++++++
 src/backend/utils/fmgr/dfmgr.c         |  70 +++++++++++++--
 src/backend/utils/init/miscinit.c      |  32 +++++++
 src/backend/utils/misc/guc.c           |  41 ++++++++-
 src/include/access/xlog.h              |   6 +-
 src/include/miscadmin.h                |   2 +
 src/include/postmaster/pgarch.h        |  38 ++++++++
 11 files changed, 359 insertions(+), 132 deletions(-)
 create mode 100644 src/backend/postmaster/shell_archive.c

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 62862255fc..b0bb3d633e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -8777,7 +8777,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() && XLogArchivingConfigured())
 			RequestXLogSwitch(false);
 
 		CreateCheckPoint(CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_IMMEDIATE);
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 787c6a2c3b..dbbeac5a82 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -23,6 +23,7 @@ OBJS = \
 	pgarch.o \
 	pgstat.o \
 	postmaster.o \
+	shell_archive.o \
 	startup.o \
 	syslogger.o \
 	walwriter.o
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 74a7d7c4d0..27deedcd60 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -25,18 +25,12 @@
  */
 #include "postgres.h"
 
-#include <fcntl.h>
-#include <signal.h>
-#include <time.h>
 #include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/wait.h>
 #include <unistd.h>
 
 #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 +72,9 @@ typedef struct PgArchData
 	int			pgprocno;		/* pgprocno of archiver process */
 } PgArchData;
 
+ArchiveModuleCallbacks ArchiveContext;
+char *XLogArchiveLibrary = NULL;
+
 
 /* ----------
  * Local data
@@ -358,11 +355,11 @@ pgarch_ArchiverCopyLoop(void)
 			 */
 			HandlePgArchInterrupts();
 
-			/* can't do anything if no command ... */
-			if (!XLogArchiveCommandSet())
+			/* can't do anything if not configured ... */
+			if (!XLogArchivingConfigured())
 			{
 				ereport(WARNING,
-						(errmsg("archive_mode enabled, yet archive_command is not set")));
+						(errmsg("archive_mode enabled, yet archiving is not configured")));
 				return;
 			}
 
@@ -443,136 +440,32 @@ pgarch_ArchiverCopyLoop(void)
 /*
  * pgarch_archiveXlog
  *
- * Invokes system(3) to copy one archive file to wherever it should go
+ * Invokes archive_file_cb 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;
-
-	snprintf(pathname, MAXPGPATH, XLOGDIR "/%s", xlog);
+	bool		ret;
 
-	/*
-	 * 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';
+	Assert(ArchiveContext.archive_file_cb != NULL);
 
-	ereport(DEBUG3,
-			(errmsg_internal("executing archive command \"%s\"",
-							 xlogarchcmd)));
+	snprintf(pathname, MAXPGPATH, XLOGDIR "/%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)));
-		}
-
+	if ((ret = (*ArchiveContext.archive_file_cb) (xlog, pathname)))
+		snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
+	else
 		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 ret;
 }
 
 /*
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index e2a76ba055..8452545946 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1022,8 +1022,13 @@ PostmasterMain(int argc, char *argv[])
 
 	/*
 	 * process any libraries that should be preloaded at postmaster start
+	 *
+	 * NB: It is important to process shared_preload_libraries before
+	 * archive_library because of assumptions made by the library loading
+	 * code.
 	 */
 	process_shared_preload_libraries();
+	process_archive_library();
 
 	/*
 	 * Initialize SSL library, if specified.
@@ -5009,8 +5014,13 @@ SubPostmasterMain(int argc, char *argv[])
 	 * exec'd this process, those libraries didn't come along with us; but we
 	 * should load them into all child processes to be consistent with the
 	 * non-EXEC_BACKEND behavior.
+	 *
+	 * NB: It is important to process shared_preload_libraries before
+	 * archive_library because of assumptions made by the library loading
+	 * code.
 	 */
 	process_shared_preload_libraries();
+	process_archive_library();
 
 	/* Run backend or appropriate child */
 	if (strcmp(argv[1], "--forkbackend") == 0)
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
new file mode 100644
index 0000000000..c0f3f1dca2
--- /dev/null
+++ b/src/backend/postmaster/shell_archive.c
@@ -0,0 +1,154 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.c
+ *
+ * This archiving function uses a user-specified shell command (the
+ * archive_command GUC) to copy write-ahead log files.  It is used as the
+ * default, but other modules may define their own custom archiving logic.
+ *
+ * Copyright (c) 2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/postmaster/shell_archive.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <sys/wait.h>
+
+#include "access/xlog.h"
+#include "postmaster/pgarch.h"
+
+static bool shell_archive_configured(void);
+static bool shell_archive_file(const char *file, const char *path);
+
+void
+shell_archive_init(ArchiveModuleCallbacks *cb)
+{
+	cb->check_configured_cb = shell_archive_configured;
+	cb->archive_file_cb = shell_archive_file;
+}
+
+static bool
+shell_archive_configured(void)
+{
+	return XLogArchiveCommand[0] != '\0';
+}
+
+static bool
+shell_archive_file(const char *file, const char *path)
+{
+	char		xlogarchcmd[MAXPGPATH];
+	char	   *dp;
+	char	   *endp;
+	const char *sp;
+	int			rc;
+
+	Assert(file != NULL);
+	Assert(path != NULL);
+
+	/*
+	 * 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, 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)));
+		}
+
+		return false;
+	}
+
+	elog(DEBUG1, "archived write-ahead log file \"%s\"", file);
+	return true;
+}
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index 96fd9d2268..a755c215aa 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -33,6 +33,7 @@
 #include "fmgr.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
+#include "postmaster/pgarch.h"
 #include "storage/shmem.h"
 #include "utils/hsearch.h"
 
@@ -85,6 +86,7 @@ static char *expand_dynamic_library_name(const char *name);
 static void check_restricted_library_name(const char *name);
 static char *substitute_libpath_macro(const char *name);
 static char *find_in_dynamic_libpath(const char *basename);
+static void init_library(const char *libname, void *handle);
 
 /* Magic structure that module needs to match to be accepted */
 static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
@@ -187,7 +189,6 @@ internal_load_library(const char *libname)
 	PGModuleMagicFunction magic_func;
 	char	   *load_error;
 	struct stat stat_buf;
-	PG_init_t	PG_init;
 
 	/*
 	 * Scan the list of loaded FILES to see if the file has been loaded.
@@ -281,12 +282,7 @@ internal_load_library(const char *libname)
 					 errhint("Extension libraries are required to use the PG_MODULE_MAGIC macro.")));
 		}
 
-		/*
-		 * If the library has a _PG_init() function, call it.
-		 */
-		PG_init = (PG_init_t) dlsym(file_scanner->handle, "_PG_init");
-		if (PG_init)
-			(*PG_init) ();
+		init_library(libname, file_scanner->handle);
 
 		/* OK to link it into list */
 		if (file_list == NULL)
@@ -295,10 +291,70 @@ internal_load_library(const char *libname)
 			file_tail->next = file_scanner;
 		file_tail = file_scanner;
 	}
+	else if (process_archive_library_in_progress)
+	{
+		/*
+		 * If we are loading an archive library, we initialize the library
+		 * even if we previously loaded it.  This allows users to use
+		 * archive libraries for multiple reasons (e.g., the same library
+		 * can be specified in shared_preload_libraries and
+		 * archive_library).
+		 *
+		 * NB: This assumes that we load archive_library after loading
+		 * shared_preload_libraries.
+		 */
+		init_library(libname, file_scanner->handle);
+	}
 
 	return file_scanner->handle;
 }
 
+/*
+ * init_library
+ *
+ * If we are loading an archive library, this function calls the library's
+ * _PG_archive_module_init() function and ensures the necessary callbacks are
+ * registered.  Otherwise, this function calls the library's _PG_init() function
+ * if it exists.
+ */
+static void
+init_library(const char *libname, void *handle)
+{
+	if (process_archive_library_in_progress)
+	{
+		ArchiveModuleInit archive_init;
+
+		ArchiveContext.check_configured_cb = NULL;
+		ArchiveContext.archive_file_cb = NULL;
+
+		archive_init = (ArchiveModuleInit) dlsym(handle, "_PG_archive_module_init");
+		if (archive_init)
+			(*archive_init) (&ArchiveContext);
+		else
+			ereport(ERROR,
+					(errmsg("incompatible archive library \"%s\"", libname),
+					 errhint("Archive modules have to declare the _PG_archive_module_init symbol.")));
+
+		if (ArchiveContext.check_configured_cb == NULL)
+			ereport(ERROR,
+					(errmsg("archive modules have to register a check callback")));
+		if (ArchiveContext.archive_file_cb == NULL)
+			ereport(ERROR,
+					(errmsg("archive modules have to register an archive callback")));
+	}
+	else
+	{
+		PG_init_t PG_init;
+
+		/*
+		 * If the library has a _PG_init() function, call it.
+		 */
+		PG_init = (PG_init_t) dlsym(handle, "_PG_init");
+		if (PG_init)
+			(*PG_init) ();
+	}
+}
+
 /*
  * Report a suitable error for an incompatible magic block.
  */
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 88801374b5..4b66094ce9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -29,6 +29,7 @@
 #include <utime.h>
 
 #include "access/htup_details.h"
+#include "access/xlog.h"
 #include "catalog/pg_authid.h"
 #include "common/file_perm.h"
 #include "libpq/libpq.h"
@@ -38,6 +39,7 @@
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "postmaster/interrupt.h"
+#include "postmaster/pgarch.h"
 #include "postmaster/postmaster.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
@@ -1614,6 +1616,9 @@ char	   *local_preload_libraries_string = NULL;
 /* Flag telling that we are loading shared_preload_libraries */
 bool		process_shared_preload_libraries_in_progress = false;
 
+/* Flag telling that we are loading archive_library */
+bool		process_archive_library_in_progress = false;
+
 /*
  * load the shared libraries listed in 'libraries'
  *
@@ -1696,6 +1701,33 @@ process_session_preload_libraries(void)
 				   true);
 }
 
+/*
+ * process the archive library
+ */
+void
+process_archive_library(void)
+{
+	process_archive_library_in_progress = true;
+
+	if (XLogArchiveLibrary && XLogArchiveLibrary[0] != '\0')
+	{
+		load_file(XLogArchiveLibrary, false);
+		ereport(DEBUG1,
+				(errmsg_internal("loaded archive library \"%s\"",
+								 XLogArchiveLibrary)));
+	}
+	else
+	{
+		/*
+		 * If no archive library was specified, fall back to archiving via
+		 * shell (i.e., archive_command).
+		 */
+		shell_archive_init(&ArchiveContext);
+	}
+
+	process_archive_library_in_progress = false;
+}
+
 void
 pg_bindtextdomain(const char *domain)
 {
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a8450..b5179011b8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -234,6 +234,8 @@ 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);
 static bool check_default_with_oids(bool *newval, void **extra, GucSource source);
+static bool check_archive_command(char **newval, void **extra, GucSource source);
+static bool check_archive_library(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,
@@ -3855,7 +3857,17 @@ static struct config_string ConfigureNamesString[] =
 		},
 		&XLogArchiveCommand,
 		"",
-		NULL, NULL, show_archive_command
+		check_archive_command, NULL, show_archive_command
+	},
+
+	{
+		{"archive_library", PGC_POSTMASTER, WAL_ARCHIVING,
+			gettext_noop("Sets the library that will be called to archive a WAL file."),
+			NULL
+		},
+		&XLogArchiveLibrary,
+		"",
+		check_archive_library, NULL, NULL
 	},
 
 	{
@@ -8948,7 +8960,8 @@ init_custom_variable(const char *name,
 	 * module might already have hooked into.
 	 */
 	if (context == PGC_POSTMASTER &&
-		!process_shared_preload_libraries_in_progress)
+		!process_shared_preload_libraries_in_progress &&
+		!process_archive_library_in_progress)
 		elog(FATAL, "cannot create PGC_POSTMASTER variables after startup");
 
 	/*
@@ -12559,4 +12572,28 @@ check_default_with_oids(bool *newval, void **extra, GucSource source)
 	return true;
 }
 
+static bool
+check_archive_command(char **newval, void **extra, GucSource source)
+{
+	if (*newval && *newval[0] != '\0' &&
+		XLogArchiveLibrary && XLogArchiveLibrary[0] != '\0')
+	{
+		GUC_check_errdetail("Cannot set parameter when \"archive_library\" is set.");
+		return false;
+	}
+	return true;
+}
+
+static bool
+check_archive_library(char **newval, void **extra, GucSource source)
+{
+	if (*newval && *newval[0] != '\0' &&
+		XLogArchiveCommand && XLogArchiveCommand[0] != '\0')
+	{
+		GUC_check_errdetail("Cannot set parameter when \"archive_command\" is set.");
+		return false;
+	}
+	return true;
+}
+
 #include "guc-file.c"
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 5e2c94a05f..09e163c0ac 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -18,6 +18,7 @@
 #include "datatype/timestamp.h"
 #include "lib/stringinfo.h"
 #include "nodes/pg_list.h"
+#include "postmaster/pgarch.h"
 #include "storage/fd.h"
 
 
@@ -71,6 +72,7 @@ extern int	XLOGbuffers;
 extern int	XLogArchiveTimeout;
 extern int	wal_retrieve_retry_interval;
 extern char *XLogArchiveCommand;
+extern char *XLogArchiveLibrary;
 extern bool EnableHotStandby;
 extern bool fullPageWrites;
 extern bool wal_log_hints;
@@ -157,7 +159,9 @@ 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 archiving configured? */
+#define XLogArchivingConfigured() \
+	(AssertMacro(ArchiveContext.check_configured_cb != NULL), ArchiveContext.check_configured_cb())
 
 /*
  * Is WAL-logging necessary for archival or log-shipping, or can we skip
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 90a3016065..8717fed0dc 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -464,6 +464,7 @@ extern void BaseInit(void);
 /* in utils/init/miscinit.c */
 extern bool IgnoreSystemIndexes;
 extern PGDLLIMPORT bool process_shared_preload_libraries_in_progress;
+extern PGDLLIMPORT bool process_archive_library_in_progress;
 extern char *session_preload_libraries_string;
 extern char *shared_preload_libraries_string;
 extern char *local_preload_libraries_string;
@@ -477,6 +478,7 @@ extern bool RecheckDataDirLockFile(void);
 extern void ValidatePgVersion(const char *path);
 extern void process_shared_preload_libraries(void);
 extern void process_session_preload_libraries(void);
+extern void process_archive_library(void);
 extern void pg_bindtextdomain(const char *domain);
 extern bool has_rolreplication(Oid roleid);
 
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index 1e47a143e1..38a5828ffa 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -32,4 +32,42 @@ extern bool PgArchCanRestart(void);
 extern void PgArchiverMain(void) pg_attribute_noreturn();
 extern void PgArchWakeup(void);
 
+/*
+ * Callback that gets called to determine if the archive module is
+ * configured.
+ */
+typedef bool (*ArchiveCheckConfiguredCB) (void);
+
+/*
+ * Callback called to archive a single WAL file.
+ */
+typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+
+/*
+ * Archive module callbacks
+ */
+typedef struct ArchiveModuleCallbacks
+{
+	ArchiveCheckConfiguredCB check_configured_cb;
+	ArchiveFileCB archive_file_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+
+/*
+ * The registered callbacks for the archiver to use.
+ */
+extern ArchiveModuleCallbacks ArchiveContext;
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+
 #endif							/* _PGARCH_H */
-- 
2.16.6

