From 23e0872805c29947ffc334a7b35c2988285a8130 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Thu, 30 Sep 2021 04:13:45 +0000
Subject: [PATCH v1 1/1] backup module proof of concept

---
 contrib/Makefile                         |   1 +
 contrib/basic_archive/Makefile           |  15 ++
 contrib/basic_archive/basic_archive.c    | 226 ++++++++++++++++++++++
 src/backend/access/transam/xlog.c        |  29 ++-
 src/backend/access/transam/xlogarchive.c | 318 +++++++++++++++++++++++++------
 src/backend/postmaster/pgarch.c          |  95 ++++++++-
 src/backend/postmaster/postmaster.c      |  10 +
 src/backend/utils/fmgr/dfmgr.c           | 100 +++++++++-
 src/backend/utils/init/miscinit.c        |  62 ++++++
 src/backend/utils/misc/guc.c             | 155 ++++++++++++++-
 src/include/access/xlog.h                |   6 +-
 src/include/access/xlog_internal.h       |   1 +
 src/include/access/xlogarchive.h         |   1 +
 src/include/fmgr.h                       |  10 +
 src/include/miscadmin.h                  |   6 +
 15 files changed, 950 insertions(+), 85 deletions(-)
 create mode 100644 contrib/basic_archive/Makefile
 create mode 100644 contrib/basic_archive/basic_archive.c

diff --git a/contrib/Makefile b/contrib/Makefile
index f27e458482..aff834ebaa 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -9,6 +9,7 @@ SUBDIRS = \
 		amcheck		\
 		auth_delay	\
 		auto_explain	\
