rebased

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
>From c23ddbe1dac8b9a79db31ad67df423848e475905 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandboss...@gmail.com>
Date: Wed, 15 Feb 2023 14:28:53 -0800
Subject: [PATCH v23 1/5] introduce routine for checking mutually exclusive
 string GUCs

---
 src/backend/postmaster/pgarch.c |  8 +++-----
 src/backend/utils/misc/guc.c    | 22 ++++++++++++++++++++++
 src/include/utils/guc.h         |  3 +++
 3 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 02f91431f5..5f1a6f190d 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -912,11 +912,9 @@ LoadArchiveLibrary(void)
 {
        ArchiveModuleInit archive_init;
 
-       if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
-               ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                                errmsg("both \"archive_command\" and 
\"archive_library\" set"),
-                                errdetail("Only one of \"archive_command\", 
\"archive_library\" may be set.")));
+       (void) CheckMutuallyExclusiveStringGUCs(XLogArchiveLibrary, 
"archive_library",
+                                                                               
        XLogArchiveCommand, "archive_command",
+                                                                               
        ERROR);
 
        /*
         * If shell archiving is enabled, use our special initialization 
function.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 547cecde24..05dc5303bc 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2659,6 +2659,28 @@ ReportGUCOption(struct config_generic *record)
        pfree(val);
 }
 
+/*
+ * If both parameters are set, emits a log message at 'elevel' and returns
+ * false.  Otherwise, returns true.
+ */
+bool
+CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+                                                                const char 
*p2val, const char *p2name,
+                                                                int elevel)
+{
+       if (p1val[0] != '\0' && p2val[0] != '\0')
+       {
+               ereport(elevel,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("both \"%s\" and \"%s\" set", p1name, 
p2name),
+                                errdetail("Only one of \"%s\", \"%s\" may be 
set.",
+                                                  p1name, p2name)));
+               return false;
+       }
+
+       return true;
+}
+
 /*
  * Convert a value from one of the human-friendly units ("kB", "min" etc.)
  * to the given base unit.  'value' and 'unit' are the input value and unit
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index e4a594b5e8..018bb7e55b 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -376,6 +376,9 @@ extern void RestrictSearchPath(void);
 extern void AtEOXact_GUC(bool isCommit, int nestLevel);
 extern void BeginReportingGUCOptions(void);
 extern void ReportChangedGUCOptions(void);
+extern bool CheckMutuallyExclusiveStringGUCs(const char *p1val, const char 
*p1name,
+                                                                               
         const char *p2val, const char *p2name,
+                                                                               
         int elevel);
 extern void ParseLongOption(const char *string, char **name, char **value);
 extern const char *get_config_unit_name(int flags);
 extern bool parse_int(const char *value, int *result, int flags,
-- 
2.39.3 (Apple Git-146)

>From b35cd2dd0b360a50af7ab9175b2887646ba57f37 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandboss...@gmail.com>
Date: Wed, 15 Feb 2023 10:36:00 -0800
Subject: [PATCH v23 2/5] refactor code for restoring via shell

---
 src/backend/Makefile                      |   2 +-
 src/backend/access/transam/timeline.c     |  12 +-
 src/backend/access/transam/xlog.c         |  50 ++++-
 src/backend/access/transam/xlogarchive.c  | 167 ++++-----------
 src/backend/access/transam/xlogrecovery.c |   3 +-
 src/backend/meson.build                   |   1 +
 src/backend/postmaster/startup.c          |  16 +-
 src/backend/restore/Makefile              |  18 ++
 src/backend/restore/meson.build           |   5 +
 src/backend/restore/shell_restore.c       | 245 ++++++++++++++++++++++
 src/include/Makefile                      |   2 +-
 src/include/access/xlogarchive.h          |   9 +-
 src/include/meson.build                   |   1 +
 src/include/postmaster/startup.h          |   1 +
 src/include/restore/shell_restore.h       |  26 +++
 src/tools/pgindent/typedefs.list          |   1 +
 16 files changed, 407 insertions(+), 152 deletions(-)
 create mode 100644 src/backend/restore/Makefile
 create mode 100644 src/backend/restore/meson.build
 create mode 100644 src/backend/restore/shell_restore.c
 create mode 100644 src/include/restore/shell_restore.h

diff --git a/src/backend/Makefile b/src/backend/Makefile
index 6700aec039..590b5002c0 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global
 SUBDIRS = access archive backup bootstrap catalog parser commands executor \
        foreign lib libpq \
        main nodes optimizer partitioning port postmaster \
-       regex replication rewrite \
+       regex replication restore rewrite \
        statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
        jit
 
diff --git a/src/backend/access/transam/timeline.c 
b/src/backend/access/transam/timeline.c
index 146751ae37..3269547de7 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -59,7 +59,8 @@ restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
                        continue;
 
                TLHistoryFileName(histfname, tli);
-               if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, 
false))
+               if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, 
false,
+                                                               
ARCHIVE_TYPE_TIMELINE_HISTORY))
                        KeepFileRestoredFromArchive(path, histfname);
        }
 }
@@ -97,7 +98,8 @@ readTimeLineHistory(TimeLineID targetTLI)
        {
                TLHistoryFileName(histfname, targetTLI);
                fromArchive =
-                       RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 
0, false);
+                       RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 
0, false,
+                                                               
ARCHIVE_TYPE_TIMELINE_HISTORY);
        }
        else
                TLHistoryFilePath(path, targetTLI);
@@ -232,7 +234,8 @@ existsTimeLineHistory(TimeLineID probeTLI)
        if (ArchiveRecoveryRequested)
        {
                TLHistoryFileName(histfname, probeTLI);
-               RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, 
false);
+               RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, 
false,
+                                                       
ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS);
        }
        else
                TLHistoryFilePath(path, probeTLI);
@@ -334,7 +337,8 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID 
parentTLI,
        if (ArchiveRecoveryRequested)
        {
                TLHistoryFileName(histfname, parentTLI);
-               RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, 
false);
+               RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, 
false,
+                                                       
ARCHIVE_TYPE_TIMELINE_HISTORY);
        }
        else
                TLHistoryFilePath(path, parentTLI);
diff --git a/src/backend/access/transam/xlog.c 
b/src/backend/access/transam/xlog.c
index 330e058c5f..85b2fd9c1e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -83,6 +83,7 @@
 #include "replication/snapbuild.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "restore/shell_restore.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
@@ -704,6 +705,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
 static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
 static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
 static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
 
 static void WALInsertLockAcquire(void);
 static void WALInsertLockAcquireExclusive(void);
@@ -5257,12 +5259,18 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, 
XLogRecPtr EndOfLog,
 {
        /*
         * Execute the recovery_end_command, if any.
+        *
+        * The command is provided 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.
         */
-       if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
-               ExecuteRecoveryCommand(recoveryEndCommand,
-                                                          
"recovery_end_command",
-                                                          true,
-                                                          
WAIT_EVENT_RECOVERY_END_COMMAND);
+       if (shell_recovery_end_configured())
+       {
+               char            lastRestartPointFname[MAXFNAMELEN];
+
+               GetOldestRestartPointFileName(lastRestartPointFname);
+               shell_recovery_end(lastRestartPointFname);
+       }
 
        /*
         * We switched to a new timeline. Clean up segments on the old timeline.
@@ -7753,12 +7761,18 @@ CreateRestartPoint(int flags)
 
        /*
         * Finally, execute archive_cleanup_command, if any.
+        *
+        * The command is provided 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.
         */
-       if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
-               ExecuteRecoveryCommand(archiveCleanupCommand,
-                                                          
"archive_cleanup_command",
-                                                          false,
-                                                          
WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+       if (shell_archive_cleanup_configured())
+       {
+               char            lastRestartPointFname[MAXFNAMELEN];
+
+               GetOldestRestartPointFileName(lastRestartPointFname);
+               shell_archive_cleanup(lastRestartPointFname);
+       }
 
        return true;
 }
@@ -9388,6 +9402,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID 
*oldtli)
        LWLockRelease(ControlFileLock);
 }
 
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint.  This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+       XLogRecPtr      restartRedoPtr;
+       TimeLineID      restartTli;
+       XLogSegNo       restartSegNo;
+
+       GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+       XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+       XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
 /* Thin wrapper around ShutdownWalRcv(). */
 void
 XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c 
b/src/backend/access/transam/xlogarchive.c
index 81999b4820..56413c6a61 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,12 +23,12 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "common/archive.h"
-#include "common/percentrepl.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/pgarch.h"
 #include "postmaster/startup.h"
 #include "replication/walsender.h"
+#include "restore/shell_restore.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 
@@ -53,12 +53,11 @@
 bool
 RestoreArchivedFile(char *path, const char *xlogfname,
                                        const char *recovername, off_t 
expectedSize,
-                                       bool cleanupEnabled)
+                                       bool cleanupEnabled, ArchiveType 
archive_type)
 {
        char            xlogpath[MAXPGPATH];
-       char       *xlogRestoreCmd;
        char            lastRestartPointFname[MAXPGPATH];
-       int                     rc;
+       bool            ret = false;
        struct stat stat_buf;
        XLogSegNo       restartSegNo;
        XLogRecPtr      restartRedoPtr;
@@ -72,8 +71,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
                goto not_available;
 
        /* In standby mode, restore_command might not be supplied */
-       if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, 
"") == 0)
-               goto not_available;
+       switch (archive_type)
+       {
+               case ARCHIVE_TYPE_WAL_SEGMENT:
+                       if (!shell_restore_file_configured())
+                               goto not_available;
+                       break;
+               case ARCHIVE_TYPE_TIMELINE_HISTORY:
+                       if (!shell_restore_file_configured())
+                               goto not_available;
+                       break;
+               case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+                       if (!shell_restore_file_configured())
+                               goto not_available;
+                       break;
+       }
 
        /*
         * When doing archive recovery, we always prefer an archived log file 
even
@@ -149,39 +161,33 @@ RestoreArchivedFile(char *path, const char *xlogfname,
        else
                XLogFileName(lastRestartPointFname, 0, 0, wal_segment_size);
 
-       /* Build the restore command to execute */
-       xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
-                                                                               
 xlogpath, xlogfname,
-                                                                               
 lastRestartPointFname);
-
-       ereport(DEBUG3,
-                       (errmsg_internal("executing restore command \"%s\"",
-                                                        xlogRestoreCmd)));
-
-       fflush(NULL);
-       pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
-
        /*
-        * PreRestoreCommand() informs the SIGTERM handler for the startup 
process
-        * that it should proc_exit() right away.  This is done for the duration
-        * of the system() call because there isn't a good way to break out 
while
-        * it is executing.  Since we might call proc_exit() in a signal 
handler,
-        * it is best to put any additional logic before or after the
-        * PreRestoreCommand()/PostRestoreCommand() section.
+        * To ensure we are responsive to server shutdown, check for shutdown
+        * requests before and after restoring a file.  If there is one, we exit
+        * right away.
         */
-       PreRestoreCommand();
+       HandleStartupProcShutdownRequests();
 
        /*
         * Copy xlog from archival storage to XLOGDIR
         */
-       rc = system(xlogRestoreCmd);
-
-       PostRestoreCommand();
+       switch (archive_type)
+       {
+               case ARCHIVE_TYPE_WAL_SEGMENT:
+                       ret = shell_restore_wal_segment(xlogfname, xlogpath,
+                                                                               
        lastRestartPointFname);
+                       break;
+               case ARCHIVE_TYPE_TIMELINE_HISTORY:
+                       ret = shell_restore_timeline_history(xlogfname, 
xlogpath);
+                       break;
+               case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+                       ret = shell_restore_timeline_history(xlogfname, 
xlogpath);
+                       break;
+       }
 
-       pgstat_report_wait_end();
-       pfree(xlogRestoreCmd);
+       HandleStartupProcShutdownRequests();
 
