Subject: [PATCH 16/17] Teach the remaining frontend application about encryption.

From: Antonin Houska <[email-masked]>
Date: Fri, 5 Jul 2019 16:24:02 +0200

pg_checksums, pg_rewind, pg_waldump
---
 src/bin/pg_checksums/Makefile       |   9 +-
 src/bin/pg_checksums/pg_checksums.c | 158 +++++++++++++++++++++++++++++++++---
 src/bin/pg_rewind/Makefile          |   9 +-
 src/bin/pg_rewind/parsexlog.c       |   9 ++
 src/bin/pg_rewind/pg_rewind.c       |  57 ++++++++++++-
 src/bin/pg_waldump/Makefile         |   9 +-
 src/bin/pg_waldump/pg_waldump.c     |  90 +++++++++++++++++---
 7 files changed, 309 insertions(+), 32 deletions(-)

diff --git a/src/bin/pg_checksums/Makefile b/src/bin/pg_checksums/Makefile
index 278b7a0f2e..74e66ea0b7 100644
--- a/src/bin/pg_checksums/Makefile
+++ b/src/bin/pg_checksums/Makefile
@@ -15,13 +15,19 @@ subdir = src/bin/pg_checksums
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS= pg_checksums.o $(WIN32RES)
+OBJS= encryption.o pg_checksums.o $(WIN32RES)
+
+override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
+LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
 
 all: pg_checksums
 
 pg_checksums: $(OBJS) | submake-libpgport
 	$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
+encryption.c: % : $(top_srcdir)/src/backend/storage/file/%
+	rm -f $@ && $(LN_S) $< .
+
 install: all installdirs
 	$(INSTALL_PROGRAM) pg_checksums$(X) '$(DESTDIR)$(bindir)/pg_checksums$(X)'
 
@@ -34,6 +40,7 @@ uninstall:
 clean distclean maintainer-clean:
 	rm -f pg_checksums$(X) $(OBJS)
 	rm -rf tmp_check
+	rm -f encryption.c
 
 check:
 	$(prove_check)
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index b591fcc864..587132faf9 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -20,15 +20,18 @@
 #include <unistd.h>
 
 #include "access/xlog_internal.h"
+#include "catalog/pg_tablespace_d.h"
 #include "common/controldata_utils.h"
 #include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/logging.h"
+#include "fe_utils/encryption.h"
 #include "getopt_long.h"
 #include "pg_getopt.h"
 #include "storage/bufpage.h"
 #include "storage/checksum.h"
 #include "storage/checksum_impl.h"
+#include "storage/encryption.h"

 static int64 files = 0;
@@ -80,6 +83,10 @@ usage(void)
 	printf(_("  -c, --check              check data checksums (default)\n"));
 	printf(_("  -d, --disable            disable data checksums\n"));
 	printf(_("  -e, --enable             enable data checksums\n"));
+#ifdef	USE_ENCRYPTION
+	printf(_("  -K, --encryption-key-command=COMMAND\n"
+			 "                           command that returns encryption key\n"));
+#endif							/* USE_ENCRYPTION */
 	printf(_("  -f, --filenode=FILENODE  check only relation with specified filenode\n"));
 	printf(_("  -N, --no-sync            do not wait for changes to be written safely to disk\n"));
 	printf(_("  -P, --progress           show progress information\n"));
@@ -167,7 +174,8 @@ skipfile(const char *fn)
 }
 
 static void