+		basic_archive	\
 		bloom		\
 		btree_gin	\
 		btree_gist	\
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
new file mode 100644
index 0000000000..ea6b460889
--- /dev/null
+++ b/contrib/basic_archive/Makefile
@@ -0,0 +1,15 @@
+# contrib/basic_archive/Makefile
+
+MODULES = basic_archive
+PGFILEDESC = "basic_archive - basic archive module"
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/basic_archive
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
new file mode 100644
index 0000000000..832a7a4d69
--- /dev/null
+++ b/contrib/basic_archive/basic_archive.c
@@ -0,0 +1,226 @@
+/*-------------------------------------------------------------------------
+ *
+ * basic_archive.c
+ *
+ * This file demonstrates a basic archive_library implemenation that is
+ * roughly equivalent to the following shell command:
+ *
+ * 		test ! -f /path/to/dest && cp /path/to/src /path/to/dest
+ *
+ * One notable difference between this module and the shell command above
+ * is that this module first copies the file to a temporary destination,
+ * syncs it to disk, and then durably moves it to the final destination.
+ *
+ * Copyright (c) 2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  contrib/basic_archive/basic_archive.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+
+PG_MODULE_MAGIC;
+
+void	_PG_init(void);
+bool	_PG_archive(const char *path, const char *file);
+
+static char *archive_directory = NULL;
+
+static bool check_archive_directory(char **newval, void **extra, GucSource source);
+static bool copy_file(const char *src, const char *dst, char *buf);
+
+void
+_PG_init(void)
+{
+	if (!process_archive_library_in_progress)
+		ereport(ERROR,
+				(errmsg("\"basic_archive\" can only be loaded via "
+						"\"archive_library\"")));
+
+	DefineCustomStringVariable("basic_archive.archive_directory",
+							   gettext_noop("Archive file destination directory."),
+							   NULL,
+							   &archive_directory,
+							   "",
+							   PGC_POSTMASTER,
+							   GUC_NOT_IN_SAMPLE,
+							   check_archive_directory, NULL, NULL);
+}
+
+static bool
+check_archive_directory(char **newval, void **extra, GucSource source)
+{
+	struct stat st;
+
+	if (*newval == NULL || *newval[0] == '\0')
+		return true;
+
+	if (stat(*newval, &st) != 0 || !S_ISDIR(st.st_mode))
+	{
+		GUC_check_errdetail("specified archive directory does not exist");
+		return false;
+	}
+
+	return true;
+}
+
+bool
+_PG_archive(const char *path, const char *file)
+{
+	char destination[MAXPGPATH];
+	char temp[MAXPGPATH];
+	struct stat st;
+	char *buf;
+
+	if (archive_directory == NULL || archive_directory[0] == '\0')
+	{
+		ereport(WARNING,
+				(errmsg("\"basic_archive.archive_directory\" not specified")));
+		return false;
+	}
+
+#define TEMP_FILE_NAME ("archtemp")
+
+	if (strlen(path) + Max(strlen(file), strlen(TEMP_FILE_NAME)) + 2 >= MAXPGPATH)
+	{
+		ereport(WARNING,
+				(errmsg("archive destination path too long")));
+		return false;
+	}
+
+	snprintf(destination, MAXPGPATH, "%s/%s", path, file);
+	snprintf(temp, MAXPGPATH, "%s/%s", path, TEMP_FILE_NAME);
+
+	/*
+	 * First, check if the file has already been archived.  If it has,
+	 * just fail, because something is wrong.
+	 */
+	if (stat(destination, &st) == 0)
+	{
+		ereport(WARNING,
+				(errmsg("archive file \"%s\" already exists",
+						destination)));
+		return false;
+	}
+	else if (errno != ENOENT)
+	{
+		ereport(WARNING,
+				(errcode_for_file_access(),
+				 errmsg("could not stat file \"%s\": %m",
+						destination)));
+		return false;
+	}
+
+	/*
+	 * Remove pre-existing temporary file, if one exists.
+	 */
+	if (unlink(temp) != 0 && errno != ENOENT)
+	{
+		ereport(WARNING,
+				(errcode_for_file_access(),
+				 errmsg("could not unlink file \"%s\": %m",
+						temp)));
+		return false;
+	}
+
+	/*
+	 * Copy the file to its temporary destination.
+	 */
+#define COPY_BUF_SIZE (64 * 1024)
+
+	buf = palloc(COPY_BUF_SIZE);
+
+	if (!copy_file(path, temp, buf))
+	{
+		pfree(buf);
+		return false;
+	}
+
+	pfree(buf);
+
+	/*
+	 * Sync the temporary file to disk and move it to its final
+	 * destination.
+	 */
+	return (durable_rename(temp, destination, WARNING) == 0);
+}
+
+static bool
+copy_file(const char *src, const char *dst, char *buf)
+{
+	int srcfd;
+	int dstfd;
+	int nbytes;
+
+	srcfd = OpenTransientFile(src, O_RDONLY | PG_BINARY);
+	if (srcfd < 0)
+	{
+		ereport(WARNING,
+				(errcode_for_file_access(),
+				 errmsg("could not open file \"%s\": %m", src)));
+		return false;
+	}
+
+	dstfd = OpenTransientFile(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
+	if (dstfd < 0)
+	{
+		ereport(WARNING,
+				(errcode_for_file_access(),
+				 errmsg("could not open file \"%s\": %m", dst)));
+		return false;
+	}
+
+	for (;;)
+	{
+		nbytes = read(srcfd, buf, COPY_BUF_SIZE);
+		if (nbytes < 0)
+		{
+			ereport(WARNING,
+					(errcode_for_file_access(),
+					 errmsg("could not read file \"%s\": %m", src)));
+			return false;
+		}
+
+		if (nbytes == 0)
+			break;
+
+		errno = 0;
+		if ((int) write(dstfd, buf, nbytes) != nbytes)
+		{
+			/* if write didn't set errno, assume problem is no disk space */
+			if (errno == 0)
+				errno = ENOSPC;
+			ereport(WARNING,
+					(errcode_for_file_access(),
+					 errmsg("could not write to file \"%s\": %m", dst)));
+			return false;
+		}
+	}
+
+	if (CloseTransientFile(dstfd) != 0)
+	{
+		ereport(WARNING,
+				(errcode_for_file_access(),
+				 errmsg("could not close file \"%s\": %m", dst)));
+		return false;
+	}
+
+	if (CloseTransientFile(srcfd) != 0)
+	{
+		ereport(WARNING,
+				(errcode_for_file_access(),
+				 errmsg("could not close file \"%s\": %m", src)));
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 1388afdfb0..74566217bd 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -95,6 +95,7 @@ int			XLOGbuffers = -1;
 int			XLogArchiveTimeout = 0;
 int			XLogArchiveMode = ARCHIVE_MODE_OFF;
 char	   *XLogArchiveCommand = NULL;
+char	   *XLogArchiveLibrary = NULL;
 bool		EnableHotStandby = false;
 bool		fullPageWrites = true;
 bool		wal_log_hints = false;
@@ -270,8 +271,11 @@ static char *primary_image_masked = NULL;
 
 /* options formerly taken from recovery.conf for archive recovery */
 char	   *recoveryRestoreCommand = NULL;
+char	   *recoveryRestoreLibrary = NULL;
 char	   *recoveryEndCommand = NULL;
+char	   *recoveryEndLibrary = NULL;
 char	   *archiveCleanupCommand = NULL;
+char	   *archiveCleanupLibrary = NULL;
 RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
 bool		recoveryTargetInclusive = true;
 int			recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
@@ -5558,18 +5562,21 @@ validateRecoveryParameters(void)
 	if (StandbyModeRequested)
 	{
 		if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
-			(recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+			(recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0) &&
+			(recoveryRestoreLibrary == NULL || strcmp(recoveryRestoreLibrary, "") == 0))
 			ereport(WARNING,
-					(errmsg("specified neither primary_conninfo nor restore_command"),
+					(errmsg("specified neither primary_conninfo nor restore_command nor restore_library"),
 					 errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
 	}
 	else
 	{
-		if (recoveryRestoreCommand == NULL ||
-			strcmp(recoveryRestoreCommand, "") == 0)
+		if ((recoveryRestoreCommand == NULL ||
+			 strcmp(recoveryRestoreCommand, "") == 0) &&
+			(recoveryRestoreLibrary == NULL ||
+			 strcmp(recoveryRestoreLibrary, "") == 0))
 			ereport(FATAL,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-					 errmsg("must specify restore_command when standby mode is not enabled")));
+					 errmsg("must specify restore_command or restore_library when standby mode is not enabled")));
 	}
 
 	/*
@@ -7997,12 +8004,15 @@ StartupXLOG(void)
 	if (ArchiveRecoveryRequested)
 	{
 		/*
-		 * And finally, execute the recovery_end_command, if any.
+		 * And finally, execute the recovery_end_command or
+		 * recovery_end_library, if any.
 		 */
 		if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
 			ExecuteRecoveryCommand(recoveryEndCommand,
 								   "recovery_end_command",
 								   true);
+		else if (recoveryEndLibrary && strcmp(recoveryEndLibrary, "") != 0)
+			ExecuteRecoveryLibrary("recovery_end_library");
 
 		/*
 		 * We switched to a new timeline. Clean up segments on the old
@@ -8739,7 +8749,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() && XLogArchiveCommandOrLibrarySet())
 			RequestXLogSwitch(false);
 
 		CreateCheckPoint(CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_IMMEDIATE);
@@ -9805,12 +9815,15 @@ CreateRestartPoint(int flags)
 							   timestamptz_to_str(xtime)) : 0));
 
 	/*
-	 * Finally, execute archive_cleanup_command, if any.
+	 * Finally, execute archive_cleanup_command or archive_cleanup_library,
+	 * if any.
 	 */
 	if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
 		ExecuteRecoveryCommand(archiveCleanupCommand,
 							   "archive_cleanup_command",
 							   false);
+	else if (archiveCleanupLibrary && strcmp(archiveCleanupLibrary, "") != 0)
+		ExecuteRecoveryLibrary("archive_cleanup_library");
 
 	return true;
 }
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 26b023e754..d78e14fba2 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,6 +23,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "common/archive.h"
+#include "fmgr.h"
 #include "miscadmin.h"
 #include "postmaster/startup.h"
 #include "postmaster/pgarch.h"
@@ -31,6 +32,16 @@
 #include "storage/ipc.h"
 #include "storage/lwlock.h"
 
+static bool execute_restore_library(const char *xlogpath, const char *xlogfname,
+									const char *lastRestartPointFname, char *path,
+									off_t expectedSize);
+static bool execute_restore_command(const char *xlogpath, const char *xlogfname,
+									const char *lastRestartPointFname, char *path,
+									off_t expectedSize);
+static void check_restored_file(const char *xlogpath, const char *xlogfname,
+								off_t expectedSize, bool *success,
+								bool *stat_failed);
+
 /*
  * Attempt to retrieve the specified file from off-line archival storage.
  * If successful, fill "path" with its complete path (note that this will be
@@ -55,9 +66,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 					bool cleanupEnabled)
 {
 	char		xlogpath[MAXPGPATH];
-	char	   *xlogRestoreCmd;
 	char		lastRestartPointFname[MAXPGPATH];
-	int			rc;
 	struct stat stat_buf;
 	XLogSegNo	restartSegNo;
 	XLogRecPtr	restartRedoPtr;
@@ -65,14 +74,23 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 
 	/*
 	 * Ignore restore_command when not in archive recovery (meaning we are in
-	 * crash recovery).
+	 * crash recovery).  In standby mode, restore_command and restore_library
+	 * might not be supplied.
 	 */
-	if (!ArchiveRecoveryRequested)
-		goto not_available;
-
-	/* In standby mode, restore_command might not be supplied */
-	if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
-		goto not_available;
+	if (!ArchiveRecoveryRequested ||
+		((recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0) &&
+		 (recoveryRestoreLibrary == NULL || strcmp(recoveryRestoreLibrary, "") == 0)))
+	{
+		/*
+		 * if an archived file is not available, there might still be a version
+		 * of this file in XLOGDIR, so return that as the filename to open.
+		 *
+		 * In many recovery scenarios we expect this to fail also, but if so
+		 * that just means we've reached the end of WAL.
+		 */
+		snprintf(path, MAXPGPATH, XLOGDIR "/%s", xlogfname);
+		return false;
+	}
 
 	/*
 	 * When doing archive recovery, we always prefer an archived log file even
@@ -148,6 +166,95 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 	else
 		XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
 
+	if (PG_restore != NULL)
+		return execute_restore_library(xlogpath, xlogfname,
+									   lastRestartPointFname, path,
+									   expectedSize);
+	else
+		return execute_restore_command(xlogpath, xlogfname,
+									   lastRestartPointFname, path,
+									   expectedSize);
+}
+
+static bool
+execute_restore_library(const char *xlogpath, const char *xlogfname,
+						const char *lastRestartPointFname, char *path,
+						off_t expectedSize)
+{
+	bool ret;
+
+	Assert(PG_restore != NULL);
+	Assert(xlogpath != NULL);
+	Assert(xlogfname != NULL);
+	Assert(lastRestartPointFname != NULL);
+	Assert(path != NULL);
+
+	ereport(DEBUG3,
+			(errmsg_internal("executing restore library \"%s\" for file \"%s\"",
+							 recoveryRestoreLibrary, xlogfname)));
+
+	/*
+	 * Check signals before restore command and reset afterwards.
+	 */
+	PreRestoreCommand();
+
+	/*
+	 * Copy xlog from archival storage to XLOGDIR
+	 *
+	 * Note that we do not catch any ERRORs (or worse) that the restore
+	 * library emits.  It is the responsibility of the library to do the
+	 * necessary error handling, memory management, etc.  If a library does
+	 * ERROR (or worse), it will bubble up and may cause unexpected
+	 * behavior.
+	 */
+	ret = (*PG_restore) (xlogpath, xlogfname, lastRestartPointFname);
+
+	PostRestoreCommand();
+
+	if (ret)
+	{
+		bool success;
+		bool stat_failed;
+
+		/*
+		 * command apparently succeeded, but let's make sure the file is
+		 * really there now and has the correct size.
+		 */
+		check_restored_file(xlogpath, xlogfname, expectedSize, &success,
+							&stat_failed);
+
+		if (success)
+			strcpy(path, xlogpath);
+
+		if (!stat_failed)
+			return success;
+	}
+
+	/*
+	 * if an archived file is not available, there might still be a version of
+	 * this file in XLOGDIR, so return that as the filename to open.
+	 *
+	 * In many recovery scenarios we expect this to fail also, but if so that
+	 * just means we've reached the end of WAL.
+	 */
+	snprintf(path, MAXPGPATH, XLOGDIR "/%s", xlogfname);
+	return false;
+}
+
+static bool
+execute_restore_command(const char *xlogpath, const char *xlogfname,
+						const char *lastRestartPointFname, char *path,
+						off_t expectedSize)
+{
+	char	   *xlogRestoreCmd;
+	int			rc;
+
+	Assert(recoveryRestoreCommand != NULL);
+	Assert(xlogpath != NULL);
+	Assert(xlogfname != NULL);
+	Assert(lastRestartPointFname != NULL);
+	Assert(path != NULL);
+
 	/* Build the restore command to execute */
 	xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
 										 xlogpath, xlogfname,
@@ -175,58 +282,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 
 	if (rc == 0)
 	{
+		bool success;
+		bool stat_failed;
+
 		/*
 		 * 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)
-			{
-				int			elevel;
-
-				/*
-				 * If we find a partial file in standby mode, we assume it's
-				 * because it's just being copied to the archive, and keep
-				 * trying.
-				 *
-				 * Otherwise treat a wrong-sized file as FATAL to ensure the
-				 * DBA would notice it, but is that too strong? We could try
-				 * to plow ahead with a local copy of the file ... but the
-				 * problem is that there probably isn't one, and we'd
-				 * incorrectly conclude we've reached the end of WAL and we're
-				 * done recovering ...
-				 */
-				if (StandbyMode && stat_buf.st_size < expectedSize)
-					elevel = DEBUG1;
-				else
-					elevel = FATAL;
-				ereport(elevel,
-						(errmsg("archive file \"%s\" has wrong size: %lld instead of %lld",
-								xlogfname,
-								(long long int) stat_buf.st_size,
-								(long long int) expectedSize)));
-				return false;
-			}
-			else
-			{
-				ereport(LOG,
-						(errmsg("restored log file \"%s\" from archive",
-								xlogfname)));
-				strcpy(path, xlogpath);
-				return true;
-			}
-		}
-		else
-		{
-			/* stat failed */
-			int			elevel = (errno == ENOENT) ? LOG : FATAL;
+		check_restored_file(xlogpath, xlogfname, expectedSize, &success,
+							&stat_failed);
 
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", xlogpath),
-					 errdetail("restore_command returned a zero exit status, but stat() failed.")));
-		}
+		if (success)
+			strcpy(path, xlogpath);
+
+		if (!stat_failed)
+			return success;
 	}
 
 	/*
@@ -260,8 +330,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 			(errmsg("could not restore file \"%s\" from archive: %s",
 					xlogfname, wait_result_to_str(rc))));
 
-not_available:
-
 	/*
 	 * if an archived file is not available, there might still be a version of
 	 * this file in XLOGDIR, so return that as the filename to open.
@@ -273,6 +341,79 @@ not_available:
 	return false;
 }
 
+/*
+ * check_restored_file
+ *
+ * Check that the file specified by xlogpath and xlogfname exists and has
+ * the expected size.  If everything checks out, *success is set to true
+ * and *stat_failed is set to false.  If the call to stat() fails, *success
+ * is set to false and *stat_failed is set to true.  Otherwise, both
+ * *success and *stat_failed are set to false.
+ */
+static void
+check_restored_file(const char *xlogpath, const char *xlogfname,
+					off_t expectedSize, bool *success, bool *stat_failed)
+{
+	struct stat stat_buf;
+
+	Assert(xlogpath != NULL);
+	Assert(xlogfname != NULL);
+	Assert(success != NULL);
+	Assert(stat_failed != NULL);
+
+	*success = false;
+	*stat_failed = false;
+
+	if (stat(xlogpath, &stat_buf) == 0)
+	{
+		if (expectedSize > 0 && stat_buf.st_size != expectedSize)
+		{
+			int			elevel;
+
+			/*
+			 * If we find a partial file in standby mode, we assume it's
+			 * because it's just being copied to the archive, and keep
+			 * trying.
+			 *
+			 * Otherwise treat a wrong-sized file as FATAL to ensure the
+			 * DBA would notice it, but is that too strong? We could try
+			 * to plow ahead with a local copy of the file ... but the
+			 * problem is that there probably isn't one, and we'd
+			 * incorrectly conclude we've reached the end of WAL and we're
+			 * done recovering ...
+			 */
+			if (StandbyMode && stat_buf.st_size < expectedSize)
+				elevel = DEBUG1;
+			else
+				elevel = FATAL;
+			ereport(elevel,
+					(errmsg("archive file \"%s\" has wrong size: %lld instead of %lld",
+							xlogfname,
+							(long long int) stat_buf.st_size,
+							(long long int) expectedSize)));
+		}
+		else
+		{
+			ereport(LOG,
+					(errmsg("restored log file \"%s\" from archive",
+							xlogfname)));
+			*success = true;
+		}
+	}
+	else
+	{
+		/* stat failed */
+		int			elevel = (errno == ENOENT) ? LOG : FATAL;
+
+		ereport(elevel,
+				(errcode_for_file_access(),
+				 errmsg("could not stat file \"%s\": %m", xlogpath),
+				 errdetail("restore_command returned a zero exit status, but stat() failed.")));
+
+		*stat_failed = true;
+	}
+}
+
 /*
  * Attempt to execute an external shell command during recovery.
  *
@@ -371,6 +512,67 @@ ExecuteRecoveryCommand(const char *command, const char *commandName, bool failOn
 	}
 }
 
+/*
+ * Attempt to execute an external library during recovery.
+ *
+ * 'libraryType' is the library to be executed.
+ *
+ * This is currently used for recovery_end_library and
+ * archive_cleanup_library.
+ */
+void
+ExecuteRecoveryLibrary(const char *libraryType)
+{
+	char		lastRestartPointFname[MAXPGPATH];
+	XLogSegNo	restartSegNo;
+	XLogRecPtr	restartRedoPtr;
+	TimeLineID	restartTli;
+	bool		ret;
+	char	   *libraryName;
+
+	Assert(libraryType);
+
+	/*
+	 * Calculate the archive file cutoff point for use during log shipping
+	 * replication. All files earlier than this point can be deleted from the
+	 * archive, though there is no requirement to do so.
+	 */
+	GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+	XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+	XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
+				 wal_segment_size);
+
+	if (strcmp(libraryType, "archive_cleanup_library") == 0)
+		libraryName = archiveCleanupLibrary;
+	else if (strcmp(libraryType, "recovery_end_library") == 0)
+		libraryName = recoveryEndLibrary;
+	else
+		elog(ERROR, "unknown library type");
+
+	ereport(DEBUG3,
+			(errmsg_internal("executing %s library \"%s\" for file \"%s\"",
+							 libraryType, libraryName, lastRestartPointFname)));
+
+	/*
+	 * Call the library.
+	 *
+	 * Note that we do not catch any ERRORs (or worse) that the library
+	 * emits.  It is the responsibility of the library to do the necessary
+	 * error handling, memory management, etc.  If a library does ERROR (or
+	 * worse), it will bubble up and may cause unexpected behavior.
+	 */
+	if (strcmp(libraryType, "archive_cleanup_library") == 0)
+		ret = (*PG_archive_cleanup) (lastRestartPointFname);
+	else if (strcmp(libraryType, "recovery_end_library") == 0)
+		ret = (*PG_recovery_end) (lastRestartPointFname);
+	else
+		elog(ERROR, "unknown library type");
+
+	if (!ret)
+		ereport(WARNING,
+				(errmsg("executing %s library \"%s\" for file \"%s\" failed",
+						libraryType, libraryName, lastRestartPointFname)));
+}
 
 /*
  * A file was restored from the archive under a temporary filename (path),
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 74a7d7c4d0..ac6c35c4be 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -103,6 +103,8 @@ static bool pgarch_readyXlog(char *xlog);
 static void pgarch_archiveDone(char *xlog);
 static void pgarch_die(int code, Datum arg);
 static void HandlePgArchInterrupts(void);
+static bool execute_archive_library(const char *pathname, const char *xlog);
+static bool execute_archive_command(const char *pathname, const char *xlog);
 
 /* Report shared memory space needed by PgArchShmemInit */
 Size