-       if (rc == 0)
+       if (ret)
        {
                /*
                 * command apparently succeeded, but let's make sure the file is
@@ -237,37 +243,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
                }
        }
 
-       /*
-        * Remember, we rollforward UNTIL the restore fails so failure here is
-        * just part of the process... that makes it difficult to determine
-        * whether the restore failed because there isn't an archive to restore,
-        * or because the administrator has specified the restore program
-        * incorrectly.  We have to assume the former.
-        *
-        * 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!) It's
-        * essential to abort on child SIGINT and SIGQUIT, because per spec
-        * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
-        * those it's a good bet we should have gotten it too.
-        *
-        * On SIGTERM, assume we have received a fast shutdown request, and exit
-        * cleanly. It's pure chance whether we receive the SIGTERM first, or 
the
-        * child process. If we receive it first, the signal handler will call
-        * proc_exit, otherwise we do it here. If we or the child process 
received
-        * SIGTERM for any other reason than a fast shutdown request, postmaster
-        * will perform an immediate shutdown when it sees us exiting
-        * unexpectedly.
-        *
-        * We treat hard shell errors such as "command not found" as fatal, too.
-        */
-       if (wait_result_is_signal(rc, SIGTERM))
-               proc_exit(1);
-
-       ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
-                       (errmsg("could not restore file \"%s\" from archive: 
%s",
-                                       xlogfname, wait_result_to_str(rc))));
-
 not_available:
 
        /*
@@ -281,74 +256,6 @@ not_available:
        return false;
 }
 
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
-                                          bool failOnSignal, uint32 
wait_event_info)
-{
-       char       *xlogRecoveryCmd;
-       char            lastRestartPointFname[MAXPGPATH];
-       int                     rc;
-       XLogSegNo       restartSegNo;
-       XLogRecPtr      restartRedoPtr;
-       TimeLineID      restartTli;
-
-       Assert(command && commandName);
-
-       /*
-        * 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);
-
-       /*
-        * construct the command to be executed
-        */
-       xlogRecoveryCmd = replace_percent_placeholders(command, commandName, 
"r", lastRestartPointFname);
-
-       ereport(DEBUG3,
-                       (errmsg_internal("executing %s \"%s\"", commandName, 
command)));
-
-       /*
-        * execute the constructed command
-        */
-       fflush(NULL);
-       pgstat_report_wait_start(wait_event_info);
-       rc = system(xlogRecoveryCmd);
-       pgstat_report_wait_end();
-
-       pfree(xlogRecoveryCmd);
-
-       if (rc != 0)
-       {
-               /*
-                * If the failure was due to any sort of signal, it's best to 
punt and
-                * abort recovery.  See comments in RestoreArchivedFile().
-                */
-               ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? 
FATAL : WARNING,
-               /*------
-                  translator: First %s represents a postgresql.conf parameter 
name like
-                 "recovery_end_command", the 2nd is the value of that 
parameter, the
-                 third an already translated error message. */
-                               (errmsg("%s \"%s\": %s", commandName,
-                                               command, 
wait_result_to_str(rc))));
-       }
-}
-
-
 /*
  * A file was restored from the archive under a temporary filename (path),
  * and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/backend/access/transam/xlogrecovery.c 
b/src/backend/access/transam/xlogrecovery.c
index b45b833172..4af31d5739 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4209,7 +4209,8 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
                        if (!RestoreArchivedFile(path, xlogfname,
                                                                         
"RECOVERYXLOG",
                                                                         
wal_segment_size,
-                                                                        
InRedo))
+                                                                        InRedo,
+                                                                        
ARCHIVE_TYPE_WAL_SEGMENT))
                                return -1;
                        break;
 
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 436c04af08..42ff1b303d 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -27,6 +27,7 @@ subdir('port')
 subdir('postmaster')
 subdir('regex')
 subdir('replication')
+subdir('restore')
 subdir('rewrite')
 subdir('statistics')
 subdir('storage')
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index ef6f98ebcd..cc665ce259 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -169,8 +169,7 @@ HandleStartupProcInterrupts(void)
        /*
         * Check if we were requested to exit without finishing recovery.
         */
-       if (shutdown_requested)
-               proc_exit(1);
+       HandleStartupProcShutdownRequests();
 
        /*
         * Emergency bailout if postmaster has died.  This is to avoid the
@@ -194,6 +193,16 @@ HandleStartupProcInterrupts(void)
                ProcessLogMemoryContextInterrupt();
 }
 
+/*
+ * If there is a pending shutdown request, exit.
+ */
+void
+HandleStartupProcShutdownRequests(void)
+{
+       if (shutdown_requested)
+               proc_exit(1);
+}
+
 
 /* --------------------------------
  *             signal handler routines
@@ -274,8 +283,7 @@ PreRestoreCommand(void)
         * shutdown request received just before this.
         */
        in_restore_command = true;
-       if (shutdown_requested)
-               proc_exit(1);
+       HandleStartupProcShutdownRequests();
 }
 
 void
diff --git a/src/backend/restore/Makefile b/src/backend/restore/Makefile
new file mode 100644
index 0000000000..983d8e980f
--- /dev/null
+++ b/src/backend/restore/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for src/backend/restore
+#
+# IDENTIFICATION
+#    src/backend/restore/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/restore
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+       shell_restore.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/restore/meson.build b/src/backend/restore/meson.build
new file mode 100644
index 0000000000..e4275b3d9c
--- /dev/null
+++ b/src/backend/restore/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+backend_sources += files(
+  'shell_restore.c'
+)
diff --git a/src/backend/restore/shell_restore.c 
b/src/backend/restore/shell_restore.c
new file mode 100644
index 0000000000..42291625c1
--- /dev/null
+++ b/src/backend/restore/shell_restore.c
@@ -0,0 +1,245 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These restore functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *       src/backend/restore/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "postmaster/startup.h"
+#include "restore/shell_restore.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static bool shell_restore_file(const char *file, const char *path,
+                                                          const char 
*lastRestartPointFileName);
+static void ExecuteRecoveryCommand(const char *command,
+                                                                  const char 
*commandName,
+                                                                  bool 
failOnSignal,
+                                                                  uint32 
wait_event_info,
+                                                                  const char 
*lastRestartPointFileName);
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a WAL segment.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_wal_segment(const char *file, const char *path,
+                                                 const char 
*lastRestartPointFileName)
+{
+       return shell_restore_file(file, path, lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a timeline
+ * history file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_timeline_history(const char *file, const char *path)
+{
+       char            lastRestartPointFname[MAXPGPATH];
+
+       XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
+       return shell_restore_file(file, path, lastRestartPointFname);
+}
+
+/*
+ * Check whether restore_command is supplied.
+ */
+bool
+shell_restore_file_configured(void)
+{
+       return recoveryRestoreCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+static bool
+shell_restore_file(const char *file, const char *path,
+                                  const char *lastRestartPointFileName)
+{
+       char       *cmd;
+       int                     rc;
+
+       /* Build the restore command to execute */
+       cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+                                                         
lastRestartPointFileName);
+
+       ereport(DEBUG3,
+                       (errmsg_internal("executing restore command \"%s\"", 
cmd)));
+
+       fflush(NULL);
+       pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
+       /*
+        * PreRestoreCommand() informs the SIGTERM handler for the startup 
process
+        * that it should proc_exit() right away.  This is done for the duration
+        * of the system() call because there isn't a good way to break out 
while
+        * it is executing.  Since we might call proc_exit() in a signal 
handler,
+        * it is best to put any additional logic before or after the
+        * PreRestoreCommand()/PostRestoreCommand() section.
+        */
+       PreRestoreCommand();
+
+       /*
+        * Copy xlog from archival storage to XLOGDIR
+        */
+       rc = system(cmd);
+
+       PostRestoreCommand();
+
+       pgstat_report_wait_end();
+       pfree(cmd);
+
+       /*
+        * Remember, we rollforward UNTIL the restore fails so failure here is
+        * just part of the process... that makes it difficult to determine
+        * whether the restore failed because there isn't an archive to restore,
+        * or because the administrator has specified the restore program
+        * incorrectly.  We have to assume the former.
+        *
+        * 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!) It's
+        * essential to abort on child SIGINT and SIGQUIT, because per spec
+        * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+        * those it's a good bet we should have gotten it too.
+        *
+        * On SIGTERM, assume we have received a fast shutdown request, and exit
+        * cleanly. It's pure chance whether we receive the SIGTERM first, or 
the
+        * child process. If we receive it first, the signal handler will call
+        * proc_exit, otherwise we do it here. If we or the child process 
received
+        * SIGTERM for any other reason than a fast shutdown request, postmaster
+        * will perform an immediate shutdown when it sees us exiting
+        * unexpectedly.
+        *
+        * We treat hard shell errors such as "command not found" as fatal, too.
+        */
+       if (rc != 0)
+       {
+               if (wait_result_is_signal(rc, SIGTERM))
+                       proc_exit(1);
+
+               ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+                               (errmsg("could not restore file \"%s\" from 
archive: %s",
+                                               file, wait_result_to_str(rc))));
+       }
+
+       return (rc == 0);
+}
+
+/*
+ * Check whether archive_cleanup_command is supplied.
+ */
+bool
+shell_archive_cleanup_configured(void)
+{
+       return archiveCleanupCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based archive cleanup command.
+ */
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+       ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+                                                  false, 
WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+                                                  lastRestartPointFileName);
+}
+
+/*
+ * Check whether recovery_end_command is supplied.
+ */
+bool
+shell_recovery_end_configured(void)
+{
+       return recoveryEndCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based end-of-recovery command.
+ */
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+       ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+                                                  
WAIT_EVENT_RECOVERY_END_COMMAND,
+                                                  lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+                                          bool failOnSignal, uint32 
wait_event_info,
+                                          const char *lastRestartPointFileName)
+{
+       char       *xlogRecoveryCmd;
+       int                     rc;
+
+       Assert(command && commandName);
+
+       /*
+        * construct the command to be executed
+        */
+       xlogRecoveryCmd = replace_percent_placeholders(command, commandName, 
"r",
+                                                                               
                   lastRestartPointFileName);
+
+       ereport(DEBUG3,
+                       (errmsg_internal("executing %s \"%s\"", commandName, 
command)));
+
+       /*
+        * execute the constructed command
+        */
+       fflush(NULL);
+       pgstat_report_wait_start(wait_event_info);
+       rc = system(xlogRecoveryCmd);
+       pgstat_report_wait_end();
+
+       pfree(xlogRecoveryCmd);
+
+       if (rc != 0)
+       {
+               /*
+                * If the failure was due to any sort of signal, it's best to 
punt and
+                * abort recovery.  See comments in shell_restore().
+                */
+               ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? 
FATAL : WARNING,
+               /*------
+                  translator: First %s represents a postgresql.conf parameter 
name like
+                 "recovery_end_command", the 2nd is the value of that 
parameter, the
+                 third an already translated error message. */
+                               (errmsg("%s \"%s\": %s", commandName,
+                                               command, 
wait_result_to_str(rc))));
+       }
+}
diff --git a/src/include/Makefile b/src/include/Makefile
index b8b576a4de..7e20f3e4c2 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -20,7 +20,7 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
 SUBDIRS = access archive bootstrap catalog commands common datatype \
        executor fe_utils foreign jit \
        lib libpq mb nodes optimizer parser partitioning postmaster \
-       regex replication rewrite \
+       regex replication restore rewrite \
        statistics storage tcop snowball snowball/libstemmer tsearch \
        tsearch/dicts utils port port/atomics port/win32 port/win32_msvc \
        port/win32_msvc/sys port/win32/arpa port/win32/netinet \
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 0701475fb4..67e0fdcdec 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -17,9 +17,16 @@
 
 #include "access/xlogdefs.h"
 
