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