@@ -358,11 +360,12 @@ pgarch_ArchiverCopyLoop(void)
 			 */
 			HandlePgArchInterrupts();
 
-			/* can't do anything if no command ... */
-			if (!XLogArchiveCommandSet())
+			/* can't do anything if no command or library... */
+			if (!XLogArchiveCommandOrLibrarySet())
 			{
 				ereport(WARNING,
-						(errmsg("archive_mode enabled, yet archive_command is not set")));
+						(errmsg("archive_mode enabled, yet archive_command "
+								"and archive_library are not set")));
 				return;
 			}
 
@@ -443,22 +446,102 @@ pgarch_ArchiverCopyLoop(void)
 /*
  * pgarch_archiveXlog
  *
- * Invokes system(3) to copy one archive file to wherever it should go
+ * Invokes system(3) or PG_archive() 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];
+
+	snprintf(pathname, MAXPGPATH, XLOGDIR "/%s", xlog);
+
+	if (PG_archive != NULL)
+		return execute_archive_library(pathname, xlog);
+	else
+		return execute_archive_command(pathname, xlog);
+}
+
+/*
+ * execute_archive_library
+ *
+ * Invokes PG_archive() to copy one archive file to wherever it should go.
+ *
+ * Returns true if successful
+ */
+static bool
+execute_archive_library(const char *pathname, const char *xlog)
+{
+	char		activitymsg[MAXFNAMELEN + 16];
+	bool		ret;
+
+	Assert(PG_archive != NULL);
+	Assert(pathname != NULL);
+	Assert(xlog != NULL);
+
+	/*
+	 * Report that we are archiving a file.
+	 */
+	ereport(DEBUG3,
+			(errmsg_internal("executing archive library \"%s\" for file \"%s\"",
+							 XLogArchiveLibrary, xlog)));
+	snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
+	set_ps_display(activitymsg);
+
+	/*
+	 * Call the archive library.
+	 *
+	 * Note that we do not catch any ERRORs (or worse) that the archive
+	 * library emits.  It is the responsibility of the library to do the
+	 * necessary error handling, memory management, etc.  If a library does
+	 * ERROR (or worse), it will bubble up and cause the archiver to
+	 * restart.
+	 */
+	ret = (*PG_archive) (pathname, xlog);
+
+	/*
+	 * Report the success or failure of the archival attempt.
+	 */
+	if (ret)
+	{
+		ereport(DEBUG1,
+				(errmsg("archived write-ahead log file \"%s\"", xlog)));
+		snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
+	}
+	else
+	{
+		ereport(LOG,
+				(errmsg("archive library \"%s\" failed for file \"%s\"",
+						XLogArchiveLibrary, xlog)));
+		snprintf(activitymsg, sizeof(activitymsg), "failed on %s", xlog);
+	}
+
+	set_ps_display(activitymsg);
+	return ret;
+}
+
+/*
+ * execute_archive_command
+ *
+ * Invokes system(3) to copy one archive file to wherever it should go.
+ *
+ * Returns true if successful
+ */
+static bool
+execute_archive_command(const char *pathname, const char *xlog)
+{
+	char		xlogarchcmd[MAXPGPATH];
 	char		activitymsg[MAXFNAMELEN + 16];
 	char	   *dp;
 	char	   *endp;
 	const char *sp;
 	int			rc;
 
-	snprintf(pathname, MAXPGPATH, XLOGDIR "/%s", xlog);
+	Assert(XLogArchiveCommand != NULL && XLogArchiveCommand[0] != '\0');
+	Assert(pathname != NULL);
+	Assert(xlog != NULL);
 
 	/*
 	 * construct the command to be executed
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index e2a76ba055..b975ca362b 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1025,6 +1025,11 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	process_shared_preload_libraries();
 
+	/*
+	 * Process any backup libraries.
+	 */
+	process_backup_libraries();
+
 	/*
 	 * Initialize SSL library, if specified.
 	 */
@@ -5012,6 +5017,11 @@ SubPostmasterMain(int argc, char *argv[])
 	 */
 	process_shared_preload_libraries();
 
+	/*
+	 * Process any backup libraries.
+	 */
+	process_backup_libraries();
+
 	/* Run backend or appropriate child */
 	if (strcmp(argv[1], "--forkbackend") == 0)
 	{
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index 96fd9d2268..0910e3b1b9 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -61,6 +61,7 @@ typedef struct df_files
 	ino_t		inode;			/* Inode number of file */
 #endif
 	void	   *handle;			/* a handle for pg_dl* functions */
+	bool		non_backup;		/* loaded as a non-backup library */
 	char		filename[FLEXIBLE_ARRAY_MEMBER];	/* Full pathname of file */
 } DynamicFileList;
 
@@ -76,6 +77,11 @@ static DynamicFileList *file_tail = NULL;
 
 char	   *Dynamic_library_path;
 
+PG_archive_t PG_archive = NULL;
+PG_restore_t PG_restore = NULL;
+PG_archive_cleanup_t PG_archive_cleanup = NULL;
+PG_recovery_end_t PG_recovery_end = NULL;
+
 static void *internal_load_library(const char *libname);
 static void incompatible_module_error(const char *libname,
 									  const Pg_magic_struct *module_magic_data) pg_attribute_noreturn();
@@ -85,6 +91,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 +194,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 +287,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 +296,95 @@ internal_load_library(const char *libname)
 			file_tail->next = file_scanner;
 		file_tail = file_scanner;
 	}
