]> granicus.if.org Git - postgresql/blob - contrib/pageinspect/heapfuncs.c
Replace heapam.h includes with {table, relation}.h where applicable.
[postgresql] / contrib / pageinspect / heapfuncs.c
1 /*-------------------------------------------------------------------------
2  *
3  * heapfuncs.c
4  *        Functions to investigate heap pages
5  *
6  * We check the input to these functions for corrupt pointers etc. that
7  * might cause crashes, but at the same time we try to print out as much
8  * information as possible, even if it's nonsense. That's because if a
9  * page is corrupt, we don't know why and how exactly it is corrupt, so we
10  * let the user judge it.
11  *
12  * These functions are restricted to superusers for the fear of introducing
13  * security holes if the input checking isn't as water-tight as it should be.
14  * You'd need to be superuser to obtain a raw page image anyway, so
15  * there's hardly any use case for using these without superuser-rights
16  * anyway.
17  *
18  * Copyright (c) 2007-2019, PostgreSQL Global Development Group
19  *
20  * IDENTIFICATION
21  *        contrib/pageinspect/heapfuncs.c
22  *
23  *-------------------------------------------------------------------------
24  */
25
26 #include "postgres.h"
27
28 #include "pageinspect.h"
29
30 #include "access/htup_details.h"
31 #include "access/relation.h"
32 #include "funcapi.h"
33 #include "catalog/pg_type.h"
34 #include "miscadmin.h"
35 #include "utils/array.h"
36 #include "utils/builtins.h"
37 #include "utils/rel.h"
38
39 /*
40  * It's not supported to create tuples with oids anymore, but when pg_upgrade
41  * was used to upgrade from an older version, tuples might still have an
42  * oid. Seems worthwhile to display that.
43  */
44 #define HeapTupleHeaderGetOidOld(tup) \
45 ( \
46         ((tup)->t_infomask & HEAP_HASOID_OLD) ? \
47            *((Oid *) ((char *)(tup) + (tup)->t_hoff - sizeof(Oid))) \
48         : \
49                 InvalidOid \
50 )
51
52
53 /*
54  * bits_to_text
55  *
56  * Converts a bits8-array of 'len' bits to a human-readable
57  * c-string representation.
58  */
59 static char *
60 bits_to_text(bits8 *bits, int len)
61 {
62         int                     i;
63         char       *str;
64
65         str = palloc(len + 1);
66
67         for (i = 0; i < len; i++)
68                 str[i] = (bits[(i / 8)] & (1 << (i % 8))) ? '1' : '0';
69
70         str[i] = '\0';
71
72         return str;
73 }
74
75
76 /*
77  * text_to_bits
78  *
79  * Converts a c-string representation of bits into a bits8-array. This is
80  * the reverse operation of previous routine.
81  */
82 static bits8 *
83 text_to_bits(char *str, int len)
84 {
85         bits8      *bits;
86         int                     off = 0;
87         char            byte = 0;
88
89         bits = palloc(len + 1);
90
91         while (off < len)
92         {
93                 if (off % 8 == 0)
94                         byte = 0;
95
96                 if ((str[off] == '0') || (str[off] == '1'))
97                         byte = byte | ((str[off] - '0') << off % 8);
98                 else
99                         ereport(ERROR,
100                                         (errcode(ERRCODE_DATA_CORRUPTED),
101                                          errmsg("illegal character '%c' in t_bits string", str[off])));
102
103                 if (off % 8 == 7)
104                         bits[off / 8] = byte;
105
106                 off++;
107         }
108
109         return bits;
110 }
111
112 /*
113  * heap_page_items
114  *
115  * Allows inspection of line pointers and tuple headers of a heap page.
116  */
117 PG_FUNCTION_INFO_V1(heap_page_items);
118
119 typedef struct heap_page_items_state
120 {
121         TupleDesc       tupd;
122         Page            page;
123         uint16          offset;
124 } heap_page_items_state;
125
126 Datum
127 heap_page_items(PG_FUNCTION_ARGS)
128 {
129         bytea      *raw_page = PG_GETARG_BYTEA_P(0);
130         heap_page_items_state *inter_call_data = NULL;
131         FuncCallContext *fctx;
132         int                     raw_page_size;
133
134         if (!superuser())
135                 ereport(ERROR,
136                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
137                                  (errmsg("must be superuser to use raw page functions"))));
138
139         raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
140
141         if (SRF_IS_FIRSTCALL())
142         {
143                 TupleDesc       tupdesc;
144                 MemoryContext mctx;
145
146                 if (raw_page_size < SizeOfPageHeaderData)
147                         ereport(ERROR,
148                                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
149                                          errmsg("input page too small (%d bytes)", raw_page_size)));
150
151                 fctx = SRF_FIRSTCALL_INIT();
152                 mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
153
154                 inter_call_data = palloc(sizeof(heap_page_items_state));
155
156                 /* Build a tuple descriptor for our result type */
157                 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
158                         elog(ERROR, "return type must be a row type");
159
160                 inter_call_data->tupd = tupdesc;
161
162                 inter_call_data->offset = FirstOffsetNumber;
163                 inter_call_data->page = VARDATA(raw_page);
164
165                 fctx->max_calls = PageGetMaxOffsetNumber(inter_call_data->page);
166                 fctx->user_fctx = inter_call_data;
167
168                 MemoryContextSwitchTo(mctx);
169         }
170
171         fctx = SRF_PERCALL_SETUP();
172         inter_call_data = fctx->user_fctx;
173
174         if (fctx->call_cntr < fctx->max_calls)
175         {
176                 Page            page = inter_call_data->page;
177                 HeapTuple       resultTuple;
178                 Datum           result;
179                 ItemId          id;
180                 Datum           values[14];
181                 bool            nulls[14];
182                 uint16          lp_offset;
183                 uint16          lp_flags;
184                 uint16          lp_len;
185
186                 memset(nulls, 0, sizeof(nulls));
187
188                 /* Extract information from the line pointer */
189
190                 id = PageGetItemId(page, inter_call_data->offset);
191
192                 lp_offset = ItemIdGetOffset(id);
193                 lp_flags = ItemIdGetFlags(id);
194                 lp_len = ItemIdGetLength(id);
195
196                 values[0] = UInt16GetDatum(inter_call_data->offset);
197                 values[1] = UInt16GetDatum(lp_offset);
198                 values[2] = UInt16GetDatum(lp_flags);
199                 values[3] = UInt16GetDatum(lp_len);
200
201                 /*
202                  * We do just enough validity checking to make sure we don't reference
203                  * data outside the page passed to us. The page could be corrupt in
204                  * many other ways, but at least we won't crash.
205                  */
206                 if (ItemIdHasStorage(id) &&
207                         lp_len >= MinHeapTupleSize &&
208                         lp_offset == MAXALIGN(lp_offset) &&
209                         lp_offset + lp_len <= raw_page_size)
210                 {
211                         HeapTupleHeader tuphdr;
212                         bytea      *tuple_data_bytea;
213                         int                     tuple_data_len;
214
215                         /* Extract information from the tuple header */
216
217                         tuphdr = (HeapTupleHeader) PageGetItem(page, id);
218
219                         values[4] = UInt32GetDatum(HeapTupleHeaderGetRawXmin(tuphdr));
220                         values[5] = UInt32GetDatum(HeapTupleHeaderGetRawXmax(tuphdr));
221                         /* shared with xvac */
222                         values[6] = UInt32GetDatum(HeapTupleHeaderGetRawCommandId(tuphdr));
223                         values[7] = PointerGetDatum(&tuphdr->t_ctid);
224                         values[8] = UInt32GetDatum(tuphdr->t_infomask2);
225                         values[9] = UInt32GetDatum(tuphdr->t_infomask);
226                         values[10] = UInt8GetDatum(tuphdr->t_hoff);
227
228                         /* Copy raw tuple data into bytea attribute */
229                         tuple_data_len = lp_len - tuphdr->t_hoff;
230                         tuple_data_bytea = (bytea *) palloc(tuple_data_len + VARHDRSZ);
231                         SET_VARSIZE(tuple_data_bytea, tuple_data_len + VARHDRSZ);
232                         memcpy(VARDATA(tuple_data_bytea), (char *) tuphdr + tuphdr->t_hoff,
233                                    tuple_data_len);
234                         values[13] = PointerGetDatum(tuple_data_bytea);
235
236                         /*
237                          * We already checked that the item is completely within the raw
238                          * page passed to us, with the length given in the line pointer.
239                          * Let's check that t_hoff doesn't point over lp_len, before using
240                          * it to access t_bits and oid.
241                          */
242                         if (tuphdr->t_hoff >= SizeofHeapTupleHeader &&
243                                 tuphdr->t_hoff <= lp_len &&
244                                 tuphdr->t_hoff == MAXALIGN(tuphdr->t_hoff))
245                         {
246                                 if (tuphdr->t_infomask & HEAP_HASNULL)
247                                 {
248                                         int                     bits_len;
249
250                                         bits_len =
251                                                 BITMAPLEN(HeapTupleHeaderGetNatts(tuphdr)) * BITS_PER_BYTE;
252                                         values[11] = CStringGetTextDatum(
253                                                                                                          bits_to_text(tuphdr->t_bits, bits_len));
254                                 }
255                                 else
256                                         nulls[11] = true;
257
258                                 if (tuphdr->t_infomask & HEAP_HASOID_OLD)
259                                         values[12] = HeapTupleHeaderGetOidOld(tuphdr);
260                                 else
261                                         nulls[12] = true;
262                         }
263                         else
264                         {
265                                 nulls[11] = true;
266                                 nulls[12] = true;
267                         }
268                 }
269                 else
270                 {
271                         /*
272                          * The line pointer is not used, or it's invalid. Set the rest of
273                          * the fields to NULL
274                          */
275                         int                     i;
276
277                         for (i = 4; i <= 13; i++)
278                                 nulls[i] = true;
279                 }
280
281                 /* Build and return the result tuple. */
282                 resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls);
283                 result = HeapTupleGetDatum(resultTuple);
284
285                 inter_call_data->offset++;
286
287                 SRF_RETURN_NEXT(fctx, result);
288         }
289         else
290                 SRF_RETURN_DONE(fctx);
291 }
292
293 /*
294  * tuple_data_split_internal
295  *
296  * Split raw tuple data taken directly from a page into an array of bytea
297  * elements. This routine does a lookup on NULL values and creates array
298  * elements accordingly. This is a reimplementation of nocachegetattr()
299  * in heaptuple.c simplified for educational purposes.
300  */
301 static Datum
302 tuple_data_split_internal(Oid relid, char *tupdata,
303                                                   uint16 tupdata_len, uint16 t_infomask,
304                                                   uint16 t_infomask2, bits8 *t_bits,
305                                                   bool do_detoast)
306 {
307         ArrayBuildState *raw_attrs;
308         int                     nattrs;
309         int                     i;
310         int                     off = 0;
311         Relation        rel;
312         TupleDesc       tupdesc;
313
314         /* Get tuple descriptor from relation OID */
315         rel = relation_open(relid, AccessShareLock);
316         tupdesc = RelationGetDescr(rel);
317
318         raw_attrs = initArrayResult(BYTEAOID, CurrentMemoryContext, false);
319         nattrs = tupdesc->natts;
320
321         if (nattrs < (t_infomask2 & HEAP_NATTS_MASK))
322                 ereport(ERROR,
323                                 (errcode(ERRCODE_DATA_CORRUPTED),
324                                  errmsg("number of attributes in tuple header is greater than number of attributes in tuple descriptor")));
325
326         for (i = 0; i < nattrs; i++)
327         {
328                 Form_pg_attribute attr;
329                 bool            is_null;
330                 bytea      *attr_data = NULL;
331
332                 attr = TupleDescAttr(tupdesc, i);
333
334                 /*
335                  * Tuple header can specify less attributes than tuple descriptor as
336                  * ALTER TABLE ADD COLUMN without DEFAULT keyword does not actually
337                  * change tuples in pages, so attributes with numbers greater than
338                  * (t_infomask2 & HEAP_NATTS_MASK) should be treated as NULL.
339                  */
340                 if (i >= (t_infomask2 & HEAP_NATTS_MASK))
341                         is_null = true;
342                 else
343                         is_null = (t_infomask & HEAP_HASNULL) && att_isnull(i, t_bits);
344
345                 if (!is_null)
346                 {
347                         int                     len;
348
349                         if (attr->attlen == -1)
350                         {
351                                 off = att_align_pointer(off, attr->attalign, -1,
352                                                                                 tupdata + off);
353
354                                 /*
355                                  * As VARSIZE_ANY throws an exception if it can't properly
356                                  * detect the type of external storage in macros VARTAG_SIZE,
357                                  * this check is repeated to have a nicer error handling.
358                                  */
359                                 if (VARATT_IS_EXTERNAL(tupdata + off) &&
360                                         !VARATT_IS_EXTERNAL_ONDISK(tupdata + off) &&
361                                         !VARATT_IS_EXTERNAL_INDIRECT(tupdata + off))
362                                         ereport(ERROR,
363                                                         (errcode(ERRCODE_DATA_CORRUPTED),
364                                                          errmsg("first byte of varlena attribute is incorrect for attribute %d", i)));
365
366                                 len = VARSIZE_ANY(tupdata + off);
367                         }
368                         else
369                         {
370                                 off = att_align_nominal(off, attr->attalign);
371                                 len = attr->attlen;
372                         }
373
374                         if (tupdata_len < off + len)
375                                 ereport(ERROR,
376                                                 (errcode(ERRCODE_DATA_CORRUPTED),
377                                                  errmsg("unexpected end of tuple data")));
378
379                         if (attr->attlen == -1 && do_detoast)
380                                 attr_data = DatumGetByteaPCopy(tupdata + off);
381                         else
382                         {
383                                 attr_data = (bytea *) palloc(len + VARHDRSZ);
384                                 SET_VARSIZE(attr_data, len + VARHDRSZ);
385                                 memcpy(VARDATA(attr_data), tupdata + off, len);
386                         }
387
388                         off = att_addlength_pointer(off, attr->attlen,
389                                                                                 tupdata + off);
390                 }
391
392                 raw_attrs = accumArrayResult(raw_attrs, PointerGetDatum(attr_data),
393                                                                          is_null, BYTEAOID, CurrentMemoryContext);
394                 if (attr_data)
395                         pfree(attr_data);
396         }
397
398         if (tupdata_len != off)
399                 ereport(ERROR,
400                                 (errcode(ERRCODE_DATA_CORRUPTED),
401                                  errmsg("end of tuple reached without looking at all its data")));
402
403         relation_close(rel, AccessShareLock);
404
405         return makeArrayResult(raw_attrs, CurrentMemoryContext);
406 }
407
408 /*
409  * tuple_data_split
410  *
411  * Split raw tuple data taken directly from page into distinct elements
412  * taking into account null values.
413  */
414 PG_FUNCTION_INFO_V1(tuple_data_split);
415
416 Datum
417 tuple_data_split(PG_FUNCTION_ARGS)
418 {
419         Oid                     relid;
420         bytea      *raw_data;
421         uint16          t_infomask;
422         uint16          t_infomask2;
423         char       *t_bits_str;
424         bool            do_detoast = false;
425         bits8      *t_bits = NULL;
426         Datum           res;
427
428         relid = PG_GETARG_OID(0);
429         raw_data = PG_ARGISNULL(1) ? NULL : PG_GETARG_BYTEA_P(1);
430         t_infomask = PG_GETARG_INT16(2);
431         t_infomask2 = PG_GETARG_INT16(3);
432         t_bits_str = PG_ARGISNULL(4) ? NULL :
433                 text_to_cstring(PG_GETARG_TEXT_PP(4));
434
435         if (PG_NARGS() >= 6)
436                 do_detoast = PG_GETARG_BOOL(5);
437
438         if (!superuser())
439                 ereport(ERROR,
440                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
441                                  errmsg("must be superuser to use raw page functions")));
442
443         if (!raw_data)
444                 PG_RETURN_NULL();
445
446         /*
447          * Convert t_bits string back to the bits8 array as represented in the
448          * tuple header.
449          */
450         if (t_infomask & HEAP_HASNULL)
451         {
452                 int                     bits_str_len;
453                 int                     bits_len;
454
455                 bits_len = BITMAPLEN(t_infomask2 & HEAP_NATTS_MASK) * BITS_PER_BYTE;
456                 if (!t_bits_str)
457                         ereport(ERROR,
458                                         (errcode(ERRCODE_DATA_CORRUPTED),
459                                          errmsg("argument of t_bits is null, but it is expected to be null and %d character long",
460                                                         bits_len)));
461
462                 bits_str_len = strlen(t_bits_str);
463                 if (bits_len != bits_str_len)
464                         ereport(ERROR,
465                                         (errcode(ERRCODE_DATA_CORRUPTED),
466                                          errmsg("unexpected length of t_bits %u, expected %d",
467                                                         bits_str_len, bits_len)));
468
469                 /* do the conversion */
470                 t_bits = text_to_bits(t_bits_str, bits_str_len);
471         }
472         else
473         {
474                 if (t_bits_str)
475                         ereport(ERROR,
476                                         (errcode(ERRCODE_DATA_CORRUPTED),
477                                          errmsg("t_bits string is expected to be NULL, but instead it is %zu bytes length",
478                                                         strlen(t_bits_str))));
479         }
480
481         /* Split tuple data */
482         res = tuple_data_split_internal(relid, (char *) raw_data + VARHDRSZ,
483                                                                         VARSIZE(raw_data) - VARHDRSZ,
484                                                                         t_infomask, t_infomask2, t_bits,
485                                                                         do_detoast);
486
487         if (t_bits)
488                 pfree(t_bits);
489
490         PG_RETURN_ARRAYTYPE_P(res);
491 }