From 64e2cd393a0bdbe7825ff866c76a37fd64b04f2c Mon Sep 17 00:00:00 2001
From: Jim Jones <jim.jones@uni-muenster.de>
Date: Sun, 18 Oct 2026 14:05:37 +0200
Subject: [PATCH v1 1/4] pageinspect: Reject unlogged relations during recovery

Unlogged tables and indexes have no data on a standby, so reading one
there must fail.  get_raw_page(), bt_metap(), bt_page_stats(),
bt_multi_page_stats(), bt_page_items() and hash_bitmap_info() read the
relation directly instead of going through the planner, so on a standby
they failed with a confusing low-level message instead of the error
that SELECT reports:

  ERROR:  could not open file "base/5/16384": No such file or directory

Check for this case and report the same error as SELECT:

  ERROR:  cannot access temporary or unlogged relations during recovery

The init fork of an unlogged relation does exist on a standby, so
get_raw_page() can still read it.

Author: Jim Jones <jim.jones@uni-muenster.de>
Reported-by: Zsolt Parragi <zsolt.parragi@percona.com>
---
 contrib/pageinspect/btreefuncs.c | 13 +++++++++++++
 contrib/pageinspect/hashfuncs.c  |  7 +++++++
 contrib/pageinspect/rawpage.c    | 11 +++++++++++
 3 files changed, 31 insertions(+)

diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c
index 9917663593b..676afe51de8 100644
--- a/contrib/pageinspect/btreefuncs.c
+++ b/contrib/pageinspect/btreefuncs.c
@@ -30,6 +30,7 @@
 #include "access/htup_details.h"
 #include "access/nbtree.h"
 #include "access/relation.h"
+#include "access/xlog.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_type.h"
@@ -241,6 +242,12 @@ bt_index_block_validate(Relation rel, int64 blkno)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary tables of other sessions")));
 
+	/* Temporary and unlogged relations are inaccessible during recovery. */
+	if (!RelationIsPermanent(rel) && RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot access temporary or unlogged relations during recovery")));
+
 	if (blkno == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -874,6 +881,12 @@ bt_metap(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary tables of other sessions")));
 
+	/* Temporary and unlogged relations are inaccessible during recovery. */
+	if (!RelationIsPermanent(rel) && RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot access temporary or unlogged relations during recovery")));
+
 	buffer = ReadBuffer(rel, 0);
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
 
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
index 30870329cc9..0f728d9add8 100644
--- a/contrib/pageinspect/hashfuncs.c
+++ b/contrib/pageinspect/hashfuncs.c
@@ -13,6 +13,7 @@
 #include "access/hash.h"
 #include "access/htup_details.h"
 #include "access/relation.h"
+#include "access/xlog.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
@@ -431,6 +432,12 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary tables of other sessions")));
 
+	/* Temporary and unlogged relations are inaccessible during recovery. */
+	if (!RelationIsPermanent(indexRel) && RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot access temporary or unlogged relations during recovery")));
+
 	if (ovflblkno < 0 || ovflblkno > MaxBlockNumber)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index d136593edb2..05ecde3c20f 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -17,6 +17,7 @@
 
 #include "access/htup_details.h"
 #include "access/relation.h"
+#include "access/xlog.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
@@ -175,6 +176,16 @@ get_raw_page_internal(text *relname, ForkNumber forknum, BlockNumber blkno)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary tables of other sessions")));
 
+	/*
+	 * Temporary and unlogged relations are inaccessible during recovery,
+	 * except for the init fork of an unlogged relation, which does exist.
+	 */
+	if (!RelationIsPermanent(rel) && RecoveryInProgress() &&
+		forknum != INIT_FORKNUM)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot access temporary or unlogged relations during recovery")));
+
 	if (blkno >= RelationGetNumberOfBlocksInFork(rel, forknum))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-- 
2.56.0

