From 6b5c6001dbf6f34fcc7432e5274f63aef6e5f717 Mon Sep 17 00:00:00 2001
From: David Christensen <david@endpoint.com>
Date: Wed, 15 Feb 2017 15:18:57 -0600
Subject: [PATCH] Add pg_disable_checksums() and supporting infrastructure

Extracted from a larger patch, this patch provides the basic infrastructure for turning data
checksums off in a cluster.  This also sets up the necessary pg_control fields to support the
necessary multiple states for handling the transitions.

We do a few things:

- Change "data_checksums" from a simple boolean to "data_checksum_state", an enum type for all of
  the potentially-required states for this feature (as well as enabling).

- Add pg_control support for parsing/displaying the new setting.

- Distinguish between the possible checksum states and be specific about whether we care about
  checksum read failures or write failures at all call sites, turning DataChecksumsEnabled() into two
  functions: DataChecksumsEnabledReads() and DataChecksumsEnabledWrites().

- Create a superuser function pg_disable_checksums() to perform the actual disabling of this in the
  cluster.

I have *not* changed the default in initdb to enable checksums, but this would be trivial.
---
 src/backend/access/transam/xlog.c       | 77 ++++++++++++++++++++++++++++++---
 src/backend/commands/dbcommands.c       |  1 +
 src/backend/storage/page/bufpage.c      |  6 +--
 src/backend/storage/page/checksum.c     |  3 ++
 src/backend/utils/misc/guc.c            | 51 +++++++++++++++++-----
 src/bin/pg_controldata/pg_controldata.c | 19 ++++++++
 src/bin/pg_resetwal/pg_resetwal.c       |  2 +
 src/include/access/xlog.h               |  7 ++-
 src/include/catalog/pg_control.h        | 10 ++++-
 src/include/catalog/pg_proc.h           |  2 +
 src/include/storage/checksum.h          | 15 +++++++
 11 files changed, 169 insertions(+), 24 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f23e108..79c3c17 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4628,8 +4628,7 @@ ReadControlFile(void)
 #endif
 
 	/* Make the initdb settings visible as GUC variables, too */
-	SetConfigOption("data_checksums", DataChecksumsEnabled() ? "yes" : "no",
-					PGC_INTERNAL, PGC_S_OVERRIDE);
+	data_checksums = ControlFile->data_checksum_state;
 }
 
 void
@@ -4685,13 +4684,36 @@ GetSystemIdentifier(void)
 }
 
 /*
- * Are checksums enabled for data pages?
+ * Returns the checksum state
  */
+ChecksumState
+DataChecksumsState(void)
+{
+	Assert(ControlFile != NULL);
+	return ControlFile->data_checksum_state;
+}
+
+/*
+ * Are read checksums enabled for data pages?
+ */
+bool
+DataChecksumsEnabledReads(void)
+{
+	Assert(ControlFile != NULL);
+	return (ControlFile->data_checksum_version > 0 &&
+			ControlFile->data_checksum_state >= CHECKSUMS_ENFORCING);
+}
+
+/*
+ * Are write checksums enabled for data pages?
+ */
+
 bool
-DataChecksumsEnabled(void)
+DataChecksumsEnabledWrites(void)
 {
 	Assert(ControlFile != NULL);
-	return (ControlFile->data_checksum_version > 0);
+	return (ControlFile->data_checksum_version > 0 &&
+			(ControlFile->data_checksum_state != CHECKSUMS_DISABLED));
 }
 
 /*
@@ -5075,6 +5097,7 @@ BootStrapXLOG(void)
 	ControlFile->wal_log_hints = wal_log_hints;
 	ControlFile->track_commit_timestamp = track_commit_timestamp;
 	ControlFile->data_checksum_version = bootstrap_data_checksum_version;
+	ControlFile->data_checksum_state = bootstrap_data_checksum_version == 0 ? CHECKSUMS_DISABLED : CHECKSUMS_ENFORCING;
 
 	/* some additional ControlFile fields are set in WriteControlFile() */
 
@@ -12016,3 +12039,47 @@ XLogRequestWalReceiverReply(void)
 {
 	doRequestWalReceiverReply = true;
 }