-scan_file(const char *fn, BlockNumber segmentno)
+scan_file(const char *fn, Oid relnode, BlockNumber segmentno,
+		  ForkNumber forkno, Oid tbspace, Oid db)
 {
 	PGAlignedBlock buf;
 	PageHeader	header = (PageHeader) buf.data;
@@ -193,6 +201,7 @@ scan_file(const char *fn, BlockNumber segmentno)
 	{
 		uint16		csum;
 		int			r = read(f, buf.data, BLCKSZ);
+		char		tweak[TWEAK_SIZE];
 
 		if (r == 0)
 			break;
@@ -204,6 +213,18 @@ scan_file(const char *fn, BlockNumber segmentno)
 		}
 		blocks++;
 
+		if (data_encrypted)
+		{
+			RelFileNode node;
+
+			node.spcNode = tbspace;
+			node.dbNode = db;
+			node.relNode = relnode;
+
+			mdtweak(tweak, &node, forkno, blockno);
+			decrypt_block(buf.data, buf.data, BLCKSZ, tweak, false);
+		}
+
 		/* New pages have no checksum yet */
 		if (PageIsNew(header))
 			continue;
@@ -232,6 +253,9 @@ scan_file(const char *fn, BlockNumber segmentno)
 				exit(1);
 			}
 
+			if (data_encrypted)
+				encrypt_block(buf.data, buf.data, BLCKSZ, tweak, false);
+
 			/* Write block with checksum */
 			if (write(f, buf.data, BLCKSZ) != BLCKSZ)
 			{
@@ -262,9 +286,13 @@ scan_file(const char *fn, BlockNumber segmentno)
  * all the items which have checksums is computed and returned back
  * to the caller without operating on the files.  This is used to compile
  * the total size of the data directory for progress reports.
+ *
+ * If db is a valid pointer, *db contains database OID. If it's NULL, the
+ * database OID needs to be recognized, possibly by recursive call.
  */
 static int64
-scan_directory(const char *basedir, const char *subdir, bool sizeonly)
+scan_directory(const char *basedir, const char *subdir, bool sizeonly,
+			   Oid tbspace, Oid *db)
 {
 	int64		dirsize = 0;
 	char		path[MAXPGPATH];
@@ -282,6 +310,9 @@ scan_directory(const char *basedir, const char *subdir, bool sizeonly)
 	{
 		char		fn[MAXPGPATH];
 		struct stat st;
+		bool		tbsp_identified = false;
+		Oid			database;
+		bool		db_identified = false;
 
 		if (strcmp(de->d_name, ".") == 0 ||
 			strcmp(de->d_name, "..") == 0)
@@ -305,12 +336,58 @@ scan_directory(const char *basedir, const char *subdir, bool sizeonly)
 			pg_log_error("could not stat file \"%s\": %m", fn);
 			exit(1);
 		}
+
+		if (tbspace != InvalidOid && db == NULL)
+		{
+			/*
+			 * If tablespace is passed by caller or identified by upper call
+			 * of this function, and if caller could not identify the database
+			 * OID, try to do it now.
+			 */
+			if (strcmp(de->d_name, TABLESPACE_VERSION_DIRECTORY) == 0)
+			{
+				/*
+				 * Major-version-specific tablespace subdirectory needs no
+				 * special attention, we'll recurse into it below.
+				 */
+			}
+			else
+			{
+				errno = 0;
+				database = strtol(de->d_name, NULL, 10);
+				if (errno != 0 || database == InvalidOid)
+				{
+					fprintf(stderr, _("%s: invalid database oid \"%s\"\n"),
+							progname, de->d_name);
+					exit(1);
+				}
+				db_identified = true;
+			}
+		}
+		else if (tbspace == InvalidOid)
+		{
+			/*
+			 * This entry should be a direct subdirectory of pg_tblspc, so the
+			 * name is supposedly tablespace oid.
+			 */
+			errno = 0;
+			tbspace = strtol(de->d_name, NULL, 10);
+			if (errno != 0 || tbspace == InvalidOid)
+			{
+				fprintf(stderr, _("%s: invalid tablespace oid \"%s\"\n"),
+						progname, de->d_name);
+				exit(1);
+			}
+			tbsp_identified = true;
+		}
+
 		if (S_ISREG(st.st_mode))
 		{
 			char		fnonly[MAXPGPATH];
 			char	   *forkpath,
 					   *segmentpath;
 			BlockNumber segmentno = 0;
+			ForkNumber	forkno;
 
 			if (skipfile(de->d_name))
 				continue;
@@ -337,10 +414,16 @@ scan_directory(const char *basedir, const char *subdir, bool sizeonly)
 
 			forkpath = strchr(fnonly, '_');
 			if (forkpath != NULL)
+			{
 				*forkpath++ = '\0';
 
+				forkno = forkname_to_number(forkpath);
+			}
+			else
+				forkno = MAIN_FORKNUM;
+
 			if (only_filenode && strcmp(only_filenode, fnonly) != 0)
-				/* filenode not to be included */
+				/* Relfilenode not to be included */
 				continue;
 
 			dirsize += st.st_size;
@@ -350,14 +433,31 @@ scan_directory(const char *basedir, const char *subdir, bool sizeonly)
 			 * the items in the data folder.
 			 */
 			if (!sizeonly)
-				scan_file(fn, segmentno);
+			{
+				Oid			relnode = atoi(fnonly);
+
+				scan_file(fn, relnode, segmentno, forkno, tbspace, *db);
+			}
 		}
 #ifndef WIN32
 		else if (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))
 #else
 		else if (S_ISDIR(st.st_mode) || pgwin32_is_junction(fn))
 #endif
-			dirsize += scan_directory(path, de->d_name, sizeonly);
+
+			/*
+			 * If database OID is not passed by caller, use the one we
+			 * identified ourselves.
+			 */
+			dirsize = scan_directory(path, de->d_name, sizeonly, tbspace,
+									 db_identified ? &database : db);
+
+		/*
+		 * If tablespace is not passed by caller, forget what we found out so
+		 * that next directory entry is examined in the same way.
+		 */
+		if (tbsp_identified)
+			tbspace = InvalidOid;
 	}
 	closedir(dir);
 	return dirsize;