+	else if (!file_scanner->non_backup || process_backup_libraries_in_progress)
+	{
+		/*
+		 * If we are loading a backup library, we initialize the library
+		 * even if we previously loaded it.  This allows users to use
+		 * backup libraries for multiple reasons (e.g., the same library
+		 * can be specified in shared_preload_libraries, archive_library,
+		 * and restore_library).  Similarly, if we are loading a previously
+		 * loaded backup library as a "non-backup" library (e.g.,
+		 * session_preload_libraries), we want to initialize it again then,
+		 * too.
+		 */
+		init_library(libname, file_scanner->handle);
+	}
+
+	/*
+	 * Record whether this library is being loaded as a "non-backup"
+	 * library (e.g., session_preload_libraries).  If the same library is
+	 * used as a backup library, we want to call its _PG_init()
+	 * function (if one exists) again when initializing the backup
+	 * tooling.  Similarly, if a backup library is loaded as a
+	 * "non-backup" library, we want to call it's _PG_init() again when
+	 * reinitializing it.
+	 */
+	file_scanner->non_backup |= !process_backup_libraries_in_progress;
 
 	return file_scanner->handle;
 }
 
+/*
+ * init_library
+ *
+ * This function calls the library's _PG_init() function if it exists.  If
+ * we are loading a backup library, we look up the relevant functions and
+ * save them for later.
+ */
+static void
+init_library(const char *libname, void *handle)
+{
+	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) ();
+
+	if (process_archive_library_in_progress)
+	{
+		PG_archive = (PG_archive_t) dlsym(handle, "_PG_archive");
+		if (PG_archive == NULL)
+			ereport(ERROR,
+					(errmsg("incompatible archive library \"%s\": missing "
+							"function \"_PG_archive()\"",
+							libname)));
+	}
+
+	if (process_restore_library_in_progress)
+	{
+		PG_restore = (PG_restore_t) dlsym(handle, "_PG_restore");
+		if (PG_restore == NULL)
+			ereport(ERROR,
+					(errmsg("incompatible restore library \"%s\": missing "
+							"function \"_PG_restore()\"",
+							libname)));
+	}
+
+	if (process_archive_cleanup_library_in_progress)
+	{
+		PG_archive_cleanup = (PG_archive_cleanup_t) dlsym(handle, "_PG_archive_cleanup");
+		if (PG_archive_cleanup == NULL)
+			ereport(ERROR,
+					(errmsg("incompatible archive cleanup library \"%s\": "
+							"missing function \"_PG_archive_cleanup()\"",
+							libname)));
+	}
+
+	if (process_recovery_end_library_in_progress)
+	{
+		PG_recovery_end = (PG_recovery_end_t) dlsym(handle, "_PG_recovery_end");
+		if (PG_recovery_end == NULL)
+			ereport(ERROR,
+					(errmsg("incompatible recovery end library \"%s\": "
+							"missing function \"_PG_recovery_end()\"",
+							libname)));
+	}
+}
+
 /*
  * 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..304cce00f7 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"
@@ -1614,6 +1615,16 @@ char	   *local_preload_libraries_string = NULL;
 /* Flag telling that we are loading shared_preload_libraries */
 bool		process_shared_preload_libraries_in_progress = false;
 