+
+/*
+ * Disable checksums in a cluster
+ * We might need a "DISABLING" state, but hopefully not
+ */
+Datum
+pg_disable_checksums(PG_FUNCTION_ARGS)
+{
+	bool		status = false;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to disable checksums"))));
+
+	LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
+
+	switch (ControlFile->data_checksum_state)
+	{
+		case CHECKSUMS_DISABLED:
+			ereport(NOTICE,
+					(errmsg("checksums already disabled; skipping")));
+			break;
+		case CHECKSUMS_ENABLING:
+		case CHECKSUMS_ENFORCING:
+		case CHECKSUMS_REVALIDATING:
+
+
+			ControlFile->data_checksum_state = CHECKSUMS_DISABLED;
+			/* ControlFile->data_checksum_version = 0; */
+			data_checksums = CHECKSUMS_DISABLED;
+			UpdateControlFile();
+			status = true;
+
+			ereport(NOTICE,
+					(errmsg("disabled checksums")));
+			break;
+
+	}
+	LWLockRelease(ControlFileLock);
+
+	PG_RETURN_BOOL(status);
+}
+
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 1ebacbc..59f19d8 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -56,6 +56,7 @@
 #include "storage/ipc.h"
 #include "storage/procarray.h"
 #include "storage/smgr.h"
