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