@@ -371,6 +471,9 @@ main(int argc, char *argv[])
 		{"pgdata", required_argument, NULL, 'D'},
 		{"disable", no_argument, NULL, 'd'},
 		{"enable", no_argument, NULL, 'e'},
+#ifdef	USE_ENCRYPTION
+		{"encryption-key-command", required_argument, NULL, 'K'},
+#endif							/* USE_ENCRYPTION */
 		{"filenode", required_argument, NULL, 'f'},
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
@@ -401,7 +504,7 @@ main(int argc, char *argv[])
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "cD:deNPf:v", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "cD:deNK:Pf:v", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -422,6 +525,12 @@ main(int argc, char *argv[])
 				}
 				only_filenode = pstrdup(optarg);
 				break;
+#ifdef	USE_ENCRYPTION
+			case 'K':
+				encryption_key_command = pg_strdup(optarg);
+				data_encrypted = true;
+				break;
+#endif							/* USE_ENCRYPTION */
 			case 'N':
 				do_sync = false;
 				break;
@@ -483,6 +592,24 @@ main(int argc, char *argv[])
 		exit(1);
 	}
 
+	if (ControlFile->data_cipher > PG_CIPHER_NONE)
+	{
+		if (encryption_key_command == NULL)
+		{
+#ifdef USE_ENCRYPTION
+			fprintf(stderr, _("%s: please specify command to retrieve encryption key\n"),
+					progname);
+#else
+			fprintf(stderr, _("%s: compile postgres with --with-openssl to use encryption\n"),
+					progname);
+#endif							/* USE_ENCRYPTION */
+			exit(1);
+		}
+
+		run_encryption_key_command(encryption_key, DataDir);
+		setup_encryption();
+	}
+
 	if (ControlFile->pg_control_version != PG_CONTROL_VERSION)
 	{
 		pg_log_error("cluster is not compatible with this version of pg_checksums");
@@ -533,6 +660,8 @@ main(int argc, char *argv[])
 	/* Operate on all files if checking or enabling checksums */
 	if (mode == PG_MODE_CHECK || mode == PG_MODE_ENABLE)
 	{
+		Oid			db_shared = 0;
+
 		/*
 		 * If progress status information is requested, we need to scan the
 		 * directory tree twice: once to know how much total data needs to be
@@ -540,14 +669,19 @@ main(int argc, char *argv[])
 		 */
 		if (showprogress)
 		{
-			total_size = scan_directory(DataDir, "global", true);
-			total_size += scan_directory(DataDir, "base", true);
-			total_size += scan_directory(DataDir, "pg_tblspc", true);
+			total_size = scan_directory(DataDir, "global", true,
+										GLOBALTABLESPACE_OID, &db_shared);
+			total_size += scan_directory(DataDir, "base", true,
+										 DEFAULTTABLESPACE_OID, NULL);
+			total_size += scan_directory(DataDir, "pg_tblspc", true,
+										 InvalidOid, NULL);
 		}
 
-		(void) scan_directory(DataDir, "global", false);
-		(void) scan_directory(DataDir, "base", false);
-		(void) scan_directory(DataDir, "pg_tblspc", false);
+		(void) scan_directory(DataDir, "global", false, GLOBALTABLESPACE_OID,
+							  &db_shared);
+		(void) scan_directory(DataDir, "base", false, DEFAULTTABLESPACE_OID,
+							  NULL);
+		(void) scan_directory(DataDir, "pg_tblspc", false, InvalidOid, NULL);
 
 		if (showprogress)
 		{
diff --git a/src/bin/pg_rewind/Makefile b/src/bin/pg_rewind/Makefile
index 859d3abc41..e41d2035f4 100644
--- a/src/bin/pg_rewind/Makefile
+++ b/src/bin/pg_rewind/Makefile
@@ -16,10 +16,10 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 override CPPFLAGS := -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)
-LDFLAGS_INTERNAL += $(libpq_pgport)
+LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
 
 OBJS	= pg_rewind.o parsexlog.o xlogreader.o datapagemap.o timeline.o \
-	fetch.o file_ops.o copy_fetch.o libpq_fetch.o filemap.o \
+	fetch.o file_ops.o copy_fetch.o libpq_fetch.o filemap.o encryption.o\
 	$(WIN32RES)
 
 EXTRA_CLEAN = xlogreader.c
@@ -32,6 +32,9 @@ pg_rewind: $(OBJS) | submake-libpq submake-libpgport
 xlogreader.c: % : $(top_srcdir)/src/backend/access/transam/%
 	rm -f $@ && $(LN_S) $< .
 
+encryption.c: % : $(top_srcdir)/src/backend/storage/file/%
+	rm -f $@ && $(LN_S) $< .
+
 install: all installdirs
 	$(INSTALL_PROGRAM) pg_rewind$(X) '$(DESTDIR)$(bindir)/pg_rewind$(X)'
 
@@ -42,7 +45,7 @@ uninstall:
 	rm -f '$(DESTDIR)$(bindir)/pg_rewind$(X)'
 
 clean distclean maintainer-clean:
-	rm -f pg_rewind$(X) $(OBJS) xlogreader.c
+	rm -f pg_rewind$(X) $(OBJS) xlogreader.c encryption.c
 	rm -rf tmp_check
 
 check:
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 287af60c4e..0624c4c5ef 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -320,6 +320,15 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
 		return -1;
 	}
 
+	if (data_encrypted)
+	{
+		char		tweak[TWEAK_SIZE];
+
+		XLogEncryptionTweak(tweak, targetHistory[private->tliIndex].tli,
+							xlogreadsegno, targetPageOff);
+		decrypt_block(readBuf, readBuf, XLOG_BLCKSZ, tweak, true);
+	}
+
 	Assert(targetSegNo == xlogreadsegno);
 
 	*pageTLI = targetHistory[private->tliIndex].tli;
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index d378053de4..58c192adad 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -27,8 +27,10 @@
 #include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/restricted_token.h"
+#include "fe_utils/encryption.h"
 #include "getopt_long.h"
 #include "storage/bufpage.h"
+#include "storage/encryption.h"
 
 static void usage(const char *progname);
 
@@ -75,6 +77,10 @@ usage(const char *progname)
 	printf(_("  -D, --target-pgdata=DIRECTORY  existing data directory to modify\n"));
 	printf(_("      --source-pgdata=DIRECTORY  source data directory to synchronize with\n"));
 	printf(_("      --source-server=CONNSTR    source server to synchronize with\n"));
+#ifdef	USE_ENCRYPTION
+	printf(_("  -K, --encryption-key-command=COMMAND\n"
+			 "                                 command that returns encryption key\n"));
+#endif							/* USE_ENCRYPTION */
 	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"));
@@ -99,6 +105,9 @@ main(int argc, char **argv)
 		{"no-sync", no_argument, NULL, 'N'},
 		{"progress", no_argument, NULL, 'P'},
 		{"debug", no_argument, NULL, 3},
+#ifdef	USE_ENCRYPTION
+		{"encryption-key-command", required_argument, NULL, 'K'},
+#endif							/* USE_ENCRYPTION */
 		{NULL, 0, NULL, 0}
 	};
 	int			option_index;
