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