+typedef enum ArchiveType
+{
+       ARCHIVE_TYPE_WAL_SEGMENT,
+       ARCHIVE_TYPE_TIMELINE_HISTORY,
+       ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS
+} ArchiveType;
+
 extern bool RestoreArchivedFile(char *path, const char *xlogfname,
                                                                const char 
*recovername, off_t expectedSize,
-                                                               bool 
cleanupEnabled);
+                                                               bool 
cleanupEnabled, ArchiveType archive_type);
 extern void ExecuteRecoveryCommand(const char *command, const char 
*commandName,
                                                                   bool 
failOnSignal, uint32 wait_event_info);
 extern void KeepFileRestoredFromArchive(const char *path, const char 
*xlogfname);
diff --git a/src/include/meson.build b/src/include/meson.build
index a28f115d86..3d30cf7972 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -150,6 +150,7 @@ header_subdirs = [
   'postmaster',
   'regex',
   'replication',
+  'restore',
   'rewrite',
   'statistics',
   'storage',
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index dde7ebde88..6ba1e48c02 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -26,6 +26,7 @@
 extern PGDLLIMPORT int log_startup_progress_interval;
 
 extern void HandleStartupProcInterrupts(void);
+extern void HandleStartupProcShutdownRequests(void);
 extern void StartupProcessMain(char *startup_data, size_t startup_data_len) 
pg_attribute_noreturn();
 extern void PreRestoreCommand(void);
 extern void PostRestoreCommand(void);
diff --git a/src/include/restore/shell_restore.h 
b/src/include/restore/shell_restore.h
new file mode 100644
index 0000000000..28b5b7bc92
--- /dev/null
+++ b/src/include/restore/shell_restore.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.h
+ *             Exports for restoring via shell.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/shell_restore.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_RESTORE_H
+#define _SHELL_RESTORE_H
+
+extern bool shell_restore_file_configured(void);
+extern bool shell_restore_wal_segment(const char *file, const char *path,
+                                                                         const 
char *lastRestartPointFileName);
+extern bool shell_restore_timeline_history(const char *file, const char *path);
+
+extern bool shell_archive_cleanup_configured(void);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+
+extern bool shell_recovery_end_configured(void);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
+#endif                                                 /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 46a84c5714..b5e4608817 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -136,6 +136,7 @@ ArchiveOpts
 ArchiveShutdownCB
 ArchiveStartupCB
 ArchiveStreamState
+ArchiveType
 ArchiverOutput
 ArchiverStage
 ArrayAnalyzeExtraData
-- 
2.39.3 (Apple Git-146)

>From 07f0503c18f3b2348ede29244a040e1bee052e4a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandboss...@gmail.com>
Date: Wed, 15 Feb 2023 14:45:17 -0800
Subject: [PATCH v23 3/5] rename archive-modules.sgml to
 archive-and-restore-modules.sgml

---
 .../{archive-modules.sgml => archive-and-restore-modules.sgml}  | 0
 doc/src/sgml/filelist.sgml                                      | 2 +-
 doc/src/sgml/postgres.sgml                                      | 2 +-
 3 files changed, 2 insertions(+), 2 deletions(-)
 rename doc/src/sgml/{archive-modules.sgml => archive-and-restore-modules.sgml} 
(100%)

diff --git a/doc/src/sgml/archive-modules.sgml 
b/doc/src/sgml/archive-and-restore-modules.sgml
similarity index 100%
rename from doc/src/sgml/archive-modules.sgml
rename to doc/src/sgml/archive-and-restore-modules.sgml
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 38ec362d8f..be387bd7aa 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -101,7 +101,7 @@
 <!ENTITY custom-scan SYSTEM "custom-scan.sgml">
 <!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
 <!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY archive-and-restore-modules SYSTEM "archive-and-restore-modules.sgml">
 <!ENTITY protocol   SYSTEM "protocol.sgml">
 <!ENTITY sources    SYSTEM "sources.sgml">
 <!ENTITY storage    SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index ec9f90e283..d668ad89f6 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -227,7 +227,7 @@ break is not needed in a wider output rendering.
   &bgworker;
   &logicaldecoding;
   &replication-origins;
-  &archive-modules;
+  &archive-and-restore-modules;
 
  </part>
 
-- 
2.39.3 (Apple Git-146)

>From 55d201782396d051f580eea81a73d7cb412fc67b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandboss...@gmail.com>
Date: Wed, 15 Feb 2023 14:48:36 -0800
Subject: [PATCH v23 4/5] restructure archive modules docs in preparation for
 restore modules

---
 doc/src/sgml/archive-and-restore-modules.sgml | 242 +++++++++---------
 1 file changed, 128 insertions(+), 114 deletions(-)

diff --git a/doc/src/sgml/archive-and-restore-modules.sgml 
b/doc/src/sgml/archive-and-restore-modules.sgml
index 10ec96eae9..6e368290b9 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -1,9 +1,9 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
+<!-- doc/src/sgml/archive-and-restore-modules.sgml -->
 
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
-  <primary>Archive Modules</primary>
+<chapter id="archive-and-restore-modules">
+ <title>Archive and Restore Modules</title>
+ <indexterm zone="archive-and-restore-modules">
+  <primary>Archive and Restore Modules</primary>
  </indexterm>
 
  <para>
@@ -14,45 +14,52 @@
   performant.
  </para>
 
- <para>
-  When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
-  will submit completed WAL files to the module, and the server will avoid
-  recycling or removing these WAL files until the module indicates that the 
files
-  were successfully archived.  It is ultimately up to the module to decide what
-  to do with each WAL file, but many recommendations are listed at
-  <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
-  Archiving modules must at least consist of an initialization function (see
-  <xref linkend="archive-module-init"/>) and the required callbacks (see
-  <xref linkend="archive-module-callbacks"/>).  However, archive modules are
-  also permitted to do much more (e.g., declare GUCs and register background
-  workers).
- </para>
-
  <para>
   The <filename>contrib/basic_archive</filename> module contains a working
   example, which demonstrates some useful techniques.
  </para>
 
- <sect1 id="archive-module-init">
-  <title>Initialization Functions</title>
-  <indexterm zone="archive-module-init">
-   <primary>_PG_archive_module_init</primary>
+ <sect1 id="archive-modules">
+  <title>Archive Modules</title>
+  <indexterm zone="archive-modules">
+   <primary>Archive Modules</primary>
   </indexterm>
+
+  <para>
+   When a custom <xref linkend="guc-archive-library"/> is configured,
+   PostgreSQL will submit completed WAL files to the module, and the server
+   will avoid recycling or removing these WAL files until the module indicates
+   that the files were successfully archived.  It is ultimately up to the
+   module to decide what to do with each WAL file, but many recommendations are
+   listed at <xref linkend="backup-archiving-wal"/>.
+  </para>
+
   <para>
-   An archive library is loaded by dynamically loading a shared library with 
the
-   <xref linkend="guc-archive-library"/>'s name as the library base name.  The
-   normal library search path is used to locate the library.  To provide the
-   required archive module callbacks and to indicate that the library is
-   actually an archive module, it needs to provide a function named
-   <function>_PG_archive_module_init</function>.  The result of the function
-   must be a pointer to a struct of type
-   <structname>ArchiveModuleCallbacks</structname>, which contains everything
-   that the core code needs to know to make use of the archive module.  The
-   return value needs to be of server lifetime, which is typically achieved by
-   defining it as a <literal>static const</literal> variable in global scope.
+   Archiving modules must at least consist of an initialization function (see
+   <xref linkend="archive-module-init"/>) and the required callbacks (see
+   <xref linkend="archive-module-callbacks"/>).  However, archive modules are
+   also permitted to do much more (e.g., declare GUCs and register background
+   workers).
+  </para>
+
+  <sect2 id="archive-module-init">
+   <title>Initialization Functions</title>
+   <indexterm zone="archive-module-init">
+    <primary>_PG_archive_module_init</primary>
+   </indexterm>
+
+   <para>
+    An archive library is loaded by dynamically loading a shared library with
+    the <xref linkend="guc-archive-library"/>'s name as the library base name.
+    The normal library search path is used to locate the library.  To provide
+    the required archive module callbacks and to indicate that the library is
+    actually an archive module, it needs to provide a function named
+    <function>_PG_archive_module_init</function>.  The result of the function
+    must be a pointer to a struct of type
+    <structname>ArchiveModuleCallbacks</structname>, which contains everything
+    that the core code needs to know to make use of the archive module.  The
+    return value needs to be of server lifetime, which is typically achieved by
+    defining it as a <literal>static const</literal> variable in global scope.
 
 <programlisting>
 typedef struct ArchiveModuleCallbacks
@@ -65,112 +72,119 @@ typedef struct ArchiveModuleCallbacks
 typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
 </programlisting>
 
-   Only the <function>archive_file_cb</function> callback is required.  The
-   others are optional.
-  </para>
- </sect1>
+    Only the <function>archive_file_cb</function> callback is required.  The
+    others are optional.
+   </para>
+  </sect2>
 
- <sect1 id="archive-module-callbacks">
-  <title>Archive Module Callbacks</title>
-  <para>
-   The archive callbacks define the actual archiving behavior of the module.
-   The server will call them as required to process each individual WAL file.
-  </para>
+  <sect2 id="archive-module-callbacks">
+   <title>Archive Module Callbacks</title>
 
-  <sect2 id="archive-module-startup">
-   <title>Startup Callback</title>
    <para>
-    The <function>startup_cb</function> callback is called shortly after the
-    module is loaded.  This callback can be used to perform any additional
-    initialization required.  If the archive module has any state, it can use
-    <structfield>state->private_data</structfield> to store it.
+    The archive callbacks define the actual archiving behavior of the module.
+    The server will call them as required to process each individual WAL file.
+   </para>
+
+   <sect3 id="archive-module-startup">
+    <title>Startup Callback</title>
+
+    <para>
+     The <function>startup_cb</function> callback is called shortly after the
+     module is loaded.  This callback can be used to perform any additional
+     initialization required.  If the archive module has any state, it can use
+     <structfield>state->private_data</structfield> to store it.
 
 <programlisting>
 typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
 </programlisting>
-   </para>
-  </sect2>
+    </para>
+   </sect3>
 
-  <sect2 id="archive-module-check">
-   <title>Check Callback</title>
-   <para>
-    The <function>check_configured_cb</function> callback is called to 
determine
-    whether the module is fully configured and ready to accept WAL files (e.g.,
-    its configuration parameters are set to valid values).  If no
-    <function>check_configured_cb</function> is defined, the server always
-    assumes the module is configured.
+   <sect3 id="archive-module-check">
+    <title>Check Callback</title>
+
+    <para>
+     The <function>check_configured_cb</function> callback is called to
+     determine whether the module is fully configured and ready to accept WAL
+     files (e.g., its configuration parameters are set to valid values).  If no
+     <function>check_configured_cb</function> is defined, the server always
+     assumes the module is configured.
 
 <programlisting>
 typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
 </programlisting>
 
-    If <literal>true</literal> is returned, the server will proceed with
-    archiving the file by calling the <function>archive_file_cb</function>
-    callback.  If <literal>false</literal> is returned, archiving will not
-    proceed, and the archiver will emit the following message to the server 
log:
+     If <literal>true</literal> is returned, the server will proceed with
+     archiving the file by calling the <function>archive_file_cb</function>
+     callback.  If <literal>false</literal> is returned, archiving will not
+     proceed, and the archiver will emit the following message to the server
+     log:
 <screen>
 WARNING:  archive_mode enabled, yet archiving is not configured
 </screen>
-    In the latter case, the server will periodically call this function, and
-    archiving will proceed only when it returns <literal>true</literal>.
-   </para>
-
-   <note>
-    <para>
-     When returning <literal>false</literal>, it may be useful to append some
-     additional information to the generic warning message.  To do that, 
provide
-     a message to the <function>arch_module_check_errdetail</function> macro
-     before returning <literal>false</literal>.  Like
-     <function>errdetail()</function>, this macro accepts a format string
-     followed by an optional list of arguments.  The resulting string will be
-     emitted as the <literal>DETAIL</literal> line of the warning message.
+     In the latter case, the server will periodically call this function, and
+     archiving will proceed only when it returns <literal>true</literal>.
     </para>
-   </note>
-  </sect2>
 
-  <sect2 id="archive-module-archive">
-   <title>Archive Callback</title>
-   <para>
-    The <function>archive_file_cb</function> callback is called to archive a
-    single WAL file.
+    <note>
+     <para>
+      When returning <literal>false</literal>, it may be useful to append some
+      additional information to the generic warning message.  To do that,
+      provide a message to the <function>arch_module_check_errdetail</function>
+      macro before returning <literal>false</literal>.  Like
+      <function>errdetail()</function>, this macro accepts a format string
+      followed by an optional list of arguments.  The resulting string will be
+      emitted as the <literal>DETAIL</literal> line of the warning message.
+     </para>
+    </note>
+   </sect3>
+
+   <sect3 id="archive-module-archive">
+    <title>Archive Callback</title>
+
+    <para>
+     The <function>archive_file_cb</function> callback is called to archive a
+     single WAL file.
 
 <programlisting>
 typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, 
const char *path);
 </programlisting>
 
-    If <literal>true</literal> is returned, the server proceeds as if the file
-    was successfully archived, which may include recycling or removing the
-    original WAL file.  If <literal>false</literal> is returned or an error is 
thrown, the server will
-    keep the original WAL file and retry archiving later.
-    <replaceable>file</replaceable> will contain just the file name of the WAL
-    file to archive, while <replaceable>path</replaceable> contains the full
-    path of the WAL file (including the file name).
-   </para>
-
-   <note>
-    <para>
-     The <function>archive_file_cb</function> callback is called in a
-     short-lived memory context that will be reset between invocations.  If you
-     need longer-lived storage, create a memory context in the module's
-     <function>startup_cb</function> callback.
+     If <literal>true</literal> is returned, the server proceeds as if the file
+     was successfully archived, which may include recycling or removing the
+     original WAL file.  If <literal>false</literal> is returned or an error is
+     thrown, the server will keep the original WAL file and retry archiving
+     later.  <replaceable>file</replaceable> will contain just the file name of
+     the WAL file to archive, while <replaceable>path</replaceable> contains 
the
+     full path of the WAL file (including the file name).
     </para>