@@ -134,7 +143,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "D:K:nNP", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -169,6 +178,12 @@ main(int argc, char **argv)
 			case 2:				/* --source-server */
 				connstr_source = pg_strdup(optarg);
 				break;
+#ifdef	USE_ENCRYPTION
+			case 4:				/* --encryption-key-command */
+			case 'K':
+				encryption_key_command = strdup(optarg);
+				break;
+#endif							/* USE_ENCRYPTION */
 		}
 	}
 
@@ -248,6 +263,28 @@ main(int argc, char **argv)
 	sanityChecks();
 
 	/*
+	 * Setup encryption if it's obvious that we'll have to deal with encrypted
+	 * XLOG.
+	 */
+	if (ControlFile_target.data_cipher > PG_CIPHER_NONE)
+	{
+		if (encryption_key_command == NULL)
+		{
+			pg_log_error("-K option must be passed for encrypted cluster");
+			exit(EXIT_FAILURE);
+		}
+
+		/*
+		 * It should not matter whether we pass the source or target data
+		 * directory. It should have been checked earlier that both clusters
+		 * are encrypted using the same key.
+		 */
+		run_encryption_key_command(encryption_key, datadir_source);
+		setup_encryption();
+		data_encrypted = true;
+	}
+
+	/*
 	 * If both clusters are already on the same timeline, there's nothing to
 	 * do.
 	 */
