From 2ef4d2bab0a5a1a32ae454148ad9e3df900efab1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Thu, 13 Aug 2026 11:48:16 +0200
Subject: [PATCH v6 1/2] Record initial state of data checksums in controlfile

The controlfile records the current state of data checksums, which
also used to be the initial state from initdb when checksums could
not be altered after initialization.  pg_control_init is documented
to return information about cluster initialization state, which it
no longer will if data checksums have been changed either using the
offline tool or with online processing.

Fix by adding a new field in the control file which tracks the init
value of data checksums, and is left read only after initialization.

While this is a regression dating back to when changing checksum
state was made possible offline with pg_checksums, it is a control
file change so it cannot be backpatched.

Backpatch to v19 where online checksums were introduced.

Author: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Discussion: https://postgr.es/m/B87ABFBE-A304-4839-8706-C80D73E6BF5C@yesql.se
Backpatch-through: 19
---
 src/backend/access/transam/xlog.c             |  1 +
 src/backend/utils/misc/pg_controldata.c       |  2 +-
 src/include/catalog/pg_control.h              | 10 ++++++--
 .../modules/test_checksums/t/001_basic.pl     | 23 +++++++++++++------
 .../modules/test_checksums/t/002_restarts.pl  | 12 +++++++++-
 .../modules/test_checksums/t/004_offline.pl   | 14 +++++++++--
 6 files changed, 49 insertions(+), 13 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index c3baca5193b..3eddec89ad0 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4283,6 +4283,7 @@ InitControlFile(uint64 sysidentifier, uint32 data_checksum_version)
 	ControlFile->wal_log_hints = wal_log_hints;
 	ControlFile->track_commit_timestamp = track_commit_timestamp;
 	ControlFile->data_checksum_version = data_checksum_version;
+	ControlFile->data_checksum_version_init = data_checksum_version;
 
 	/*
 	 * Set the data_checksum_version value into XLogCtl, which is where all
diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c
index d229ae35209..d4feec95b26 100644
--- a/src/backend/utils/misc/pg_controldata.c
+++ b/src/backend/utils/misc/pg_controldata.c
@@ -254,7 +254,7 @@ pg_control_init(PG_FUNCTION_ARGS)
 	values[9] = BoolGetDatum(ControlFile->float8ByVal);
 	nulls[9] = false;
 
-	values[10] = Int32GetDatum(ControlFile->data_checksum_version);
+	values[10] = Int32GetDatum(ControlFile->data_checksum_version_init);
 	nulls[10] = false;
 
 	values[11] = BoolGetDatum(ControlFile->default_char_signedness);
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index 80b3a730e03..7b5404460ec 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -22,7 +22,7 @@
 
 
 /* Version identifier for this pg_control format */
-#define PG_CONTROL_VERSION	1902
+#define PG_CONTROL_VERSION	1903
 
 /* Nonce key length, see below */
 #define MOCK_AUTH_NONCE_LEN		32
@@ -228,7 +228,13 @@ typedef struct ControlFileData
 
 	bool		float8ByVal;	/* float8, int8, etc pass-by-value? */
 
