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