LCOV - code coverage report
Current view: top level - contrib/pageinspect - rawpage.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 107 117 91.5 %
Date: 2025-04-24 13:15:39 Functions: 18 18 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * rawpage.c
       4             :  *    Functions to extract a raw page as bytea and inspect it
       5             :  *
       6             :  * Access-method specific inspection functions are in separate files.
       7             :  *
       8             :  * Copyright (c) 2007-2025, PostgreSQL Global Development Group
       9             :  *
      10             :  * IDENTIFICATION
      11             :  *    contrib/pageinspect/rawpage.c
      12             :  *
      13             :  *-------------------------------------------------------------------------
      14             :  */
      15             : 
      16             : #include "postgres.h"
      17             : 
      18             : #include "access/htup_details.h"
      19             : #include "access/relation.h"
      20             : #include "catalog/namespace.h"
      21             : #include "catalog/pg_type.h"
      22             : #include "funcapi.h"
      23             : #include "miscadmin.h"
      24             : #include "pageinspect.h"
      25             : #include "storage/bufmgr.h"
      26             : #include "storage/checksum.h"
      27             : #include "utils/builtins.h"
      28             : #include "utils/pg_lsn.h"
      29             : #include "utils/rel.h"
      30             : #include "utils/varlena.h"
      31             : 
      32          48 : PG_MODULE_MAGIC_EXT(
      33             :                     .name = "pageinspect",
      34             :                     .version = PG_VERSION
      35             : );
      36             : 
      37             : static bytea *get_raw_page_internal(text *relname, ForkNumber forknum,
      38             :                                     BlockNumber blkno);
      39             : 
      40             : 
      41             : /*
      42             :  * get_raw_page
      43             :  *
      44             :  * Returns a copy of a page from shared buffers as a bytea
      45             :  */
      46          44 : PG_FUNCTION_INFO_V1(get_raw_page_1_9);
      47             : 
      48             : Datum
      49         240 : get_raw_page_1_9(PG_FUNCTION_ARGS)
      50             : {
      51         240 :     text       *relname = PG_GETARG_TEXT_PP(0);
      52         240 :     int64       blkno = PG_GETARG_INT64(1);
      53             :     bytea      *raw_page;
      54             : 
      55         240 :     if (blkno < 0 || blkno > MaxBlockNumber)
      56           2 :         ereport(ERROR,
      57             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      58             :                  errmsg("invalid block number")));
      59             : 
      60         238 :     raw_page = get_raw_page_internal(relname, MAIN_FORKNUM, blkno);
      61             : 
      62         228 :     PG_RETURN_BYTEA_P(raw_page);
      63             : }
      64             : 
      65             : /*
      66             :  * entry point for old extension version
      67             :  */
      68          14 : PG_FUNCTION_INFO_V1(get_raw_page);
      69             : 
      70             : Datum
      71           4 : get_raw_page(PG_FUNCTION_ARGS)
      72             : {
      73           4 :     text       *relname = PG_GETARG_TEXT_PP(0);
      74           4 :     uint32      blkno = PG_GETARG_UINT32(1);
      75             :     bytea      *raw_page;
      76             : 
      77             :     /*
      78             :      * We don't normally bother to check the number of arguments to a C
      79             :      * function, but here it's needed for safety because early 8.4 beta
      80             :      * releases mistakenly redefined get_raw_page() as taking three arguments.
      81             :      */
      82           4 :     if (PG_NARGS() != 2)
      83           0 :         ereport(ERROR,
      84             :                 (errmsg("wrong number of arguments to get_raw_page()"),
      85             :                  errhint("Run the updated pageinspect.sql script.")));
      86             : 
      87           4 :     raw_page = get_raw_page_internal(relname, MAIN_FORKNUM, blkno);
      88             : 
      89           4 :     PG_RETURN_BYTEA_P(raw_page);
      90             : }
      91             : 
      92             : /*
      93             :  * get_raw_page_fork
      94             :  *
      95             :  * Same, for any fork
      96             :  */
      97          16 : PG_FUNCTION_INFO_V1(get_raw_page_fork_1_9);
      98             : 
      99             : Datum
     100          24 : get_raw_page_fork_1_9(PG_FUNCTION_ARGS)
     101             : {
     102          24 :     text       *relname = PG_GETARG_TEXT_PP(0);
     103          24 :     text       *forkname = PG_GETARG_TEXT_PP(1);
     104          24 :     int64       blkno = PG_GETARG_INT64(2);
     105             :     bytea      *raw_page;
     106             :     ForkNumber  forknum;
     107             : 
     108          24 :     forknum = forkname_to_number(text_to_cstring(forkname));
     109             : 
     110          22 :     if (blkno < 0 || blkno > MaxBlockNumber)
     111           2 :         ereport(ERROR,
     112             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     113             :                  errmsg("invalid block number")));
     114             : 
     115          20 :     raw_page = get_raw_page_internal(relname, forknum, blkno);
     116             : 
     117          14 :     PG_RETURN_BYTEA_P(raw_page);
     118             : }
     119             : 
     120             : /*
     121             :  * Entry point for old extension version
     122             :  */
     123          14 : PG_FUNCTION_INFO_V1(get_raw_page_fork);
     124             : 
     125             : Datum
     126           2 : get_raw_page_fork(PG_FUNCTION_ARGS)
     127             : {
     128           2 :     text       *relname = PG_GETARG_TEXT_PP(0);
     129           2 :     text       *forkname = PG_GETARG_TEXT_PP(1);
     130           2 :     uint32      blkno = PG_GETARG_UINT32(2);
     131             :     bytea      *raw_page;
     132             :     ForkNumber  forknum;
     133             : 
     134           2 :     forknum = forkname_to_number(text_to_cstring(forkname));
     135             : 
     136           2 :     raw_page = get_raw_page_internal(relname, forknum, blkno);
     137             : 
     138           2 :     PG_RETURN_BYTEA_P(raw_page);
     139             : }
     140             : 
     141             : /*
     142             :  * workhorse
     143             :  */
     144             : static bytea *
     145         264 : get_raw_page_internal(text *relname, ForkNumber forknum, BlockNumber blkno)
     146             : {
     147             :     bytea      *raw_page;
     148             :     RangeVar   *relrv;
     149             :     Relation    rel;
     150             :     char       *raw_page_data;
     151             :     Buffer      buf;
     152             : 
     153         264 :     if (!superuser())
     154           0 :         ereport(ERROR,
     155             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     156             :                  errmsg("must be superuser to use raw page functions")));
     157             : 
     158         264 :     relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
     159         264 :     rel = relation_openrv(relrv, AccessShareLock);
     160             : 
     161         262 :     if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
     162           4 :         ereport(ERROR,
     163             :                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     164             :                  errmsg("cannot get raw page from relation \"%s\"",
     165             :                         RelationGetRelationName(rel)),
     166             :                  errdetail_relkind_not_supported(rel->rd_rel->relkind)));
     167             : 
     168             :     /*
     169             :      * Reject attempts to read non-local temporary relations; we would be
     170             :      * likely to get wrong data since we have no visibility into the owning
     171             :      * session's local buffers.
     172             :      */
     173         258 :     if (RELATION_IS_OTHER_TEMP(rel))
     174           0 :         ereport(ERROR,
     175             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     176             :                  errmsg("cannot access temporary tables of other sessions")));
     177             : 
     178         258 :     if (blkno >= RelationGetNumberOfBlocksInFork(rel, forknum))
     179          10 :         ereport(ERROR,
     180             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     181             :                  errmsg("block number %u is out of range for relation \"%s\"",
     182             :                         blkno, RelationGetRelationName(rel))));
     183             : 
     184             :     /* Initialize buffer to copy to */
     185         248 :     raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
     186         248 :     SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ);
     187         248 :     raw_page_data = VARDATA(raw_page);
     188             : 
     189             :     /* Take a verbatim copy of the page */
     190             : 
     191         248 :     buf = ReadBufferExtended(rel, forknum, blkno, RBM_NORMAL, NULL);
     192         248 :     LockBuffer(buf, BUFFER_LOCK_SHARE);
     193             : 
     194         248 :     memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
     195             : 
     196         248 :     LockBuffer(buf, BUFFER_LOCK_UNLOCK);
     197         248 :     ReleaseBuffer(buf);
     198             : 
     199         248 :     relation_close(rel, AccessShareLock);
     200             : 
     201         248 :     return raw_page;
     202             : }
     203             : 
     204             : 
     205             : /*
     206             :  * get_page_from_raw
     207             :  *
     208             :  * Get a palloc'd, maxalign'ed page image from the result of get_raw_page()
     209             :  *
     210             :  * On machines with MAXALIGN = 8, the payload of a bytea is not maxaligned,
     211             :  * since it will start 4 bytes into a palloc'd value.  On alignment-picky
     212             :  * machines, this will cause failures in accesses to 8-byte-wide values
     213             :  * within the page.  We don't need to worry if accessing only 4-byte or
     214             :  * smaller fields, but when examining a struct that contains 8-byte fields,
     215             :  * use this function for safety.
     216             :  */
     217             : Page
     218         304 : get_page_from_raw(bytea *raw_page)
     219             : {
     220             :     Page        page;
     221             :     int         raw_page_size;
     222             : 
     223         304 :     raw_page_size = VARSIZE_ANY_EXHDR(raw_page);
     224             : 
     225         304 :     if (raw_page_size != BLCKSZ)
     226          28 :         ereport(ERROR,
     227             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     228             :                  errmsg("invalid page size"),
     229             :                  errdetail("Expected %d bytes, got %d.",
     230             :                            BLCKSZ, raw_page_size)));
     231             : 
     232         276 :     page = palloc(raw_page_size);
     233             : 
     234         276 :     memcpy(page, VARDATA_ANY(raw_page), raw_page_size);
     235             : 
     236         276 :     return page;
     237             : }
     238             : 
     239             : 
     240             : /*
     241             :  * page_header
     242             :  *
     243             :  * Allows inspection of page header fields of a raw page
     244             :  */
     245             : 
     246          28 : PG_FUNCTION_INFO_V1(page_header);
     247             : 
     248             : Datum
     249           8 : page_header(PG_FUNCTION_ARGS)
     250             : {
     251           8 :     bytea      *raw_page = PG_GETARG_BYTEA_P(0);
     252             : 
     253             :     TupleDesc   tupdesc;
     254             : 
     255             :     Datum       result;
     256             :     HeapTuple   tuple;
     257             :     Datum       values[9];
     258             :     bool        nulls[9];
     259             : 
     260             :     Page        page;
     261             :     PageHeader  pageheader;
     262             :     XLogRecPtr  lsn;
     263             : 
     264           8 :     if (!superuser())
     265           0 :         ereport(ERROR,
     266             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     267             :                  errmsg("must be superuser to use raw page functions")));
     268             : 
     269           8 :     page = get_page_from_raw(raw_page);
     270           6 :     pageheader = (PageHeader) page;
     271             : 
     272             :     /* Build a tuple descriptor for our result type */
     273           6 :     if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
     274           0 :         elog(ERROR, "return type must be a row type");
     275             : 
     276             :     /* Extract information from the page header */
     277             : 
     278           6 :     lsn = PageGetLSN(page);
     279             : 
     280             :     /* pageinspect >= 1.2 uses pg_lsn instead of text for the LSN field. */
     281           6 :     if (TupleDescAttr(tupdesc, 0)->atttypid == TEXTOID)
     282             :     {
     283             :         char        lsnchar[64];
     284             : 
     285           0 :         snprintf(lsnchar, sizeof(lsnchar), "%X/%X", LSN_FORMAT_ARGS(lsn));
     286           0 :         values[0] = CStringGetTextDatum(lsnchar);
     287             :     }
     288             :     else
     289           6 :         values[0] = LSNGetDatum(lsn);
     290           6 :     values[1] = UInt16GetDatum(pageheader->pd_checksum);
     291           6 :     values[2] = UInt16GetDatum(pageheader->pd_flags);
     292             : 
     293             :     /* pageinspect >= 1.10 uses int4 instead of int2 for those fields */
     294           6 :     switch (TupleDescAttr(tupdesc, 3)->atttypid)
     295             :     {
     296           2 :         case INT2OID:
     297             :             Assert(TupleDescAttr(tupdesc, 4)->atttypid == INT2OID &&
     298             :                    TupleDescAttr(tupdesc, 5)->atttypid == INT2OID &&
     299             :                    TupleDescAttr(tupdesc, 6)->atttypid == INT2OID);
     300           2 :             values[3] = UInt16GetDatum(pageheader->pd_lower);
     301           2 :             values[4] = UInt16GetDatum(pageheader->pd_upper);
     302           2 :             values[5] = UInt16GetDatum(pageheader->pd_special);
     303           2 :             values[6] = UInt16GetDatum(PageGetPageSize(page));
     304           2 :             break;
     305           4 :         case INT4OID:
     306             :             Assert(TupleDescAttr(tupdesc, 4)->atttypid == INT4OID &&
     307             :                    TupleDescAttr(tupdesc, 5)->atttypid == INT4OID &&
     308             :                    TupleDescAttr(tupdesc, 6)->atttypid == INT4OID);
     309           4 :             values[3] = Int32GetDatum(pageheader->pd_lower);
     310           4 :             values[4] = Int32GetDatum(pageheader->pd_upper);
     311           4 :             values[5] = Int32GetDatum(pageheader->pd_special);
     312           4 :             values[6] = Int32GetDatum(PageGetPageSize(page));
     313           4 :             break;
     314           0 :         default:
     315           0 :             elog(ERROR, "incorrect output types");
     316             :             break;
     317             :     }
     318             : 
     319           6 :     values[7] = UInt16GetDatum(PageGetPageLayoutVersion(page));
     320           6 :     values[8] = TransactionIdGetDatum(pageheader->pd_prune_xid);
     321             : 
     322             :     /* Build and return the tuple. */
     323             : 
     324           6 :     memset(nulls, 0, sizeof(nulls));
     325             : 
     326           6 :     tuple = heap_form_tuple(tupdesc, values, nulls);
     327           6 :     result = HeapTupleGetDatum(tuple);
     328             : 
     329           6 :     PG_RETURN_DATUM(result);
     330             : }
     331             : 
     332             : /*
     333             :  * page_checksum
     334             :  *
     335             :  * Compute checksum of a raw page
     336             :  */
     337             : 
     338          16 : PG_FUNCTION_INFO_V1(page_checksum_1_9);
     339          14 : PG_FUNCTION_INFO_V1(page_checksum);
     340             : 
     341             : static Datum
     342          46 : page_checksum_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
     343             : {
     344          46 :     bytea      *raw_page = PG_GETARG_BYTEA_P(0);
     345          46 :     int64       blkno = (ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1));
     346             :     Page        page;
     347             : 
     348          46 :     if (!superuser())
     349           0 :         ereport(ERROR,
     350             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     351             :                  errmsg("must be superuser to use raw page functions")));
     352             : 
     353          46 :     if (blkno < 0 || blkno > MaxBlockNumber)
     354           2 :         ereport(ERROR,
     355             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     356             :                  errmsg("invalid block number")));
     357             : 
     358          44 :     page = get_page_from_raw(raw_page);
     359             : 
     360          42 :     if (PageIsNew(page))
     361           2 :         PG_RETURN_NULL();
     362             : 
     363          40 :     PG_RETURN_INT16(pg_checksum_page(page, blkno));
     364             : }
     365             : 
     366             : Datum
     367          44 : page_checksum_1_9(PG_FUNCTION_ARGS)
     368             : {
     369          44 :     return page_checksum_internal(fcinfo, PAGEINSPECT_V1_9);
     370             : }
     371             : 
     372             : /*
     373             :  * Entry point for old extension version
     374             :  */
     375             : Datum
     376           2 : page_checksum(PG_FUNCTION_ARGS)
     377             : {
     378           2 :     return page_checksum_internal(fcinfo, PAGEINSPECT_V1_8);
     379             : }

Generated by: LCOV version 1.14