-   </note>
-  </sect2>
 
-  <sect2 id="archive-module-shutdown">
-   <title>Shutdown Callback</title>
-   <para>
-    The <function>shutdown_cb</function> callback is called when the archiver
-    process exits (e.g., after an error) or the value of
-    <xref linkend="guc-archive-library"/> changes.  If no
-    <function>shutdown_cb</function> is defined, no special action is taken in
-    these situations.  If the archive module has any state, this callback 
should
-    free it to avoid leaks.
+    <note>
+     <para>
+      The <function>archive_file_cb</function> callback is called in a
+      short-lived memory context that will be reset between invocations.  If 
you
+      need longer-lived storage, create a memory context in the module's
+      <function>startup_cb</function> callback.
+     </para>
+    </note>
+   </sect3>
+
+   <sect3 id="archive-module-shutdown">
+    <title>Shutdown Callback</title>
+
+    <para>
+     The <function>shutdown_cb</function> callback is called when the archiver
+     process exits (e.g., after an error) or the value of
+     <xref linkend="guc-archive-library"/> changes.  If no
+     <function>shutdown_cb</function> is defined, no special action is taken in
+     these situations.  If the archive module has any state, this callback
+     should free it to avoid leaks.
 
 <programlisting>
 typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
 </programlisting>
-   </para>
+    </para>
+   </sect3>
   </sect2>
  </sect1>
 </chapter>
-- 
2.39.3 (Apple Git-146)

>From 6812b4add91d8a56984ab4c9bdfb0a19397ea9f3 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nat...@postgresql.org>
Date: Mon, 20 May 2024 21:27:39 -0500
Subject: [PATCH v23 5/5] introduce restore_library

---
 contrib/basic_archive/Makefile                |   2 +
 contrib/basic_archive/basic_archive.c         | 153 +++++++-
 contrib/basic_archive/meson.build             |   5 +
 contrib/basic_archive/t/001_restore.pl        |  44 +++
 doc/src/sgml/archive-and-restore-modules.sgml | 356 +++++++++++++++++-
 doc/src/sgml/backup.sgml                      |  40 +-
 doc/src/sgml/basic-archive.sgml               |  31 +-
 doc/src/sgml/config.sgml                      |  53 ++-
 doc/src/sgml/high-availability.sgml           |  18 +-
 src/backend/access/transam/xlog.c             |  20 +-
 src/backend/access/transam/xlogarchive.c      |  96 ++++-
 src/backend/access/transam/xlogrecovery.c     |  14 +-
 src/backend/postmaster/checkpointer.c         |  54 +++
 src/backend/postmaster/startup.c              |  44 ++-
 src/backend/restore/shell_restore.c           |  85 ++++-
 src/backend/utils/misc/guc_tables.c           |  11 +
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/include/restore/restore_module.h          | 143 +++++++
 src/include/restore/shell_restore.h           |  16 +-
 src/tools/pgindent/typedefs.list              |   2 +
 20 files changed, 1104 insertions(+), 84 deletions(-)
 create mode 100644 contrib/basic_archive/t/001_restore.pl
 create mode 100644 src/include/restore/restore_module.h

diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 100ed81f12..e61f7d676f 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -10,6 +10,8 @@ REGRESS_OPTS = --temp-config 
$(top_srcdir)/contrib/basic_archive/basic_archive.c
 # typical installcheck users do not have (e.g. buildfarm clients).
 NO_INSTALLCHECK = 1
 
+TAP_TESTS = 1
+
 ifdef USE_PGXS
 PG_CONFIG = pg_config
 PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c 
b/contrib/basic_archive/basic_archive.c
index 028cf51c25..e84bad27d2 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
  * a file is successfully archived and then the system crashes before
  * a durable record of the success has been made.
  *
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ *             cp /path/to/archivedir/%f %p
+ *
  * Copyright (c) 2022-2024, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
@@ -33,6 +38,7 @@
 #include "archive/archive_module.h"
 #include "common/int.h"
 #include "miscadmin.h"
+#include "restore/restore_module.h"
 #include "storage/copydir.h"
 #include "storage/fd.h"
 #include "utils/guc.h"
@@ -46,6 +52,16 @@ static bool basic_archive_configured(ArchiveModuleState 
*state);
 static bool basic_archive_file(ArchiveModuleState *state, const char *file, 
const char *path);
 static bool check_archive_directory(char **newval, void **extra, GucSource 
source);
 static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_configured(RestoreModuleState *state);
+static bool basic_restore_wal_segment(RestoreModuleState *state,
+                                                                         const 
char *file, const char *path,
+                                                                         const 
char *lastRestartPointFileName);
+static bool basic_restore_timeline_history(RestoreModuleState *state,
+                                                                               
   const char *file, const char *path);