-	/* Are data pages protected by checksums? Zero if no checksum version */
+	/*
+	 * Data checksum state at cluster initialization. Since the state can be
+	 * changed during runtime, we need to store the initial value for system
+	 * functions which report initdb settings.
+	 */
+	uint32		data_checksum_version_init;
+	/* Current data checksums state */
 	uint32		data_checksum_version;
 
 	/*
diff --git a/src/test/modules/test_checksums/t/001_basic.pl b/src/test/modules/test_checksums/t/001_basic.pl
index 72e0d0df46f..7477f00947d 100644
--- a/src/test/modules/test_checksums/t/001_basic.pl
+++ b/src/test/modules/test_checksums/t/001_basic.pl
@@ -14,24 +14,33 @@ use lib $FindBin::RealBin;
 
 use DataChecksums::Utils;
 
-# Initialize node with checksums disabled.
+# Initialize node with checksums enabled to test pg_control_init returning
+# 1 for this cluster, the remaining tests will initialize to off to test the
+# return value for a cluster initialized without checksums
 my $node = PostgreSQL::Test::Cluster->new('basic_node');
-$node->init(no_data_checksums => 1);
+$node->init;
 $node->start;
 
-# Create some content to have un-checksummed data in the cluster
+# Create some content to have data in the cluster
 $node->safe_psql('postgres',
 	"CREATE TABLE t AS SELECT generate_series(1,10000) AS a;");
 
-# Ensure that checksums are turned off
-test_checksum_state($node, 'off');
+# Ensure that checksums are turned on
+test_checksum_state($node, 'on');
+
+# Disable data checksums and wait for the state transition to 'off'
+disable_data_checksums($node, wait => 'off');
+
+# Make sure pg_control_init reports the initial enabled state
+my $result = $node->safe_psql('postgres',
+	'SELECT data_page_checksum_version FROM pg_control_init();');
+is($result, '1', 'ensure pg_control_init reports enabled state');
 
 # Enable data checksums and wait for the state transition to 'on'
 enable_data_checksums($node, wait => 'on');
 
 # Run a dummy query just to make sure we can read back data
-my $result =
-  $node->safe_psql('postgres', "SELECT count(*) FROM t WHERE a > 1 ");
+$result = $node->safe_psql('postgres', "SELECT count(*) FROM t WHERE a > 1 ");
 is($result, '9999', 'ensure checksummed pages can be read back');
 
 # Enable data checksums again which should be a no-op so we explicitly don't
diff --git a/src/test/modules/test_checksums/t/002_restarts.pl b/src/test/modules/test_checksums/t/002_restarts.pl
index d98c8024f29..bef0bb90993 100644
--- a/src/test/modules/test_checksums/t/002_restarts.pl
+++ b/src/test/modules/test_checksums/t/002_restarts.pl
@@ -30,6 +30,11 @@ $node->safe_psql('postgres',
 # Ensure that checksums are disabled
 test_checksum_state($node, 'off');
 
+# Make sure pg_control_init reports the initial disabled state
+$result = $node->safe_psql('postgres',
+	'SELECT data_page_checksum_version FROM pg_control_init();');
+is($result, '0', 'ensure pg_control_init reports disabled state');
+
 SKIP:
 {
 	skip 'Data checksum delay tests not enabled in PG_TEST_EXTRA', 6
@@ -134,9 +139,14 @@ wait_for_checksum_state($node, "off");
 $block_session->quit;
 
 # Finish test suite by enabling checksums and make sure all data can be read
-# back and no processes are left over
+# back, no processes are left over and the initial state is still correctly
+# reported
 enable_data_checksums($node, wait => 'on');
 
+$result = $node->safe_psql('postgres',
+	'SELECT data_page_checksum_version FROM pg_control_init();');
+is($result, '0', 'ensure pg_control_init still reports disabled state');
+
 $result = $node->safe_psql('postgres', "SELECT count(*) FROM t WHERE a > 1");
 is($result, '9999', 'ensure checksummed pages can be read back');
 
diff --git a/src/test/modules/test_checksums/t/004_offline.pl b/src/test/modules/test_checksums/t/004_offline.pl
index 73c279e75e0..48254111410 100644
--- a/src/test/modules/test_checksums/t/004_offline.pl
+++ b/src/test/modules/test_checksums/t/004_offline.pl
@@ -20,6 +20,11 @@ my $node = PostgreSQL::Test::Cluster->new('offline_node');
 $node->init(no_data_checksums => 1);
 $node->start;
 
+# Make sure pg_control_init reports the initial state as disabled
+my $result = $node->safe_psql('postgres',
+	'SELECT data_page_checksum_version FROM pg_control_init();');
+is($result, '0', 'ensure pg_control_init reports disabled state');
+
 # Create some content to have un-checksummed data in the cluster
 $node->safe_psql('postgres',
 	"CREATE TABLE t AS SELECT generate_series(1,10000) AS a;");
@@ -35,9 +40,14 @@ $node->start;
 # Ensure that checksums are enabled
 test_checksum_state($node, 'on');
 
+# Make sure pg_control_init still reports the initial state as disabled even
+# though the current state has changed.
+$result = $node->safe_psql('postgres',
+	'SELECT data_page_checksum_version FROM pg_control_init();');
+is($result, '0', 'ensure pg_control_init still reports disabled state');
+
 # Run a dummy query just to make sure we can read back some data
-my $result =
-  $node->safe_psql('postgres', "SELECT count(*) FROM t WHERE a > 1");
+$result = $node->safe_psql('postgres', "SELECT count(*) FROM t WHERE a > 1");
 is($result, '9999', 'ensure checksummed pages can be read back');
 
 # Disable checksums offline again using pg_checksums
-- 
2.39.3 (Apple Git-146)