+#include "storage/checksum.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
diff --git a/src/backend/storage/page/bufpage.c b/src/backend/storage/page/bufpage.c
index 6fc5fa4..0a0ed51 100644
--- a/src/backend/storage/page/bufpage.c
+++ b/src/backend/storage/page/bufpage.c
@@ -93,7 +93,7 @@ PageIsVerified(Page page, BlockNumber blkno)
 	 */
 	if (!PageIsNew(page))
 	{
-		if (DataChecksumsEnabled())
+		if (DataChecksumsEnabledReads())
 		{
 			checksum = pg_checksum_page((char *) page, blkno);
 
@@ -1145,7 +1145,7 @@ PageSetChecksumCopy(Page page, BlockNumber blkno)
 	static char *pageCopy = NULL;
 
 	/* If we don't need a checksum, just return the passed-in data */
-	if (PageIsNew(page) || !DataChecksumsEnabled())
+	if (PageIsNew(page) || !DataChecksumsEnabledWrites())
 		return (char *) page;
 
 	/*
@@ -1172,7 +1172,7 @@ void
 PageSetChecksumInplace(Page page, BlockNumber blkno)
 {
 	/* If we don't need a checksum, just return */
-	if (PageIsNew(page) || !DataChecksumsEnabled())
+	if (PageIsNew(page) || !DataChecksumsEnabledWrites())
 		return;
 
 	((PageHeader) page)->pd_checksum = pg_checksum_page((char *) page, blkno);
diff --git a/src/backend/storage/page/checksum.c b/src/backend/storage/page/checksum.c
index b96440a..9fe8959 100644
--- a/src/backend/storage/page/checksum.c
+++ b/src/backend/storage/page/checksum.c
@@ -21,3 +21,6 @@
  * that file from the exported Postgres headers.  (Compare our CRC code.)
  */
 #include "storage/checksum_impl.h"
+
+/* global variable to store global checksum state */
+ChecksumState			data_checksums = CHECKSUMS_DISABLED;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5d8fb2e..1b790ca 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -67,6 +67,7 @@
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/bufmgr.h"
+#include "storage/checksum.h"
 #include "storage/dsm_impl.h"
 #include "storage/standby.h"
 #include "storage/fd.h"
@@ -116,6 +117,7 @@ extern char *default_tablespace;
 extern char *temp_tablespaces;
 extern bool ignore_checksum_failure;
 extern bool synchronize_seqscans;
+extern ChecksumState	data_checksums;
 
 #ifdef TRACE_SYNCSCAN
 extern bool trace_syncscan;
@@ -190,6 +192,7 @@ static void assign_application_name(const char *newval, void *extra);
 static bool check_cluster_name(char **newval, void **extra, GucSource source);
 static const char *show_unix_socket_permissions(void);
 static const char *show_log_file_mode(void);
+static const char *show_data_checksum_mode(void);
 
 /* Private functions in guc-file.l that need to be called from guc.c */
 static ConfigVariable *ProcessConfigFileInternal(GucContext context,
@@ -417,6 +420,17 @@ static const struct config_enum_entry password_encryption_options[] = {
 	{"no", PASSWORD_TYPE_PLAINTEXT, true},
 	{"1", PASSWORD_TYPE_MD5, true},
 	{"0", PASSWORD_TYPE_PLAINTEXT, true},
+};
+
+/*
+ * Although only "on", "off", and "force" are documented, we
+ * accept all the likely variants of "on" and "off".
+ */
+static const struct config_enum_entry data_checksum_options[] = {
+	{"disabled", CHECKSUMS_DISABLED, false},
+	{"enabling", CHECKSUMS_ENABLING, false},
+	{"enforcing", CHECKSUMS_ENFORCING, false},
+	{"revalidating", CHECKSUMS_REVALIDATING, false},
 	{NULL, 0, false}
 };
 
@@ -515,7 +529,6 @@ static int	max_identifier_length;
 static int	block_size;
 static int	segment_size;
 static int	wal_block_size;
-static bool data_checksums;
 static int	wal_segment_size;
 static bool integer_datetimes;
 static bool assert_enabled;
@@ -1631,17 +1644,6 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 
 	{
-		{"data_checksums", PGC_INTERNAL, PRESET_OPTIONS,
-			gettext_noop("Shows whether data checksums are turned on for this cluster."),
-			NULL,
-			GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
-		},
-		&data_checksums,
-		false,
-		NULL, NULL, NULL
-	},
-
-	{
 		{"syslog_sequence_numbers", PGC_SIGHUP, LOGGING_WHERE,
 			gettext_noop("Add sequence number to syslog messages to avoid duplicate suppression."),
 			NULL
@@ -3847,6 +3849,18 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
+		{"data_checksums", PGC_INTERNAL, PRESET_OPTIONS,
+			gettext_noop("Shows the status of data_checksums in this cluster."),
+			NULL,
+			GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+
+		},
+		&data_checksums,
+		CHECKSUMS_DISABLED, data_checksum_options,
+		NULL, NULL, show_data_checksum_mode
+	},
+
+	{
 		{"force_parallel_mode", PGC_USERSET, QUERY_TUNING_OTHER,
 			gettext_noop("Forces use of parallel query facilities."),
 			gettext_noop("If possible, run query using a parallel worker and with parallel restrictions.")
@@ -10457,4 +10471,17 @@ show_log_file_mode(void)
 	return buf;
 }
 
+
+static const char *
+show_data_checksum_mode(void)
+{
+	static char *states[] = {"disabled", "enabling", "enabled", "revalidating", "invalid"};
+	int			state = DataChecksumsState();
+
+	if (state < CHECKSUMS_DISABLED || state > CHECKSUMS_REVALIDATING)
+		state = 4;				/* invalid */
+
+	return states[state];
+}
+
 #include "guc-file.c"
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index 20077a6..8563b45 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -67,6 +67,23 @@ dbState(DBState state)
 }
 
 static const char *
+checksumState(ChecksumState state)
+{
+	switch (state)
+	{
+		case CHECKSUMS_DISABLED:
+			return _("disabled");
+		case CHECKSUMS_ENABLING:
+			return _("enabling");
+		case CHECKSUMS_ENFORCING:
+			return _("enforcing");
+		case CHECKSUMS_REVALIDATING:
+			return _("revalidating");
+	}
+	return _("unrecognized status code");
+}
+
+static const char *
 wal_level_str(WalLevel wal_level)
 {
 	switch (wal_level)
@@ -301,5 +318,7 @@ main(int argc, char *argv[])
 		   (ControlFile->float8ByVal ? _("by value") : _("by reference")));
 	printf(_("Data page checksum version:           %u\n"),
 		   ControlFile->data_checksum_version);
+	printf(_("Data page checksum status:            %s\n"),
+		   checksumState(ControlFile->data_checksum_state));
 	return 0;
 }
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 96b7097..a50768f 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -696,6 +696,8 @@ PrintControlValues(bool guessed)
 		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
 	printf(_("Data page checksum version:           %u\n"),
 		   ControlFile.data_checksum_version);
+	printf(_("Data page checksum state:             %u\n"),
+		   ControlFile.data_checksum_state);
 }
 
 
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 9f036c7..98c018e 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -19,6 +19,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/pg_list.h"
 #include "storage/fd.h"
+#include "storage/checksum.h"
 
 
 /* Sync methods */
@@ -153,7 +154,7 @@ extern PGDLLIMPORT int wal_level;
  * of the bits make it to disk, but the checksum wouldn't match.  Also WAL-log
  * them if forced by wal_log_hints=on.
  */
-#define XLogHintBitIsNeeded() (DataChecksumsEnabled() || wal_log_hints)
+#define XLogHintBitIsNeeded() (DataChecksumsEnabledWrites() || wal_log_hints)
 
 /* Do we need to WAL-log information required only for Hot Standby and logical replication? */
 #define XLogStandbyInfoActive() (wal_level >= WAL_LEVEL_REPLICA)
@@ -256,7 +257,9 @@ extern char *XLogFileNameP(TimeLineID tli, XLogSegNo segno);
 
 extern void UpdateControlFile(void);
 extern uint64 GetSystemIdentifier(void);
-extern bool DataChecksumsEnabled(void);
+extern ChecksumState DataChecksumsState(void);
+extern bool DataChecksumsEnabledReads(void);
+extern bool DataChecksumsEnabledWrites(void);
 extern XLogRecPtr GetFakeLSNForUnloggedRel(void);
 extern Size XLOGShmemSize(void);
 extern void XLOGShmemInit(void);
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index 23731e9..db57d62 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -18,10 +18,10 @@
 #include "access/xlogdefs.h"
 #include "pgtime.h"				/* for pg_time_t */
 #include "port/pg_crc32c.h"
-
+#include "storage/checksum.h"
 
 /* Version identifier for this pg_control format */
-#define PG_CONTROL_VERSION	960
+#define PG_CONTROL_VERSION	1000
 
 /*
  * Body of CheckPoint XLOG records.  This is declared here because we keep
@@ -225,6 +225,12 @@ typedef struct ControlFileData
 	/* Are data pages protected by checksums? Zero if no checksum version */
 	uint32		data_checksum_version;
 
+	/*
+	 * What state the cluster is in for the in-place upgrade of the checksum
+	 * setting
+	 */
+	ChecksumState data_checksum_state;
+
 	/* CRC of all above ... MUST BE LAST! */
 	pg_crc32c	crc;
 } ControlFileData;
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index bb7053a..8c9a891 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3173,6 +3173,8 @@ DESCR("export a snapshot");
 DATA(insert OID = 3810 (  pg_is_in_recovery		PGNSP PGUID 12 1 0 0 0 f f f f t f v s 0 0 16 "" _null_ _null_ _null_ _null_ _null_ pg_is_in_recovery _null_ _null_ _null_ ));
 DESCR("true if server is in recovery");
 
+DATA(insert OID = 3353 (  pg_disable_checksums		PGNSP PGUID 12 1 0 0 0 f f f f t f v s 0 0 16 "" _null_ _null_ _null_ _null_ _null_ pg_disable_checksums _null_ _null_ _null_ ));
+DESCR("disables cluster checksums");
 DATA(insert OID = 3820 ( pg_last_wal_receive_location	PGNSP PGUID 12 1 0 0 0 f f f f t f v s 0 0 3220 "" _null_ _null_ _null_ _null_ _null_ pg_last_wal_receive_location _null_ _null_ _null_ ));
 DESCR("current wal flush location");
 DATA(insert OID = 3821 ( pg_last_wal_replay_location	PGNSP PGUID 12 1 0 0 0 f f f f t f v s 0 0 3220 "" _null_ _null_ _null_ _null_ _null_ pg_last_wal_replay_location _null_ _null_ _null_ ));
diff --git a/src/include/storage/checksum.h b/src/include/storage/checksum.h
index 682043f..14229cc 100644
--- a/src/include/storage/checksum.h
+++ b/src/include/storage/checksum.h
@@ -15,10 +15,25 @@
 
 #include "storage/block.h"
 
+
+/*
+ * Checksum state indicator.  Note this is stored in pg_control; if you change
+ * it, you must bump PG_CONTROL_VERSION
+ */
+typedef enum ChecksumState
+{
+	CHECKSUMS_DISABLED = 0,
+	CHECKSUMS_ENABLING,
+	CHECKSUMS_ENFORCING,
+	CHECKSUMS_REVALIDATING
+}	ChecksumState;
+
+
 /*
  * Compute the checksum for a Postgres page.  The page must be aligned on a
  * 4-byte boundary.
  */
 extern uint16 pg_checksum_page(char *page, BlockNumber blkno);
+extern ChecksumState	data_checksums;
 
 #endif   /* CHECKSUM_H */
-- 
2.8.4 (Apple Git-73)

