From 1281e9e9c9393efc80442bf4ec98c8f91664682f Mon Sep 17 00:00:00 2001
From: Nitin Motiani <nitinmotiani@google.com>
Date: Fri, 12 Sep 2025 08:49:07 +0000
Subject: [PATCH v3] Fix "unexpected zero page" in pgstattuple for hash and
 gist indexes

* pgstattuple calls check functions for all the pages in hash and
  gist indexes. These calls are made from pgstat_hash_page() and
  pgstat_gist_page(). For zero pages, these call lead to ERRCODE_INDEX_CORRUPTED.

* Since zero pages are normal after a crash, these errors are false alarms.
  pgstat_btree_page() avoids these errors by not running these checks.

* In this patch, we do the same for gist and hash indexes. For hash index,
  ReadBufferExtended() replaces _hash_getbuf_with_strategy() which was
  calling _hash_check_page(). Instead we do the required check for special
  size explicitly for the non-PageIsNew() cases.

* Similarly for gist index, we remove gistcheckpage() and explicitly
  checks the special size for the non-PageIsNew() cases before checking
  if the page is a leaf.

* The comments in the else clauses have been updated accordingly.

Reported-by: Noah Misch <noah@leadboat.com>
---
 contrib/pgstattuple/pgstattuple.c | 22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index b5de68b7232..2511edccecc 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -424,7 +424,7 @@ pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
 		/* fully empty page */
 		stat->free_space += BLCKSZ;
 	}
-	else
+	else if (PageGetSpecialSize(page) == MAXALIGN(sizeof(BTPageOpaqueData)))
 	{
 		BTPageOpaque opaque;
 
@@ -458,10 +458,16 @@ pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
 	Buffer		buf;
 	Page		page;
 
-	buf = _hash_getbuf_with_strategy(rel, blkno, HASH_READ, 0, bstrategy);
+	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
+	LockBuffer(buf, HASH_READ);
 	page = BufferGetPage(buf);
 
-	if (PageGetSpecialSize(page) == MAXALIGN(sizeof(HashPageOpaqueData)))
+	if (PageIsNew(page))
+	{
+		/* fully empty page */
+		stat->free_space += BLCKSZ;
+	}
+	else if (PageGetSpecialSize(page) == MAXALIGN(sizeof(HashPageOpaqueData)))
 	{
 		HashPageOpaque opaque;
 
@@ -502,10 +508,14 @@ pgstat_gist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
 
 	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
 	LockBuffer(buf, GIST_SHARE);
-	gistcheckpage(rel, buf);
 	page = BufferGetPage(buf);
-
-	if (GistPageIsLeaf(page))
+	if (PageIsNew(page))
+	{
+		/* fully empty page */
+		stat->free_space += BLCKSZ;
+	}
+	else if (PageGetSpecialSize(page) == MAXALIGN(sizeof(GISTPageOpaqueData)) &&
+			 GistPageIsLeaf(page))
 	{
 		pgstat_index_page(stat, page, FirstOffsetNumber,
 						  PageGetMaxOffsetNumber(page));
-- 
2.51.0.618.g983fd99d29-goog