+/*
+ * Flags telling that we are loading archive_library, restore_library,
+ * archive_cleanup_library, and recovery_end_library.
+ */
+bool		process_backup_libraries_in_progress = false;
+bool		process_archive_library_in_progress = false;
+bool		process_restore_library_in_progress = false;
+bool		process_archive_cleanup_library_in_progress = false;
+bool		process_recovery_end_library_in_progress = false;
+
 /*
  * load the shared libraries listed in 'libraries'
  *
@@ -1696,6 +1707,57 @@ process_session_preload_libraries(void)
 				   true);
 }
 
+/*
+ * process backup libraries
+ */
+void
+process_backup_libraries(void)
+{
+	process_backup_libraries_in_progress = true;
+
+	if (XLogArchiveLibrary && XLogArchiveLibrary[0] != '\0')
+	{
+		process_archive_library_in_progress = true;
+		load_file(XLogArchiveLibrary, false);
+		ereport(DEBUG1,
+				(errmsg_internal("loaded archive library \"%s\"",
+								 XLogArchiveLibrary)));
+		process_archive_library_in_progress = false;
+	}
+
+	if (recoveryRestoreLibrary && recoveryRestoreLibrary[0] != '\0')
+	{
+		process_restore_library_in_progress = true;
+		load_file(recoveryRestoreLibrary, false);
+		ereport(DEBUG1,
+				(errmsg_internal("loaded restore library \"%s\"",
+								 recoveryRestoreLibrary)));
+		process_restore_library_in_progress = false;
+	}
+
+	if (archiveCleanupLibrary && archiveCleanupLibrary[0] != '\0')
+	{
+		process_archive_cleanup_library_in_progress = true;
+		load_file(archiveCleanupLibrary, false);
+		ereport(DEBUG1,
+				(errmsg_internal("loaded archive cleanup library \"%s\"",
+								 archiveCleanupLibrary)));
+		process_archive_cleanup_library_in_progress = false;
+	}
+
+	if (recoveryEndLibrary && recoveryEndLibrary[0] != '\0')
+	{
+		process_recovery_end_library_in_progress = true;
+		load_file(recoveryEndLibrary, false);
+		ereport(DEBUG1,
+				(errmsg_internal("loaded recovery end library \"%s\"",
+								 recoveryEndLibrary)));
+		process_recovery_end_library_in_progress = false;
+	}
+
+	process_backup_libraries_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..1fd9ca5cbc 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -234,6 +234,14 @@ 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);
+static bool check_restore_command(char **newval, void **extra, GucSource source);
+static bool check_restore_library(char **newval, void **extra, GucSource source);
+static bool check_archive_cleanup_command(char **newval, void **extra, GucSource source);
+static bool check_archive_cleanup_library(char **newval, void **extra, GucSource source);
+static bool check_recovery_end_command(char **newval, void **extra, GucSource source);
+static bool check_recovery_end_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 +3863,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
 	},
 
 	{
@@ -3865,7 +3883,17 @@ static struct config_string ConfigureNamesString[] =
 		},
 		&recoveryRestoreCommand,
 		"",
