From a91b429ec9b26999fbfac6536e879ba419201f1d Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Tue, 6 Apr 2021 14:23:43 -0400
Subject: [PATCH] cfe-08-pg_alterckey_over_cfe-07-bin squash commit

---
 doc/src/sgml/ref/allfiles.sgml            |   1 +
 doc/src/sgml/ref/pg_alterckey.sgml (new)  | 210 +++++++
 doc/src/sgml/reference.sgml               |   1 +
 src/bin/Makefile                          |   1 +
 src/bin/pg_alterckey/.gitignore (new)     |   1 +
 src/bin/pg_alterckey/Makefile (new)       |  38 ++
 src/bin/pg_alterckey/README (new)         |  24 +
 src/bin/pg_alterckey/pg_alterckey.c (new) | 724 ++++++++++++++++++++++
 8 files changed, 1000 insertions(+)

diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index d67270ccc3..59fe707895 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -196,6 +196,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropuser           SYSTEM "dropuser.sgml">
 <!ENTITY ecpgRef            SYSTEM "ecpg-ref.sgml">
 <!ENTITY initdb             SYSTEM "initdb.sgml">
+<!ENTITY pgalterckey        SYSTEM "pg_alterckey.sgml">
 <!ENTITY pgamcheck          SYSTEM "pg_amcheck.sgml">
 <!ENTITY pgarchivecleanup   SYSTEM "pgarchivecleanup.sgml">
 <!ENTITY pgBasebackup       SYSTEM "pg_basebackup.sgml">
diff --git a/doc/src/sgml/ref/pg_alterckey.sgml b/doc/src/sgml/ref/pg_alterckey.sgml
new file mode 100644
index 0000000000..867c439b8e
--- /dev/null
+++ b/doc/src/sgml/ref/pg_alterckey.sgml
@@ -0,0 +1,210 @@
+<!--
+doc/src/sgml/ref/pg_alterckey.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="app-pg_alterckey">
+ <indexterm zone="app-pg_alterckey">
+  <primary>pg_alterckey</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle><application>pg_alterckey</application></refentrytitle>
+  <manvolnum>1</manvolnum>
+  <refmiscinfo>Application</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>pg_alterckey</refname>
+  <refpurpose>alter the <productname>PostgreSQL</productname> cluster key</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+  <cmdsynopsis>
+   <command>pg_alterckey</command>
+
+   <group choice="plain">
+    <arg choice="plain"><option>-R</option></arg>
+    <arg choice="plain"><option>--authprompt</option></arg>
+   </group>
+
+   <arg choice="plain">
+    <replaceable class="parameter">old_cluster_key_command</replaceable>
+    <replaceable class="parameter">new_cluster_key_command</replaceable>
+   </arg>
+
+   <arg choice="opt">
+    <group choice="plain">
+     <arg choice="plain"><option>-D</option></arg>
+     <arg choice="plain"><option>--pgdata</option></arg>
+    </group>
+    <replaceable class="parameter">datadir</replaceable>
+   </arg>
+
+  </cmdsynopsis>
+
+  <cmdsynopsis>
+   <command>pg_alterckey</command>
+
+   <group choice="opt">
+    <arg choice="plain"><option>-R</option></arg>
+    <arg choice="plain"><option>--authprompt</option></arg>
+   </group>
+
+   <group choice="plain">
+    <arg choice="plain"><option>-r</option></arg>
+    <arg choice="plain"><option>--repair</option></arg>
+   </group>
+
+   <arg choice="opt">
+    <group choice="opt">
+     <arg choice="plain"><option>-D</option></arg>
+     <arg choice="plain"><option>--pgdata</option></arg>
+    </group>
+    <replaceable class="parameter">datadir</replaceable>
+   </arg>
+
+  </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id="r1-app-pg_alterckey-1">
+  <title>Description</title>
+  <para>
+   <command>pg_alterckey</command> alters the cluster key used
+   for cluster file encryption.  The cluster key is initially set
+   during <xref linkend="app-initdb"/>.  The command can be run while the
+   server is running or stopped.  The new password must be used the next
+   time the server is started.
+  </para>
+
+  <para>
+   <command>pg_alterckey</command> changes the key encryption key
+   (<acronym>KEK</acronym>) which encrypts the data encryption keys;
+   it does not change the data encryption keys.  It does this by
+   decrypting each data encryption key using the <replaceable
+   class="parameter">old_cluster_key_command</replaceable>,
+   re-encrypting it using the <replaceable
+   class="parameter">new_cluster_key_command</replaceable>, and then
+   writes the result back to the cluster directory.
+  </para>
+
+  <para>
+   See the <xref linkend="app-initdb"/> documentation for how to define
+   the old and new passphrase commands.  You can use different executables
+   for these commands, or you can use the same executable with different
+   arguments to specify retrieval of the old or new key.
+  </para>
+
+  <para>
+   <command>pg_alterckey</command> manages data encryption keys,
+   which are critical to allowing Postgres to access its decrypted
+   data.  For this reason, it is very careful to preserve these
+   keys in most possible failure conditions, e.g., operating system
+   failure during cluster encryption key rotation.
+  </para>
+
+  <para>
+   When started, <command>pg_alterckey</command> repairs any files that
+   remain from previous failures before altering the cluster encryption
+   key.  During this repair phase, <command>pg_alterckey</command> will
+   either roll back the cluster key or roll forward the changes that
+   were previously requested.  The server will not start if repair is
+   needed, though a running server is unaffected by an unrepaired cluster
+   key configuration.  Therefore, if <command>pg_alterckey</command>
+   fails for any reason, it is recommended you run the command with
+   <option>--repair</option> to simply roll back or forward any previous
+   changes.  This will report if it rolled the cluster key back or forward,
+   and then run the command again to change the cluster key if needed.
+  </para>
+
+  <para>
+   You can specify the data directory on the command line, or use
+   the environment variable <envar>PGDATA</envar>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Options</title>
+
+   <para>
+    <variablelist>
+     <varlistentry>
+      <term><option>-R</option></term>
+      <term><option>--authprompt</option></term>
+      <listitem>
+       <para>
+        Allows the <option>old_cluster_key_command</option> and
+        <option>new_cluster_key_command</option> commands
+        to prompt for a passphrase or PIN.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+
+   <para>
+    Other options:
+
+    <variablelist>
+     <varlistentry>
+       <term><option>-V</option></term>
+       <term><option>--version</option></term>
+       <listitem>
+       <para>
+       Print the <application>pg_alterckey</application> version and exit.
+       </para>
+       </listitem>
+     </varlistentry>
+
+     <varlistentry>
+       <term><option>-?</option></term>
+       <term><option>--help</option></term>
+       <listitem>
+       <para>
+       Show help about <application>pg_alterckey</application> command line
+       arguments, and exit.
+       </para>
+       </listitem>
+     </varlistentry>
+
+    </variablelist>
+   </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Environment</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><envar>PGDATA</envar></term>
+
+    <listitem>
+     <para>
+      Default data directory location
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><envar>PG_COLOR</envar></term>
+    <listitem>
+     <para>
+      Specifies whether to use color in diagnostic messages. Possible values
+      are <literal>always</literal>, <literal>auto</literal> and
+      <literal>never</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="app-initdb"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index da421ff24e..dff7a42645 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -240,6 +240,7 @@
    </para>
   </partintro>
 
