pageinspect: Fix handling of all-zero pages
authorMichael Paquier <[email protected]>
Thu, 14 Apr 2022 06:08:03 +0000 (15:08 +0900)
committerMichael Paquier <[email protected]>
Thu, 14 Apr 2022 06:08:03 +0000 (15:08 +0900)
Getting from get_raw_page() an all-zero page is considered as a valid
case by the buffer manager and it can happen for example when finding a
corrupted page with zero_damaged_pages enabled (using zero_damaged_pages
to look at corrupted pages happens), or after a crash when a relation
file is extended before any WAL for its new data is generated (before a
vacuum or autovacuum job comes in to do some cleanup).

However, all the functions of pageinspect, as of the index AMs (except
hash that has its own idea of new pages), heap, the FSM or the page
header have never worked with all-zero pages, causing various crashes
when going through the page internals.

This commit changes all the pageinspect functions to be compliant with
all-zero pages, where the choice is made to return NULL or no rows for
SRFs when finding a new page.  get_raw_page() still works the same way,
returning a batch of zeros in the bytea of the page retrieved.  A hard
error could be used but NULL, while more invasive, is useful when
scanning relation files in full to get a batch of results for a single
relation in one query.  Tests are added for all the code paths
impacted.

Reported-by: Daria Lepikhova
Author: Michael Paquier
Discussion: https://postgr.es/m/561e187b-3549-c8d5-03f5-525c14e65bd0@postgrespro.ru
Backpatch-through: 10

18 files changed:
contrib/pageinspect/brinfuncs.c
contrib/pageinspect/btreefuncs.c
contrib/pageinspect/expected/brin.out
contrib/pageinspect/expected/btree.out
contrib/pageinspect/expected/gin.out
contrib/pageinspect/expected/gist.out
contrib/pageinspect/expected/hash.out
contrib/pageinspect/expected/page.out
contrib/pageinspect/fsmfuncs.c
contrib/pageinspect/ginfuncs.c
contrib/pageinspect/gistfuncs.c
contrib/pageinspect/rawpage.c
contrib/pageinspect/sql/brin.sql
contrib/pageinspect/sql/btree.sql
contrib/pageinspect/sql/gin.sql
contrib/pageinspect/sql/gist.sql
contrib/pageinspect/sql/hash.sql
contrib/pageinspect/sql/page.sql

index 0387b2847a896a06336b68c72c2ee90103506749..c2a37277e0834daf3aeff54242cfa5c9d48bc25d 100644 (file)
@@ -58,6 +58,9 @@ brin_page_type(PG_FUNCTION_ARGS)
 
        page = get_page_from_raw(raw_page);
 
