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 : }
|