From 92b3c9ff64f1e6d99e58fb0fe9002fa5d34619a3 Mon Sep 17 00:00:00 2001
From: Paul Guo <paulguo@gmail.com>
Date: Tue, 19 Mar 2019 12:41:30 +0800
Subject: [PATCH v8 2/2] Ensure target clean shutdown at the beginning of
 pg_rewind

---
 doc/src/sgml/ref/pg_rewind.sgml | 16 ++++++++
 src/bin/pg_rewind/pg_rewind.c   | 72 ++++++++++++++++++++++++++++++++-
 2 files changed, 87 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index b8b43e8220..122f792bff 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -176,6 +176,22 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-s</option></term>
+      <term><option>--no-ensure-shutdowned</option></term>
+      <listitem>
+       <para>
+        By default, <application>pg_rewind</application> makes sure target is
+        cleanly shutdown before doing recovery by launching a single mode
+        postgres instance if needed to ensure the target has completed crash
+        recovery and pg_control is in clean shutdown state. Clean shutdown
+        state is a prerequisite of <application>pg_rewind</application>.
+        With this option, <application>pg_rewind</application> skips this and
+        then users need to do this themselves.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-n</option></term>
       <term><option>--dry-run</option></term>
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 2312e564f6..342a072c18 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -41,6 +41,7 @@ static void digestControlFile(ControlFileData *ControlFile, char *source,
 static void syncTargetDirectory(void);
 static void sanityChecks(void);
 static void findCommonAncestorTimeline(XLogRecPtr *recptr, int *tliIndex);
+static void ensureCleanShutdown(const char *argv0);
 
 static ControlFileData ControlFile_target;
 static ControlFileData ControlFile_source;
@@ -83,6 +84,7 @@ usage(const char *progname)
 	printf(_("      --source-pgdata=DIRECTORY  source data directory to synchronize with\n"));
 	printf(_("      --source-server=CONNSTR    source server to synchronize with\n"));
 	printf(_("  -R, --write-recovery-conf      write configuration for replication\n"));
+	printf(_("  -s, --no-ensure-shutdowned     do not auto-fix unclean shutdown\n"));
 	printf(_("  -n, --dry-run                  stop before modifying anything\n"));
 	printf(_("  -N, --no-sync                  do not wait for changes to be written\n"
 			 "                                 safely to disk\n"));
@@ -103,6 +105,7 @@ main(int argc, char **argv)
 		{"write-recovery-conf", no_argument, NULL, 'R'},
 		{"source-pgdata", required_argument, NULL, 1},
 		{"source-server", required_argument, NULL, 2},
+		{"no-ensure-shutdowned", no_argument, NULL, 's'},
 		{"version", no_argument, NULL, 'V'},
 		{"dry-run", no_argument, NULL, 'n'},
 		{"no-sync", no_argument, NULL, 'N'},
@@ -119,6 +122,7 @@ main(int argc, char **argv)
 	XLogRecPtr	chkptredo;
 	size_t		size;
 	char	   *buffer;
+	bool		no_ensure_shutdowned = false;
 	bool		rewind_needed;
 	XLogRecPtr	endrec;
 	TimeLineID	endtli;
@@ -144,7 +148,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNPR", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "D:nNPRs", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -152,6 +156,10 @@ main(int argc, char **argv)
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
 				exit(1);
 
+			case 's':
+				no_ensure_shutdowned = true;
+				break;
+
 			case 'P':
 				showprogress = true;
 				break;
@@ -258,6 +266,24 @@ main(int argc, char **argv)
 	digestControlFile(&ControlFile_target, buffer, size);
 	pg_free(buffer);
 
+	/*
+	 * If the target instance was not cleanly shut down, run a single-user
+	 * postgres session really quickly and reload the control file to get the
+	 * new state. Note if no_ensure_shutdowned is specified, pg_rewind won't
+	 * do that automatically. That means users need to do themselves in
+	 * advance, else pg_rewind will soon quit, see sanityChecks().
+	 */
+	if (!no_ensure_shutdowned &&
+		ControlFile_target.state != DB_SHUTDOWNED &&
+		ControlFile_target.state != DB_SHUTDOWNED_IN_RECOVERY)
+	{
+		ensureCleanShutdown(argv[0]);
+
+		buffer = slurpFile(datadir_target, "global/pg_control", &size);
+		digestControlFile(&ControlFile_target, buffer, size);
+		pg_free(buffer);
+	}
+
 	buffer = fetchFile("global/pg_control", &size);
 	digestControlFile(&ControlFile_source, buffer, size);
 	pg_free(buffer);
@@ -772,3 +798,47 @@ syncTargetDirectory(void)
 
 	fsync_pgdata(datadir_target, PG_VERSION_NUM);
 }
+
+/*
+ * Ensure clean shutdown of target instance by launching single-user mode
+ * postgres to do crash recovery.
+ */
+static void
+ensureCleanShutdown(const char *argv0)
+{
+	int		ret;
+#define MAXCMDLEN (2 * MAXPGPATH)
+	char	exec_path[MAXPGPATH];
+	char	cmd[MAXCMDLEN];
+
+	/* locate postgres binary */
+	if ((ret = find_other_exec(argv0, "postgres",
+							   PG_BACKEND_VERSIONSTR,
+							   exec_path)) < 0)
+	{
+		char        full_path[MAXPGPATH];
+
+		if (find_my_exec(argv0, full_path) < 0)
+			strlcpy(full_path, progname, sizeof(full_path));
+
+		if (ret == -1)
+			pg_fatal("The program \"postgres\" is needed by %s but was \n"
+					 "not found in the same directory as \"%s\".\n"
+					 "Check your installation.\n", progname, full_path);
+		else
+			pg_fatal("The program \"postgres\" was found by \"%s\"\n"
+					 "but was not the same version as %s.\n"
+					 "Check your installation.\n", full_path, progname);
+	}
+
+	/* only skip processing after ensuring presence of postgres */
+	if (dry_run)
+		return;
+
+	/* finally run postgres single-user mode */
+	snprintf(cmd, MAXCMDLEN, "\"%s\" --single -D \"%s\" template1 < %s",
+			 exec_path, datadir_target, DEVNULL);
+
+	if (system(cmd) != 0)
+		pg_fatal("postgres single-user mode of target instance failed for command: %s\n", cmd);
+}
-- 
2.17.2