+       if (PageIsNew(page))
+               PG_RETURN_NULL();
+
        /* verify the special space has the expected size */
        if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
                        ereport(ERROR,
@@ -95,6 +98,9 @@ verify_brin_page(bytea *raw_page, uint16 type, const char *strtype)
 {
        Page            page = get_page_from_raw(raw_page);
 
+       if (PageIsNew(page))
+               return page;
+
        /* verify the special space has the expected size */
        if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
                        ereport(ERROR,
@@ -156,6 +162,13 @@ brin_page_items(PG_FUNCTION_ARGS)
        /* minimally verify the page we got */
        page = verify_brin_page(raw_page, BRIN_PAGETYPE_REGULAR, "regular");
 
+       if (PageIsNew(page))
+       {
+               brin_free_desc(bdesc);
+               index_close(indexRel, AccessShareLock);
+               PG_RETURN_NULL();
+       }
+
        /*
         * Initialize output functions for all indexed datatypes; simplifies
         * calling them later.
@@ -331,6 +344,9 @@ brin_metapage_info(PG_FUNCTION_ARGS)
 
        page = verify_brin_page(raw_page, BRIN_PAGETYPE_META, "metapage");
 
+       if (PageIsNew(page))
+               PG_RETURN_NULL();
+
        /* Build a tuple descriptor for our result type */
        if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
                elog(ERROR, "return type must be a row type");
@@ -382,6 +398,12 @@ brin_revmap_data(PG_FUNCTION_ARGS)
                /* minimally verify the page we got */
                page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap");
 
+               if (PageIsNew(page))
+               {
+                       MemoryContextSwitchTo(mctx);
+                       PG_RETURN_NULL();
+               }
+
                state = palloc(sizeof(*state));
                state->tids = ((RevmapContents *) PageGetContents(page))->rm_tids;
                state->idx = 0;
index 3daa31c84da5bc77437fde855ab346b195429e23..62f2c1b3159651f3580233bd2da53213d40754fd 100644 (file)
@@ -611,6 +611,12 @@ bt_page_items_bytea(PG_FUNCTION_ARGS)
 
                uargs->page = get_page_from_raw(raw_page);
 
+               if (PageIsNew(uargs->page))
+               {
+                       MemoryContextSwitchTo(mctx);
+                       PG_RETURN_NULL();
+               }
+
                uargs->offset = FirstOffsetNumber;
 
                /* verify the special space has the expected size */
index 62ee783b604d1b08a2ee15392f1a6f6a4c682c9f..d19cdc3b957f8fe1910a3e7e66679331504a17c7 100644 (file)
@@ -62,4 +62,29 @@ ERROR:  input page is not a valid BRIN page
 SELECT * FROM brin_revmap_data(get_raw_page('test1', 0));
 ERROR:  input page is not a valid BRIN page
 \set VERBOSITY default
+-- Tests with all-zero pages.
+SHOW block_size \gset
+SELECT brin_page_type(decode(repeat('00', :block_size), 'hex'));
+ brin_page_type 
+----------------
+(1 row)
+
+SELECT brin_page_items(decode(repeat('00', :block_size), 'hex'), 'test1_a_idx');
+ brin_page_items 
+-----------------
+(0 rows)
+
+SELECT brin_metapage_info(decode(repeat('00', :block_size), 'hex'));
+ brin_metapage_info 
+--------------------
+(1 row)
+
+SELECT brin_revmap_data(decode(repeat('00', :block_size), 'hex'));
+ brin_revmap_data 
+------------------
+(1 row)
+
 DROP TABLE test1;
index 89d216099071222c5a9359c7f3d7d69fb515cba4..035a81a75923058f6df998596adc47095add6169 100644 (file)
@@ -99,4 +99,10 @@ ERROR:  input page is not a valid btree page
 SELECT bt_page_items(get_raw_page('test1_a_brin', 0));
 ERROR:  input page is not a valid btree page
 \set VERBOSITY default
+-- Tests with all-zero pages.
+SHOW block_size \gset
+SELECT bt_page_items(decode(repeat('00', :block_size), 'hex'));
+-[ RECORD 1 ]-+-
+bt_page_items | 
+
 DROP TABLE test1;
index e9fdb4cf20fbb456cdd6342cf46ec791c89960ce..ff1da6a5a1773ce0fff58cfe317fbd98ebce8314 100644 (file)
@@ -54,4 +54,18 @@ ERROR:  input page is not a valid GIN data leaf page
 SELECT * FROM gin_leafpage_items(get_raw_page('test1', 0));
 ERROR:  input page is not a valid GIN data leaf page
 \set VERBOSITY default
+-- Tests with all-zero pages.
+SHOW block_size \gset
+SELECT gin_leafpage_items(decode(repeat('00', :block_size), 'hex'));
+-[ RECORD 1 ]------+-
+gin_leafpage_items | 
+
+SELECT gin_metapage_info(decode(repeat('00', :block_size), 'hex'));
+-[ RECORD 1 ]-----+-
+gin_metapage_info | 
+
+SELECT gin_page_opaque_info(decode(repeat('00', :block_size), 'hex'));
+-[ RECORD 1 ]--------+-
+gin_page_opaque_info | 
+
 DROP TABLE test1;
index c18a7091b2500fb75b751da0223666706941a397..a51bded0cb6e7ce6957ea193e5bee53753078676 100644 (file)
@@ -87,4 +87,22 @@ ERROR:  input page is not a valid GiST page
 SELECT gist_page_items_bytea(get_raw_page('test_gist_btree', 0));
 ERROR:  input page is not a valid GiST page
 \set VERBOSITY default
+-- Tests with all-zero pages.
+SHOW block_size \gset
+SELECT gist_page_items_bytea(decode(repeat('00', :block_size), 'hex'));
+ gist_page_items_bytea 
+-----------------------
+(0 rows)
+
+SELECT gist_page_items(decode(repeat('00', :block_size), 'hex'), 'test_gist_idx'::regclass);
+ gist_page_items 
+-----------------
+(0 rows)
+
+SELECT gist_page_opaque_info(decode(repeat('00', :block_size), 'hex'));
+ gist_page_opaque_info 
+-----------------------
+(1 row)
+
 DROP TABLE test_gist;
index 96c9511457027b9fd9abe4a02c90891f32cea15e..5d6a5188345f9405f3013d7e9cb939ae76352554 100644 (file)
@@ -190,4 +190,16 @@ ERROR:  input page is not a valid hash page
 SELECT hash_page_type(get_raw_page('test_hash', 0));
 ERROR:  input page is not a valid hash page
 \set VERBOSITY default
+-- Tests with all-zero pages.
+SHOW block_size \gset
+SELECT hash_metapage_info(decode(repeat('00', :block_size), 'hex'));
+ERROR:  page is not a hash meta page
+SELECT hash_page_items(decode(repeat('00', :block_size), 'hex'));
+ERROR:  page is not a hash bucket or overflow page
+SELECT hash_page_stats(decode(repeat('00', :block_size), 'hex'));
+ERROR:  page is not a hash bucket or overflow page
+SELECT hash_page_type(decode(repeat('00', :block_size), 'hex'));
+-[ RECORD 1 ]--+-------
+hash_page_type | unused
+
 DROP TABLE test_hash;
index 9bbdda7f1803a3b51e1e300388c34be1a38ad683..3bdc37bbf590df5dba607d0ff7c36c4016337cc4 100644 (file)
@@ -218,3 +218,23 @@ ERROR:  invalid page size
 SELECT page_header('ccc'::bytea);
 ERROR:  invalid page size
 \set VERBOSITY default
+-- Tests with all-zero pages.
+SHOW block_size \gset
+SELECT fsm_page_contents(decode(repeat('00', :block_size), 'hex'));
+ fsm_page_contents 
+-------------------
+(1 row)
+
+SELECT page_header(decode(repeat('00', :block_size), 'hex'));
+      page_header      
+-----------------------
+ (0/0,0,0,0,0,0,0,0,0)
+(1 row)
+
+SELECT page_checksum(decode(repeat('00', :block_size), 'hex'), 1);
+ page_checksum 
+---------------
+              
+(1 row)
+
index b914da1d4a9d38d676022a8466be2c4282d8aed7..2b9679e4541617675a03c4750556c6c6d951ae4d 100644 (file)
@@ -46,6 +46,10 @@ fsm_page_contents(PG_FUNCTION_ARGS)
                                 errmsg("must be superuser to use raw page functions")));
 
        page = get_page_from_raw(raw_page);
+
+       if (PageIsNew(page))
+               PG_RETURN_NULL();
+
        fsmpage = (FSMPage) PageGetContents(page);
 
        initStringInfo(&sinfo);
index 1c56fa18cdcbc4e1b9812893b85e8f9173893000..31aca7b0006281e3d79b3ab2f1590215bc5fa278 100644 (file)
@@ -49,6 +49,9 @@ gin_metapage_info(PG_FUNCTION_ARGS)
 
        page = get_page_from_raw(raw_page);
 
+       if (PageIsNew(page))
+               PG_RETURN_NULL();
+
        if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -58,6 +61,7 @@ gin_metapage_info(PG_FUNCTION_ARGS)
                                                   (int) PageGetSpecialSize(page))));
 
        opaq = GinPageGetOpaque(page);