+static bool basic_restore_file(const char *file, const char *path);
+static bool basic_restore_timeline_history_exists(RestoreModuleState *state,
+                                                                               
                  const char *file,
+                                                                               
                  const char *path);
 
 static const ArchiveModuleCallbacks basic_archive_callbacks = {
        .startup_cb = NULL,
@@ -54,6 +70,21 @@ static const ArchiveModuleCallbacks basic_archive_callbacks 
= {
        .shutdown_cb = NULL
 };
 
+static const RestoreModuleCallbacks basic_restore_callbacks = {
+       .startup_cb = NULL,
+       .restore_wal_segment_configured_cb = basic_restore_configured,
+       .restore_wal_segment_cb = basic_restore_wal_segment,
+       .restore_timeline_history_configured_cb = basic_restore_configured,
+       .restore_timeline_history_cb = basic_restore_timeline_history,
+       .timeline_history_exists_configured_cb = basic_restore_configured,
+       .timeline_history_exists_cb = basic_restore_timeline_history_exists,
+       .archive_cleanup_configured_cb = NULL,
+       .archive_cleanup_cb = NULL,
+       .recovery_end_configured_cb = NULL,
+       .recovery_end_cb = NULL,
+       .shutdown_cb = NULL
+};
+
 /*
  * _PG_init
  *
@@ -63,7 +94,7 @@ void
 _PG_init(void)
 {
        DefineCustomStringVariable("basic_archive.archive_directory",
-                                                          
gettext_noop("Archive file destination directory."),
+                                                          
gettext_noop("Archive file source/destination directory."),
                                                           NULL,
                                                           &archive_directory,
                                                           "",
@@ -85,6 +116,17 @@ _PG_archive_module_init(void)
        return &basic_archive_callbacks;
 }
 
+/*
+ * _PG_restore_module_init
+ *
+ * Returns the module's restore callbacks.
+ */
+const RestoreModuleCallbacks *
+_PG_restore_module_init(void)
+{
+       return &basic_restore_callbacks;
+}
+
 /*
  * check_archive_directory
  *
@@ -307,3 +349,112 @@ compare_files(const char *file1, const char *file2)
 
        return ret;
 }
+
+/*
+ * basic_restore_configured
+ *
+ * Checks that archive_directory is not blank.
+ */
+static bool
+basic_restore_configured(RestoreModuleState *state)
+{
+       return archive_directory != NULL && archive_directory[0] != '\0';
+}
+
+/*
+ * basic_restore_wal_segment
+ *
+ * Retrieves one archived WAL segment.
+ */
+static bool
+basic_restore_wal_segment(RestoreModuleState *state,
+                                                 const char *file, const char 
*path,
+                                                 const char 
*lastRestartPointFileName)
+{
+       return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_timeline_history
+ *
+ * Retrieves one timeline history file.
+ */
+static bool
+basic_restore_timeline_history(RestoreModuleState *state,
+                                                          const char *file, 
const char *path)
+{
+       return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file.
+ */
+static bool
+basic_restore_file(const char *file, const char *path)
+{
+       char            archive_path[MAXPGPATH];
+       struct stat st;
+
+       ereport(DEBUG1,
+                       (errmsg("restoring \"%s\" via basic_archive",
+                                       file)));
+
+       /*
+        * If the file doesn't exist, return false to indicate that there are no
+        * more files to restore.
+        */
+       snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+       if (stat(archive_path, &st) != 0)
+       {
+               int                     elevel = (errno == ENOENT) ? DEBUG1 : 
ERROR;
+
+               ereport(elevel,
+                               (errcode_for_file_access(),
+                                errmsg("could not stat file \"%s\": %m",
+                                               archive_path)));
+               return false;
+       }
+
+       copy_file(archive_path, path);
+
+       ereport(DEBUG1,
+                       (errmsg("restored \"%s\" via basic_archive",
+                                       file)));
+       return true;
+}
+
+/*
+ * basic_restore_timeline_history_exists
+ *
+ * Check whether a timeline history file is present in the archive directory.
+ */
+static bool
+basic_restore_timeline_history_exists(RestoreModuleState *state,
+                                                                         const 
char *file, const char *path)
+{
+       char            archive_path[MAXPGPATH];
+       struct stat st;
+
+       ereport(DEBUG1,
+                       (errmsg("checking existence of \"%s\" via 
basic_archive",
+                                       file)));
+
+       snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+       if (stat(archive_path, &st) != 0)
+       {
+               int                     elevel = (errno == ENOENT) ? DEBUG1 : 
ERROR;
+
+               ereport(elevel,
+                               (errcode_for_file_access(),
+                                errmsg("could not stat file \"%s\": %m",
+                                               archive_path)));
+               return false;
+       }
+
+       ereport(DEBUG1,
+                       (errmsg("verified existence of \"%s\" via 
basic_archive",
+                                       file)));
+       return true;
+}
diff --git a/contrib/basic_archive/meson.build 
b/contrib/basic_archive/meson.build
index cc2f62bf36..8e36fa7ed6 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -31,4 +31,9 @@ tests += {
     # typical installcheck users do not have (e.g. buildfarm clients).
     'runningcheck': false,
   },
+  'tap': {
+    'tests': [
+      't/001_restore.pl',
+    ],
+  },
 }
diff --git a/contrib/basic_archive/t/001_restore.pl 
b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..6c01f736cf
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = 
'$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = 
'$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL is replayed
+my $result = $restore->poll_query_until("postgres", "SELECT count(*) = 1 FROM 
test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml 
b/doc/src/sgml/archive-and-restore-modules.sgml
index 6e368290b9..bcfa88dee0 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -8,15 +8,17 @@
 
  <para>
   PostgreSQL provides infrastructure to create custom modules for continuous
-  archiving (see <xref linkend="continuous-archiving"/>).  While archiving via
-  a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
-  simpler, a custom archive module will often be considerably more robust and
-  performant.
+  archiving and recovery (see <xref linkend="continuous-archiving"/>).  While a
+  shell command (i.e., <xref linkend="guc-archive-command"/>,
+  <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+  often be considerably more robust and performant.
  </para>
 
  <para>
   The <filename>contrib/basic_archive</filename> module contains a working
-  example, which demonstrates some useful techniques.
+  example, which demonstrates some useful techniques.  As demonstrated in
+  <filename>basic_archive</filename> a single module may serve as both an
+  archive module and a restore module.
  </para>
 
  <sect1 id="archive-modules">
@@ -187,4 +189,348 @@ typedef void (*ArchiveShutdownCB) (ArchiveModuleState 
*state);
    </sect3>
   </sect2>
  </sect1>
+
+ <sect1 id="restore-modules">
+  <title>Restore Modules</title>
+  <indexterm zone="restore-modules">
+   <primary>Restore Modules</primary>
+  </indexterm>
+
+  <para>
+   When a custom <xref linkend="guc-restore-library"/> is configured,
+   PostgreSQL will use the module for restore actions.  It is
+   ultimately up to the module to decide how to accomplish each task,
+   but some recommendations are listed at
+   <xref linkend="backup-pitr-recovery"/>.
+  </para>
+
+  <para>
+   Restore modules must at least consist of an initialization function
+   (see <xref linkend="restore-module-init"/>) and the required callbacks (see
+   <xref linkend="restore-module-callbacks"/>).  However, restore modules are
+   also permitted to do much more (e.g., declare GUCs and register background
+   workers).
+  </para>
+
+  <sect2 id="restore-module-init">
+   <title>Initialization Functions</title>
+   <indexterm zone="restore-module-init">
+    <primary>_PG_restore_module_init</primary>
+   </indexterm>
+
+   <para>
+    An restore library is loaded by dynamically loading a shared library with
+    the <xref linkend="guc-restore-library"/>'s name as the library base name.
+    The normal library search path is used to locate the library.  To provide
+    the required restore module callbacks and to indicate that the library is
+    actually a restore module, it needs to provide a function named
+    <function>_PG_restore_module_init</function>.  The result of the function
+    must be a pointer to a struct of type
+    <structname>RestoreModuleCallbacks</structname>, which contains everything
+    that the core code needs to know how to make use of the restore module.
+    The return value needs to be of server lifetime, which is typically
+    achieved by defining it as a <literal>static const</literal> variable in
+    global scope.
+
+<programlisting>
+typedef struct RestoreModuleCallbacks
+{
+    RestoreStartupCB startup_cb;
+    RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+    RestoreWalSegmentCB restore_wal_segment_cb;
+    RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+    RestoreTimelineHistoryCB restore_timeline_history_cb;
+    TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+    TimelineHistoryExistsCB timeline_history_exists_cb;
+    ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+    ArchiveCleanupCB archive_cleanup_cb;
+    RecoveryEndConfiguredCB recovery_end_configured_cb;
+    RecoveryEndCB recovery_end_cb;
+    RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+</programlisting>
+
+    The <function>restore_wal_segment_configured_cb</function>,
+    <function>restore_wal_segment_cb</function>,
+    <function>restore_timeline_history_configured_cb</function>,
+    <function>restore_timeline_history_cb</function>,
+    <function>timeline_history_exists_configured_cb</function>, and
+    <function>timeline_history_exists_cb</function> callbacks are required for
+    archive recovery but optional for streaming replication.  The others are
+    always optional.
+   </para>
+  </sect2>
+
+  <sect2 id="restore-module-callbacks">
+   <title>Restore Module Callbacks</title>
+
+   <para>
+    The restore callbacks define the actual behavior of the module.  The server
+    will call them as required to execute restore actions.
+   </para>
+
+   <sect3 id="restore-module-startup">
+    <title>Startup Callback</title>
+
+    <para>
+     The <function>startup_cb</function> callback is called shortly after the
+     module is loaded.  This callback can be used to perform any additional
+     initialization required.  If the restore module has state, it can use
+     <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+</programlisting>
+    </para>
+   </sect3>
+
+   <sect3 id="restore-module-restore-wal-segment-configured">
+    <title>Restore WAL Segment Configured Callback</title>
+    <para>
+     The <function>restore_wal_segment_configured_cb</function> callback is
+     called to determine whether the module is fully configured and ready to
+     accept requests to retrieve WAL files (e.g., its configuration parameters
+     are set to valid values).  If no
+     <function>restore_wal_segment_configured_cb</function> is defined, the
+     server always assumes the module is not configured to retrieve WAL files.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentConfiguredCB)  (RestoreModuleState *state);
+</programlisting>
+
+     If <literal>true</literal> is returned, the server will retrieve WAL files
+     by calling the <function>restore_wal_segment_cb</function> callback.  If
+     <literal>false</literal> is returned, the server will not use the
+     <function>restore_wal_segment_cb</function> callback to retrieve WAL
+     files.
+    </para>
+   </sect3>
+
+   <sect3 id="restore-module-restore-wal-segment">
+    <title>Restore WAL Segment Callback</title>
+    <para>
+     The <function>restore_wal_segment_cb</function> callback is called to
+     retrieve a single archived segment of the WAL file series for archive
+     recovery or streaming replication.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state, const char 
*file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+     This callback must return <literal>true</literal> only if the file was
+     successfully retrieved.  If the file is not available in the archives, the
+     callback must return <literal>false</literal>.
+     <replaceable>file</replaceable> will contain just the file name
+     of the WAL file to retrieve, while <replaceable>path</replaceable>
+     contains the destination's relative path (including the file name).
+     <replaceable>lastRestartPointFileName</replaceable> will contain the name
+     of the file containing the last valid restart point.  That is the earliest
+     file that must be kept to allow a restore to be restartable, so this
+     information can be used to truncate the archive to just the minimum
+     required to support restarting from the current restore.
+     <replaceable>lastRestartPointFileName</replaceable> is typically only used
+     by warm-standby configurations (see <xref linkend="warm-standby"/>).  Note
+     that if multiple standby servers are restoring from the same archive
+     directory, you will need to ensure that you do not delete WAL files until
+     they are no longer needed by any of the servers.
+    </para>
+   </sect3>
+
+   <sect3 id="restore-module-restore-timeline-history-configured">
+    <title>Restore Timeline History Configured Callback</title>
+    <para>
+     The <function>restore_timeline_history_configured_cb</function> callback
+     is called to determine whether the module is fully configured and ready to
+     accept requests to retrieve timeline history files (e.g., its
+     configuration parameters are set to valid values).  If no
+     <function>restore_timeline_history_configured_cb</function> is defined,
+     the server always assumes the module is not configured to retrieve
+     timeline history files.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryConfiguredCB)  (RestoreModuleState 
*state);
+</programlisting>
+
+     If <literal>true</literal> is returned, the server will retrieve timeline
+     history files by calling the
+     <function>restore_timeline_history_cb</function> callback.  If
+     <literal>false</literal> is returned, the server will not use the
+     <function>restore_timeline_history_cb</function> callback to retrieve
+     timeline history files.
+    </para>
+   </sect3>
+
+   <sect3 id="restore-module-restore-timeline-history">
+    <title>Restore Timeline History Callback</title>
+    <para>
+     The <function>restore_timeline_history_cb</function> callback is called to
+     retrieve a single archived timeline history file for archive recovery or
+     streaming replication.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state, const 
char *file, const char *path);
+</programlisting>
+
+     This callback must return <literal>true</literal> only if the file was
+     successfully retrieved.  If the file is not available in the archives, the
+     callback must return <literal>false</literal>.
+     <replaceable>file</replaceable> will contain just the file name
+     of the WAL file to retrieve, while <replaceable>path</replaceable>
+     contains the destination's relative path (including the file name).
+    </para>
+   </sect3>
+
+   <sect3 id="restore-module-timeline-history-exists-configured">
+    <title>Timeline History Exists Configured Callback</title>
+    <para>
+     The <function>timeline_history_exists_configured_cb</function> callback is
+     called to determine whether the module is fully configured and ready to
+     accept requests to determine whether a timeline history file exists in the
+     archives (e.g., its configuration parameters are set to valid values).  If
+     no <function>timeline_history_exists_configured_cb</function> is defined,
+     the server always assumes the module is not configured to check whether
+     certain timeline history files exist.
+
+<programlisting>
+typedef bool (*TimelineHistoryConfiguredCB)  (RestoreModuleState *state);
+</programlisting>
+
+     If <literal>true</literal> is returned, the server will check whether
+     certain timeline history files are present in the archives by calling the
+     <function>timeline_history_exists_cb</function> callback.  If
+     <literal>false</literal> is returned, the server will not use the
+     <function>timeline_history_exists_cb</function> callback to check if
+     timeline history files exist in the archives.
+    </para>
+   </sect3>
+
+   <sect3 id="restore-module-timeline-history-exists">
+    <title>Timeline History Exists Callback</title>
+    <para>
+     The <function>timeline_history_exists_cb</function> callback is called to
+     check whether a timeline history file is present in the archives.
+
+<programlisting>
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state, const char 
*file, const char *path);
+</programlisting>
+
+     This callback must return <literal>true</literal> only if the file is
+     present in the archives.  If the file is not available in the archives,
+     the callback must return <literal>false</literal>.
+     <replaceable>file</replaceable> will contain just the file name
+     of the timeline history file.
+    </para>
+
+    <para>
+     Some restore modules might not have a dedicated way to determine whether a
+     timeline history file exists.  For example,
+     <varname>restore_command</varname> only tells the server how to retrieve
+     files.  In this case, the <function>timeline_history_exists_cb</function>
+     callback should copy the file to <replaceable>path</replaceable>, which
+     contains the destination's relative path (including the file name), and
+     the restore module should set
+     <structfield>state->timeline_history_exists_cb_copies</structfield> to
+     <literal>true</literal>.  It is recommended to set this flag in the
+     module's <function>startup_cb</function> callback.  This flag tells the
+     server that it should verify that the file was successfully retrieved
+     after invoking this callback.
+    </para>
+   </sect3>
+
+   <sect3 id="restore-module-archive-cleanup-configured">
+    <title>Archive Cleanup Configured Callback</title>
+    <para>
+     The <function>archive_cleanup_configured_cb</function> callback is called
+     to determine whether the module is fully configured and ready to accept
+     requests to remove old WAL files from the archives (e.g., its
+     configuration parameters are set to valid values).  If no
+     <function>archive_cleanup_configured_cb</function> is defined, the server
+     always assumes the module is not configured to remove old WAL files.
+
+<programlisting>
+typedef bool (*ArchiveCleanupConfiguredCB)  (RestoreModuleState *state);
+</programlisting>
+
+     If <literal>true</literal> is returned, the server will remove old WAL
+     files by calling the <function>archive_cleanup_cb</function> callback.  If
+     <literal>false</literal> is returned, the server will not use the
+     <function>archive_cleanup_cb</function> callback to remove old WAL files.
+    </para>
+   </sect3>
+
+   <sect3 id="restore-module-archive-cleanup">
+    <title>Archive Cleanup Callback</title>
+    <para>
+     The <function>archive_cleanup_cb</function> callback is called at every
+     restart point by the checkpointer process and is intended to provide a
+     mechanism for cleaning up old archived WAL files that are no longer
+     needed by the standby server.
+
+<programlisting>
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state, const char 
*lastRestartPointFileName);
+</programlisting>
+
+     <replaceable>lastRestartPointFileName</replaceable> will contain the
+     name of the file that includes the last valid restart point, like in
+     <link 
linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+    </para>
+   </sect3>
+
+   <sect3 id="restore-module-recovery-end-configured">
+    <title>Recovery End Configured Callback</title>
+    <para>
+     The <function>recovery_end_configured_cb</function> callback is called to
+     determine whether the module is fully configured and ready to execute its
+     <function>recovery_end_cb</function> callback once at the end of recovery
+     (e.g., its configuration parameters are set to valid values).  If no
+     <function>recovery_end_configured_cb</function> is defined, the server
+     always assumes the module is not configured to execute its
+     <function>recovery_end_cb</function> callback.
+
+<programlisting>
+typedef bool (*RecoveryEndConfiguredCB)  (RestoreModuleState *state);
+</programlisting>
+
+     If <literal>true</literal> is returned, the server will execute the
+     <function>recovery_end_cb</function> callback once at the end of recovery.
+     If <literal>false</literal> is returned, the server will not use the
+     <function>recovery_end_cb</function> callback at the end of recovery.
+    </para>
+   </sect3>
+
+   <sect3 id="restore-module-recovery-end">
+    <title>Recovery End Callback</title>
+    <para>
+     The <function>recovery_end_cb</function> callback is called once at the
+     end of recovery and is intended to provide a mechanism for cleanup
+     following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (RestoreModuleState *state, const char 
*lastRestartPointFileName);
+</programlisting>
+
+     <replaceable>lastRestartPointFileName</replaceable> will contain the name
+     of the file containing the last valid restart point, like in
+     <link 
linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+    </para>
+   </sect3>
+
+   <sect3 id="restore-module-shutdown">
+    <title>Shutdown Callback</title>
+    <para>
+     The <function>shutdown_cb</function> callback is called when a process
+     that has loaded the restore module exits (e.g., after an error) or the
+     value of <xref linkend="guc-restore-library"/> changes.  If no
+     <function>shutdown_cb</function> is defined, no special action is taken in
+     these situations.  If the restore module has state, this callback should
+     free it to avoid leaks.
+
+<programlisting>
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+ </programlisting>
+    </para>
+   </sect3>
+  </sect2>
+ </sect1>
 </chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 91da3c26ba..4469b45f60 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1265,9 +1265,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
    <para>
     The key part of all this is to set up a recovery configuration that
     describes how you want to recover and how far the recovery should
-    run.  The one thing that you absolutely must specify is the 
<varname>restore_command</varname>,
+    run.  The one thing that you absolutely must specify is either
+    <varname>restore_command</varname> or a <varname>restore_library</varname>,
     which tells <productname>PostgreSQL</productname> how to retrieve archived
