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 :
32 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 : */
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 : */
68 8 : PG_FUNCTION_INFO_V1(get_raw_page);
69 :
70 : Datum
71 2 : get_raw_page(PG_FUNCTION_ARGS)
72 : {
73 2 : text *relname = PG_GETARG_TEXT_PP(0);
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)
83 0 : ereport(ERROR,
84 : (errmsg("wrong number of arguments to get_raw_page()"),
85 : errhint("Run the updated pageinspect.sql script.")));
86 :
87 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 : */
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 : */
123 8 : PG_FUNCTION_INFO_V1(get_raw_page_fork);
124 :
125 : Datum
126 1 : get_raw_page_fork(PG_FUNCTION_ARGS)
127 : {
128 1 : text *relname = PG_GETARG_TEXT_PP(0);
129 1 : text *forkname = PG_GETARG_TEXT_PP(1);
130 1 : uint32 blkno = PG_GETARG_UINT32(2);
131 : bytea *raw_page;
132 : ForkNumber forknum;
133 :
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 :
153 133 : if (!superuser())
154 0 : ereport(ERROR,
155 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
156 : errmsg("must be superuser to use raw page functions")));
157 :
158 133 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
159 133 : rel = relation_openrv(relrv, AccessShareLock);
160 :
161 132 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
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 : */
173 130 : 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 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 */
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 :
191 125 : buf = ReadBufferExtended(rel, forknum, blkno, RBM_NORMAL, NULL);
192 125 : LockBuffer(buf, BUFFER_LOCK_SHARE);
193 :
194 125 : memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
195 :
196 125 : LockBuffer(buf, BUFFER_LOCK_UNLOCK);
197 125 : ReleaseBuffer(buf);
198 :
199 125 : relation_close(rel, AccessShareLock);
200 :
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
216 163 : get_page_from_raw(bytea *raw_page)
217 : {
218 : Page page;
219 : int raw_page_size;
220 :
221 163 : raw_page_size = VARSIZE_ANY_EXHDR(raw_page);
222 :
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 :
232 149 : memcpy(page, VARDATA_ANY(raw_page), raw_page_size);
233 :
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 :
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())
263 0 : ereport(ERROR,
264 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
265 : errmsg("must be superuser to use raw page functions")));
266 :
267 5 : page = get_page_from_raw(raw_page);
268 4 : pageheader = (PageHeader) page;
269 :
270 : /* Build a tuple descriptor for our result type */
271 4 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
272 0 : elog(ERROR, "return type must be a row type");
273 :
274 : /* Extract information from the page header */
275 :
276 4 : lsn = PageGetLSN(page);
277 :
278 : /* pageinspect >= 1.2 uses pg_lsn instead of text for the LSN field. */
279 4 : if (TupleDescAttr(tupdesc, 0)->atttypid == TEXTOID)
280 : {
281 : char lsnchar[64];
282 :
283 0 : snprintf(lsnchar, sizeof(lsnchar), "%X/%08X", LSN_FORMAT_ARGS(lsn));
284 0 : values[0] = CStringGetTextDatum(lsnchar);
285 : }
286 : else
287 4 : values[0] = LSNGetDatum(lsn);
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 */
292 4 : switch (TupleDescAttr(tupdesc, 3)->atttypid)
293 : {
294 1 : case INT2OID:
295 : Assert(TupleDescAttr(tupdesc, 4)->atttypid == INT2OID &&
296 : TupleDescAttr(tupdesc, 5)->atttypid == INT2OID &&
297 : TupleDescAttr(tupdesc, 6)->atttypid == INT2OID);
298 1 : values[3] = UInt16GetDatum(pageheader->pd_lower);
299 1 : values[4] = UInt16GetDatum(pageheader->pd_upper);
300 1 : values[5] = UInt16GetDatum(pageheader->pd_special);
301 1 : values[6] = UInt16GetDatum(PageGetPageSize(page));
302 1 : break;
303 3 : case INT4OID:
304 : Assert(TupleDescAttr(tupdesc, 4)->atttypid == INT4OID &&
305 : TupleDescAttr(tupdesc, 5)->atttypid == INT4OID &&
306 : TupleDescAttr(tupdesc, 6)->atttypid == INT4OID);
307 3 : values[3] = Int32GetDatum(pageheader->pd_lower);
308 3 : values[4] = Int32GetDatum(pageheader->pd_upper);
309 3 : values[5] = Int32GetDatum(pageheader->pd_special);
310 3 : values[6] = Int32GetDatum(PageGetPageSize(page));
311 3 : break;
312 0 : default:
313 0 : elog(ERROR, "incorrect output types");
314 : break;
315 : }
316 :
317 4 : values[7] = UInt16GetDatum(PageGetPageLayoutVersion(page));
318 4 : values[8] = TransactionIdGetDatum(pageheader->pd_prune_xid);
319 :
320 : /* Build and return the tuple. */
321 :
322 4 : memset(nulls, 0, sizeof(nulls));
323 :
324 4 : tuple = heap_form_tuple(tupdesc, values, nulls);
325 4 : result = HeapTupleGetDatum(tuple);
326 :
327 4 : PG_RETURN_DATUM(result);
328 : }
329 :
330 : /*
331 : * page_checksum
332 : *
333 : * Compute checksum of a raw page
334 : */
335 :
336 9 : PG_FUNCTION_INFO_V1(page_checksum_1_9);
337 8 : PG_FUNCTION_INFO_V1(page_checksum);
338 :
339 : static Datum
340 23 : page_checksum_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
341 : {
342 23 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
343 23 : int64 blkno = (ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1));
344 : Page page;
345 :
346 23 : if (!superuser())
347 0 : ereport(ERROR,
348 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
349 : errmsg("must be superuser to use raw page functions")));
350 :
351 23 : if (blkno < 0 || blkno > MaxBlockNumber)
352 1 : ereport(ERROR,
353 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
354 : errmsg("invalid block number")));
355 :
356 22 : page = get_page_from_raw(raw_page);
357 :
358 21 : if (PageIsNew(page))
359 1 : PG_RETURN_NULL();
360 :
361 20 : PG_RETURN_INT16(pg_checksum_page(page, blkno));
362 : }
363 :
364 : Datum
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 : }
|