+
        if (opaq->flags != GIN_META)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -115,6 +119,9 @@ gin_page_opaque_info(PG_FUNCTION_ARGS)
 
        page = get_page_from_raw(raw_page);
 
+       if (PageIsNew(page))
+               PG_RETURN_NULL();
+
        if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -200,6 +207,12 @@ gin_leafpage_items(PG_FUNCTION_ARGS)
 
                page = get_page_from_raw(raw_page);
 
+               if (PageIsNew(page))
+               {
+                       MemoryContextSwitchTo(mctx);
+                       PG_RETURN_NULL();
+               }
+
                if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
index b2bbf4f6cbc9f7ba23e9b06155a1a8a354f611f1..9c29fbc7aa60d9449283620873ae4aeddc68406e 100644 (file)
@@ -55,6 +55,9 @@ gist_page_opaque_info(PG_FUNCTION_ARGS)
 
        page = get_page_from_raw(raw_page);
 
+       if (PageIsNew(page))
+               PG_RETURN_NULL();
+
        /* verify the special space has the expected size */
        if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GISTPageOpaqueData)))
                        ereport(ERROR,
@@ -130,6 +133,9 @@ gist_page_items_bytea(PG_FUNCTION_ARGS)
 
        page = get_page_from_raw(raw_page);
 
+       if (PageIsNew(page))
+               PG_RETURN_NULL();
+
        /* verify the special space has the expected size */
        if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GISTPageOpaqueData)))
                        ereport(ERROR,
@@ -220,6 +226,12 @@ gist_page_items(PG_FUNCTION_ARGS)
 
        page = get_page_from_raw(raw_page);
 
+       if (PageIsNew(page))
+       {
+               index_close(indexRel, AccessShareLock);
+               PG_RETURN_NULL();
+       }
+
        /* Avoid bogus PageGetMaxOffsetNumber() call with deleted pages */
        if (GistPageIsDeleted(page))
                elog(NOTICE, "page is deleted");
index 92ffb2d930e47f161c72c4c80a1d0c66ec4d7e86..730a46b1d84fa494fa927380d8ec6095d3e2d34c 100644 (file)
@@ -352,6 +352,9 @@ page_checksum_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
 
        page = get_page_from_raw(raw_page);
 
+       if (PageIsNew(page))
+               PG_RETURN_NULL();
+
        PG_RETURN_INT16(pg_checksum_page((char *) page, blkno));
 }
 