-    WAL file segments.  Like the <varname>archive_command</varname>, this is
+    WAL file segments.
+   </para>
+
+   <para>
+    Like the <varname>archive_library</varname> parameter,
+    <varname>restore_library</varname> is a shared library.  Since such
+    libraries are written in <literal>C</literal>, creating your own may
+    require considerably more effort than writing a shell command.  However,
+    restore modules can be more performance than restoring via shell, and they
+    will have access to many useful server resources.  For more information
+    about creating a <varname>restore_library</varname>, see
+    <xref linkend="restore-modules"/>.
+   </para>
+
+   <para>
+    Like the <varname>archive_command</varname>,
+    <varname>restore_command</varname> is
     a shell command string.  It can contain <literal>%f</literal>, which is
     replaced by the name of the desired WAL file, and <literal>%p</literal>,
     which is replaced by the path name to copy the WAL file to.
@@ -1286,14 +1303,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
    </para>
 
    <para>
-    It is important that the command return nonzero exit status on failure.
-    The command <emphasis>will</emphasis> be called requesting files that are 
not
-    present in the archive; it must return nonzero when so asked.  This is not
-    an error condition.  An exception is that if the command was terminated by
+    It is important that the <varname>restore_command</varname> return nonzero
+    exit status on failure, or, if you are using a
+    <varname>restore_library</varname>, that the restore callbacks return
+    <literal>false</literal> on failure.  The command or library
+    <emphasis>will</emphasis> be called requesting files that are not
+    present in the archive; it must fail when so asked.  This is not
+    an error condition.  An exception is that if the
+    <varname>restore_command</varname> was terminated by
     a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
     part of a database server shutdown) or an error by the shell (such as
     command not found), then recovery will abort and the server will not start
-    up.
+    up.  Likewise, if the restore callbacks provided by the
+    <varname>restore_library</varname> emit an <literal>ERROR</literal> or
+    <literal>FATAL</literal>, recovery will abort and the server won't start.
    </para>
 
    <para>
@@ -1317,7 +1340,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
     close as possible given the available WAL segments).  Therefore, a normal
     recovery will end with a <quote>file not found</quote> message, the exact 
text
     of the error message depending upon your choice of
-    <varname>restore_command</varname>.  You may also see an error message
+    <varname>restore_command</varname> or <varname>restore_library</varname>.
+    You may also see an error message
     at the start of recovery for a file named something like
     <filename>00000001.history</filename>.  This is also normal and does not
     indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..f3a5a502bd 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
  </indexterm>
 
  <para>
-  <filename>basic_archive</filename> is an example of an archive module.  This
-  module copies completed WAL segment files to the specified directory.  This
-  may not be especially useful, but it can serve as a starting point for
-  developing your own archive module.  For more information about archive
-  modules, see <xref linkend="archive-modules"/>.
+  <filename>basic_archive</filename> is an example of an archive and restore
+  module.  This module copies completed WAL segment files to or from the
+  specified directory.  This may not be especially useful, but it can serve as
+  a starting point for developing your own archive and restore modules.  For
+  more information about archive and restore modules, see\
+  <xref linkend="archive-and-restore-modules"/>.
  </para>
 
  <para>
-  In order to function, this module must be loaded via
+  For use as an archive module, this module must be loaded via
   <xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
-  must be enabled.
+  must be enabled.  For use as a restore module, this module must be loaded
+  via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+  <xref linkend="runtime-config-wal-archive-recovery"/>).
  </para>
 
  <sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
     </term>
     <listitem>
      <para>
-      The directory where the server should copy WAL segment files.  This
-      directory must already exist.  The default is an empty string, which
-      effectively halts WAL archiving, but if <xref 
linkend="guc-archive-mode"/>
-      is enabled, the server will accumulate WAL segment files in the
-      expectation that a value will soon be provided.
+      The directory where the server should copy WAL segment files to or from.
+      This directory must already exist.  The default is an empty string,
+      which, when used for archiving, effectively halts WAL archival, but if
+      <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+      WAL segment files in the expectation that a value will soon be provided.
+      When an empty string is used for recovery, restore will fail.
      </para>
     </listitem>
    </varlistentry>
@@ -46,7 +50,7 @@
 
   <para>
    These parameters must be set in <filename>postgresql.conf</filename>.
-   Typical usage might be:
+   Typical usage as an archive module might be:
   </para>
 
 <programlisting>
@@ -61,6 +65,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
   <title>Notes</title>
 
   <para>
+   When <filename>basic_archive</filename> is used as an archive module,
    Server crashes may leave temporary files with the prefix
    <filename>archtemp</filename> in the archive directory.  It is recommended 
to
    delete such files before restarting the server after a crash.  It is safe to
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 698169afdb..6e791a5a36 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3916,7 +3916,8 @@ include_dir 'conf.d'
      recovery when the end of archived WAL is reached, but will keep trying to
      continue recovery by connecting to the sending server as specified by the
      <varname>primary_conninfo</varname> setting and/or by fetching new WAL
-     segments using <varname>restore_command</varname>.  For this mode, the
+     segments using <varname>restore_command</varname> or
+     <varname>restore_library</varname>.  For this mode, the
      parameters from this section and <xref
      linkend="runtime-config-replication-standby"/> are of interest.
      Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3944,7 +3945,8 @@ include_dir 'conf.d'
       <listitem>
        <para>
         The local shell command to execute to retrieve an archived segment of
-        the WAL file series. This parameter is required for archive recovery,
+        the WAL file series.  Either <varname>restore_command</varname> or
+        <xref linkend="guc-restore-library"/> is required for archive recovery,
         but optional for streaming replication.
         Any <literal>%f</literal> in the string is
         replaced by the name of the file to retrieve from the archive,
@@ -3979,7 +3981,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" 
"%p"'  # Windows
 
        <para>
         This parameter can only be set in the 
<filename>postgresql.conf</filename>
-        file or on the server command line.
+        file or on the server command line.  It is only used if
+        <varname>restore_library</varname> is set to an empty string.  If both
+        <varname>restore_command</varname> and
+        <varname>restore_library</varname> are set, an error will be raised.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="guc-restore-library" xreflabel="restore_library">
+      <term><varname>restore_library</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>restore_library</varname> configuration 
parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        The library to use for restore actions, including retrieving archived
+        files and executing tasks at restartpoints and at recovery end.  Either
+        <xref linkend="guc-restore-command"/> or
+        <varname>restore_library</varname> is required for archive recovery,
+        but optional for streaming replication.  If this parameter is set to an
+        empty string (the default), restoring via shell is enabled, and
+        <varname>restore_command</varname>,
+        <varname>archive_cleanup_command</varname>, and
+        <varname>recovery_end_command</varname> are used.  If both
+        <varname>restore_library</varname> and any of
+        <varname>restore_command</varname>,
+        <varname>archive_cleanup_command</varname>, or
+        <varname>recovery_end_command</varname> are set, an error will be
+        raised.  Otherwise, the specified shared library is used for restoring.
+        For more information, see <xref linkend="restore-modules"/>.
+       </para>
+
+       <para>
+        This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command 
line.
        </para>
       </listitem>
      </varlistentry>
@@ -4024,7 +4061,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" 
"%p"'  # Windows
        </para>
        <para>
         This parameter can only be set in the 
<filename>postgresql.conf</filename>
-        file or on the server command line.
+        file or on the server command line.  It is only used if
+        <varname>restore_library</varname> is set to an empty string.  If both
+        <varname>archive_cleanup_command</varname> and
+        <varname>restore_library</varname> are set, an error will be raised.
        </para>
       </listitem>
      </varlistentry>
@@ -4053,7 +4093,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" 
"%p"'  # Windows
        </para>
        <para>
         This parameter can only be set in the 
<filename>postgresql.conf</filename>
-        file or on the server command line.
+        file or on the server command line.  It is only used if
+        <varname>restore_library</varname> is set to an empty string.  If both
+        <varname>recovery_end_command</varname> and
+        <varname>restore_library</varname> are set, an error will be raised.
        </para>
       </listitem>
      </varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml 
b/doc/src/sgml/high-availability.sgml
index b48209fc2f..a86a9fe93a 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable 
transactional order.
    <para>
     In standby mode, the server continuously applies WAL received from the
     primary server. The standby server can read WAL from a WAL archive
-    (see <xref linkend="guc-restore-command"/>) or directly from the primary
+    (see <xref linkend="guc-restore-command"/> and
+    <xref linkend="guc-restore-library"/>) or directly from the primary
     over a TCP connection (streaming replication). The standby server will
     also attempt to restore any WAL found in the standby cluster's
     <filename>pg_wal</filename> directory. That typically happens after a 
server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable 
transactional order.
 
    <para>
     At startup, the standby begins by restoring all WAL available in the
-    archive location, calling <varname>restore_command</varname>. Once it
+    archive location, either by calling <varname>restore_command</varname> or
+    by executing the <varname>restore_library</varname>'s callbacks.  Once it
     reaches the end of WAL available there and 
<varname>restore_command</varname>
+    or <varname>restore_library</varname>
     fails, it tries to restore any WAL available in the 
<filename>pg_wal</filename> directory.
     If that fails, and streaming replication has been configured, the
     standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable 
transactional order.
     server (see <xref linkend="backup-pitr-recovery"/>). Create a file
     <link 
linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
     in the standby's cluster data
-    directory. Set <xref linkend="guc-restore-command"/> to a simple command 
to copy files from
+    directory. Set <xref linkend="guc-restore-command"/> or
+    <xref linkend="guc-restore-library"/> to copy files from
     the WAL archive. If you plan to have multiple standby servers for high
     availability purposes, make sure that 
<varname>recovery_target_timeline</varname> is set to
     <literal>latest</literal> (the default), to make the standby server follow 
the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable 
transactional order.
 
    <note>
      <para>
-     <xref linkend="guc-restore-command"/> should return immediately
+     <xref linkend="guc-restore-command"/> and
+     <xref linkend="guc-restore-library"/> should return immediately
      if the file does not exist; the server will retry the command again if
      necessary.
     </para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable 
transactional order.
 
    <para>
     If you're using a WAL archive, its size can be minimized using the <xref
-    linkend="guc-archive-cleanup-command"/> parameter to remove files that are 
no
-    longer required by the standby server.
+    linkend="guc-archive-cleanup-command"/> parameter or the
+    <xref linkend="guc-restore-library"/>'s archive cleanup callbacks to remove
+    files that are no longer required by the standby server.
     The <application>pg_archivecleanup</application> utility is designed 
specifically to
     be used with <varname>archive_cleanup_command</varname> in typical 
single-standby
     configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/xlog.c 
b/src/backend/access/transam/xlog.c
index 85b2fd9c1e..cb7c53de50 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -83,7 +83,7 @@
 #include "replication/snapbuild.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