-		NULL, NULL, NULL
+		check_restore_command, NULL, NULL
+	},
+
+	{
+		{"restore_library", PGC_POSTMASTER, WAL_ARCHIVE_RECOVERY,
+			gettext_noop("Sets the library that will be called to retrieve an archived WAL file."),
+			NULL
+		},
+		&recoveryRestoreLibrary,
+		"",
+		check_restore_library, NULL, NULL
 	},
 
 	{
@@ -3875,7 +3903,17 @@ static struct config_string ConfigureNamesString[] =
 		},
 		&archiveCleanupCommand,
 		"",
-		NULL, NULL, NULL
+		check_archive_cleanup_command, NULL, NULL
+	},
+
+	{
+		{"archive_cleanup_library", PGC_POSTMASTER, WAL_ARCHIVE_RECOVERY,
+			gettext_noop("Sets the library that will be executed at every restart point."),
+			NULL
+		},
+		&archiveCleanupLibrary,
+		"",
+		check_archive_cleanup_library, NULL, NULL
 	},
 
 	{
@@ -3885,7 +3923,17 @@ static struct config_string ConfigureNamesString[] =
 		},
 		&recoveryEndCommand,
 		"",
-		NULL, NULL, NULL
+		check_recovery_end_command, NULL, NULL
+	},
+
+	{
+		{"recovery_end_library", PGC_POSTMASTER, WAL_ARCHIVE_RECOVERY,
+			gettext_noop("Sets the library that will be executed once at the end of recovery."),
+			NULL
+		},
+		&recoveryEndLibrary,
+		"",
+		check_recovery_end_library, NULL, NULL
 	},
 
 	{
@@ -8948,7 +8996,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_backup_libraries_in_progress)
 		elog(FATAL, "cannot create PGC_POSTMASTER variables after startup");
 
 	/*
@@ -12559,4 +12608,100 @@ 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;
+}
+
+static bool
+check_restore_command(char **newval, void **extra, GucSource source)
+{
+	if (*newval && *newval[0] != '\0' &&
+		recoveryRestoreLibrary && recoveryRestoreLibrary[0] != '\0')
+	{
+		GUC_check_errdetail("Cannot set parameter when \"restore_library\" is set.");
+		return false;
+	}
+	return true;
+}
+
+static bool
+check_restore_library(char **newval, void **extra, GucSource source)
+{
+	if (*newval && *newval[0] != '\0' &&
+		recoveryRestoreCommand && recoveryRestoreCommand[0] != '\0')
+	{
+		GUC_check_errdetail("Cannot set parameter when \"restore_command\" is set.");
+		return false;
+	}
+	return true;
+}
+
+static bool
+check_archive_cleanup_command(char **newval, void **extra, GucSource source)
+{
+	if (*newval && *newval[0] != '\0' &&
+		archiveCleanupLibrary && archiveCleanupLibrary[0] != '\0')
+	{
+		GUC_check_errdetail("Cannot set parameter when \"archive_cleanup_library\" is set.");
+		return false;
+	}
+	return true;
+}
+
+static bool
+check_archive_cleanup_library(char **newval, void **extra, GucSource source)
+{
+	if (*newval && *newval[0] != '\0' &&
+		archiveCleanupCommand && archiveCleanupCommand[0] != '\0')
+	{
+		GUC_check_errdetail("Cannot set parameter when \"archive_cleanup_command\" is set.");
+		return false;
+	}
+	return true;
+}
+
+static bool
+check_recovery_end_command(char **newval, void **extra, GucSource source)
+{
+	if (*newval && *newval[0] != '\0' &&
+		recoveryEndLibrary && recoveryEndLibrary[0] != '\0')
+	{
+		GUC_check_errdetail("Cannot set parameter when \"recovery_end_library\" is set.");
+		return false;
+	}
+	return true;
+}
+
+static bool
+check_recovery_end_library(char **newval, void **extra, GucSource source)
+{
+	if (*newval && *newval[0] != '\0' &&
+		recoveryEndCommand && recoveryEndCommand[0] != '\0')
+	{
+		GUC_check_errdetail("Cannot set parameter when \"recovery_end_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..094b74050c 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -71,6 +71,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;
@@ -81,8 +82,11 @@ extern bool *wal_consistency_checking;
 extern char *wal_consistency_checking_string;
 extern bool log_checkpoints;
 extern char *recoveryRestoreCommand;
+extern char *recoveryRestoreLibrary;
 extern char *recoveryEndCommand;
+extern char *recoveryEndLibrary;
 extern char *archiveCleanupCommand;
+extern char *archiveCleanupLibrary;
 extern bool recoveryTargetInclusive;
 extern int	recoveryTargetAction;
 extern int	recovery_min_apply_delay;
@@ -157,7 +161,7 @@ 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')
+#define XLogArchiveCommandOrLibrarySet() (XLogArchiveCommand[0] != '\0' || XLogArchiveLibrary[0] != '\0')
 
 /*
  * Is WAL-logging necessary for archival or log-shipping, or can we skip
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index c0da76cab4..9c6c3c5f06 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -332,5 +332,6 @@ extern bool ArchiveRecoveryRequested;
 extern bool InArchiveRecovery;
 extern bool StandbyMode;
 extern char *recoveryRestoreCommand;
+extern char *recoveryRestoreLibrary;
 
 #endif							/* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 3edd1a976c..4c5e63697c 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -22,6 +22,7 @@ extern bool RestoreArchivedFile(char *path, const char *xlogfname,
 								bool cleanupEnabled);
 extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
 								   bool failOnSignal);
+extern void ExecuteRecoveryLibrary(const char *libraryType);
 extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
 extern void XLogArchiveNotify(const char *xlog);
 extern void XLogArchiveNotifySeg(XLogSegNo segno);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index ab7b85c86e..d2f28d084a 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -718,6 +718,16 @@ extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid);
  */
 extern char *Dynamic_library_path;
 