@@ -446,6 +483,24 @@ sanityChecks(void)
 		ControlFile_source.state != DB_SHUTDOWNED &&
 		ControlFile_source.state != DB_SHUTDOWNED_IN_RECOVERY)
 		pg_fatal("source data directory must be shut down cleanly");
+
+	/*
+	 * Since slave receives XLOG stream encrypted by master, handling
+	 * differently encrypted clusters is not the typical use case for
+	 * pg_rewind. Yet we should check the encryption.
+	 */
+	if (ControlFile_source.data_cipher > PG_CIPHER_NONE ||
+		ControlFile_target.data_cipher > PG_CIPHER_NONE)
+	{
+		if (ControlFile_source.data_cipher !=
+			ControlFile_target.data_cipher)
+			pg_fatal("source and target server must be both unencrypted or both encrypted\n");
+
+		if (memcmp(ControlFile_source.encryption_verification,
+				   ControlFile_target.encryption_verification,
+				   ENCRYPTION_SAMPLE_SIZE))
+			pg_fatal("both source and target server must use the same encryption key");
+	}
 }
 
 /*
diff --git a/src/bin/pg_waldump/Makefile b/src/bin/pg_waldump/Makefile
index 135979cef3..9eb1c2f01d 100644
--- a/src/bin/pg_waldump/Makefile
+++ b/src/bin/pg_waldump/Makefile
@@ -7,10 +7,11 @@ subdir = src/bin/pg_waldump
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = pg_waldump.o compat.o xlogreader.o rmgrdesc.o \
+OBJS = pg_waldump.o compat.o xlogreader.o rmgrdesc.o encryption.o \
 	$(RMGRDESCOBJS) $(WIN32RES)
 
 override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
+LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
 
 RMGRDESCSOURCES = $(sort $(notdir $(wildcard $(top_srcdir)/src/backend/access/rmgrdesc/*desc.c)))
 RMGRDESCOBJS = $(patsubst %.c,%.o,$(RMGRDESCSOURCES))
@@ -27,6 +28,9 @@ xlogreader.c: % : $(top_srcdir)/src/backend/access/transam/%
 $(RMGRDESCSOURCES): % : $(top_srcdir)/src/backend/access/rmgrdesc/%
 	rm -f $@ && $(LN_S) $< .
 
+encryption.c: % : $(top_srcdir)/src/backend/storage/file/%
+	rm -f $@ && $(LN_S) $< .
+
 install: all installdirs
 	$(INSTALL_PROGRAM) pg_waldump$(X) '$(DESTDIR)$(bindir)/pg_waldump$(X)'
 
@@ -37,7 +41,8 @@ uninstall:
 	rm -f '$(DESTDIR)$(bindir)/pg_waldump$(X)'
 
 clean distclean maintainer-clean:
-	rm -f pg_waldump$(X) $(OBJS) $(RMGRDESCSOURCES) xlogreader.c
+	rm -f pg_waldump$(X) $(OBJS) $(RMGRDESCSOURCES) xlogreader.c \
+encryption.c
 	rm -rf tmp_check
 
 check:
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index b95d467805..4df84efb01 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -22,6 +22,8 @@
 #include "access/transam.h"
 #include "common/fe_memutils.h"
 #include "common/logging.h"
+#include "fe_utils/encryption.h"
+#include "storage/encryption.h"
 #include "getopt_long.h"
 #include "rmgrdesc.h"
 
@@ -194,27 +196,59 @@ search_directory(const char *directory, const char *fname)
 		PGAlignedXLogBlock buf;
 		int			r;
 
-		r = read(fd, buf.data, XLOG_BLCKSZ);
-		if (r == XLOG_BLCKSZ)
+		if (data_encrypted)
 		{
-			XLogLongPageHeader longhdr = (XLogLongPageHeader) buf.data;
-
-			WalSegSz = longhdr->xlp_seg_size;
+			/*
+			 * Segment size affects calculation of segNo and thus also the
+			 * encryption tweak, so we cannot get the size from the header
+			 * until the page is decrypted. Here we need to take more
+			 * expensive approach and really check the file size.
+			 */
+			WalSegSz = (int) lseek(fd, 0, SEEK_END);
+			if (WalSegSz <= 0)
+				fatal_error("Could not determine size of WAL segment \"%s\"", fname);
 