-#include "restore/shell_restore.h"
+#include "restore/restore_module.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
@@ -5258,18 +5258,19 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, 
XLogRecPtr EndOfLog,
                                                        TimeLineID newTLI)
 {
        /*
-        * Execute the recovery_end_command, if any.
+        * Execute the recovery-end callback, if any.
         *
-        * The command is provided the archive file cutoff point for use during
+        * The callback is provided 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.
         */
-       if (shell_recovery_end_configured())
+       if (recovery_end_configured())
        {
                char            lastRestartPointFname[MAXFNAMELEN];
 
                GetOldestRestartPointFileName(lastRestartPointFname);
-               shell_recovery_end(lastRestartPointFname);
+               RestoreCallbacks->recovery_end_cb(restore_module_state,
+                                                                               
  lastRestartPointFname);
        }
 
        /*
@@ -7760,18 +7761,19 @@ CreateRestartPoint(int flags)
                                                           
timestamptz_to_str(xtime)) : 0));
 
        /*
-        * Finally, execute archive_cleanup_command, if any.
+        * Finally, execute archive-cleanup callback, if any.
         *
-        * The command is provided the archive file cutoff point for use during
+        * The callback is provided 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.
         */
-       if (shell_archive_cleanup_configured())
+       if (archive_cleanup_configured())
        {
                char            lastRestartPointFname[MAXFNAMELEN];
 
                GetOldestRestartPointFileName(lastRestartPointFname);
-               shell_archive_cleanup(lastRestartPointFname);
+               RestoreCallbacks->archive_cleanup_cb(restore_module_state,
+                                                                               
         lastRestartPointFname);
        }
 
        return true;
diff --git a/src/backend/access/transam/xlogarchive.c 
b/src/backend/access/transam/xlogarchive.c
index 56413c6a61..f6fe65e8ed 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,14 +23,21 @@
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
 #include "common/archive.h"
+#include "fmgr.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/pgarch.h"
 #include "postmaster/startup.h"
 #include "replication/walsender.h"
+#include "restore/restore_module.h"
 #include "restore/shell_restore.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
+#include "utils/memutils.h"
+
+char      *restoreLibrary = "";
+const RestoreModuleCallbacks *RestoreCallbacks = NULL;
+RestoreModuleState *restore_module_state;
 
 /*
  * Attempt to retrieve the specified file from off-line archival storage.
@@ -74,15 +81,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
        switch (archive_type)
        {
                case ARCHIVE_TYPE_WAL_SEGMENT:
-                       if (!shell_restore_file_configured())
+                       if (!restore_wal_segment_configured())
                                goto not_available;
                        break;
                case ARCHIVE_TYPE_TIMELINE_HISTORY:
-                       if (!shell_restore_file_configured())
+                       if (!restore_timeline_history_configured())
                                goto not_available;
                        break;
                case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
-                       if (!shell_restore_file_configured())
+                       if (!timeline_history_exists_configured())
                                goto not_available;
                        break;
        }
@@ -174,14 +181,17 @@ RestoreArchivedFile(char *path, const char *xlogfname,
        switch (archive_type)
        {
                case ARCHIVE_TYPE_WAL_SEGMENT:
-                       ret = shell_restore_wal_segment(xlogfname, xlogpath,
-                                                                               
        lastRestartPointFname);
+                       ret = 
RestoreCallbacks->restore_wal_segment_cb(restore_module_state,
+                                                                               
                                   xlogfname, xlogpath,
+                                                                               
                                   lastRestartPointFname);
                        break;
                case ARCHIVE_TYPE_TIMELINE_HISTORY:
-                       ret = shell_restore_timeline_history(xlogfname, 
xlogpath);
+                       ret = 
RestoreCallbacks->restore_timeline_history_cb(restore_module_state,
+                                                                               
                                                xlogfname, xlogpath);
                        break;
                case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
-                       ret = shell_restore_timeline_history(xlogfname, 
xlogpath);
+                       ret = 
RestoreCallbacks->timeline_history_exists_cb(restore_module_state,
+                                                                               
                                           xlogfname, xlogpath);
                        break;
        }
 
@@ -189,6 +199,16 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 
        if (ret)
        {
+               /*
+                * Some restore functions might not copy the file (e.g., 
checking
+                * whether a timeline history file exists), but they can set a 
flag to
+                * tell us if they do.  We only need to verify file existence 
if this
+                * flag is enabled.
+                */
+               if (archive_type == ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS &&
+                       
!restore_module_state->timeline_history_exists_cb_copies)
+                       return true;
+
                /*
                 * command apparently succeeded, but let's make sure the file is
                 * really there now and has the correct size.
@@ -630,3 +650,65 @@ XLogArchiveCleanup(const char *xlog)
        unlink(archiveStatusPath);
        /* should we complain about failure? */
 }
+
+/*
+ * Loads all the restore callbacks into our global RestoreCallbacks.  The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRestoreCallbacks(void)
+{
+       RestoreModuleInit init;
+
+       /*
+        * If the shell command is enabled, use our special initialization
+        * function.  Otherwise, load the library and call its
+        * _PG_restore_module_init().
+        */
+       if (restoreLibrary[0] == '\0')
+               init = shell_restore_init;
+       else
+               init = (RestoreModuleInit)
+                       load_external_function(restoreLibrary, 
"_PG_restore_module_init",
+                                                                  false, NULL);
+
+       if (init == NULL)
+               ereport(ERROR,
+                               (errmsg("restore modules have to define the 
symbol "
+                                               "_PG_restore_module_init")));
+
+       RestoreCallbacks = (*init) ();
+
+       /* restore state should be freed before calling this function */
+       Assert(restore_module_state == NULL);
+       restore_module_state = (RestoreModuleState *)
+               MemoryContextAllocZero(TopMemoryContext,
+                                                          
sizeof(RestoreModuleState));
+
+       if (RestoreCallbacks->startup_cb != NULL)
+               RestoreCallbacks->startup_cb(restore_module_state);
+}
+
+/*
+ * Call the shutdown callback of the loaded restore module, if defined.  Also,
+ * free the restore module state if it was allocated.
+ *
+ * Processes that load restore libraries should register this via
+ * before_shmem_exit().
+ */
+void
+call_restore_module_shutdown_cb(int code, Datum arg)
+{
+       if (RestoreCallbacks != NULL &&
+               RestoreCallbacks->shutdown_cb != NULL &&
+               restore_module_state != NULL)
+               RestoreCallbacks->shutdown_cb(restore_module_state);
+
+       if (restore_module_state != NULL)
+       {
+               pfree(restore_module_state);
+               restore_module_state = NULL;
+       }
+}
diff --git a/src/backend/access/transam/xlogrecovery.c 
b/src/backend/access/transam/xlogrecovery.c
index 4af31d5739..2afdbddfd1 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -51,6 +51,7 @@
 #include "replication/slot.h"
 #include "replication/slotsync.h"
 #include "replication/walreceiver.h"
+#include "restore/restore_module.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1117,18 +1118,21 @@ validateRecoveryParameters(void)
        if (StandbyModeRequested)
        {
                if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 
0) &&
-                       (recoveryRestoreCommand == NULL || 
strcmp(recoveryRestoreCommand, "") == 0))
+                       (!restore_wal_segment_configured() ||
+                        !restore_timeline_history_configured() ||
+                        !timeline_history_exists_configured()))
                        ereport(WARNING,
-                                       (errmsg("specified neither 
\"primary_conninfo\" nor \"restore_command\""),
+                                       (errmsg("specified neither 
\"primary_conninfo\" nor \"restore_command\" nor a configured 
\"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 (!restore_wal_segment_configured() ||
+                       !restore_timeline_history_configured() ||
+                       !timeline_history_exists_configured())
                        ereport(FATAL,
                                        
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                                        errmsg("must specify 
\"restore_command\" when standby mode is not enabled")));
+                                        errmsg("must specify 
\"restore_command\" or a configured \"restore_library\" when standby mode is 
not enabled")));
        }
 
        /*
diff --git a/src/backend/postmaster/checkpointer.c 
b/src/backend/postmaster/checkpointer.c
index 3c68a9904d..e8769e9309 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -46,6 +46,7 @@
 #include "postmaster/bgwriter.h"
 #include "postmaster/interrupt.h"
 #include "replication/syncrep.h"
+#include "restore/restore_module.h"
 #include "storage/bufmgr.h"
 #include "storage/condition_variable.h"
 #include "storage/fd.h"
@@ -230,6 +231,25 @@ CheckpointerMain(char *startup_data, size_t 
startup_data_len)
                                                                                
                 ALLOCSET_DEFAULT_SIZES);
        MemoryContextSwitchTo(checkpointer_context);
 
+       /*
+        * Load restore_library so that we can use its archive-cleanup callback,
+        * if one is defined.
+        *
+        * We also take this opportunity to set up the before_shmem_exit hook 
for
+        * the shutdown callback and to check that only one of restore_library,
+        * archive_cleanup_command is set.  Note that we emit a WARNING upon
+        * detecting misconfigured parameters, and we clear the callbacks so 
that
+        * the archive-cleanup functionality is skipped.  We judge this
+        * functionality to not be important enough to require blocking
+        * checkpoints or shutting down the server when the parameters are
+        * misconfigured.
+        */
+       before_shmem_exit(call_restore_module_shutdown_cb, 0);
+       if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+                                                                               
 archiveCleanupCommand, "archive_cleanup_command",
+                                                                               
 WARNING))
+               LoadRestoreCallbacks();
+
        /*
         * If an exception is encountered, processing resumes here.
         *
@@ -562,6 +582,10 @@ HandleCheckpointerInterrupts(void)
 
        if (ConfigReloadPending)
        {
+               bool            archive_cleanup_settings_changed = false;
+               char       *prevRestoreLibrary = pstrdup(restoreLibrary);
+               char       *prevArchiveCleanupCommand = 
pstrdup(archiveCleanupCommand);
+
                ConfigReloadPending = false;
                ProcessConfigFile(PGC_SIGHUP);
 
@@ -577,6 +601,36 @@ HandleCheckpointerInterrupts(void)
                 * because of SIGHUP.
                 */
                UpdateSharedMemoryConfig();
+
+               /*
+                * If the archive-cleanup settings have changed, call the 
currently
+                * loaded shutdown callback and clear all the restore callbacks.
+                * There's presently no good way to unload a library besides
+                * restarting the process, and there should be little harm in 
leaving
+                * it around, so we just leave it loaded.
+                */
+               if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+                       strcmp(prevArchiveCleanupCommand, 
archiveCleanupCommand) != 0)
+               {
+                       archive_cleanup_settings_changed = true;
+                       call_restore_module_shutdown_cb(0, (Datum) 0);
+                       RestoreCallbacks = NULL;
+               }
+
+               /*
+                * As in CheckpointerMain(), we only emit a WARNING if we 
detect that
+                * both restore_library and archive_cleanup_command are set.  
We do
+                * this even if the archive-cleanup settings haven't changed to 
remind
+                * the user about the misconfiguration.
+                */
+               if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, 
"restore_library",
+                                                                               
         archiveCleanupCommand, "archive_cleanup_command",
+                                                                               
         WARNING) &&
+                       archive_cleanup_settings_changed)
+                       LoadRestoreCallbacks();
+
+               pfree(prevRestoreLibrary);
+               pfree(prevArchiveCleanupCommand);
        }
        if (ShutdownRequestPending)
        {
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index cc665ce259..f8d3d6b3d2 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -26,6 +26,7 @@
 #include "miscadmin.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/startup.h"
+#include "restore/restore_module.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
 #include "storage/procsignal.h"
@@ -119,13 +120,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
  * Re-read the config file.
  *
  * If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it.  Also, check for invalid combinations of the command/library
+ * parameters and reload the restore callbacks if necessary.
  */
 static void
 StartupRereadConfig(void)
 {
        char       *conninfo = pstrdup(PrimaryConnInfo);
        char       *slotname = pstrdup(PrimarySlotName);
+       char       *prevRestoreLibrary = pstrdup(restoreLibrary);
+       char       *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+       char       *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
        bool            tempSlot = wal_receiver_create_temp_slot;
        bool            conninfoChanged;
        bool            slotnameChanged;
@@ -147,6 +152,30 @@ StartupRereadConfig(void)
 
        if (conninfoChanged || slotnameChanged || tempSlotChanged)
                StartupRequestWalReceiverRestart();
+
+       /*
+        * If the restore settings have changed, call the currently loaded
+        * shutdown callback and load the new callbacks.  There's presently no
+        * good way to unload a library besides restarting the process, and 
there
+        * should be little harm in leaving it around, so we just leave it 
loaded.
+        */
+       (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, 
"restore_library",
+                                                                               
        recoveryRestoreCommand, "restore_command",
+                                                                               
        ERROR);
+       (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, 
"restore_library",
+                                                                               
        recoveryEndCommand, "recovery_end_command",
+                                                                               
        ERROR);
+       if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+               strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+               strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+       {
+               call_restore_module_shutdown_cb(0, (Datum) 0);
+               LoadRestoreCallbacks();
+       }
+
+       pfree(prevRestoreLibrary);
+       pfree(prevRestoreCommand);
+       pfree(prevRecoveryEndCommand);
 }
 
 /* Handle various signals that might be sent to the startup process */
@@ -261,6 +290,19 @@ StartupProcessMain(char *startup_data, size_t 
startup_data_len)
         */
        sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
+       /*
+        * Check for invalid combinations of the command/library parameters and
+        * load the callbacks.
+        */
+       before_shmem_exit(call_restore_module_shutdown_cb, 0);
+       (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, 
"restore_library",
+                                                                               
        recoveryRestoreCommand, "restore_command",
+                                                                               
        ERROR);
+       (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, 
"restore_library",
+                                                                               
        recoveryEndCommand, "recovery_end_command",
+                                                                               
        ERROR);
+       LoadRestoreCallbacks();
+
        /*
         * Do what we came for.
         */
diff --git a/src/backend/restore/shell_restore.c 
b/src/backend/restore/shell_restore.c
index 42291625c1..be25f04044 100644
--- a/src/backend/restore/shell_restore.c
+++ b/src/backend/restore/shell_restore.c
@@ -3,7 +3,8 @@
  * shell_restore.c
  *
  * These restore functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC).  It is used as the default, but other modules may
+ * define their own restore logic.
  *
  * Copyright (c) 2024, PostgreSQL Global Development Group
  *
@@ -22,25 +23,76 @@
 #include "common/archive.h"
 #include "common/percentrepl.h"
 #include "postmaster/startup.h"
+#include "restore/restore_module.h"
 #include "restore/shell_restore.h"
 #include "storage/ipc.h"
 #include "utils/wait_event.h"
 