+   &pgalterckey;
    &clusterdb;
    &createdb;
    &createuser;
diff --git a/src/bin/Makefile b/src/bin/Makefile
index 2fe0ae6652..6db542c1bf 100644
--- a/src/bin/Makefile
+++ b/src/bin/Makefile
@@ -17,6 +17,7 @@ SUBDIRS = \
 	initdb \
 	pg_amcheck \
 	pg_archivecleanup \
+	pg_alterckey \
 	pg_basebackup \
 	pg_checksums \
 	pg_config \
diff --git a/src/bin/pg_alterckey/.gitignore b/src/bin/pg_alterckey/.gitignore
new file mode 100644
index 0000000000..4c4f39f2cc
--- /dev/null
+++ b/src/bin/pg_alterckey/.gitignore
@@ -0,0 +1 @@
+/pg_alterckey
diff --git a/src/bin/pg_alterckey/Makefile b/src/bin/pg_alterckey/Makefile
new file mode 100644
index 0000000000..2133a4ba06
--- /dev/null
+++ b/src/bin/pg_alterckey/Makefile
@@ -0,0 +1,38 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/bin/pg_alterckey
+#
+# Copyright (c) 1998-2021, PostgreSQL Global Development Group
+#
+# src/bin/pg_alterckey/Makefile
+#
+#-------------------------------------------------------------------------
+
+PGFILEDESC = "pg_alterckey - alter the cluster key"
+PGAPPICON=win32
+
+subdir = src/bin/pg_alterckey
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+	$(WIN32RES) \
+	pg_alterckey.o
+
+all: pg_alterckey
+
+pg_alterckey: $(OBJS) | submake-libpgport
+	$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
+install: all installdirs
+	$(INSTALL_PROGRAM) pg_alterckey$(X) '$(DESTDIR)$(bindir)/pg_alterckey$(X)'
+
+installdirs:
+	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+
+uninstall:
+	rm -f '$(DESTDIR)$(bindir)/pg_alterckey$(X)'
+
+clean distclean maintainer-clean:
+	rm -f pg_alterckey$(X) $(OBJS)
+	rm -rf tmp_check
diff --git a/src/bin/pg_alterckey/README b/src/bin/pg_alterckey/README
new file mode 100644
index 0000000000..6db3739271
--- /dev/null
+++ b/src/bin/pg_alterckey/README
@@ -0,0 +1,24 @@
+pg_alterckey
+============
+
+This directory contains the code to generate the pg_alterckey binary.
+
+Architecture
+------------
+
+pg_alterckey allows altering of the cluster encryption key (key
+encryption key or KEK) which is stored outside of the file system;  see
+src/backend/crypto/README for more details.  This must be done in a
+crash-safe manner since the keys are critical to reading an encrypted
+cluster.  The active data encryption keys (DEK) are encrypted/wrapped by
+the KEK and stored in PGDATA/pg_cryptokeys/live as separate files,
+currently files 0 and 1.
+
+This process can be interrupted at anytime;  the new execution of
+pg_alterckey will repair any previously interrupted execution of
+pg_alterckey.
+
+pg_alterckey should never be run concurrently.  A lock file prevents
+almost all concurrent execution.  pg_alterckey can be run if the
+database server is running or stopped, so it can't use database locking
+that is only available when the server is running.
diff --git a/src/bin/pg_alterckey/pg_alterckey.c b/src/bin/pg_alterckey/pg_alterckey.c
new file mode 100644
index 0000000000..7e3d4ad915
--- /dev/null
+++ b/src/bin/pg_alterckey/pg_alterckey.c
@@ -0,0 +1,724 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_alterckey.c
+ *    A utility to change the cluster key (key encryption key, KEK)
+ *    used for cluster file encryption.  The KEK wrap data encryption
+ *    keys (DEK).
+ *
+ * The theory of operation is fairly simple:
+ *    1. Create lock file
+ *    2. Retrieve current and new cluster key using the supplied
+ *       commands.
+ *    3. Revert any failed alter operation.
+ *    4. Create a "new" directory
+ *    5. Unwrap each DEK in "live" using the old KEK
+ *    6. Wrap each DEK using the new KEK and write it to "new"
+ *    7. Rename "live" to "old"
+ *    8. Rename "new" to "live"
+ *    9. Remove "old"
+ *   10. Remove lock file
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/bin/pg_alterckey/pg_alterckey.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+
+#define FRONTEND 1
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "common/file_perm.h"
+#include "common/file_utils.h"
+#include "common/hex.h"
+#include "common/restricted_token.h"
+#include "crypto/kmgr.h"
+#include "common/logging.h"
+#include "getopt_long.h"
+#include "pg_getopt.h"
+
+typedef enum
+{
+	SUCCESS_EXIT = 0,
+	ERROR_EXIT,
+	RMDIR_EXIT,
+	REPAIR_EXIT
+} exit_action;
+
+#define MAX_WRAPPED_KEY_LENGTH 64
+
+static int	lock_fd = -1;
+static bool pass_terminal_fd = false;
+int			terminal_fd = -1;
+static bool repair_mode = false;
+static char *old_cluster_key_cmd = NULL,
+		   *new_cluster_key_cmd = NULL;
+static char old_cluster_key[KMGR_CLUSTER_KEY_LEN],
+			new_cluster_key[KMGR_CLUSTER_KEY_LEN];
+static CryptoKey data_key;
+unsigned char in_key[MAX_WRAPPED_KEY_LENGTH],
+			out_key[MAX_WRAPPED_KEY_LENGTH];
+int			in_klen,
+			out_klen;
+static char top_path[MAXPGPATH],
+			pid_path[MAXPGPATH],
+			live_path[MAXPGPATH],
+			new_path[MAXPGPATH],
+			old_path[MAXPGPATH];
+
+static char *DataDir = NULL;
+static const char *progname;
+
+static void create_lockfile(void);
+static void recover_failure(void);
+static void retrieve_cluster_keys(void);
+static void bzero_keys_and_exit(exit_action action);
+static void reencrypt_data_keys(void);
+static void install_new_keys(void);
+
+static void
+usage(const char *progname)
+{
+	printf(_("%s changes the cluster key of a PostgreSQL database cluster.\n\n"), progname);
+	printf(_("Usage:\n"));
+	printf(_("  %s [OPTION] old_cluster_key_command new_cluster_key_command [DATADIR]\n"), progname);
+	printf(_("  %s [repair_option] [DATADIR]\n"), progname);
+	printf(_("\nOptions:\n"));
+	printf(_("  -R, --authprompt       prompt for a passphrase or PIN\n"));
+	printf(_(" [-D, --pgdata=]DATADIR  data directory\n"));
+	printf(_("  -V, --version          output version information, then exit\n"));
+	printf(_("  -?, --help             show this help, then exit\n"));
+	printf(_("\nRepair options:\n"));
+	printf(_("  -r, --repair           repair previous failure\n"));
+	printf(_("\nIf no data directory (DATADIR) is specified, "
+			 "the environment variable PGDATA\nis used.\n\n"));
+	printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+}
+
+
+int
+main(int argc, char *argv[])
+{
+	static struct option long_options[] = {
+		{"authprompt", required_argument, NULL, 'R'},
+		{"repair", required_argument, NULL, 'r'},
+		{"pgdata", required_argument, NULL, 'D'},
+		{NULL, 0, NULL, 0}
+	};
+
+	int			c;
+
+	pg_logging_init(argv[0]);
+	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_alterckey"));
+	progname = get_progname(argv[0]);
+
+	if (argc > 1)
+	{
+		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
+		{
+			usage(progname);
+			exit(0);
+		}
+		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
+		{
+			puts("pg_alterckey (PostgreSQL) " PG_VERSION);
+			exit(0);
+		}
+	}
+
+	/* check for -r/-R */
+	while ((c = getopt_long(argc, argv, "D:rR", long_options, NULL)) != -1)
+	{
+		switch (c)
+		{
+			case 'D':
+				DataDir = optarg;
+				break;
+			case 'r':
+				repair_mode = true;
+				break;
+			case 'R':
+				pass_terminal_fd = true;
+				break;
+			default:
+				fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
+				exit(1);
+		}
+	}
+
+	if (!repair_mode)
+	{
+		/* get cluster key commands */
+		if (optind < argc)
+			old_cluster_key_cmd = argv[optind++];
+		else
+		{
+			pg_log_error("missing old_cluster_key_command");
+			fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+					progname);
+			exit(1);
+		}
+
+		if (optind < argc)
+			new_cluster_key_cmd = argv[optind++];
+		else
+		{
+			pg_log_error("missing new_cluster_key_command");
+			fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+					progname);
+			exit(1);
+		}
+	}
+
+	if (DataDir == NULL)
+	{
+		if (optind < argc)
+			DataDir = argv[optind++];
+		else
+			DataDir = getenv("PGDATA");
+
+		/* If no DataDir was specified, and none could be found, error out */
+		if (DataDir == NULL)
+		{
+			pg_log_error("no data directory specified");
+			fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
+			exit(1);
+		}
+	}
+
+	/* Complain if any arguments remain */
+	if (optind < argc)
+	{
+		pg_log_error("too many command-line arguments (first is \"%s\")",
+					 argv[optind]);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	/*
+	 * Disallow running as root because we create directories in PGDATA
+	 */
+#ifndef WIN32
+	if (geteuid() == 0)
+	{
+		pg_log_error("%s: cannot be run as root\n"
+					 "Please log in (using, e.g., \"su\") as the "
+					 "(unprivileged) user that will\n"
+					 "own the server process.\n",
+					 progname);
+		exit(1);
+	}
+#endif
+
+	get_restricted_token();
+
+	/* Set mask based on PGDATA permissions */
+	if (!GetDataDirectoryCreatePerm(DataDir))
+	{
+		pg_log_error("could not read permissions of directory \"%s\": %m",
+					 DataDir);
+		exit(1);
+	}
+
+	umask(pg_mode_mask);
+
+	snprintf(top_path, sizeof(top_path), "%s/%s", DataDir, KMGR_DIR);
+	snprintf(pid_path, sizeof(pid_path), "%s/%s", DataDir, KMGR_DIR_PID);
+	snprintf(live_path, sizeof(live_path), "%s/%s", DataDir, LIVE_KMGR_DIR);
+	snprintf(new_path, sizeof(new_path), "%s/%s", DataDir, NEW_KMGR_DIR);
+	snprintf(old_path, sizeof(old_path), "%s/%s", DataDir, OLD_KMGR_DIR);
+
+	/* Complain if any arguments remain */
+	if (optind < argc)
+	{
+		pg_log_error("too many command-line arguments (first is \"%s\")",
+					 argv[optind]);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	if (DataDir == NULL)
+	{
+		pg_log_error("no data directory specified");
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
+		exit(1);
+	}
+
+	create_lockfile();
+
+	recover_failure();
+
+	if (!repair_mode)
+	{
+		retrieve_cluster_keys();
+		reencrypt_data_keys();
+		install_new_keys();
+	}
+
+#ifndef WIN32
+	/* remove file system reference to file */
+	if (unlink(pid_path) < 0)
+	{
+		pg_log_error("could not delete lock file \"%s\": %m", KMGR_DIR_PID);
+		exit(1);
+	}
+#endif
+
+	close(lock_fd);
+
+	bzero_keys_and_exit(SUCCESS_EXIT);
+}
+
+/* Create a lock file;  this prevents almost all cases of concurrent access */
+void
+create_lockfile(void)
+{
+	struct stat buffer;
+	char		lock_pid_str[20];
+
+	if (stat(top_path, &buffer) != 0 || !S_ISDIR(buffer.st_mode))
+	{
+		pg_log_error("cluster file encryption directory \"%s\" is missing;  is it enabled?", KMGR_DIR_PID);
+		fprintf(stderr, _("Exiting with no changes made.\n"));
+		exit(1);
+	}
+
+	/* Does a lockfile exist? */
+	if ((lock_fd = open(pid_path, O_RDONLY, 0)) != -1)
+	{
+		int			lock_pid;
+		int			len;
+
+		/* read the PID */
+		if ((len = read(lock_fd, lock_pid_str, sizeof(lock_pid_str) - 1)) == 0)
+		{
+			pg_log_error("cannot read pid from lock file \"%s\": %m", KMGR_DIR_PID);
+			fprintf(stderr, _("Exiting with no changes made.\n"));
+			exit(1);
+		}
+		lock_pid_str[len] = '\0';
+
+		if ((lock_pid = atoi(lock_pid_str)) == 0)
+		{
+			pg_log_error("invalid pid in lock file \"%s\": %m", KMGR_DIR_PID);
+			fprintf(stderr, _("Exiting with no changes made.\n"));
+			exit(1);
+		}
+
+		/* Is the PID running? */
+		if (kill(lock_pid, 0) == 0)
+		{
+			pg_log_error("active process %d currently holds a lock on this operation, recorded in \"%s\"",
+						 lock_pid, KMGR_DIR_PID);
+			fprintf(stderr, _("Exiting with no changes made.\n"));
+			exit(1);
+		}
+
+		close(lock_fd);
+
+		if (repair_mode)
+			printf("old lock file removed\n");
+
+		/* ----------
+		 * pid is no longer running, so remove the lock file.
+		 * This is not 100% safe from concurrent access, e.g.:
+		 *
+		 *    process 1 exits and leaves stale lock file
+		 *    process 2 checks stale lock file of process 1
+		 *    process 3 checks stale lock file of process 1
+		 *    process 2 remove the lock file of process 1
+		 *    process 4 creates a lock file
+		 *    process 3 remove the lock file of process 4
+		 *    process 5 creates a lock file
+		 *
+		 * The sleep(2) helps with this since it reduces the likelihood
+		 * a process that did an unlock will interfere with another unlock
+		 * process.  We could ask users to remove the lock, but that seems
+		 * even more error-prone, especially since this might happen
+		 * on server start.  Many PG tools seem to have problems with
+		 * concurrent access.
+		 * ----------
+		 */
+		unlink(pid_path);
+
+		/* Sleep to reduce the likelihood of concurrent unlink */
+		pg_usleep(2000000L);	/* 2 seconds */
+	}
+
+	/* Create our own lockfile? */
+#ifndef WIN32
+	lock_fd = open(pid_path, O_RDWR | O_CREAT | O_EXCL, pg_file_create_mode);
+#else
+	/* delete on close */
+	lock_fd = open(pid_path, O_RDWR | O_CREAT | O_EXCL | O_TEMPORARY,
+				   pg_file_create_mode);
+#endif
+
+	if (lock_fd == -1)
+	{
+		if (errno == EEXIST)
+			pg_log_error("an active process currently holds a lock on this operation, recorded in \"%s\"",
+						 KMGR_DIR_PID);
+		else
+			pg_log_error("unable to create lock file \"%s\": %m", KMGR_DIR_PID);
+		fprintf(stderr, _("Exiting with no changes made.\n"));
+		exit(1);
+	}
+
+	snprintf(lock_pid_str, sizeof(lock_pid_str), "%d\n", getpid());
+	if (write(lock_fd, lock_pid_str, strlen(lock_pid_str)) != strlen(lock_pid_str))
+	{
+		pg_log_error("could not write pid to lock file \"%s\": %m", KMGR_DIR_PID);
+		fprintf(stderr, _("Exiting with no changes made.\n"));
+		exit(1);
+	}
+}
+
+/*
+ * ----------
+ *	recover_failure
+ *
+ * A previous pg_alterckey might have failed, so it might need recovery.
+ * The normal operation is:
+ * 1.     reencrypt  LIVE_KMGR_DIR -> NEW_KMGR_DIR
+ * 2.     rename     KMGR_DIR      -> OLD_KMGR_DIR
+ * 3.     rename     NEW_KMGR_DIR  -> LIVE_KMGR_DIR
+ *        remove     OLD_KMGR_DIR
+ *
+ * There are eight possible directory configurations:
+ *
+ *                            LIVE_KMGR_DIR   NEW_KMGR_DIR    OLD_KMGR_DIR
+ *
+ * Normal:
+ * 0. normal                       X
+ * 1. remove new                   X               X
+ * 2. install new                                  X               X
+ * 3. remove old                   X                               X
+ *
+ * Abnormal:
+ *    fatal
+ *    restore old                                                  X
+ *    install new                                  X
+ *    remove old and new           X               X               X
+ *
+ * We don't handle the abnormal cases, just report an error.
+ * ----------
+ */
+static void
+recover_failure(void)
+{
+	struct stat buffer;
+	bool		is_live,
+				is_new,
+				is_old;
+
+	is_live = !stat(live_path, &buffer);
+	is_new = !stat(new_path, &buffer);
+	is_old = !stat(old_path, &buffer);
+
+	/* normal #0 */
+	if (is_live && !is_new && !is_old)
+	{
+		if (repair_mode)
+			printf("repair unnecessary\n");
+		return;
+	}
+	/* remove new #1 */
+	else if (is_live && is_new && !is_old)
+	{
+		if (!rmtree(new_path, true))
+		{
+			pg_log_error("unable to remove new directory \"%s\": %m", NEW_KMGR_DIR);
+			fprintf(stderr, _("Exiting with no changes made.\n"));
+			exit(1);
+		}
+		printf(_("removed files created during previously aborted alter operation\n"));
+		return;
+	}
+	/* install new #2 */
+	else if (!is_live && is_new && is_old)
+	{
+		if (rename(new_path, live_path) != 0)
+		{
+			pg_log_error("unable to rename directory \"%s\" to \"%s\": %m",
+						 NEW_KMGR_DIR, LIVE_KMGR_DIR);
+			fprintf(stderr, _("Exiting with no changes made.\n"));
+			exit(1);
+		}
+		printf(_("Installed new cluster password supplied in previous alter operation\n"));
+		return;
+	}
+	/* remove old #3 */
+	else if (is_live && !is_new && is_old)
+	{
+		if (!rmtree(old_path, true))
+		{
+			pg_log_error("unable to remove old directory \"%s\": %m", OLD_KMGR_DIR);
+			fprintf(stderr, _("Exiting with no changes made.\n"));
+			exit(1);
+		}
+		printf(_("Removed old files invalidated during previous alter operation\n"));
+		return;
+	}
+	else
+	{
+		pg_log_error("cluster file encryption directory \"%s\" is in an abnormal state and cannot be processed",
+					 KMGR_DIR);
+		fprintf(stderr, _("Exiting with no changes made.\n"));
+		exit(1);
+	}
+}
+
+/* Retrieve old and new cluster keys */
+void
+retrieve_cluster_keys(void)
+{
+	int			cluster_key_len;
+	char		cluster_key_hex[ALLOC_KMGR_CLUSTER_KEY_LEN];
+
+	/*
+	 * If we have been asked to pass an open file descriptor to the user
+	 * terminal to the commands, set one up.
+	 */
+	if (pass_terminal_fd)
+	{
+#ifndef WIN32
+		terminal_fd = open("/dev/tty", O_RDWR, 0);
+#else
+		terminal_fd = open("CONOUT$", O_RDWR, 0);
+#endif
+		if (terminal_fd < 0)
+		{
+			pg_log_error(_("%s: could not open terminal: %s\n"),
+						 progname, strerror(errno));
+			exit(1);
+		}
+	}
+
+	/* Get old key encryption key from the cluster key command */
+	cluster_key_len = kmgr_run_cluster_key_command(old_cluster_key_cmd,
+												   (char *) cluster_key_hex,
+												   ALLOC_KMGR_CLUSTER_KEY_LEN,
+												   live_path, terminal_fd);
+	if (pg_hex_decode(cluster_key_hex, cluster_key_len,
+					  (char *) old_cluster_key, KMGR_CLUSTER_KEY_LEN) !=
+		KMGR_CLUSTER_KEY_LEN)
+	{
+		pg_log_error("cluster key must be at %d hex bytes", KMGR_CLUSTER_KEY_LEN);
+		bzero_keys_and_exit(ERROR_EXIT);
+	}
+
+	/*
+	 * Create new key directory here in case the new cluster key command needs
+	 * it to exist.
+	 */
+	if (mkdir(new_path, pg_dir_create_mode) != 0)
+	{
+		pg_log_error("unable to create new cluster key directory \"%s\": %m", NEW_KMGR_DIR);
+		bzero_keys_and_exit(ERROR_EXIT);
+	}
+
+	/* Get new key */
+	cluster_key_len = kmgr_run_cluster_key_command(new_cluster_key_cmd,
+												   (char *) cluster_key_hex,
+												   ALLOC_KMGR_CLUSTER_KEY_LEN,
+												   new_path, terminal_fd);
+	if (pg_hex_decode(cluster_key_hex, cluster_key_len,
+					  (char *) new_cluster_key, KMGR_CLUSTER_KEY_LEN) !=
+		KMGR_CLUSTER_KEY_LEN)
+	{
+		pg_log_error("cluster key must be at %d hex bytes", KMGR_CLUSTER_KEY_LEN);
+		bzero_keys_and_exit(ERROR_EXIT);
+	}
+
+	if (pass_terminal_fd)
+		close(terminal_fd);
+
+	/* output newline */
+	puts("");
+
+	if (memcmp(old_cluster_key, new_cluster_key, KMGR_CLUSTER_KEY_LEN) == 0)
+	{
+		pg_log_error("cluster keys are identical, exiting\n");
+		bzero_keys_and_exit(RMDIR_EXIT);
+	}
+}
+
+/* Decrypt old keys encrypted with old pass phrase and reencrypt with new one */
+void
+reencrypt_data_keys(void)
+{
+	PgCipherCtx *old_ctx,
+			   *new_ctx;
+
+	old_ctx = pg_cipher_ctx_create(PG_CIPHER_AES_KWP,
+								   (unsigned char *) old_cluster_key,
+								   KMGR_CLUSTER_KEY_LEN, false);
+	if (!old_ctx)
+		pg_log_error("could not initialize encryption context");
+
+	new_ctx = pg_cipher_ctx_create(PG_CIPHER_AES_KWP,
+								   (unsigned char *) new_cluster_key,
+								   KMGR_CLUSTER_KEY_LEN, true);
+	if (!new_ctx)
+		pg_log_error("could not initialize encryption context");
+
+	for (int id = 0; id < KMGR_NUM_DATA_KEYS; id++)
+	{
+		char		src_path[MAXPGPATH],
+					dst_path[MAXPGPATH];
+		int			src_fd,
+					dst_fd;
+		int			len;
+		struct stat st;
+
+		CryptoKeyFilePath(src_path, live_path, id);
+		CryptoKeyFilePath(dst_path, new_path, id);
+
+		if ((src_fd = open(src_path, O_RDONLY | PG_BINARY, 0)) < 0)
+		{
+			pg_log_error("could not open file \"%s\": %m", src_path);
+			bzero_keys_and_exit(RMDIR_EXIT);
+		}
+
+		if ((dst_fd = open(dst_path, O_RDWR | O_CREAT | O_TRUNC | PG_BINARY,
+						   pg_file_create_mode)) < 0)
+		{
+			pg_log_error("could not open file \"%s\": %m", dst_path);
+			bzero_keys_and_exit(RMDIR_EXIT);
+		}
+
+		if (fstat(src_fd, &st))
+		{
+			pg_log_error("could not stat file \"%s\": %m", src_path);
+			bzero_keys_and_exit(RMDIR_EXIT);
+		}
+
+		in_klen = st.st_size;
+
+		if (in_klen > MAX_WRAPPED_KEY_LENGTH)
+		{
+			pg_log_error("invalid wrapped key length (%d) for file \"%s\"", in_klen, src_path);
+			bzero_keys_and_exit(RMDIR_EXIT);
+		}
+
+		/* Read the source key */
+		len = read(src_fd, in_key, in_klen);
+		if (len != in_klen)
+		{
+			if (len < 0)
+				pg_log_error("could read file \"%s\": %m", src_path);
+			else
+				pg_log_error("could read file \"%s\": read %d of %u",
+							 src_path, len, in_klen);
+			bzero_keys_and_exit(RMDIR_EXIT);
+		}
+
+		/* decrypt with old key */
+		if (!kmgr_unwrap_data_key(old_ctx, in_key, in_klen, &data_key))
+		{
+			pg_log_error("incorrect old key specified");
+			bzero_keys_and_exit(RMDIR_EXIT);
+		}
+
+		if (KMGR_MAX_KEY_LEN_BYTES + pg_cipher_blocksize(new_ctx) > MAX_WRAPPED_KEY_LENGTH)
+		{
+			pg_log_error("invalid max wrapped key length");
+			bzero_keys_and_exit(RMDIR_EXIT);
+		}
+
+		/* encrypt with new key */
+		if (!kmgr_wrap_data_key(new_ctx, &data_key, out_key, &out_klen))
+		{
+			pg_log_error("could not encrypt new key");
+			bzero_keys_and_exit(RMDIR_EXIT);
+		}
+
+		/* Write to the dest key */
+		len = write(dst_fd, out_key, out_klen);
+		if (len != out_klen)
+		{
+			pg_log_error("could not write fie \"%s\"", dst_path);
+			bzero_keys_and_exit(RMDIR_EXIT);
+		}
+
+		close(src_fd);
+		close(dst_fd);
+	}
+
+	/* The cluster key is correct, free the cipher context */
+	pg_cipher_ctx_free(old_ctx);
+	pg_cipher_ctx_free(new_ctx);
+}
+
+/* Install new keys */
+void
+install_new_keys(void)
+{
+	/*
+	 * Issue fsync's so key rotation is less likely to be left in an
+	 * inconsistent state in case of a crash during this operation.
+	 */
+	
+	if (rename(live_path, old_path) != 0)
+	{
+		pg_log_error("unable to rename directory \"%s\" to \"%s\": %m",
+					 LIVE_KMGR_DIR, OLD_KMGR_DIR);
+		bzero_keys_and_exit(RMDIR_EXIT);
+	}
+	fsync_dir_recurse(top_path);
+
+	if (rename(new_path, live_path) != 0)
+	{
+		pg_log_error("unable to rename directory \"%s\" to \"%s\": %m",
+					 NEW_KMGR_DIR, LIVE_KMGR_DIR);
+		bzero_keys_and_exit(REPAIR_EXIT);
+	}
+	fsync_dir_recurse(top_path);
+
+	if (!rmtree(old_path, true))
+	{
+		pg_log_error("unable to remove old directory \"%s\": %m", OLD_KMGR_DIR);
+		bzero_keys_and_exit(REPAIR_EXIT);
+	}
+	fsync_dir_recurse(top_path);
+}
+
+/* Erase memory and exit */
+void
+bzero_keys_and_exit(exit_action action)
+{
+	explicit_bzero(old_cluster_key, sizeof(old_cluster_key));
+	explicit_bzero(new_cluster_key, sizeof(new_cluster_key));
+
+	explicit_bzero(in_key, sizeof(in_key));
+	explicit_bzero(&data_key, sizeof(data_key));
+	explicit_bzero(out_key, sizeof(out_key));
+
+	if (action == RMDIR_EXIT)
+	{
+		if (!rmtree(new_path, true))
+			pg_log_error("unable to remove new directory \"%s\": %m", NEW_KMGR_DIR);
+		printf("Re-running pg_alterckey to repair might be needed before the next server start\n");
+		exit(1);
+	}
+	else if (action == REPAIR_EXIT)
+	{
+		unlink(pid_path);
+		printf("Re-running pg_alterckey to repair might be needed before the next server start\n");
+	}
+
+	/* return 0 or 1 */
+	exit(action != SUCCESS_EXIT);
+}
-- 
2.20.1