+			/*
+			 * Verification of the file size is the only useful thing we can
+			 * do. If anything else is wrong, the XLOG reader should find out
+			 * after decryption.
+			 */
 			if (!IsValidWalSegSize(WalSegSz))
-				fatal_error(ngettext("WAL segment size must be a power of two between 1 MB and 1 GB, but the WAL file \"%s\" header specifies %d byte",
-									 "WAL segment size must be a power of two between 1 MB and 1 GB, but the WAL file \"%s\" header specifies %d bytes",
+				fatal_error(ngettext("WAL segment size must be a power of two between 1 MB and 1 GB, but size of the WAL file \"%s\" is %d byte",
+									 "WAL segment size must be a power of two between 1 MB and 1 GB, but size of the WAL file \"%s\" is %d bytes",
 									 WalSegSz),
 							fname, WalSegSz);
 		}
 		else
 		{
-			if (errno != 0)
-				fatal_error("could not read file \"%s\": %s",
-							fname, strerror(errno));
+			r = read(fd, buf.data, XLOG_BLCKSZ);
+			if (r == XLOG_BLCKSZ)
+			{
+				XLogLongPageHeader longhdr;
+
+				longhdr = (XLogLongPageHeader) buf.data;
+
+				if (longhdr->std.xlp_magic != XLOG_PAGE_MAGIC)
+					fatal_error(gettext("WAL segment \"%s\" has an incorrect magic number. If it's encrypted, use the -K option to pass encryption credentials"),
+								fname);
+
+				WalSegSz = longhdr->xlp_seg_size;
+
+				if (!IsValidWalSegSize(WalSegSz))
+					fatal_error(ngettext("WAL segment size must be a power of two between 1 MB and 1 GB, but the WAL file \"%s\" header specifies %d byte",
+										 "WAL segment size must be a power of two between 1 MB and 1 GB, but the WAL file \"%s\" header specifies %d bytes",
+										 WalSegSz),
+								fname, WalSegSz);
+			}
 			else
-				fatal_error("could not read file \"%s\": read %d of %zu",
-							fname, r, (Size) XLOG_BLCKSZ);
+			{
+				if (errno != 0)
+					fatal_error("could not read file \"%s\": %s",
+								fname, strerror(errno));
+				else
+					fatal_error("could not read file \"%s\": read %d of %zu",
+								fname, r, (Size) XLOG_BLCKSZ);
+			}
 		}
 		close(fd);
 		return true;
@@ -444,6 +478,19 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
 	XLogDumpXLogRead(private->inpath, private->timeline, targetPagePtr,
 					 readBuff, count);
 
+	if (data_encrypted)
+	{
+		char		tweak[TWEAK_SIZE];
+		XLogSegNo	readSegNo;
+		uint32		readSegOff;
+
+		XLByteToSeg(targetPagePtr, readSegNo, WalSegSz);
+		readSegOff = targetPagePtr % WalSegSz;
+
+		XLogEncryptionTweak(tweak, private->timeline, readSegNo, readSegOff);
+		decrypt_block(readBuff, readBuff, count, tweak, true);
+	}
+
 	return count;
 }
 
