From 05360e06c2d4a01b8d9eed6df43faf95feb06cef Mon Sep 17 00:00:00 2001
From: Zsolt Parragi <zsolt.parragi@percona.com>
Date: Thu, 23 Jul 2026 07:25:47 +0000
Subject: [PATCH] pg_resetwal: do not allow zero next multixact offset

Offset 0 is the "invalid" marker in pg_multixact/offsets since offsets
went 64-bit and the allocator stopped skipping it. pg_resetwal could
still produce it via -O 0 or guessed control values, breaking the first
multixact created after the reset ("MultiXact n has invalid offset",
and vacuum of the affected table fails from then on). Reject -O 0 like
-m and -o already do, and guess 1 like initdb does.
---
 src/bin/pg_resetwal/pg_resetwal.c      | 6 +++++-
 src/bin/pg_resetwal/t/001_basic.pl     | 4 ++++
 src/bin/pg_resetwal/t/002_corrupted.pl | 7 +++++++
 3 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index d072e7c2ea4..1542a56ca4b 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -303,6 +303,10 @@ main(int argc, char *argv[])
 					pg_log_error_hint("Try \"%s --help\" for more information.", progname);
 					exit(1);
 				}
+
+				/* offset 0 means "invalid" in pg_multixact/offsets */
+				if (next_mxoff_val == 0)
+					pg_fatal("next multitransaction offset (-O) must not be 0");
 				next_mxoff_given = true;
 				break;
 
@@ -700,7 +704,7 @@ GuessControlValues(void)
 		FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId);
 	ControlFile.checkPointCopy.nextOid = FirstGenbkiObjectId;
 	ControlFile.checkPointCopy.nextMulti = FirstMultiXactId;
-	ControlFile.checkPointCopy.nextMultiOffset = 0;
+	ControlFile.checkPointCopy.nextMultiOffset = 1;
 	ControlFile.checkPointCopy.oldestXid = FirstNormalTransactionId;
 	ControlFile.checkPointCopy.oldestXidDB = InvalidOid;
 	ControlFile.checkPointCopy.oldestMulti = FirstMultiXactId;
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index d686584eb96..cff0b6423f3 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -145,6 +145,10 @@ command_fails_like(
 	[ 'pg_resetwal', '-O' => '-1', $node->data_dir ],
 	qr/error: invalid argument for option -O/,
 	'fails with -O value -1');
+command_fails_like(
+	[ 'pg_resetwal', '-O' => '0', $node->data_dir ],
+	qr/must not be 0/,
+	'fails with -O value 0');
 # --wal-segsize
 command_fails_like(
 	[ 'pg_resetwal', '--wal-segsize' => 'foo', $node->data_dir ],
diff --git a/src/bin/pg_resetwal/t/002_corrupted.pl b/src/bin/pg_resetwal/t/002_corrupted.pl
index dcd06913778..a83ae631d5c 100644
--- a/src/bin/pg_resetwal/t/002_corrupted.pl
+++ b/src/bin/pg_resetwal/t/002_corrupted.pl
@@ -64,4 +64,11 @@ command_ok(
 	[ 'pg_resetwal', '--force', $node->data_dir ],
 	'runs with force when control file values were guessed');
 
+# Offset 0 means "invalid" in pg_multixact/offsets, so the guessed
+# next multixact offset must not be 0.
+command_like(
+	[ 'pg_controldata', $node->data_dir ],
+	qr/^Latest checkpoint's NextMultiOffset:\s*[1-9]/m,
+	'guessed next multixact offset is not 0');
+
 done_testing();
-- 
2.54.0