+static void shell_restore_startup(RestoreModuleState *state);
+static bool shell_restore_file_configured(RestoreModuleState *state);
 static bool shell_restore_file(const char *file, const char *path,
                                                           const char 
*lastRestartPointFileName);
+static bool shell_restore_wal_segment(RestoreModuleState *state,
+                                                                         const 
char *file, const char *path,
+                                                                         const 
char *lastRestartPointFileName);
+static bool shell_restore_timeline_history(RestoreModuleState *state,
+                                                                               
   const char *file, const char *path);
+static bool shell_archive_cleanup_configured(RestoreModuleState *state);
+static void shell_archive_cleanup(RestoreModuleState *state,
+                                                                 const char 
*lastRestartPointFileName);
+static bool shell_recovery_end_configured(RestoreModuleState *state);
+static void shell_recovery_end(RestoreModuleState *state,
+                                                          const char 
*lastRestartPointFileName);
 static void ExecuteRecoveryCommand(const char *command,
                                                                   const char 
*commandName,
                                                                   bool 
failOnSignal,
                                                                   uint32 
wait_event_info,
                                                                   const char 
*lastRestartPointFileName);
 
+static const RestoreModuleCallbacks shell_restore_callbacks = {
+       .startup_cb = shell_restore_startup,
+       .restore_wal_segment_configured_cb = shell_restore_file_configured,
+       .restore_wal_segment_cb = shell_restore_wal_segment,
+       .restore_timeline_history_configured_cb = shell_restore_file_configured,
+       .restore_timeline_history_cb = shell_restore_timeline_history,
+       .timeline_history_exists_configured_cb = shell_restore_file_configured,
+       .timeline_history_exists_cb = shell_restore_timeline_history,
+       .archive_cleanup_configured_cb = shell_archive_cleanup_configured,
+       .archive_cleanup_cb = shell_archive_cleanup,
+       .recovery_end_configured_cb = shell_recovery_end_configured,
+       .recovery_end_cb = shell_recovery_end,
+       .shutdown_cb = NULL
+};
+
+/*
+ * shell_restore_init
+ *
+ * Returns the callbacks for restoring via shell.
+ */
+const RestoreModuleCallbacks *
+shell_restore_init(void)
+{
+       return &shell_restore_callbacks;
+}
+
+/*
+ * Indicates that our timeline_history_exists_cb only attempts to retrieve the
+ * file, so the caller should verify the file exists afterwards.
+ */
+static void
+shell_restore_startup(RestoreModuleState *state)
+{
+       state->timeline_history_exists_cb_copies = true;
+}
+
 /*
  * Attempt to execute a shell-based restore command to retrieve a WAL segment.
  *
  * Returns true if the command has succeeded, false otherwise.
  */
-bool
-shell_restore_wal_segment(const char *file, const char *path,
+static bool
+shell_restore_wal_segment(RestoreModuleState *state,
+                                                 const char *file, const char 
*path,
                                                  const char 
*lastRestartPointFileName)
 {
        return shell_restore_file(file, path, lastRestartPointFileName);
@@ -52,8 +104,9 @@ shell_restore_wal_segment(const char *file, const char *path,
  *
  * Returns true if the command has succeeded, false otherwise.
  */
-bool
-shell_restore_timeline_history(const char *file, const char *path)
+static bool
+shell_restore_timeline_history(RestoreModuleState *state,
+                                                          const char *file, 
const char *path)
 {
        char            lastRestartPointFname[MAXPGPATH];
 
@@ -64,8 +117,8 @@ shell_restore_timeline_history(const char *file, const char 
*path)
 /*
  * Check whether restore_command is supplied.
  */
-bool
-shell_restore_file_configured(void)
+static bool
+shell_restore_file_configured(RestoreModuleState *state)
 {
        return recoveryRestoreCommand[0] != '\0';
 }
@@ -152,8 +205,8 @@ shell_restore_file(const char *file, const char *path,
 /*
  * Check whether archive_cleanup_command is supplied.
  */
-bool
-shell_archive_cleanup_configured(void)
+static bool
+shell_archive_cleanup_configured(RestoreModuleState *state)
 {
        return archiveCleanupCommand[0] != '\0';
 }
@@ -161,8 +214,9 @@ shell_archive_cleanup_configured(void)
 /*
  * Attempt to execute a shell-based archive cleanup command.
  */
-void
-shell_archive_cleanup(const char *lastRestartPointFileName)
+static void
+shell_archive_cleanup(RestoreModuleState *state,
+                                         const char *lastRestartPointFileName)
 {
        ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
                                                   false, 
WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
@@ -172,8 +226,8 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
 /*
  * Check whether recovery_end_command is supplied.
  */
-bool
-shell_recovery_end_configured(void)
+static bool
+shell_recovery_end_configured(RestoreModuleState *state)
 {
        return recoveryEndCommand[0] != '\0';
 }
@@ -181,8 +235,9 @@ shell_recovery_end_configured(void)
 /*
  * Attempt to execute a shell-based end-of-recovery command.
  */
-void
-shell_recovery_end(const char *lastRestartPointFileName)
+static void
+shell_recovery_end(RestoreModuleState *state,
+                                  const char *lastRestartPointFileName)
 {
        ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
                                                   
WAIT_EVENT_RECOVERY_END_COMMAND,
diff --git a/src/backend/utils/misc/guc_tables.c 
b/src/backend/utils/misc/guc_tables.c
index 46c258be28..8265d57c4d 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -70,6 +70,7 @@
 #include "replication/slot.h"
 #include "replication/slotsync.h"
 #include "replication/syncrep.h"
+#include "restore/restore_module.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -3967,6 +3968,16 @@ struct config_string ConfigureNamesString[] =
                NULL, NULL, NULL
        },
 
+       {
+               {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+                       gettext_noop("Sets the library that will be called for 
restore actions."),
+                       NULL
+               },
+               &restoreLibrary,
+               "",
+               NULL, NULL, NULL
+       },
+
        {
                {"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
                        gettext_noop("Sets the shell command that will be 
executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample 
b/src/backend/utils/misc/postgresql.conf.sample
index 83d5df8e46..a9a2475c4b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -285,6 +285,7 @@
                                # placeholders: %p = path of file to restore
                                #               %f = file name only
                                # e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = ''          # library to use for restore actions
 #archive_cleanup_command = ''  # command to execute at every restartpoint
 #recovery_end_command = ''     # command to execute at completion of recovery
 
diff --git a/src/include/restore/restore_module.h 
b/src/include/restore/restore_module.h
new file mode 100644
index 0000000000..cc148e855a
--- /dev/null
+++ b/src/include/restore/restore_module.h
@@ -0,0 +1,143 @@
+/*-------------------------------------------------------------------------
+ *
+ * restore_module.h
+ *             Exports for restore modules.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/restore_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _RESTORE_MODULE_H
+#define _RESTORE_MODULE_H
+
+/*
+ * The value of the restore_library GUC.
+ */
+extern PGDLLIMPORT char *restoreLibrary;
+
+typedef struct RestoreModuleState
+{
+       /*
+        * Private data pointer for use by a restore module.  This can be used 
to
+        * store state for the module that will be passed to each of its
+        * callbacks.
+        */
+       void       *private_data;
+
+       /*
+        * Indicates whether the callback that checks for timeline existence
+        * merely copies the file.  If set to true, the server will verify that
+        * the timeline history file exists after the callback returns.
+        */
+       bool            timeline_history_exists_cb_copies;
+} RestoreModuleState;
+
+/*
+ * Restore module callbacks
+ *
+ * These callback functions should be defined by restore libraries and returned
+ * via _PG_restore_module_init().  For more information about the purpose of
+ * each callback, refer to the restore modules documentation.
+ */
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state,
+                                                                        const 
char *file, const char *path,
+                                                                        const 
char *lastRestartPointFileName);
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state,
+                                                                               
  const char *file, const char *path);
+typedef bool (*TimelineHistoryExistsConfiguredCB) (RestoreModuleState *state);
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state,
+                                                                               
 const char *file, const char *path);
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state,
+                                                                 const char 
*lastRestartPointFileName);
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+typedef void (*RecoveryEndCB) (RestoreModuleState *state,
+                                                          const char 
*lastRestartPointFileName);
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+
+typedef struct RestoreModuleCallbacks
+{
+       RestoreStartupCB startup_cb;
+       RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+       RestoreWalSegmentCB restore_wal_segment_cb;
+       RestoreTimelineHistoryConfiguredCB 
restore_timeline_history_configured_cb;
+       RestoreTimelineHistoryCB restore_timeline_history_cb;
+       TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+       TimelineHistoryExistsCB timeline_history_exists_cb;
+       ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+       ArchiveCleanupCB archive_cleanup_cb;
+       RecoveryEndConfiguredCB recovery_end_configured_cb;
+       RecoveryEndCB recovery_end_cb;
+       RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_restore_module_init that is looked up
+ * when loading a restore library.
+ */
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+
+extern PGDLLEXPORT const RestoreModuleCallbacks *_PG_restore_module_init(void);
+
+extern void LoadRestoreCallbacks(void);
+extern void call_restore_module_shutdown_cb(int code, Datum arg);
+
+extern const RestoreModuleCallbacks *RestoreCallbacks;
+extern RestoreModuleState *restore_module_state;
+
+/*
+ * The following *_configured() functions are convenient wrappers around the
+ * *_configured_cb() callback functions with additional defensive NULL checks.
+ */
+
+static inline bool
+restore_wal_segment_configured(void)
+{
+       return RestoreCallbacks != NULL &&
+               RestoreCallbacks->restore_wal_segment_configured_cb != NULL &&
+               RestoreCallbacks->restore_wal_segment_cb != NULL &&
+               
RestoreCallbacks->restore_wal_segment_configured_cb(restore_module_state);
+}
+
+static inline bool
+restore_timeline_history_configured(void)
+{
+       return RestoreCallbacks != NULL &&
+               RestoreCallbacks->restore_timeline_history_configured_cb != 
NULL &&
+               RestoreCallbacks->restore_timeline_history_cb != NULL &&
+               
RestoreCallbacks->restore_timeline_history_configured_cb(restore_module_state);
+}
+
+static inline bool
+timeline_history_exists_configured(void)
+{
+       return RestoreCallbacks != NULL &&
+               RestoreCallbacks->timeline_history_exists_configured_cb != NULL 
&&
+               RestoreCallbacks->timeline_history_exists_cb != NULL &&
+               
RestoreCallbacks->timeline_history_exists_configured_cb(restore_module_state);
+}
+
+static inline bool
+archive_cleanup_configured(void)
+{
+       return RestoreCallbacks != NULL &&
+               RestoreCallbacks->archive_cleanup_configured_cb != NULL &&
+               RestoreCallbacks->archive_cleanup_cb != NULL &&
+               
RestoreCallbacks->archive_cleanup_configured_cb(restore_module_state);
+}
+
+static inline bool
+recovery_end_configured(void)
+{
+       return RestoreCallbacks != NULL &&
+               RestoreCallbacks->recovery_end_configured_cb != NULL &&
+               RestoreCallbacks->recovery_end_cb != NULL &&
+               
RestoreCallbacks->recovery_end_configured_cb(restore_module_state);
+}
+
+#endif                                                 /* _RESTORE_MODULE_H */
diff --git a/src/include/restore/shell_restore.h 
b/src/include/restore/shell_restore.h
index 28b5b7bc92..6352623866 100644
--- a/src/include/restore/shell_restore.h
+++ b/src/include/restore/shell_restore.h
@@ -12,15 +12,13 @@
 #ifndef _SHELL_RESTORE_H
 #define _SHELL_RESTORE_H
 
-extern bool shell_restore_file_configured(void);
-extern bool shell_restore_wal_segment(const char *file, const char *path,
-                                                                         const 
char *lastRestartPointFileName);
-extern bool shell_restore_timeline_history(const char *file, const char *path);
+#include "restore/restore_module.h"
 
-extern bool shell_archive_cleanup_configured(void);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-
-extern bool shell_recovery_end_configured(void);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Since the logic for restoring via shell commands is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const RestoreModuleCallbacks *shell_restore_init(void);
 
 #endif                                                 /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b5e4608817..5859126c23 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2448,6 +2448,8 @@ ResourceReleaseCallback
 ResourceReleaseCallbackItem
 ResourceReleasePhase
 ResourceReleasePriority
+RestoreModuleCallbacks
+RestoreModuleState
 RestoreOptions
 RestorePass
 RestrictInfo
-- 
2.39.3 (Apple Git-146)

Reply via email to