@@ -792,6 +839,10 @@ usage(void)
 	printf(_("  -b, --bkp-details      output detailed information about backup blocks\n"));
 	printf(_("  -e, --end=RECPTR       stop reading at WAL location RECPTR\n"));
 	printf(_("  -f, --follow           keep retrying after reaching end of WAL\n"));
+#ifdef	USE_ENCRYPTION
+	printf(_("  -K, --encryption-key-command=COMMAND\n"
+			 "                         command that returns encryption key\n"));
+#endif							/* USE_ENCRYPTION */
 	printf(_("  -n, --limit=N          number of records to display\n"));
 	printf(_("  -p, --path=PATH        directory in which to find log segment files or a\n"
 			 "                         directory with a ./pg_wal that contains such files\n"
@@ -827,6 +878,7 @@ main(int argc, char **argv)
 		{"end", required_argument, NULL, 'e'},
 		{"follow", no_argument, NULL, 'f'},
 		{"help", no_argument, NULL, '?'},
+		{"encryption-key-command", required_argument, NULL, 'K'},
 		{"limit", required_argument, NULL, 'n'},
 		{"path", required_argument, NULL, 'p'},
 		{"rmgr", required_argument, NULL, 'r'},
@@ -884,7 +936,7 @@ main(int argc, char **argv)
 		goto bad_argument;
 	}
 
-	while ((option = getopt_long(argc, argv, "be:fn:p:r:s:t:x:z",
+	while ((option = getopt_long(argc, argv, "be:fK:n:p:r:s:t:x:z",
 								 long_options, &optindex)) != -1)
 	{
 		switch (option)
@@ -904,6 +956,12 @@ main(int argc, char **argv)
 			case 'f':
 				config.follow = true;
 				break;
+#ifdef	USE_ENCRYPTION
+			case 'K':
+				encryption_key_command = pg_strdup(optarg);
+				data_encrypted = true;
+				break;
+#endif							/* USE_ENCRYPTION */
 			case 'n':
 				if (sscanf(optarg, "%d", &config.stop_after_records) != 1)
 				{
@@ -1099,6 +1157,12 @@ main(int argc, char **argv)
 
 	/* done with argument parsing, do the actual work */
 
+	if (data_encrypted)
+	{
+		run_encryption_key_command(encryption_key, NULL);
+		setup_encryption();
+	}
+
 	/* we have everything we need, start reading */
 	xlogreader_state = XLogReaderAllocate(WalSegSz, XLogDumpReadPage,
 										  &private);
-- 
2.13.7

--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v04-0017-User-and-development-documentation.patch