index dc5d1661b6d03c3e5ccf4aa17651e03d4df2e981..45098c1ef5e4baa34e5f4e9dc0ad79e59c846dbc 100644 (file)
@@ -27,4 +27,11 @@ SELECT * FROM brin_metapage_info(get_raw_page('test1', 0));
 SELECT * FROM brin_revmap_data(get_raw_page('test1', 0));
 \set VERBOSITY default
 
+-- Tests with all-zero pages.
+SHOW block_size \gset
+SELECT brin_page_type(decode(repeat('00', :block_size), 'hex'));
+SELECT brin_page_items(decode(repeat('00', :block_size), 'hex'), 'test1_a_idx');
+SELECT brin_metapage_info(decode(repeat('00', :block_size), 'hex'));
+SELECT brin_revmap_data(decode(repeat('00', :block_size), 'hex'));
+
 DROP TABLE test1;
index 44d83f90bac5bad931d3b8fab65b8a5ad239a259..1f554f0f67881591625eaad9abfc7018376d36c0 100644 (file)
@@ -44,4 +44,8 @@ SELECT bt_page_items(get_raw_page('test1', 0));
 SELECT bt_page_items(get_raw_page('test1_a_brin', 0));
 \set VERBOSITY default
 
+-- Tests with all-zero pages.
+SHOW block_size \gset
+SELECT bt_page_items(decode(repeat('00', :block_size), 'hex'));
+
 DROP TABLE test1;
index 6499b5c72b76faa8cf80511f51a85e174a0d85bf..b57466d7ebfd8e5d4f405f1a567d926c25f5103a 100644 (file)
@@ -32,4 +32,10 @@ SELECT * FROM gin_page_opaque_info(get_raw_page('test1', 0));
 SELECT * FROM gin_leafpage_items(get_raw_page('test1', 0));
 \set VERBOSITY default
 
+-- Tests with all-zero pages.
+SHOW block_size \gset
+SELECT gin_leafpage_items(decode(repeat('00', :block_size), 'hex'));
+SELECT gin_metapage_info(decode(repeat('00', :block_size), 'hex'));
+SELECT gin_page_opaque_info(decode(repeat('00', :block_size), 'hex'));
+
 DROP TABLE test1;
index e76f6fa8d1c859ede5527f5d3ec9073b89f47d95..1edf2b307f61aab923dc3e67308fd17c36a86dbd 100644 (file)
@@ -44,4 +44,10 @@ SELECT gist_page_items_bytea(get_raw_page('test_gist', 0));
 SELECT gist_page_items_bytea(get_raw_page('test_gist_btree', 0));
 \set VERBOSITY default
 
+-- Tests with all-zero pages.
+SHOW block_size \gset
+SELECT gist_page_items_bytea(decode(repeat('00', :block_size), 'hex'));
+SELECT gist_page_items(decode(repeat('00', :block_size), 'hex'), 'test_gist_idx'::regclass);
+SELECT gist_page_opaque_info(decode(repeat('00', :block_size), 'hex'));
+
 DROP TABLE test_gist;
index ccc984c086617adc85dc6531a1d5a1913eac0849..320fb9fa9f19852cad07901161c50318f9458977 100644 (file)
@@ -98,4 +98,11 @@ SELECT hash_page_stats(get_raw_page('test_hash', 0));
 SELECT hash_page_type(get_raw_page('test_hash', 0));
 \set VERBOSITY default
 
+-- Tests with all-zero pages.
+SHOW block_size \gset
+SELECT hash_metapage_info(decode(repeat('00', :block_size), 'hex'));
+SELECT hash_page_items(decode(repeat('00', :block_size), 'hex'));
+SELECT hash_page_stats(decode(repeat('00', :block_size), 'hex'));
+SELECT hash_page_type(decode(repeat('00', :block_size), 'hex'));
+
 DROP TABLE test_hash;
index 38b16815412d0884950b89b8f481d954400930a8..b5c41cc8ac53a1e00c3f5b20aa57aef04080b2ee 100644 (file)
@@ -91,3 +91,9 @@ SELECT fsm_page_contents('aaa'::bytea);
 SELECT page_checksum('bbb'::bytea, 0);
 SELECT page_header('ccc'::bytea);
 \set VERBOSITY default
+
+-- Tests with all-zero pages.
+SHOW block_size \gset
+SELECT fsm_page_contents(decode(repeat('00', :block_size), 'hex'));
+SELECT page_header(decode(repeat('00', :block_size), 'hex'));
+SELECT page_checksum(decode(repeat('00', :block_size), 'hex'), 1);