+typedef bool (*PG_archive_t) (const char *path, const char *file);
+extern PG_archive_t PG_archive;
+typedef bool (*PG_restore_t) (const char *path, const char *file,
+							  const char *last_restartpoint_file);
+extern PG_restore_t PG_restore;
+typedef bool (*PG_archive_cleanup_t) (const char *last_restartpoint_file);
+extern PG_archive_cleanup_t PG_archive_cleanup;
+typedef bool (*PG_recovery_end_t) (const char *last_restartpoint_file);
+extern PG_recovery_end_t PG_recovery_end;
+
 extern void *load_external_function(const char *filename, const char *funcname,
 									bool signalNotFound, void **filehandle);
 extern void *lookup_external_function(void *filehandle, const char *funcname);
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 90a3016065..2f68d7c1dc 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -464,6 +464,11 @@ 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_backup_libraries_in_progress;
+extern PGDLLIMPORT bool process_archive_library_in_progress;
+extern PGDLLIMPORT bool process_restore_library_in_progress;
+extern PGDLLIMPORT bool process_archive_cleanup_library_in_progress;
+extern PGDLLIMPORT bool process_recovery_end_library_in_progress;
 extern char *session_preload_libraries_string;
 extern char *shared_preload_libraries_string;
 extern char *local_preload_libraries_string;
@@ -477,6 +482,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_backup_libraries(void);
 extern void pg_bindtextdomain(const char *domain);
 extern bool has_rolreplication(Oid roleid);
 
-- 
2.16.6

