Age Owner Branch data TLA 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-2026, 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 : :
354 tgl@sss.pgh.pa.us 32 :CBC 25 : 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 : : */
1881 peter@eisentraut.org 46 : 24 : PG_FUNCTION_INFO_V1(get_raw_page_1_9);
47 : :
48 : : Datum
49 : 121 : get_raw_page_1_9(PG_FUNCTION_ARGS)
50 : : {
51 : 121 : text *relname = PG_GETARG_TEXT_PP(0);
52 : 121 : int64 blkno = PG_GETARG_INT64(1);
53 : : bytea *raw_page;
54 : :
55 [ + + - + ]: 121 : if (blkno < 0 || blkno > MaxBlockNumber)
56 [ + - ]: 1 : ereport(ERROR,
57 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
58 : : errmsg("invalid block number")));
59 : :
60 : 120 : raw_page = get_raw_page_internal(relname, MAIN_FORKNUM, blkno);
61 : :
62 : 115 : PG_RETURN_BYTEA_P(raw_page);
63 : : }
64 : :
65 : : /*
66 : : * entry point for old extension version
67 : : */
6877 bruce@momjian.us 68 : 8 : PG_FUNCTION_INFO_V1(get_raw_page);
69 : :
70 : : Datum
71 : 2 : get_raw_page(PG_FUNCTION_ARGS)
72 : : {
3290 noah@leadboat.com 73 : 2 : text *relname = PG_GETARG_TEXT_PP(0);
6124 tgl@sss.pgh.pa.us 74 : 2 : 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 [ - + ]: 2 : if (PG_NARGS() != 2)
6124 tgl@sss.pgh.pa.us 83 [ # # ]:UBC 0 : ereport(ERROR,
84 : : (errmsg("wrong number of arguments to get_raw_page()"),
85 : : errhint("Run the updated pageinspect.sql script.")));
86 : :
6124 tgl@sss.pgh.pa.us 87 :CBC 2 : raw_page = get_raw_page_internal(relname, MAIN_FORKNUM, blkno);
88 : :
89 : 2 : PG_RETURN_BYTEA_P(raw_page);
90 : : }
91 : :
92 : : /*
93 : : * get_raw_page_fork
94 : : *
95 : : * Same, for any fork
96 : : */
1881 peter@eisentraut.org 97 : 9 : PG_FUNCTION_INFO_V1(get_raw_page_fork_1_9);
98 : :
99 : : Datum
100 : 12 : get_raw_page_fork_1_9(PG_FUNCTION_ARGS)
101 : : {
102 : 12 : text *relname = PG_GETARG_TEXT_PP(0);
103 : 12 : text *forkname = PG_GETARG_TEXT_PP(1);
104 : 12 : int64 blkno = PG_GETARG_INT64(2);
105 : : bytea *raw_page;
106 : : ForkNumber forknum;
107 : :
108 : 12 : forknum = forkname_to_number(text_to_cstring(forkname));
109 : :
110 [ + + - + ]: 11 : if (blkno < 0 || blkno > MaxBlockNumber)
111 [ + - ]: 1 : ereport(ERROR,
112 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
113 : : errmsg("invalid block number")));
114 : :
115 : 10 : raw_page = get_raw_page_internal(relname, forknum, blkno);
116 : :
117 : 7 : PG_RETURN_BYTEA_P(raw_page);
118 : : }
119 : :
120 : : /*
121 : : * Entry point for old extension version
122 : : */
6124 tgl@sss.pgh.pa.us 123 : 8 : PG_FUNCTION_INFO_V1(get_raw_page_fork);
124 : :
125 : : Datum
126 : 1 : get_raw_page_fork(PG_FUNCTION_ARGS)
127 : : {
3290 noah@leadboat.com 128 : 1 : text *relname = PG_GETARG_TEXT_PP(0);
129 : 1 : text *forkname = PG_GETARG_TEXT_PP(1);
6375 heikki.linnakangas@i 130 : 1 : uint32 blkno = PG_GETARG_UINT32(2);
131 : : bytea *raw_page;
132 : : ForkNumber forknum;
133 : :
6124 tgl@sss.pgh.pa.us 134 : 1 : forknum = forkname_to_number(text_to_cstring(forkname));
135 : :
136 : 1 : raw_page = get_raw_page_internal(relname, forknum, blkno);
137 : :
138 : 1 : PG_RETURN_BYTEA_P(raw_page);
139 : : }
140 : :
141 : : /*
142 : : * workhorse
143 : : */
144 : : static bytea *
145 : 133 : 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 : :
6877 bruce@momjian.us 153 [ - + ]: 133 : if (!superuser())
6877 bruce@momjian.us 154 [ # # ]:UBC 0 : ereport(ERROR,
155 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
156 : : errmsg("must be superuser to use raw page functions")));
157 : :
6877 bruce@momjian.us 158 :CBC 133 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
159 : 133 : rel = relation_openrv(relrv, AccessShareLock);
160 : :
1711 peter@eisentraut.org 161 [ + + + + : 132 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
+ + + - +
- ]
6877 bruce@momjian.us 162 [ + - ]: 2 : 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 : : */
6193 tgl@sss.pgh.pa.us 173 [ + + - + ]: 130 : if (RELATION_IS_OTHER_TEMP(rel))
6193 tgl@sss.pgh.pa.us 174 [ # # ]:UBC 0 : ereport(ERROR,
175 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
176 : : errmsg("cannot access temporary tables of other sessions")));
177 : :
4254 tgl@sss.pgh.pa.us 178 [ + + ]:CBC 130 : if (blkno >= RelationGetNumberOfBlocksInFork(rel, forknum))
179 [ + - ]: 5 : 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 */
6877 bruce@momjian.us 185 : 125 : raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
186 : 125 : SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ);
187 : 125 : raw_page_data = VARDATA(raw_page);
188 : :
189 : : /* Take a verbatim copy of the page */
190 : :
6344 heikki.linnakangas@i 191 : 125 : buf = ReadBufferExtended(rel, forknum, blkno, RBM_NORMAL, NULL);
6877 bruce@momjian.us 192 : 125 : LockBuffer(buf, BUFFER_LOCK_SHARE);
193 : :
3616 kgrittn@postgresql.o 194 : 125 : memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
195 : :
6877 bruce@momjian.us 196 : 125 : LockBuffer(buf, BUFFER_LOCK_UNLOCK);
197 : 125 : ReleaseBuffer(buf);
198 : :
199 : 125 : relation_close(rel, AccessShareLock);
200 : :
6124 tgl@sss.pgh.pa.us 201 : 125 : 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. PageHeaderData requires
212 : : * 8 byte alignment, so always use this function when accessing page header
213 : : * fields from a raw page bytea.
214 : : */
215 : : Page
3327 216 : 163 : get_page_from_raw(bytea *raw_page)
217 : : {
218 : : Page page;
219 : : int raw_page_size;
220 : :
3290 noah@leadboat.com 221 [ - + - - : 163 : raw_page_size = VARSIZE_ANY_EXHDR(raw_page);
- - - - -
+ ]
222 : :
3327 tgl@sss.pgh.pa.us 223 [ + + ]: 163 : if (raw_page_size != BLCKSZ)
224 [ + - ]: 14 : ereport(ERROR,
225 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
226 : : errmsg("invalid page size"),
227 : : errdetail("Expected %d bytes, got %d.",
228 : : BLCKSZ, raw_page_size)));
229 : :
230 : 149 : page = palloc(raw_page_size);
231 : :
3290 noah@leadboat.com 232 [ - + ]: 149 : memcpy(page, VARDATA_ANY(raw_page), raw_page_size);
233 : :
3327 tgl@sss.pgh.pa.us 234 : 149 : return page;
235 : : }
236 : :
237 : :
238 : : /*
239 : : * page_header
240 : : *
241 : : * Allows inspection of page header fields of a raw page
242 : : */
243 : :
6877 bruce@momjian.us 244 : 17 : PG_FUNCTION_INFO_V1(page_header);
245 : :
246 : : Datum
247 : 5 : page_header(PG_FUNCTION_ARGS)
248 : : {
249 : 5 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
250 : :
251 : : TupleDesc tupdesc;
252 : :
253 : : Datum result;
254 : : HeapTuple tuple;
255 : : Datum values[9];
256 : : bool nulls[9];
257 : :
258 : : Page page;
259 : : PageHeader pageheader;
260 : : XLogRecPtr lsn;
261 : :
262 [ - + ]: 5 : if (!superuser())
6877 bruce@momjian.us 263 [ # # ]:UBC 0 : ereport(ERROR,
264 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
265 : : errmsg("must be superuser to use raw page functions")));
266 : :
1343 peter@eisentraut.org 267 :CBC 5 : page = get_page_from_raw(raw_page);
268 : 4 : pageheader = (PageHeader) page;
269 : :
270 : : /* Build a tuple descriptor for our result type */
6877 bruce@momjian.us 271 [ - + ]: 4 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
6877 bruce@momjian.us 272 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
273 : :
274 : : /* Extract information from the page header */
275 : :
6877 bruce@momjian.us 276 :CBC 4 : lsn = PageGetLSN(page);
277 : :
278 : : /* pageinspect >= 1.2 uses pg_lsn instead of text for the LSN field. */
3129 andres@anarazel.de 279 [ - + ]: 4 : if (TupleDescAttr(tupdesc, 0)->atttypid == TEXTOID)
280 : : {
281 : : char lsnchar[64];
282 : :
251 alvherre@kurilemu.de 283 :UNC 0 : snprintf(lsnchar, sizeof(lsnchar), "%X/%08X", LSN_FORMAT_ARGS(lsn));
4395 rhaas@postgresql.org 284 :UBC 0 : values[0] = CStringGetTextDatum(lsnchar);
285 : : }
286 : : else
4395 rhaas@postgresql.org 287 :CBC 4 : values[0] = LSNGetDatum(lsn);
1343 peter@eisentraut.org 288 : 4 : values[1] = UInt16GetDatum(pageheader->pd_checksum);
289 : 4 : values[2] = UInt16GetDatum(pageheader->pd_flags);
290 : :
291 : : /* pageinspect >= 1.10 uses int4 instead of int2 for those fields */
1707 michael@paquier.xyz 292 [ + + - ]: 4 : switch (TupleDescAttr(tupdesc, 3)->atttypid)
293 : : {
294 : 1 : case INT2OID:
295 [ + - + - : 1 : Assert(TupleDescAttr(tupdesc, 4)->atttypid == INT2OID &&
- + ]
296 : : TupleDescAttr(tupdesc, 5)->atttypid == INT2OID &&
297 : : TupleDescAttr(tupdesc, 6)->atttypid == INT2OID);
1343 peter@eisentraut.org 298 : 1 : values[3] = UInt16GetDatum(pageheader->pd_lower);
299 : 1 : values[4] = UInt16GetDatum(pageheader->pd_upper);
300 : 1 : values[5] = UInt16GetDatum(pageheader->pd_special);
1707 michael@paquier.xyz 301 : 1 : values[6] = UInt16GetDatum(PageGetPageSize(page));
302 : 1 : break;
303 : 3 : case INT4OID:
304 [ + - + - : 3 : Assert(TupleDescAttr(tupdesc, 4)->atttypid == INT4OID &&
- + ]
305 : : TupleDescAttr(tupdesc, 5)->atttypid == INT4OID &&
306 : : TupleDescAttr(tupdesc, 6)->atttypid == INT4OID);
1343 peter@eisentraut.org 307 : 3 : values[3] = Int32GetDatum(pageheader->pd_lower);
308 : 3 : values[4] = Int32GetDatum(pageheader->pd_upper);
309 : 3 : values[5] = Int32GetDatum(pageheader->pd_special);
1707 michael@paquier.xyz 310 : 3 : values[6] = Int32GetDatum(PageGetPageSize(page));
311 : 3 : break;
1707 michael@paquier.xyz 312 :UBC 0 : default:
313 [ # # ]: 0 : elog(ERROR, "incorrect output types");
314 : : break;
315 : : }
316 : :
6877 bruce@momjian.us 317 :CBC 4 : values[7] = UInt16GetDatum(PageGetPageLayoutVersion(page));
1343 peter@eisentraut.org 318 : 4 : values[8] = TransactionIdGetDatum(pageheader->pd_prune_xid);
319 : :
320 : : /* Build and return the tuple. */
321 : :
6877 bruce@momjian.us 322 : 4 : memset(nulls, 0, sizeof(nulls));
323 : :
6695 324 : 4 : tuple = heap_form_tuple(tupdesc, values, nulls);
325 : 4 : result = HeapTupleGetDatum(tuple);
326 : :
6877 327 : 4 : PG_RETURN_DATUM(result);
328 : : }
329 : :
330 : : /*
331 : : * page_checksum
332 : : *
333 : : * Compute checksum of a raw page
334 : : */
335 : :
1881 peter@eisentraut.org 336 : 9 : PG_FUNCTION_INFO_V1(page_checksum_1_9);
3285 peter_e@gmx.net 337 : 8 : PG_FUNCTION_INFO_V1(page_checksum);
338 : :
339 : : static Datum
1881 peter@eisentraut.org 340 : 23 : page_checksum_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
341 : : {
3285 peter_e@gmx.net 342 : 23 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
1881 peter@eisentraut.org 343 [ + + ]: 23 : int64 blkno = (ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1));
344 : : Page page;
345 : :
3285 peter_e@gmx.net 346 [ - + ]: 23 : if (!superuser())
3285 peter_e@gmx.net 347 [ # # ]:UBC 0 : ereport(ERROR,
348 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
349 : : errmsg("must be superuser to use raw page functions")));
350 : :
1881 peter@eisentraut.org 351 [ + + - + ]:CBC 23 : if (blkno < 0 || blkno > MaxBlockNumber)
352 [ + - ]: 1 : ereport(ERROR,
353 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
354 : : errmsg("invalid block number")));
355 : :
1460 michael@paquier.xyz 356 : 22 : page = get_page_from_raw(raw_page);
357 : :
1431 358 [ + + ]: 21 : if (PageIsNew(page))
359 : 1 : PG_RETURN_NULL();
360 : :
396 peter@eisentraut.org 361 : 20 : PG_RETURN_INT16(pg_checksum_page(page, blkno));
362 : : }
363 : :
364 : : Datum
1881 365 : 22 : page_checksum_1_9(PG_FUNCTION_ARGS)
366 : : {
367 : 22 : return page_checksum_internal(fcinfo, PAGEINSPECT_V1_9);
368 : : }
369 : :
370 : : /*
371 : : * Entry point for old extension version
372 : : */
373 : : Datum
374 : 1 : page_checksum(PG_FUNCTION_ARGS)
375 : : {
376 : 1 : return page_checksum_internal(fcinfo, PAGEINSPECT_V1_8);
377 : : }
|