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-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 : :
164 tgl@sss.pgh.pa.us 32 :CBC 27 : 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 : : */
1691 peter@eisentraut.org 46 : 25 : PG_FUNCTION_INFO_V1(get_raw_page_1_9);
47 : :
48 : : Datum
49 : 123 : get_raw_page_1_9(PG_FUNCTION_ARGS)
50 : : {
51 : 123 : text *relname = PG_GETARG_TEXT_PP(0);
52 : 123 : int64 blkno = PG_GETARG_INT64(1);
53 : : bytea *raw_page;
54 : :
55 [ + + - + ]: 123 : if (blkno < 0 || blkno > MaxBlockNumber)
56 [ + - ]: 1 : ereport(ERROR,
57 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
58 : : errmsg("invalid block number")));
59 : :
60 : 122 : raw_page = get_raw_page_internal(relname, MAIN_FORKNUM, blkno);
61 : :
62 : 117 : PG_RETURN_BYTEA_P(raw_page);
63 : : }
64 : :
65 : : /*
66 : : * entry point for old extension version
67 : : */
6687 bruce@momjian.us 68 : 7 : PG_FUNCTION_INFO_V1(get_raw_page);
69 : :
70 : : Datum
71 : 2 : get_raw_page(PG_FUNCTION_ARGS)
72 : : {
3100 noah@leadboat.com 73 : 2 : text *relname = PG_GETARG_TEXT_PP(0);
5934 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)
5934 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 : :
5934 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 : : */
1691 peter@eisentraut.org 97 : 8 : 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 : : */
5934 tgl@sss.pgh.pa.us 123 : 7 : PG_FUNCTION_INFO_V1(get_raw_page_fork);
124 : :
125 : : Datum
126 : 1 : get_raw_page_fork(PG_FUNCTION_ARGS)
127 : : {
3100 noah@leadboat.com 128 : 1 : text *relname = PG_GETARG_TEXT_PP(0);
129 : 1 : text *forkname = PG_GETARG_TEXT_PP(1);
6185 heikki.linnakangas@i 130 : 1 : uint32 blkno = PG_GETARG_UINT32(2);
131 : : bytea *raw_page;
132 : : ForkNumber forknum;
133 : :
5934 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 : 135 : 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 : :
6687 bruce@momjian.us 153 [ - + ]: 135 : if (!superuser())
6687 bruce@momjian.us 154 [ # # ]:UBC 0 : ereport(ERROR,
155 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
156 : : errmsg("must be superuser to use raw page functions")));
157 : :
6687 bruce@momjian.us 158 :CBC 135 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
159 : 135 : rel = relation_openrv(relrv, AccessShareLock);
160 : :
1521 peter@eisentraut.org 161 [ + + + + : 134 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
+ + + - +
- ]
6687 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 : : */
6003 tgl@sss.pgh.pa.us 173 [ + + - + ]: 132 : if (RELATION_IS_OTHER_TEMP(rel))
6003 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 : :
4064 tgl@sss.pgh.pa.us 178 [ + + ]:CBC 132 : 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 */
6687 bruce@momjian.us 185 : 127 : raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
186 : 127 : SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ);
187 : 127 : raw_page_data = VARDATA(raw_page);
188 : :
189 : : /* Take a verbatim copy of the page */
190 : :
6154 heikki.linnakangas@i 191 : 127 : buf = ReadBufferExtended(rel, forknum, blkno, RBM_NORMAL, NULL);
6687 bruce@momjian.us 192 : 127 : LockBuffer(buf, BUFFER_LOCK_SHARE);
193 : :
3426 kgrittn@postgresql.o 194 : 127 : memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
195 : :
6687 bruce@momjian.us 196 : 127 : LockBuffer(buf, BUFFER_LOCK_UNLOCK);
197 : 127 : ReleaseBuffer(buf);
198 : :
199 : 127 : relation_close(rel, AccessShareLock);
200 : :
5934 tgl@sss.pgh.pa.us 201 : 127 : 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
3137 218 : 155 : get_page_from_raw(bytea *raw_page)
219 : : {
220 : : Page page;
221 : : int raw_page_size;
222 : :
3100 noah@leadboat.com 223 [ - + - - : 155 : raw_page_size = VARSIZE_ANY_EXHDR(raw_page);
- - - - -
+ ]
224 : :
3137 tgl@sss.pgh.pa.us 225 [ + + ]: 155 : if (raw_page_size != BLCKSZ)
226 [ + - ]: 14 : 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 : 141 : page = palloc(raw_page_size);
233 : :
3100 noah@leadboat.com 234 [ - + ]: 141 : memcpy(page, VARDATA_ANY(raw_page), raw_page_size);
235 : :
3137 tgl@sss.pgh.pa.us 236 : 141 : return page;
237 : : }
238 : :
239 : :
240 : : /*
241 : : * page_header
242 : : *
243 : : * Allows inspection of page header fields of a raw page
244 : : */
245 : :
6687 bruce@momjian.us 246 : 14 : PG_FUNCTION_INFO_V1(page_header);
247 : :
248 : : Datum
249 : 4 : page_header(PG_FUNCTION_ARGS)
250 : : {
251 : 4 : 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 [ - + ]: 4 : if (!superuser())
6687 bruce@momjian.us 265 [ # # ]:UBC 0 : ereport(ERROR,
266 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
267 : : errmsg("must be superuser to use raw page functions")));
268 : :
1153 peter@eisentraut.org 269 :CBC 4 : page = get_page_from_raw(raw_page);
270 : 3 : pageheader = (PageHeader) page;
271 : :
272 : : /* Build a tuple descriptor for our result type */
6687 bruce@momjian.us 273 [ - + ]: 3 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
6687 bruce@momjian.us 274 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
275 : :
276 : : /* Extract information from the page header */
277 : :
6687 bruce@momjian.us 278 :CBC 3 : lsn = PageGetLSN(page);
279 : :
280 : : /* pageinspect >= 1.2 uses pg_lsn instead of text for the LSN field. */
2939 andres@anarazel.de 281 [ - + ]: 3 : if (TupleDescAttr(tupdesc, 0)->atttypid == TEXTOID)
282 : : {
283 : : char lsnchar[64];
284 : :
61 alvherre@kurilemu.de 285 :UNC 0 : snprintf(lsnchar, sizeof(lsnchar), "%X/%08X", LSN_FORMAT_ARGS(lsn));
4205 rhaas@postgresql.org 286 :UBC 0 : values[0] = CStringGetTextDatum(lsnchar);
287 : : }
288 : : else
4205 rhaas@postgresql.org 289 :CBC 3 : values[0] = LSNGetDatum(lsn);
1153 peter@eisentraut.org 290 : 3 : values[1] = UInt16GetDatum(pageheader->pd_checksum);
291 : 3 : values[2] = UInt16GetDatum(pageheader->pd_flags);
292 : :
293 : : /* pageinspect >= 1.10 uses int4 instead of int2 for those fields */
1517 michael@paquier.xyz 294 [ + + - ]: 3 : switch (TupleDescAttr(tupdesc, 3)->atttypid)
295 : : {
296 : 1 : case INT2OID:
297 [ + - + - : 1 : Assert(TupleDescAttr(tupdesc, 4)->atttypid == INT2OID &&
- + ]
298 : : TupleDescAttr(tupdesc, 5)->atttypid == INT2OID &&
299 : : TupleDescAttr(tupdesc, 6)->atttypid == INT2OID);
1153 peter@eisentraut.org 300 : 1 : values[3] = UInt16GetDatum(pageheader->pd_lower);
301 : 1 : values[4] = UInt16GetDatum(pageheader->pd_upper);
302 : 1 : values[5] = UInt16GetDatum(pageheader->pd_special);
1517 michael@paquier.xyz 303 : 1 : values[6] = UInt16GetDatum(PageGetPageSize(page));
304 : 1 : break;
305 : 2 : case INT4OID:
306 [ + - + - : 2 : Assert(TupleDescAttr(tupdesc, 4)->atttypid == INT4OID &&
- + ]
307 : : TupleDescAttr(tupdesc, 5)->atttypid == INT4OID &&
308 : : TupleDescAttr(tupdesc, 6)->atttypid == INT4OID);
1153 peter@eisentraut.org 309 : 2 : values[3] = Int32GetDatum(pageheader->pd_lower);
310 : 2 : values[4] = Int32GetDatum(pageheader->pd_upper);
311 : 2 : values[5] = Int32GetDatum(pageheader->pd_special);
1517 michael@paquier.xyz 312 : 2 : values[6] = Int32GetDatum(PageGetPageSize(page));
313 : 2 : break;
1517 michael@paquier.xyz 314 :UBC 0 : default:
315 [ # # ]: 0 : elog(ERROR, "incorrect output types");
316 : : break;
317 : : }
318 : :
6687 bruce@momjian.us 319 :CBC 3 : values[7] = UInt16GetDatum(PageGetPageLayoutVersion(page));
1153 peter@eisentraut.org 320 : 3 : values[8] = TransactionIdGetDatum(pageheader->pd_prune_xid);
321 : :
322 : : /* Build and return the tuple. */
323 : :
6687 bruce@momjian.us 324 : 3 : memset(nulls, 0, sizeof(nulls));
325 : :
6505 326 : 3 : tuple = heap_form_tuple(tupdesc, values, nulls);
327 : 3 : result = HeapTupleGetDatum(tuple);
328 : :
6687 329 : 3 : PG_RETURN_DATUM(result);
330 : : }
331 : :
332 : : /*
333 : : * page_checksum
334 : : *
335 : : * Compute checksum of a raw page
336 : : */
337 : :
1691 peter@eisentraut.org 338 : 8 : PG_FUNCTION_INFO_V1(page_checksum_1_9);
3095 peter_e@gmx.net 339 : 7 : PG_FUNCTION_INFO_V1(page_checksum);
340 : :
341 : : static Datum
1691 peter@eisentraut.org 342 : 23 : page_checksum_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
343 : : {
3095 peter_e@gmx.net 344 : 23 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
1691 peter@eisentraut.org 345 [ + + ]: 23 : int64 blkno = (ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1));
346 : : Page page;
347 : :
3095 peter_e@gmx.net 348 [ - + ]: 23 : if (!superuser())
3095 peter_e@gmx.net 349 [ # # ]:UBC 0 : ereport(ERROR,
350 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
351 : : errmsg("must be superuser to use raw page functions")));
352 : :
1691 peter@eisentraut.org 353 [ + + - + ]:CBC 23 : if (blkno < 0 || blkno > MaxBlockNumber)
354 [ + - ]: 1 : ereport(ERROR,
355 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
356 : : errmsg("invalid block number")));
357 : :
1270 michael@paquier.xyz 358 : 22 : page = get_page_from_raw(raw_page);
359 : :
1241 360 [ + + ]: 21 : if (PageIsNew(page))
361 : 1 : PG_RETURN_NULL();
362 : :
206 peter@eisentraut.org 363 : 20 : PG_RETURN_INT16(pg_checksum_page(page, blkno));
364 : : }
365 : :
366 : : Datum
1691 367 : 22 : page_checksum_1_9(PG_FUNCTION_ARGS)
368 : : {
369 : 22 : return page_checksum_internal(fcinfo, PAGEINSPECT_V1_9);
370 : : }
371 : :
372 : : /*
373 : : * Entry point for old extension version
374 : : */
375 : : Datum
376 : 1 : page_checksum(PG_FUNCTION_ARGS)
377 : : {
378 : 1 : return page_checksum_internal(fcinfo, PAGEINSPECT_V1_8);
379 : : }
|