PostgreSQL Source Code  git master
verify_heapam.c File Reference
#include "postgres.h"
#include "access/detoast.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/heaptoast.h"
#include "access/multixact.h"
#include "access/toast_internals.h"
#include "access/visibilitymap.h"
#include "catalog/pg_am.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/procarray.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
Include dependency graph for verify_heapam.c:

Go to the source code of this file.

Data Structures

struct  ToastedAttribute
 
struct  HeapCheckContext
 

Macros

#define HEAPCHECK_RELATION_COLS   4
 

Typedefs

typedef enum XidBoundsViolation XidBoundsViolation
 
typedef enum XidCommitStatus XidCommitStatus
 
typedef enum SkipPages SkipPages
 
typedef struct ToastedAttribute ToastedAttribute
 
typedef struct HeapCheckContext HeapCheckContext
 

Enumerations

enum  XidBoundsViolation {
  XID_INVALID, XID_IN_FUTURE, XID_PRECEDES_CLUSTERMIN, XID_PRECEDES_RELMIN,
  XID_BOUNDS_OK
}
 
enum  XidCommitStatus { XID_COMMITTED, XID_IS_CURRENT_XID, XID_IN_PROGRESS, XID_ABORTED }
 
enum  SkipPages { SKIP_PAGES_ALL_FROZEN, SKIP_PAGES_ALL_VISIBLE, SKIP_PAGES_NONE }
 

Functions

 PG_FUNCTION_INFO_V1 (verify_heapam)
 
static void check_tuple (HeapCheckContext *ctx)
 
static void check_toast_tuple (HeapTuple toasttup, HeapCheckContext *ctx, ToastedAttribute *ta, int32 *expected_chunk_seq, uint32 extsize)
 
static bool check_tuple_attribute (HeapCheckContext *ctx)
 
static void check_toasted_attribute (HeapCheckContext *ctx, ToastedAttribute *ta)
 
static bool check_tuple_header (HeapCheckContext *ctx)
 
static bool check_tuple_visibility (HeapCheckContext *ctx)
 
static void report_corruption (HeapCheckContext *ctx, char *msg)
 
static void report_toast_corruption (HeapCheckContext *ctx, ToastedAttribute *ta, char *msg)
 
static TupleDesc verify_heapam_tupdesc (void)
 
static FullTransactionId FullTransactionIdFromXidAndCtx (TransactionId xid, const HeapCheckContext *ctx)
 
static void update_cached_xid_range (HeapCheckContext *ctx)
 
static void update_cached_mxid_range (HeapCheckContext *ctx)
 
static XidBoundsViolation check_mxid_in_range (MultiXactId mxid, HeapCheckContext *ctx)
 
static XidBoundsViolation check_mxid_valid_in_rel (MultiXactId mxid, HeapCheckContext *ctx)
 
static XidBoundsViolation get_xid_status (TransactionId xid, HeapCheckContext *ctx, XidCommitStatus *status)
 
Datum verify_heapam (PG_FUNCTION_ARGS)
 
static void report_corruption_internal (Tuplestorestate *tupstore, TupleDesc tupdesc, BlockNumber blkno, OffsetNumber offnum, AttrNumber attnum, char *msg)
 
static bool fxid_in_cached_range (FullTransactionId fxid, const HeapCheckContext *ctx)
 

Macro Definition Documentation

◆ HEAPCHECK_RELATION_COLS

#define HEAPCHECK_RELATION_COLS   4

Definition at line 31 of file verify_heapam.c.

Referenced by report_corruption_internal(), and verify_heapam_tupdesc().

Typedef Documentation

◆ HeapCheckContext

◆ SkipPages

typedef enum SkipPages SkipPages

◆ ToastedAttribute

◆ XidBoundsViolation

◆ XidCommitStatus

Enumeration Type Documentation

◆ SkipPages

enum SkipPages
Enumerator
SKIP_PAGES_ALL_FROZEN 
SKIP_PAGES_ALL_VISIBLE 
SKIP_PAGES_NONE 

Definition at line 54 of file verify_heapam.c.

◆ XidBoundsViolation

Enumerator
XID_INVALID 
XID_IN_FUTURE 
XID_PRECEDES_CLUSTERMIN 
XID_PRECEDES_RELMIN 
XID_BOUNDS_OK 

Definition at line 37 of file verify_heapam.c.

◆ XidCommitStatus

Enumerator
XID_COMMITTED 
XID_IS_CURRENT_XID 
XID_IN_PROGRESS 
XID_ABORTED 

Definition at line 46 of file verify_heapam.c.

Function Documentation

◆ check_mxid_in_range()

static XidBoundsViolation check_mxid_in_range ( MultiXactId  mxid,
HeapCheckContext ctx 
)
static

Definition at line 1611 of file verify_heapam.c.

References MultiXactIdPrecedes(), MultiXactIdPrecedesOrEquals(), HeapCheckContext::next_mxact, HeapCheckContext::oldest_mxact, HeapCheckContext::relminmxid, TransactionIdIsValid, XID_BOUNDS_OK, XID_IN_FUTURE, XID_INVALID, XID_PRECEDES_CLUSTERMIN, and XID_PRECEDES_RELMIN.

Referenced by check_mxid_valid_in_rel().

1612 {
1613  if (!TransactionIdIsValid(mxid))
1614  return XID_INVALID;
1615  if (MultiXactIdPrecedes(mxid, ctx->relminmxid))
1616  return XID_PRECEDES_RELMIN;
1617  if (MultiXactIdPrecedes(mxid, ctx->oldest_mxact))
1618  return XID_PRECEDES_CLUSTERMIN;
1619  if (MultiXactIdPrecedesOrEquals(ctx->next_mxact, mxid))
1620  return XID_IN_FUTURE;
1621  return XID_BOUNDS_OK;
1622 }
MultiXactId oldest_mxact
Definition: verify_heapam.c:96
MultiXactId next_mxact
Definition: verify_heapam.c:95
bool MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2)
Definition: multixact.c:3159
bool MultiXactIdPrecedesOrEquals(MultiXactId multi1, MultiXactId multi2)
Definition: multixact.c:3173
#define TransactionIdIsValid(xid)
Definition: transam.h:41
TransactionId relminmxid

◆ check_mxid_valid_in_rel()

static XidBoundsViolation check_mxid_valid_in_rel ( MultiXactId  mxid,
HeapCheckContext ctx 
)
static

Definition at line 1633 of file verify_heapam.c.

References check_mxid_in_range(), update_cached_mxid_range(), and XID_BOUNDS_OK.

Referenced by check_tuple_visibility().

1634 {
1635  XidBoundsViolation result;
1636 
1637  result = check_mxid_in_range(mxid, ctx);
1638  if (result == XID_BOUNDS_OK)
1639  return XID_BOUNDS_OK;
1640 
1641  /* The range may have advanced. Recheck. */
1643  return check_mxid_in_range(mxid, ctx);
1644 }
static XidBoundsViolation check_mxid_in_range(MultiXactId mxid, HeapCheckContext *ctx)
static void update_cached_mxid_range(HeapCheckContext *ctx)
XidBoundsViolation
Definition: verify_heapam.c:37

◆ check_toast_tuple()

static void check_toast_tuple ( HeapTuple  toasttup,
HeapCheckContext ctx,
ToastedAttribute ta,
int32 expected_chunk_seq,
uint32  extsize 
)
static

Definition at line 1174 of file verify_heapam.c.

References DatumGetInt32, DatumGetPointer, fastgetattr, header(), psprintf(), RelationData::rd_att, report_toast_corruption(), TOAST_MAX_CHUNK_SIZE, ToastedAttribute::toast_pointer, HeapCheckContext::toast_rel, varatt_external::va_valueid, VARATT_IS_EXTENDED, VARATT_IS_SHORT, VARHDRSZ, VARHDRSZ_SHORT, VARSIZE, and VARSIZE_SHORT.

Referenced by check_toasted_attribute().

1177 {
1178  int32 chunk_seq;
1179  int32 last_chunk_seq = (extsize - 1) / TOAST_MAX_CHUNK_SIZE;
1180  Pointer chunk;
1181  bool isnull;
1182  int32 chunksize;
1183  int32 expected_size;
1184 
1185  /* Sanity-check the sequence number. */
1186  chunk_seq = DatumGetInt32(fastgetattr(toasttup, 2,
1187  ctx->toast_rel->rd_att, &isnull));
1188  if (isnull)
1189  {
1190  report_toast_corruption(ctx, ta,
1191  psprintf("toast value %u has toast chunk with null sequence number",
1192  ta->toast_pointer.va_valueid));
1193  return;
1194  }
1195  if (chunk_seq != *expected_chunk_seq)
1196  {
1197  /* Either the TOAST index is corrupt, or we don't have all chunks. */
1198  report_toast_corruption(ctx, ta,
1199  psprintf("toast value %u index scan returned chunk %d when expecting chunk %d",
1201  chunk_seq, *expected_chunk_seq));
1202  }
1203  *expected_chunk_seq = chunk_seq + 1;
1204 
1205  /* Sanity-check the chunk data. */
1206  chunk = DatumGetPointer(fastgetattr(toasttup, 3,
1207  ctx->toast_rel->rd_att, &isnull));
1208  if (isnull)
1209  {
1210  report_toast_corruption(ctx, ta,
1211  psprintf("toast value %u chunk %d has null data",
1213  chunk_seq));
1214  return;
1215  }
1216  if (!VARATT_IS_EXTENDED(chunk))
1217  chunksize = VARSIZE(chunk) - VARHDRSZ;
1218  else if (VARATT_IS_SHORT(chunk))
1219  {
1220  /*
1221  * could happen due to heap_form_tuple doing its thing
1222  */
1223  chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
1224  }
1225  else
1226  {
1227  /* should never happen */
1228  uint32 header = ((varattrib_4b *) chunk)->va_4byte.va_header;
1229 
1230  report_toast_corruption(ctx, ta,
1231  psprintf("toast value %u chunk %d has invalid varlena header %0x",
1233  chunk_seq, header));
1234  return;
1235  }
1236 
1237  /*
1238  * Some checks on the data we've found
1239  */
1240  if (chunk_seq > last_chunk_seq)
1241  {
1242  report_toast_corruption(ctx, ta,
1243  psprintf("toast value %u chunk %d follows last expected chunk %d",
1245  chunk_seq, last_chunk_seq));
1246  return;
1247  }
1248 
1249  expected_size = chunk_seq < last_chunk_seq ? TOAST_MAX_CHUNK_SIZE
1250  : extsize - (last_chunk_seq * TOAST_MAX_CHUNK_SIZE);
1251 
1252  if (chunksize != expected_size)
1253  report_toast_corruption(ctx, ta,
1254  psprintf("toast value %u chunk %d has size %u, but expected size %u",
1256  chunk_seq, chunksize, expected_size));
1257 }
#define fastgetattr(tup, attnum, tupleDesc, isnull)
Definition: htup_details.h:711
#define TOAST_MAX_CHUNK_SIZE
Definition: heaptoast.h:84
#define DatumGetInt32(X)
Definition: postgres.h:516
struct varatt_external toast_pointer
Definition: verify_heapam.c:68
#define VARHDRSZ_SHORT
Definition: postgres.h:292
#define VARSIZE(PTR)
Definition: postgres.h:316
#define VARHDRSZ
Definition: c.h:627
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
signed int int32
Definition: c.h:429
char * Pointer
Definition: c.h:418
#define VARATT_IS_SHORT(PTR)
Definition: postgres.h:339
unsigned int uint32
Definition: c.h:441
static void report_toast_corruption(HeapCheckContext *ctx, ToastedAttribute *ta, char *msg)
#define VARSIZE_SHORT(PTR)
Definition: postgres.h:318
TupleDesc rd_att
Definition: rel.h:110
static void header(const char *fmt,...) pg_attribute_printf(1
Definition: pg_regress.c:210
#define VARATT_IS_EXTENDED(PTR)
Definition: postgres.h:340
#define DatumGetPointer(X)
Definition: postgres.h:593

◆ check_toasted_attribute()

static void check_toasted_attribute ( HeapCheckContext ctx,
ToastedAttribute ta 
)
static

Definition at line 1445 of file verify_heapam.c.

References BTEqualStrategyNumber, check_toast_tuple(), ForwardScanDirection, init_toast_snapshot(), ObjectIdGetDatum, psprintf(), report_toast_corruption(), ScanKeyInit(), systable_beginscan_ordered(), systable_endscan_ordered(), systable_getnext_ordered(), TOAST_MAX_CHUNK_SIZE, ToastedAttribute::toast_pointer, HeapCheckContext::toast_rel, varatt_external::va_valueid, HeapCheckContext::valid_toast_index, and VARATT_EXTERNAL_GET_EXTSIZE.

Referenced by verify_heapam().

1446 {
1447  SnapshotData SnapshotToast;
1448  ScanKeyData toastkey;
1449  SysScanDesc toastscan;
1450  bool found_toasttup;
1451  HeapTuple toasttup;
1452  uint32 extsize;
1453  int32 expected_chunk_seq = 0;
1454  int32 last_chunk_seq;
1455 
1457  last_chunk_seq = (extsize - 1) / TOAST_MAX_CHUNK_SIZE;
1458 
1459  /*
1460  * Setup a scan key to find chunks in toast table with matching va_valueid
1461  */
1462  ScanKeyInit(&toastkey,
1463  (AttrNumber) 1,
1464  BTEqualStrategyNumber, F_OIDEQ,
1466 
1467  /*
1468  * Check if any chunks for this toasted object exist in the toast table,
1469  * accessible via the index.
1470  */
1471  init_toast_snapshot(&SnapshotToast);
1472  toastscan = systable_beginscan_ordered(ctx->toast_rel,
1473  ctx->valid_toast_index,
1474  &SnapshotToast, 1,
1475  &toastkey);
1476  found_toasttup = false;
1477  while ((toasttup =
1478  systable_getnext_ordered(toastscan,
1479  ForwardScanDirection)) != NULL)
1480  {
1481  found_toasttup = true;
1482  check_toast_tuple(toasttup, ctx, ta, &expected_chunk_seq, extsize);
1483  }
1484  systable_endscan_ordered(toastscan);
1485 
1486  if (!found_toasttup)
1487  report_toast_corruption(ctx, ta,
1488  psprintf("toast value %u not found in toast table",
1489  ta->toast_pointer.va_valueid));
1490  else if (expected_chunk_seq <= last_chunk_seq)
1491  report_toast_corruption(ctx, ta,
1492  psprintf("toast value %u was expected to end at chunk %d, but ended while expecting chunk %d",
1494  last_chunk_seq, expected_chunk_seq));
1495 }
#define TOAST_MAX_CHUNK_SIZE
Definition: heaptoast.h:84
struct varatt_external toast_pointer
Definition: verify_heapam.c:68
void init_toast_snapshot(Snapshot toast_snapshot)
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
HeapTuple systable_getnext_ordered(SysScanDesc sysscan, ScanDirection direction)
Definition: genam.c:707
Relation valid_toast_index
signed int int32
Definition: c.h:429
static void check_toast_tuple(HeapTuple toasttup, HeapCheckContext *ctx, ToastedAttribute *ta, int32 *expected_chunk_seq, uint32 extsize)
#define ObjectIdGetDatum(X)
Definition: postgres.h:551
unsigned int uint32
Definition: c.h:441
static void report_toast_corruption(HeapCheckContext *ctx, ToastedAttribute *ta, char *msg)
void systable_endscan_ordered(SysScanDesc sysscan)
Definition: genam.c:732
#define VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer)
Definition: postgres.h:371
SysScanDesc systable_beginscan_ordered(Relation heapRelation, Relation indexRelation, Snapshot snapshot, int nkeys, ScanKey key)
Definition: genam.c:642
void ScanKeyInit(ScanKey entry, AttrNumber attributeNumber, StrategyNumber strategy, RegProcedure procedure, Datum argument)
Definition: scankey.c:76
int16 AttrNumber
Definition: attnum.h:21
#define BTEqualStrategyNumber
Definition: stratnum.h:31

◆ check_tuple()

static void check_tuple ( HeapCheckContext ctx)
static

Definition at line 1502 of file verify_heapam.c.

References HeapCheckContext::attnum, check_tuple_attribute(), check_tuple_header(), check_tuple_visibility(), HeapCheckContext::natts, HeapCheckContext::offset, psprintf(), HeapCheckContext::rel, RelationGetDescr, and report_corruption().

Referenced by verify_heapam().

1503 {
1504  /*
1505  * Check various forms of tuple header corruption, and if the header is
1506  * too corrupt, do not continue with other checks.
1507  */
1508  if (!check_tuple_header(ctx))
1509  return;
1510 
1511  /*
1512  * Check tuple visibility. If the inserting transaction aborted, we
1513  * cannot assume our relation description matches the tuple structure, and
1514  * therefore cannot check it.
1515  */
1516  if (!check_tuple_visibility(ctx))
1517  return;
1518 
1519  /*
1520  * The tuple is visible, so it must be compatible with the current version
1521  * of the relation descriptor. It might have fewer columns than are
1522  * present in the relation descriptor, but it cannot have more.
1523  */
1524  if (RelationGetDescr(ctx->rel)->natts < ctx->natts)
1525  {
1526  report_corruption(ctx,
1527  psprintf("number of attributes %u exceeds maximum expected for table %u",
1528  ctx->natts,
1529  RelationGetDescr(ctx->rel)->natts));
1530  return;
1531  }
1532 
1533  /*
1534  * Check each attribute unless we hit corruption that confuses what to do
1535  * next, at which point we abort further attribute checks for this tuple.
1536  * Note that we don't abort for all types of corruption, only for those
1537  * types where we don't know how to continue. We also don't abort the
1538  * checking of toasted attributes collected from the tuple prior to
1539  * aborting. Those will still be checked later along with other toasted
1540  * attributes collected from the page.
1541  */
1542  ctx->offset = 0;
1543  for (ctx->attnum = 0; ctx->attnum < ctx->natts; ctx->attnum++)
1544  if (!check_tuple_attribute(ctx))
1545  break; /* cannot continue */
1546 
1547  /* revert attnum to -1 until we again examine individual attributes */
1548  ctx->attnum = -1;
1549 }
#define RelationGetDescr(relation)
Definition: rel.h:503
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
static void report_corruption(HeapCheckContext *ctx, char *msg)
static bool check_tuple_header(HeapCheckContext *ctx)
AttrNumber attnum
static bool check_tuple_attribute(HeapCheckContext *ctx)
static bool check_tuple_visibility(HeapCheckContext *ctx)

◆ check_tuple_attribute()

static bool check_tuple_attribute ( HeapCheckContext ctx)
static

Definition at line 1280 of file verify_heapam.c.

References att_addlength_pointer, att_align_nominal, att_align_pointer, att_isnull, ToastedAttribute::attnum, HeapCheckContext::attnum, ToastedAttribute::blkno, HeapCheckContext::blkno, DatumGetPointer, fetchatt, HEAP_HASEXTERNAL, HEAP_HASNULL, lappend(), HeapCheckContext::lp_len, ToastedAttribute::offnum, HeapCheckContext::offnum, HeapCheckContext::offset, palloc0(), psprintf(), RelationData::rd_rel, HeapCheckContext::rel, RelationGetDescr, report_corruption(), HeapTupleHeaderData::t_bits, HeapTupleHeaderData::t_hoff, HeapTupleHeaderData::t_infomask, ToastedAttribute::toast_pointer, HeapCheckContext::toast_rel, HeapCheckContext::toasted_attributes, HeapCheckContext::tuphdr, HeapCheckContext::tuple_could_be_pruned, TupleDescAttr, varatt_external::va_valueid, VARATT_EXTERNAL_GET_POINTER, VARATT_IS_EXTERNAL, VARTAG_EXTERNAL, and VARTAG_ONDISK.

Referenced by check_tuple().

1281 {
1282  Datum attdatum;
1283  struct varlena *attr;
1284  char *tp; /* pointer to the tuple data */
1285  uint16 infomask;
1286  Form_pg_attribute thisatt;
1287  struct varatt_external toast_pointer;
1288 
1289  infomask = ctx->tuphdr->t_infomask;
1290  thisatt = TupleDescAttr(RelationGetDescr(ctx->rel), ctx->attnum);
1291 
1292  tp = (char *) ctx->tuphdr + ctx->tuphdr->t_hoff;
1293 
1294  if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len)
1295  {
1296  report_corruption(ctx,
1297  psprintf("attribute with length %u starts at offset %u beyond total tuple length %u",
1298  thisatt->attlen,
1299  ctx->tuphdr->t_hoff + ctx->offset,
1300  ctx->lp_len));
1301  return false;
1302  }
1303 
1304  /* Skip null values */
1305  if (infomask & HEAP_HASNULL && att_isnull(ctx->attnum, ctx->tuphdr->t_bits))
1306  return true;
1307 
1308  /* Skip non-varlena values, but update offset first */
1309  if (thisatt->attlen != -1)
1310  {
1311  ctx->offset = att_align_nominal(ctx->offset, thisatt->attalign);
1312  ctx->offset = att_addlength_pointer(ctx->offset, thisatt->attlen,
1313  tp + ctx->offset);
1314  if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len)
1315  {
1316  report_corruption(ctx,
1317  psprintf("attribute with length %u ends at offset %u beyond total tuple length %u",
1318  thisatt->attlen,
1319  ctx->tuphdr->t_hoff + ctx->offset,
1320  ctx->lp_len));
1321  return false;
1322  }
1323  return true;
1324  }
1325 
1326  /* Ok, we're looking at a varlena attribute. */
1327  ctx->offset = att_align_pointer(ctx->offset, thisatt->attalign, -1,
1328  tp + ctx->offset);
1329 
1330  /* Get the (possibly corrupt) varlena datum */
1331  attdatum = fetchatt(thisatt, tp + ctx->offset);
1332 
1333  /*
1334  * We have the datum, but we cannot decode it carelessly, as it may still
1335  * be corrupt.
1336  */
1337 
1338  /*
1339  * Check that VARTAG_SIZE won't hit a TrapMacro on a corrupt va_tag before
1340  * risking a call into att_addlength_pointer
1341  */
1342  if (VARATT_IS_EXTERNAL(tp + ctx->offset))
1343  {
1344  uint8 va_tag = VARTAG_EXTERNAL(tp + ctx->offset);
1345 
1346  if (va_tag != VARTAG_ONDISK)
1347  {
1348  report_corruption(ctx,
1349  psprintf("toasted attribute has unexpected TOAST tag %u",
1350  va_tag));
1351  /* We can't know where the next attribute begins */
1352  return false;
1353  }
1354  }
1355 
1356  /* Ok, should be safe now */
1357  ctx->offset = att_addlength_pointer(ctx->offset, thisatt->attlen,
1358  tp + ctx->offset);
1359 
1360  if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len)
1361  {
1362  report_corruption(ctx,
1363  psprintf("attribute with length %u ends at offset %u beyond total tuple length %u",
1364  thisatt->attlen,
1365  ctx->tuphdr->t_hoff + ctx->offset,
1366  ctx->lp_len));
1367 
1368  return false;
1369  }
1370 
1371  /*
1372  * heap_deform_tuple would be done with this attribute at this point,
1373  * having stored it in values[], and would continue to the next attribute.
1374  * We go further, because we need to check if the toast datum is corrupt.
1375  */
1376 
1377  attr = (struct varlena *) DatumGetPointer(attdatum);
1378 
1379  /*
1380  * Now we follow the logic of detoast_external_attr(), with the same
1381  * caveats about being paranoid about corruption.
1382  */
1383 
1384  /* Skip values that are not external */
1385  if (!VARATT_IS_EXTERNAL(attr))
1386  return true;
1387 
1388  /* It is external, and we're looking at a page on disk */
1389 
1390  /*
1391  * Must copy attr into toast_pointer for alignment considerations
1392  */
1393  VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
1394 
1395  /* The tuple header better claim to contain toasted values */
1396  if (!(infomask & HEAP_HASEXTERNAL))
1397  {
1398  report_corruption(ctx,
1399  psprintf("toast value %u is external but tuple header flag HEAP_HASEXTERNAL not set",
1400  toast_pointer.va_valueid));
1401  return true;
1402  }
1403 
1404  /* The relation better have a toast table */
1405  if (!ctx->rel->rd_rel->reltoastrelid)
1406  {
1407  report_corruption(ctx,
1408  psprintf("toast value %u is external but relation has no toast relation",
1409  toast_pointer.va_valueid));
1410  return true;
1411  }
1412 
1413  /* If we were told to skip toast checking, then we're done. */
1414  if (ctx->toast_rel == NULL)
1415  return true;
1416 
1417  /*
1418  * If this tuple is eligible to be pruned, we cannot check the toast.
1419  * Otherwise, we push a copy of the toast tuple so we can check it after
1420  * releasing the main table buffer lock.
1421  */
1422  if (!ctx->tuple_could_be_pruned)
1423  {
1424  ToastedAttribute *ta;
1425 
1426  ta = (ToastedAttribute *) palloc0(sizeof(ToastedAttribute));
1427 
1429  ta->blkno = ctx->blkno;
1430  ta->offnum = ctx->offnum;
1431  ta->attnum = ctx->attnum;
1433  }
1434 
1435  return true;
1436 }
BlockNumber blkno
#define att_align_nominal(cur_offset, attalign)
Definition: tupmacs.h:148
HeapTupleHeader tuphdr
bits8 t_bits[FLEXIBLE_ARRAY_MEMBER]
Definition: htup_details.h:177
#define RelationGetDescr(relation)
Definition: rel.h:503
struct varatt_external toast_pointer
Definition: verify_heapam.c:68
#define VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr)
Definition: detoast.h:22
#define att_isnull(ATT, BITS)
Definition: tupmacs.h:25
#define TupleDescAttr(tupdesc, i)
Definition: tupdesc.h:92
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
BlockNumber blkno
Definition: verify_heapam.c:69
unsigned char uint8
Definition: c.h:439
static void report_corruption(HeapCheckContext *ctx, char *msg)
OffsetNumber offnum
Definition: verify_heapam.c:70
AttrNumber attnum
Form_pg_class rd_rel
Definition: rel.h:109
#define VARTAG_EXTERNAL(PTR)
Definition: postgres.h:321
#define fetchatt(A, T)
Definition: tupmacs.h:41
AttrNumber attnum
Definition: verify_heapam.c:71
#define VARATT_IS_EXTERNAL(PTR)
Definition: postgres.h:326
unsigned short uint16
Definition: c.h:440
#define HEAP_HASNULL
Definition: htup_details.h:189
FormData_pg_attribute * Form_pg_attribute
Definition: pg_attribute.h:207
#define att_addlength_pointer(cur_offset, attlen, attptr)
Definition: tupmacs.h:176
OffsetNumber offnum
List * lappend(List *list, void *datum)
Definition: list.c:336
void * palloc0(Size size)
Definition: mcxt.c:1093
uintptr_t Datum
Definition: postgres.h:411
#define att_align_pointer(cur_offset, attalign, attlen, attptr)
Definition: tupmacs.h:126
List * toasted_attributes
#define DatumGetPointer(X)
Definition: postgres.h:593
#define HEAP_HASEXTERNAL
Definition: htup_details.h:191
Definition: c.h:621

◆ check_tuple_header()

static bool check_tuple_header ( HeapCheckContext ctx)
static

Definition at line 654 of file verify_heapam.c.

References BITMAPLEN, HEAP_HASNULL, HEAP_XMAX_COMMITTED, HEAP_XMAX_IS_MULTI, HeapCheckContext::lp_len, MAXALIGN, HeapCheckContext::natts, psprintf(), pstrdup(), report_corruption(), SizeofHeapTupleHeader, HeapTupleHeaderData::t_hoff, HeapTupleHeaderData::t_infomask, and HeapCheckContext::tuphdr.

Referenced by check_tuple().

655 {
656  HeapTupleHeader tuphdr = ctx->tuphdr;
657  uint16 infomask = tuphdr->t_infomask;
658  bool result = true;
659  unsigned expected_hoff;
660 
661  if (ctx->tuphdr->t_hoff > ctx->lp_len)
662  {
663  report_corruption(ctx,
664  psprintf("data begins at offset %u beyond the tuple length %u",
665  ctx->tuphdr->t_hoff, ctx->lp_len));
666  result = false;
667  }
668 
669  if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
671  {
672  report_corruption(ctx,
673  pstrdup("multixact should not be marked committed"));
674 
675  /*
676  * This condition is clearly wrong, but it's not enough to justify
677  * skipping further checks, because we don't rely on this to determine
678  * whether the tuple is visible or to interpret other relevant header
679  * fields.
680  */
681  }
682 
683  if (infomask & HEAP_HASNULL)
684  expected_hoff = MAXALIGN(SizeofHeapTupleHeader + BITMAPLEN(ctx->natts));
685  else
686  expected_hoff = MAXALIGN(SizeofHeapTupleHeader);
687  if (ctx->tuphdr->t_hoff != expected_hoff)
688  {
689  if ((infomask & HEAP_HASNULL) && ctx->natts == 1)
690  report_corruption(ctx,
691  psprintf("tuple data should begin at byte %u, but actually begins at byte %u (1 attribute, has nulls)",
692  expected_hoff, ctx->tuphdr->t_hoff));
693  else if ((infomask & HEAP_HASNULL))
694  report_corruption(ctx,
695  psprintf("tuple data should begin at byte %u, but actually begins at byte %u (%u attributes, has nulls)",
696  expected_hoff, ctx->tuphdr->t_hoff, ctx->natts));
697  else if (ctx->natts == 1)
698  report_corruption(ctx,
699  psprintf("tuple data should begin at byte %u, but actually begins at byte %u (1 attribute, no nulls)",
700  expected_hoff, ctx->tuphdr->t_hoff));
701  else
702  report_corruption(ctx,
703  psprintf("tuple data should begin at byte %u, but actually begins at byte %u (%u attributes, no nulls)",
704  expected_hoff, ctx->tuphdr->t_hoff, ctx->natts));
705  result = false;
706  }
707 
708  return result;
709 }
#define SizeofHeapTupleHeader
Definition: htup_details.h:184
HeapTupleHeader tuphdr
char * pstrdup(const char *in)
Definition: mcxt.c:1299
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
static void report_corruption(HeapCheckContext *ctx, char *msg)
#define BITMAPLEN(NATTS)
Definition: htup_details.h:546
#define HEAP_XMAX_COMMITTED
Definition: htup_details.h:206
unsigned short uint16
Definition: c.h:440
#define HEAP_HASNULL
Definition: htup_details.h:189
#define HEAP_XMAX_IS_MULTI
Definition: htup_details.h:208
#define MAXALIGN(LEN)
Definition: c.h:757

◆ check_tuple_visibility()

static bool check_tuple_visibility ( HeapCheckContext ctx)
static

Definition at line 740 of file verify_heapam.c.

References check_mxid_valid_in_rel(), EpochFromFullTransactionId, get_xid_status(), HEAP_MOVED_IN, HEAP_MOVED_OFF, HEAP_XMAX_INVALID, HEAP_XMAX_IS_LOCKED_ONLY, HEAP_XMAX_IS_MULTI, HeapTupleGetUpdateXid(), HeapTupleHeaderGetRawXmax, HeapTupleHeaderGetXmin, HeapTupleHeaderGetXvac, HeapTupleHeaderXminCommitted, HeapTupleHeaderXminInvalid, HeapCheckContext::next_fxid, HeapCheckContext::next_mxact, HeapCheckContext::oldest_fxid, HeapCheckContext::oldest_mxact, psprintf(), pstrdup(), HeapCheckContext::relfrozenfxid, HeapCheckContext::relminmxid, report_corruption(), HeapCheckContext::safe_xmin, HeapTupleHeaderData::t_infomask, TransactionIdPrecedes(), HeapCheckContext::tuphdr, HeapCheckContext::tuple_could_be_pruned, XID_ABORTED, XID_BOUNDS_OK, XID_COMMITTED, XID_IN_FUTURE, XID_IN_PROGRESS, XID_INVALID, XID_IS_CURRENT_XID, XID_PRECEDES_CLUSTERMIN, XID_PRECEDES_RELMIN, and XidFromFullTransactionId.

Referenced by check_tuple().

741 {
742  TransactionId xmin;
743  TransactionId xvac;
744  TransactionId xmax;
745  XidCommitStatus xmin_status;
746  XidCommitStatus xvac_status;
747  XidCommitStatus xmax_status;
748  HeapTupleHeader tuphdr = ctx->tuphdr;
749 
750  ctx->tuple_could_be_pruned = true; /* have not yet proven otherwise */
751 
752  /* If xmin is normal, it should be within valid range */
753  xmin = HeapTupleHeaderGetXmin(tuphdr);
754  switch (get_xid_status(xmin, ctx, &xmin_status))
755  {
756  case XID_INVALID:
757  case XID_BOUNDS_OK:
758  break;
759  case XID_IN_FUTURE:
760  report_corruption(ctx,
761  psprintf("xmin %u equals or exceeds next valid transaction ID %u:%u",
762  xmin,
765  return false;
767  report_corruption(ctx,
768  psprintf("xmin %u precedes oldest valid transaction ID %u:%u",
769  xmin,
772  return false;
773  case XID_PRECEDES_RELMIN:
774  report_corruption(ctx,
775  psprintf("xmin %u precedes relation freeze threshold %u:%u",
776  xmin,
779  return false;
780  }
781 
782  /*
783  * Has inserting transaction committed?
784  */
785  if (!HeapTupleHeaderXminCommitted(tuphdr))
786  {
787  if (HeapTupleHeaderXminInvalid(tuphdr))
788  return false; /* inserter aborted, don't check */
789  /* Used by pre-9.0 binary upgrades */
790  else if (tuphdr->t_infomask & HEAP_MOVED_OFF)
791  {
792  xvac = HeapTupleHeaderGetXvac(tuphdr);
793 
794  switch (get_xid_status(xvac, ctx, &xvac_status))
795  {
796  case XID_INVALID:
797  report_corruption(ctx,
798  pstrdup("old-style VACUUM FULL transaction ID for moved off tuple is invalid"));
799  return false;
800  case XID_IN_FUTURE:
801  report_corruption(ctx,
802  psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple equals or exceeds next valid transaction ID %u:%u",
803  xvac,
806  return false;
807  case XID_PRECEDES_RELMIN:
808  report_corruption(ctx,
809  psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes relation freeze threshold %u:%u",
810  xvac,
813  return false;
815  report_corruption(ctx,
816  psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes oldest valid transaction ID %u:%u",
817  xvac,
820  return false;
821  case XID_BOUNDS_OK:
822  break;
823  }
824 
825  switch (xvac_status)
826  {
827  case XID_IS_CURRENT_XID:
828  report_corruption(ctx,
829  psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple matches our current transaction ID",
830  xvac));
831  return false;
832  case XID_IN_PROGRESS:
833  report_corruption(ctx,
834  psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple appears to be in progress",
835  xvac));
836  return false;
837 
838  case XID_COMMITTED:
839 
840  /*
841  * The tuple is dead, because the xvac transaction moved
842  * it off and committed. It's checkable, but also
843  * prunable.
844  */
845  return true;
846 
847  case XID_ABORTED:
848 
849  /*
850  * The original xmin must have committed, because the xvac
851  * transaction tried to move it later. Since xvac is
852  * aborted, whether it's still alive now depends on the
853  * status of xmax.
854  */
855  break;
856  }
857  }
858  /* Used by pre-9.0 binary upgrades */
859  else if (tuphdr->t_infomask & HEAP_MOVED_IN)
860  {
861  xvac = HeapTupleHeaderGetXvac(tuphdr);
862 
863  switch (get_xid_status(xvac, ctx, &xvac_status))
864  {
865  case XID_INVALID:
866  report_corruption(ctx,
867  pstrdup("old-style VACUUM FULL transaction ID for moved in tuple is invalid"));
868  return false;
869  case XID_IN_FUTURE:
870  report_corruption(ctx,
871  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple equals or exceeds next valid transaction ID %u:%u",
872  xvac,
875  return false;
876  case XID_PRECEDES_RELMIN:
877  report_corruption(ctx,
878  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes relation freeze threshold %u:%u",
879  xvac,
882  return false;
884  report_corruption(ctx,
885  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes oldest valid transaction ID %u:%u",
886  xvac,
889  return false;
890  case XID_BOUNDS_OK:
891  break;
892  }
893 
894  switch (xvac_status)
895  {
896  case XID_IS_CURRENT_XID:
897  report_corruption(ctx,
898  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple matches our current transaction ID",
899  xvac));
900  return false;
901  case XID_IN_PROGRESS:
902  report_corruption(ctx,
903  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple appears to be in progress",
904  xvac));
905  return false;
906 
907  case XID_COMMITTED:
908 
909  /*
910  * The original xmin must have committed, because the xvac
911  * transaction moved it later. Whether it's still alive
912  * now depends on the status of xmax.
913  */
914  break;
915 
916  case XID_ABORTED:
917 
918  /*
919  * The tuple is dead, because the xvac transaction moved
920  * it off and committed. It's checkable, but also
921  * prunable.
922  */
923  return true;
924  }
925  }
926  else if (xmin_status != XID_COMMITTED)
927  {
928  /*
929  * Inserting transaction is not in progress, and not committed, so
930  * it might have changed the TupleDesc in ways we don't know
931  * about. Thus, don't try to check the tuple structure.
932  *
933  * If xmin_status happens to be XID_IS_CURRENT_XID, then in theory
934  * any such DDL changes ought to be visible to us, so perhaps we
935  * could check anyway in that case. But, for now, let's be
936  * conservative and treat this like any other uncommitted insert.
937  */
938  return false;
939  }
940  }
941 
942  /*
943  * Okay, the inserter committed, so it was good at some point. Now what
944  * about the deleting transaction?
945  */
946 
947  if (tuphdr->t_infomask & HEAP_XMAX_IS_MULTI)
948  {
949  /*
950  * xmax is a multixact, so sanity-check the MXID. Note that we do this
951  * prior to checking for HEAP_XMAX_INVALID or
952  * HEAP_XMAX_IS_LOCKED_ONLY. This might therefore complain about
953  * things that wouldn't actually be a problem during a normal scan,
954  * but eventually we're going to have to freeze, and that process will
955  * ignore hint bits.
956  *
957  * Even if the MXID is out of range, we still know that the original
958  * insert committed, so we can check the tuple itself. However, we
959  * can't rule out the possibility that this tuple is dead, so don't
960  * clear ctx->tuple_could_be_pruned. Possibly we should go ahead and
961  * clear that flag anyway if HEAP_XMAX_INVALID is set or if
962  * HEAP_XMAX_IS_LOCKED_ONLY is true, but for now we err on the side of
963  * avoiding possibly-bogus complaints about missing TOAST entries.
964  */
965  xmax = HeapTupleHeaderGetRawXmax(tuphdr);
966  switch (check_mxid_valid_in_rel(xmax, ctx))
967  {
968  case XID_INVALID:
969  report_corruption(ctx,
970  pstrdup("multitransaction ID is invalid"));
971  return true;
972  case XID_PRECEDES_RELMIN:
973  report_corruption(ctx,
974  psprintf("multitransaction ID %u precedes relation minimum multitransaction ID threshold %u",
975  xmax, ctx->relminmxid));
976  return true;
978  report_corruption(ctx,
979  psprintf("multitransaction ID %u precedes oldest valid multitransaction ID threshold %u",
980  xmax, ctx->oldest_mxact));
981  return true;
982  case XID_IN_FUTURE:
983  report_corruption(ctx,
984  psprintf("multitransaction ID %u equals or exceeds next valid multitransaction ID %u",
985  xmax,
986  ctx->next_mxact));
987  return true;
988  case XID_BOUNDS_OK:
989  break;
990  }
991  }
992 
993  if (tuphdr->t_infomask & HEAP_XMAX_INVALID)
994  {
995  /*
996  * This tuple is live. A concurrently running transaction could
997  * delete it before we get around to checking the toast, but any such
998  * running transaction is surely not less than our safe_xmin, so the
999  * toast cannot be vacuumed out from under us.
1000  */
1001  ctx->tuple_could_be_pruned = false;
1002  return true;
1003  }
1004 
1005  if (HEAP_XMAX_IS_LOCKED_ONLY(tuphdr->t_infomask))
1006  {
1007  /*
1008  * "Deleting" xact really only locked it, so the tuple is live in any
1009  * case. As above, a concurrently running transaction could delete
1010  * it, but it cannot be vacuumed out from under us.
1011  */
1012  ctx->tuple_could_be_pruned = false;
1013  return true;
1014  }
1015 
1016  if (tuphdr->t_infomask & HEAP_XMAX_IS_MULTI)
1017  {
1018  /*
1019  * We already checked above that this multixact is within limits for
1020  * this table. Now check the update xid from this multixact.
1021  */
1022  xmax = HeapTupleGetUpdateXid(tuphdr);
1023  switch (get_xid_status(xmax, ctx, &xmax_status))
1024  {
1025  case XID_INVALID:
1026  /* not LOCKED_ONLY, so it has to have an xmax */
1027  report_corruption(ctx,
1028  pstrdup("update xid is invalid"));
1029  return true;
1030  case XID_IN_FUTURE:
1031  report_corruption(ctx,
1032  psprintf("update xid %u equals or exceeds next valid transaction ID %u:%u",
1033  xmax,
1036  return true;
1037  case XID_PRECEDES_RELMIN:
1038  report_corruption(ctx,
1039  psprintf("update xid %u precedes relation freeze threshold %u:%u",
1040  xmax,
1043  return true;
1045  report_corruption(ctx,
1046  psprintf("update xid %u precedes oldest valid transaction ID %u:%u",
1047  xmax,
1050  return true;
1051  case XID_BOUNDS_OK:
1052  break;
1053  }
1054 
1055  switch (xmax_status)
1056  {
1057  case XID_IS_CURRENT_XID:
1058  case XID_IN_PROGRESS:
1059 
1060  /*
1061  * The delete is in progress, so it cannot be visible to our
1062  * snapshot.
1063  */
1064  ctx->tuple_could_be_pruned = false;
1065  break;
1066  case XID_COMMITTED:
1067 
1068  /*
1069  * The delete committed. Whether the toast can be vacuumed
1070  * away depends on how old the deleting transaction is.
1071  */
1073  ctx->safe_xmin);
1074  break;
1075  case XID_ABORTED:
1076 
1077  /*
1078  * The delete aborted or crashed. The tuple is still live.
1079  */
1080  ctx->tuple_could_be_pruned = false;
1081  break;
1082  }
1083 
1084  /* Tuple itself is checkable even if it's dead. */
1085  return true;
1086  }
1087 
1088  /* xmax is an XID, not a MXID. Sanity check it. */
1089  xmax = HeapTupleHeaderGetRawXmax(tuphdr);
1090  switch (get_xid_status(xmax, ctx, &xmax_status))
1091  {
1092  case XID_IN_FUTURE:
1093  report_corruption(ctx,
1094  psprintf("xmax %u equals or exceeds next valid transaction ID %u:%u",
1095  xmax,
1098  return false; /* corrupt */
1099  case XID_PRECEDES_RELMIN:
1100  report_corruption(ctx,
1101  psprintf("xmax %u precedes relation freeze threshold %u:%u",
1102  xmax,
1105  return false; /* corrupt */
1107  report_corruption(ctx,
1108  psprintf("xmax %u precedes oldest valid transaction ID %u:%u",
1109  xmax,
1112  return false; /* corrupt */
1113  case XID_BOUNDS_OK:
1114  case XID_INVALID:
1115  break;
1116  }
1117 
1118  /*
1119  * Whether the toast can be vacuumed away depends on how old the deleting
1120  * transaction is.
1121  */
1122  switch (xmax_status)
1123  {
1124  case XID_IS_CURRENT_XID:
1125  case XID_IN_PROGRESS:
1126 
1127  /*
1128  * The delete is in progress, so it cannot be visible to our
1129  * snapshot.
1130  */
1131  ctx->tuple_could_be_pruned = false;
1132  break;
1133 
1134  case XID_COMMITTED:
1135 
1136  /*
1137  * The delete committed. Whether the toast can be vacuumed away
1138  * depends on how old the deleting transaction is.
1139  */
1141  ctx->safe_xmin);
1142  break;
1143 
1144  case XID_ABORTED:
1145 
1146  /*
1147  * The delete aborted or crashed. The tuple is still live.
1148  */
1149  ctx->tuple_could_be_pruned = false;
1150  break;
1151  }
1152 
1153  /* Tuple itself is checkable even if it's dead. */
1154  return true;
1155 }
FullTransactionId oldest_fxid
Definition: verify_heapam.c:87
HeapTupleHeader tuphdr
uint32 TransactionId
Definition: c.h:587
TransactionId HeapTupleGetUpdateXid(HeapTupleHeader tuple)
Definition: heapam.c:6816
FullTransactionId next_fxid
Definition: verify_heapam.c:84
MultiXactId oldest_mxact
Definition: verify_heapam.c:96
char * pstrdup(const char *in)
Definition: mcxt.c:1299
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
static void report_corruption(HeapCheckContext *ctx, char *msg)
#define HeapTupleHeaderXminInvalid(tup)
Definition: htup_details.h:329
TransactionId safe_xmin
Definition: verify_heapam.c:89
MultiXactId next_mxact
Definition: verify_heapam.c:95
#define XidFromFullTransactionId(x)
Definition: transam.h:48
#define HeapTupleHeaderGetRawXmax(tup)
Definition: htup_details.h:375
#define HEAP_XMAX_INVALID
Definition: htup_details.h:207
#define HeapTupleHeaderXminCommitted(tup)
Definition: htup_details.h:324
static XidBoundsViolation get_xid_status(TransactionId xid, HeapCheckContext *ctx, XidCommitStatus *status)
#define HeapTupleHeaderGetXvac(tup)
Definition: htup_details.h:415
#define HEAP_MOVED_IN
Definition: htup_details.h:213
bool TransactionIdPrecedes(TransactionId id1, TransactionId id2)
Definition: transam.c:300
#define HEAP_XMAX_IS_LOCKED_ONLY(infomask)
Definition: htup_details.h:230
FullTransactionId relfrozenfxid
#define EpochFromFullTransactionId(x)
Definition: transam.h:47
#define HEAP_XMAX_IS_MULTI
Definition: htup_details.h:208
#define HEAP_MOVED_OFF
Definition: htup_details.h:210
#define HeapTupleHeaderGetXmin(tup)
Definition: htup_details.h:313
XidCommitStatus
Definition: verify_heapam.c:46
TransactionId relminmxid
static XidBoundsViolation check_mxid_valid_in_rel(MultiXactId mxid, HeapCheckContext *ctx)

◆ FullTransactionIdFromXidAndCtx()

static FullTransactionId FullTransactionIdFromXidAndCtx ( TransactionId  xid,
const HeapCheckContext ctx 
)
static

Definition at line 1557 of file verify_heapam.c.

References epoch, EpochFromFullTransactionId, FullTransactionIdFromEpochAndXid(), HeapCheckContext::next_fxid, HeapCheckContext::next_xid, and TransactionIdIsNormal.

Referenced by get_xid_status(), update_cached_xid_range(), and verify_heapam().

1558 {
1559  uint32 epoch;
1560 
1561  if (!TransactionIdIsNormal(xid))
1562  return FullTransactionIdFromEpochAndXid(0, xid);
1563  epoch = EpochFromFullTransactionId(ctx->next_fxid);
1564  if (xid > ctx->next_xid)
1565  epoch--;
1566  return FullTransactionIdFromEpochAndXid(epoch, xid);
1567 }
FullTransactionId next_fxid
Definition: verify_heapam.c:84
unsigned int uint32
Definition: c.h:441
TransactionId next_xid
Definition: verify_heapam.c:85
#define EpochFromFullTransactionId(x)
Definition: transam.h:47
static FullTransactionId FullTransactionIdFromEpochAndXid(uint32 epoch, TransactionId xid)
Definition: transam.h:71
static const unsigned __int64 epoch
Definition: gettimeofday.c:34
#define TransactionIdIsNormal(xid)
Definition: transam.h:42

◆ fxid_in_cached_range()

static bool fxid_in_cached_range ( FullTransactionId  fxid,
const HeapCheckContext ctx 
)
inlinestatic

Definition at line 1600 of file verify_heapam.c.

References FullTransactionIdPrecedes, FullTransactionIdPrecedesOrEquals, HeapCheckContext::next_fxid, and HeapCheckContext::oldest_fxid.

Referenced by get_xid_status().

1601 {
1602  return (FullTransactionIdPrecedesOrEquals(ctx->oldest_fxid, fxid) &&
1603  FullTransactionIdPrecedes(fxid, ctx->next_fxid));
1604 }
FullTransactionId oldest_fxid
Definition: verify_heapam.c:87
FullTransactionId next_fxid
Definition: verify_heapam.c:84
#define FullTransactionIdPrecedesOrEquals(a, b)
Definition: transam.h:52
#define FullTransactionIdPrecedes(a, b)
Definition: transam.h:51

◆ get_xid_status()

static XidBoundsViolation get_xid_status ( TransactionId  xid,
HeapCheckContext ctx,
XidCommitStatus status 
)
static

Definition at line 1665 of file verify_heapam.c.

References BootstrapTransactionId, HeapCheckContext::cached_status, HeapCheckContext::cached_xid, FrozenTransactionId, FullTransactionIdFromXidAndCtx(), FullTransactionIdPrecedes, FullTransactionIdPrecedesOrEquals, fxid_in_cached_range(), LW_SHARED, LWLockAcquire(), LWLockRelease(), HeapCheckContext::next_fxid, HeapCheckContext::oldest_fxid, VariableCacheData::oldestClogXid, HeapCheckContext::relfrozenfxid, ShmemVariableCache, status(), TransactionIdDidCommit(), TransactionIdIsCurrentTransactionId(), TransactionIdIsInProgress(), TransactionIdIsValid, update_cached_xid_range(), XID_ABORTED, XID_BOUNDS_OK, XID_COMMITTED, XID_IN_FUTURE, XID_IN_PROGRESS, XID_INVALID, XID_IS_CURRENT_XID, XID_PRECEDES_CLUSTERMIN, and XID_PRECEDES_RELMIN.

Referenced by check_tuple_visibility().

1667 {
1668  FullTransactionId fxid;
1669  FullTransactionId clog_horizon;
1670 
1671  /* Quick check for special xids */
1672  if (!TransactionIdIsValid(xid))
1673  return XID_INVALID;
1674  else if (xid == BootstrapTransactionId || xid == FrozenTransactionId)
1675  {
1676  if (status != NULL)
1677  *status = XID_COMMITTED;
1678  return XID_BOUNDS_OK;
1679  }
1680 
1681  /* Check if the xid is within bounds */
1682  fxid = FullTransactionIdFromXidAndCtx(xid, ctx);
1683  if (!fxid_in_cached_range(fxid, ctx))
1684  {
1685  /*
1686  * We may have been checking against stale values. Update the cached
1687  * range to be sure, and since we relied on the cached range when we
1688  * performed the full xid conversion, reconvert.
1689  */
1691  fxid = FullTransactionIdFromXidAndCtx(xid, ctx);
1692  }
1693 
1695  return XID_IN_FUTURE;
1696  if (FullTransactionIdPrecedes(fxid, ctx->oldest_fxid))
1697  return XID_PRECEDES_CLUSTERMIN;
1698  if (FullTransactionIdPrecedes(fxid, ctx->relfrozenfxid))
1699  return XID_PRECEDES_RELMIN;
1700 
1701  /* Early return if the caller does not request clog checking */
1702  if (status == NULL)
1703  return XID_BOUNDS_OK;
1704 
1705  /* Early return if we just checked this xid in a prior call */
1706  if (xid == ctx->cached_xid)
1707  {
1708  *status = ctx->cached_status;
1709  return XID_BOUNDS_OK;
1710  }
1711 
1712  *status = XID_COMMITTED;
1713  LWLockAcquire(XactTruncationLock, LW_SHARED);
1714  clog_horizon =
1716  ctx);
1717  if (FullTransactionIdPrecedesOrEquals(clog_horizon, fxid))
1718  {
1721  else if (TransactionIdIsInProgress(xid))
1723  else if (TransactionIdDidCommit(xid))
1724  *status = XID_COMMITTED;
1725  else
1726  *status = XID_ABORTED;
1727  }
1728  LWLockRelease(XactTruncationLock);
1729  ctx->cached_xid = xid;
1730  ctx->cached_status = *status;
1731  return XID_BOUNDS_OK;
1732 }
FullTransactionId oldest_fxid
Definition: verify_heapam.c:87
bool TransactionIdIsCurrentTransactionId(TransactionId xid)
Definition: xact.c:869
bool TransactionIdIsInProgress(TransactionId xid)
Definition: procarray.c:1359
FullTransactionId next_fxid
Definition: verify_heapam.c:84
TransactionId cached_xid
bool TransactionIdDidCommit(TransactionId transactionId)
Definition: transam.c:125
void LWLockRelease(LWLock *lock)
Definition: lwlock.c:1803
static void update_cached_xid_range(HeapCheckContext *ctx)
#define FullTransactionIdPrecedesOrEquals(a, b)
Definition: transam.h:52
#define BootstrapTransactionId
Definition: transam.h:32
TransactionId oldestClogXid
Definition: transam.h:253
VariableCache ShmemVariableCache
Definition: varsup.c:34
FullTransactionId relfrozenfxid
#define FrozenTransactionId
Definition: transam.h:33
bool LWLockAcquire(LWLock *lock, LWLockMode mode)
Definition: lwlock.c:1199
static bool fxid_in_cached_range(FullTransactionId fxid, const HeapCheckContext *ctx)
XidCommitStatus cached_status
#define FullTransactionIdPrecedes(a, b)
Definition: transam.h:51
#define TransactionIdIsValid(xid)
Definition: transam.h:41
static FullTransactionId FullTransactionIdFromXidAndCtx(TransactionId xid, const HeapCheckContext *ctx)
static void static void status(const char *fmt,...) pg_attribute_printf(1
Definition: pg_regress.c:227

◆ PG_FUNCTION_INFO_V1()

PG_FUNCTION_INFO_V1 ( verify_heapam  )

◆ report_corruption()

static void report_corruption ( HeapCheckContext ctx,
char *  msg 
)
static

Definition at line 586 of file verify_heapam.c.

References HeapCheckContext::attnum, HeapCheckContext::blkno, HeapCheckContext::is_corrupt, HeapCheckContext::offnum, report_corruption_internal(), HeapCheckContext::tupdesc, and HeapCheckContext::tupstore.

Referenced by check_tuple(), check_tuple_attribute(), check_tuple_header(), check_tuple_visibility(), and verify_heapam().

587 {
589  ctx->offnum, ctx->attnum, msg);
590  ctx->is_corrupt = true;
591 }
BlockNumber blkno
AttrNumber attnum
static void report_corruption_internal(Tuplestorestate *tupstore, TupleDesc tupdesc, BlockNumber blkno, OffsetNumber offnum, AttrNumber attnum, char *msg)
OffsetNumber offnum
Tuplestorestate * tupstore

◆ report_corruption_internal()

static void report_corruption_internal ( Tuplestorestate tupstore,
TupleDesc  tupdesc,
BlockNumber  blkno,
OffsetNumber  offnum,
AttrNumber  attnum,
char *  msg 
)
static

Definition at line 548 of file verify_heapam.c.

References CStringGetTextDatum, heap_form_tuple(), HEAPCHECK_RELATION_COLS, Int32GetDatum, Int64GetDatum(), MemSet, pfree(), tuplestore_puttuple(), and values.

Referenced by report_corruption(), and report_toast_corruption().

551 {
553  bool nulls[HEAPCHECK_RELATION_COLS];
554  HeapTuple tuple;
555 
556  MemSet(values, 0, sizeof(values));
557  MemSet(nulls, 0, sizeof(nulls));
558  values[0] = Int64GetDatum(blkno);
559  values[1] = Int32GetDatum(offnum);
560  values[2] = Int32GetDatum(attnum);
561  nulls[2] = (attnum < 0);
562  values[3] = CStringGetTextDatum(msg);
563 
564  /*
565  * In principle, there is nothing to prevent a scan over a large, highly
566  * corrupted table from using work_mem worth of memory building up the
567  * tuplestore. That's ok, but if we also leak the msg argument memory
568  * until the end of the query, we could exceed work_mem by more than a
569  * trivial amount. Therefore, free the msg argument each time we are
570  * called rather than waiting for our current memory context to be freed.
571  */
572  pfree(msg);
573 
574  tuple = heap_form_tuple(tupdesc, values, nulls);
575  tuplestore_puttuple(tupstore, tuple);
576 }
#define HEAPCHECK_RELATION_COLS
Definition: verify_heapam.c:31
#define MemSet(start, val, len)
Definition: c.h:1008
HeapTuple heap_form_tuple(TupleDesc tupleDescriptor, Datum *values, bool *isnull)
Definition: heaptuple.c:1020
void pfree(void *pointer)
Definition: mcxt.c:1169
void tuplestore_puttuple(Tuplestorestate *state, HeapTuple tuple)
Definition: tuplestore.c:730
Datum Int64GetDatum(int64 X)
Definition: fmgr.c:1697
uintptr_t Datum
Definition: postgres.h:411
int16 attnum
Definition: pg_attribute.h:83
static Datum values[MAXATTR]
Definition: bootstrap.c:156
#define Int32GetDatum(X)
Definition: postgres.h:523
#define CStringGetTextDatum(s)
Definition: builtins.h:86

◆ report_toast_corruption()

static void report_toast_corruption ( HeapCheckContext ctx,
ToastedAttribute ta,
char *  msg 
)
static

Definition at line 602 of file verify_heapam.c.

References ToastedAttribute::attnum, ToastedAttribute::blkno, HeapCheckContext::is_corrupt, ToastedAttribute::offnum, report_corruption_internal(), HeapCheckContext::tupdesc, and HeapCheckContext::tupstore.

Referenced by check_toast_tuple(), and check_toasted_attribute().

604 {
606  ta->offnum, ta->attnum, msg);
607  ctx->is_corrupt = true;
608 }
BlockNumber blkno
Definition: verify_heapam.c:69
OffsetNumber offnum
Definition: verify_heapam.c:70
AttrNumber attnum
Definition: verify_heapam.c:71
static void report_corruption_internal(Tuplestorestate *tupstore, TupleDesc tupdesc, BlockNumber blkno, OffsetNumber offnum, AttrNumber attnum, char *msg)
Tuplestorestate * tupstore

◆ update_cached_mxid_range()

static void update_cached_mxid_range ( HeapCheckContext ctx)
static

Definition at line 1590 of file verify_heapam.c.

References HeapCheckContext::next_mxact, HeapCheckContext::oldest_mxact, and ReadMultiXactIdRange().

Referenced by check_mxid_valid_in_rel(), and verify_heapam().

1591 {
1593 }
MultiXactId oldest_mxact
Definition: verify_heapam.c:96
MultiXactId next_mxact
Definition: verify_heapam.c:95
void ReadMultiXactIdRange(MultiXactId *oldest, MultiXactId *next)
Definition: multixact.c:743

◆ update_cached_xid_range()

static void update_cached_xid_range ( HeapCheckContext ctx)
static

Definition at line 1573 of file verify_heapam.c.

References FullTransactionIdFromXidAndCtx(), LW_SHARED, LWLockAcquire(), LWLockRelease(), HeapCheckContext::next_fxid, HeapCheckContext::next_xid, VariableCacheData::nextXid, HeapCheckContext::oldest_fxid, HeapCheckContext::oldest_xid, VariableCacheData::oldestXid, ShmemVariableCache, and XidFromFullTransactionId.

Referenced by get_xid_status(), and verify_heapam().

1574 {
1575  /* Make cached copies */
1576  LWLockAcquire(XidGenLock, LW_SHARED);
1579  LWLockRelease(XidGenLock);
1580 
1581  /* And compute alternate versions of the same */
1584 }
TransactionId oldest_xid
Definition: verify_heapam.c:86
FullTransactionId oldest_fxid
Definition: verify_heapam.c:87
FullTransactionId next_fxid
Definition: verify_heapam.c:84
TransactionId oldestXid
Definition: transam.h:222
FullTransactionId nextXid
Definition: transam.h:220
#define XidFromFullTransactionId(x)
Definition: transam.h:48
void LWLockRelease(LWLock *lock)
Definition: lwlock.c:1803
VariableCache ShmemVariableCache
Definition: varsup.c:34
TransactionId next_xid
Definition: verify_heapam.c:85
bool LWLockAcquire(LWLock *lock, LWLockMode mode)
Definition: lwlock.c:1199
static FullTransactionId FullTransactionIdFromXidAndCtx(TransactionId xid, const HeapCheckContext *ctx)

◆ verify_heapam()

Datum verify_heapam ( PG_FUNCTION_ARGS  )

Definition at line 211 of file verify_heapam.c.

References AccessShareLock, ReturnSetInfo::allowedModes, BAS_BULKREAD, BUFFER_LOCK_SHARE, BufferGetPage, CHECK_FOR_INTERRUPTS, check_toasted_attribute(), check_tuple(), ReturnSetInfo::econtext, ExprContext::ecxt_per_query_memory, ereport, errcode(), errdetail_relkind_not_supported(), errhint(), errmsg(), ERROR, fb(), FirstOffsetNumber, FullTransactionIdFromXidAndCtx(), GetAccessStrategy(), GetTransactionSnapshot(), HeapTupleHeaderGetNatts, InvalidBuffer, InvalidTransactionId, IsA, ItemIdGetLength, ItemIdGetOffset, ItemIdGetRedirect, ItemIdIsDead, ItemIdIsRedirected, ItemIdIsUsed, lfirst, list_free_deep(), LockBuffer(), MAIN_FORKNUM, MAXALIGN, MemoryContextSwitchTo(), NIL, OffsetNumberNext, PageGetItem, PageGetItemId, PageGetMaxOffsetNumber, PG_ARGISNULL, PG_GETARG_BOOL, PG_GETARG_INT64, PG_GETARG_OID, PG_GETARG_TEXT_PP, PG_RETURN_NULL, pg_strcasecmp(), psprintf(), RBM_NORMAL, ReadBufferExtended(), relation_close(), relation_open(), RelationGetNumberOfBlocks, RelationGetRelationName, ReleaseBuffer(), report_corruption(), ReturnSetInfo::returnMode, ReturnSetInfo::setDesc, ReturnSetInfo::setResult, SFRM_Materialize, SFRM_Materialize_Random, SizeofHeapTupleHeader, skip, SKIP_PAGES_ALL_FROZEN, SKIP_PAGES_ALL_VISIBLE, SKIP_PAGES_NONE, table_close(), table_open(), text_to_cstring(), toast_close_indexes(), toast_open_indexes(), TransactionIdIsNormal, tuplestore_begin_heap(), UnlockReleaseBuffer(), update_cached_mxid_range(), update_cached_xid_range(), verify_heapam_tupdesc(), VISIBILITYMAP_ALL_FROZEN, VISIBILITYMAP_ALL_VISIBLE, visibilitymap_get_status(), work_mem, and SnapshotData::xmin.

212 {
213  ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
214  MemoryContext old_context;
215  bool random_access;
216  HeapCheckContext ctx;
217  Buffer vmbuffer = InvalidBuffer;
218  Oid relid;
219  bool on_error_stop;
220  bool check_toast;
221  SkipPages skip_option = SKIP_PAGES_NONE;
222  BlockNumber first_block;
223  BlockNumber last_block;
224  BlockNumber nblocks;
225  const char *skip;
226 
227  /* Check to see if caller supports us returning a tuplestore */
228  if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
229  ereport(ERROR,
230  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
231  errmsg("set-valued function called in context that cannot accept a set")));
232  if (!(rsinfo->allowedModes & SFRM_Materialize))
233  ereport(ERROR,
234  (errcode(ERRCODE_SYNTAX_ERROR),
235  errmsg("materialize mode required, but it is not allowed in this context")));
236 
237  /* Check supplied arguments */
238  if (PG_ARGISNULL(0))
239  ereport(ERROR,
240  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
241  errmsg("relation cannot be null")));
242  relid = PG_GETARG_OID(0);
243 
244  if (PG_ARGISNULL(1))
245  ereport(ERROR,
246  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
247  errmsg("on_error_stop cannot be null")));
248  on_error_stop = PG_GETARG_BOOL(1);
249 
250  if (PG_ARGISNULL(2))
251  ereport(ERROR,
252  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
253  errmsg("check_toast cannot be null")));
254  check_toast = PG_GETARG_BOOL(2);
255 
256  if (PG_ARGISNULL(3))
257  ereport(ERROR,
258  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
259  errmsg("skip cannot be null")));
261  if (pg_strcasecmp(skip, "all-visible") == 0)
262  skip_option = SKIP_PAGES_ALL_VISIBLE;
263  else if (pg_strcasecmp(skip, "all-frozen") == 0)
264  skip_option = SKIP_PAGES_ALL_FROZEN;
265  else if (pg_strcasecmp(skip, "none") == 0)
266  skip_option = SKIP_PAGES_NONE;
267  else
268  ereport(ERROR,
269  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
270  errmsg("invalid skip option"),
271  errhint("Valid skip options are \"all-visible\", \"all-frozen\", and \"none\".")));
272 
273  memset(&ctx, 0, sizeof(HeapCheckContext));
274  ctx.cached_xid = InvalidTransactionId;
275  ctx.toasted_attributes = NIL;
276 
277  /*
278  * Any xmin newer than the xmin of our snapshot can't become all-visible
279  * while we're running.
280  */
281  ctx.safe_xmin = GetTransactionSnapshot()->xmin;
282 
283  /*
284  * If we report corruption when not examining some individual attribute,
285  * we need attnum to be reported as NULL. Set that up before any
286  * corruption reporting might happen.
287  */
288  ctx.attnum = -1;
289 
290  /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
291  old_context = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
292  random_access = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
293  ctx.tupdesc = verify_heapam_tupdesc();
294  ctx.tupstore = tuplestore_begin_heap(random_access, false, work_mem);
295  rsinfo->returnMode = SFRM_Materialize;
296  rsinfo->setResult = ctx.tupstore;
297  rsinfo->setDesc = ctx.tupdesc;
298  MemoryContextSwitchTo(old_context);
299 
300  /* Open relation, check relkind and access method */
301  ctx.rel = relation_open(relid, AccessShareLock);
302 
303  /*
304  * Check that a relation's relkind and access method are both supported.
305  */
306  if (ctx.rel->rd_rel->relkind != RELKIND_RELATION &&
307  ctx.rel->rd_rel->relkind != RELKIND_MATVIEW &&
308  ctx.rel->rd_rel->relkind != RELKIND_TOASTVALUE)
309  ereport(ERROR,
310  (errcode(ERRCODE_WRONG_OBJECT_TYPE),
311  errmsg("cannot check relation \"%s\"",
312  RelationGetRelationName(ctx.rel)),
313  errdetail_relkind_not_supported(ctx.rel->rd_rel->relkind)));
314 
315  if (ctx.rel->rd_rel->relam != HEAP_TABLE_AM_OID)
316  ereport(ERROR,
317  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
318  errmsg("only heap AM is supported")));
319 
320  /* Early exit if the relation is empty */
321  nblocks = RelationGetNumberOfBlocks(ctx.rel);
322  if (!nblocks)
323  {
325  PG_RETURN_NULL();
326  }
327 
328  ctx.bstrategy = GetAccessStrategy(BAS_BULKREAD);
329  ctx.buffer = InvalidBuffer;
330  ctx.page = NULL;
331 
332  /* Validate block numbers, or handle nulls. */
333  if (PG_ARGISNULL(4))
334  first_block = 0;
335  else
336  {
337  int64 fb = PG_GETARG_INT64(4);
338 
339  if (fb < 0 || fb >= nblocks)
340  ereport(ERROR,
341  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
342  errmsg("starting block number must be between 0 and %u",
343  nblocks - 1)));
344  first_block = (BlockNumber) fb;
345  }
346  if (PG_ARGISNULL(5))
347  last_block = nblocks - 1;
348  else
349  {
350  int64 lb = PG_GETARG_INT64(5);
351 
352  if (lb < 0 || lb >= nblocks)
353  ereport(ERROR,
354  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
355  errmsg("ending block number must be between 0 and %u",
356  nblocks - 1)));
357  last_block = (BlockNumber) lb;
358  }
359 
360  /* Optionally open the toast relation, if any. */
361  if (ctx.rel->rd_rel->reltoastrelid && check_toast)
362  {
363  int offset;
364 
365  /* Main relation has associated toast relation */
366  ctx.toast_rel = table_open(ctx.rel->rd_rel->reltoastrelid,
368  offset = toast_open_indexes(ctx.toast_rel,
370  &(ctx.toast_indexes),
371  &(ctx.num_toast_indexes));
372  ctx.valid_toast_index = ctx.toast_indexes[offset];
373  }
374  else
375  {
376  /*
377  * Main relation has no associated toast relation, or we're
378  * intentionally skipping it.
379  */
380  ctx.toast_rel = NULL;
381  ctx.toast_indexes = NULL;
382  ctx.num_toast_indexes = 0;
383  }
384 
387  ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
388  ctx.relfrozenfxid = FullTransactionIdFromXidAndCtx(ctx.relfrozenxid, &ctx);
389  ctx.relminmxid = ctx.rel->rd_rel->relminmxid;
390 
391  if (TransactionIdIsNormal(ctx.relfrozenxid))
392  ctx.oldest_xid = ctx.relfrozenxid;
393 
394  for (ctx.blkno = first_block; ctx.blkno <= last_block; ctx.blkno++)
395  {
396  OffsetNumber maxoff;
397 
399 
400  /* Optionally skip over all-frozen or all-visible blocks */
401  if (skip_option != SKIP_PAGES_NONE)
402  {
403  int32 mapbits;
404 
405  mapbits = (int32) visibilitymap_get_status(ctx.rel, ctx.blkno,
406  &vmbuffer);
407  if (skip_option == SKIP_PAGES_ALL_FROZEN)
408  {
409  if ((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0)
410  continue;
411  }
412 
413  if (skip_option == SKIP_PAGES_ALL_VISIBLE)
414  {
415  if ((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0)
416  continue;
417  }
418  }
419 
420  /* Read and lock the next page. */
421  ctx.buffer = ReadBufferExtended(ctx.rel, MAIN_FORKNUM, ctx.blkno,
422  RBM_NORMAL, ctx.bstrategy);
423  LockBuffer(ctx.buffer, BUFFER_LOCK_SHARE);
424  ctx.page = BufferGetPage(ctx.buffer);
425 
426  /* Perform tuple checks */
427  maxoff = PageGetMaxOffsetNumber(ctx.page);
428  for (ctx.offnum = FirstOffsetNumber; ctx.offnum <= maxoff;
429  ctx.offnum = OffsetNumberNext(ctx.offnum))
430  {
431  ctx.itemid = PageGetItemId(ctx.page, ctx.offnum);
432 
433  /* Skip over unused/dead line pointers */
434  if (!ItemIdIsUsed(ctx.itemid) || ItemIdIsDead(ctx.itemid))
435  continue;
436 
437  /*
438  * If this line pointer has been redirected, check that it
439  * redirects to a valid offset within the line pointer array
440  */
441  if (ItemIdIsRedirected(ctx.itemid))
442  {
443  OffsetNumber rdoffnum = ItemIdGetRedirect(ctx.itemid);
444  ItemId rditem;
445 
446  if (rdoffnum < FirstOffsetNumber)
447  {
448  report_corruption(&ctx,
449  psprintf("line pointer redirection to item at offset %u precedes minimum offset %u",
450  (unsigned) rdoffnum,
451  (unsigned) FirstOffsetNumber));
452  continue;
453  }
454  if (rdoffnum > maxoff)
455  {
456  report_corruption(&ctx,
457  psprintf("line pointer redirection to item at offset %u exceeds maximum offset %u",
458  (unsigned) rdoffnum,
459  (unsigned) maxoff));
460  continue;
461  }
462  rditem = PageGetItemId(ctx.page, rdoffnum);
463  if (!ItemIdIsUsed(rditem))
464  report_corruption(&ctx,
465  psprintf("line pointer redirection to unused item at offset %u",
466  (unsigned) rdoffnum));
467  continue;
468  }
469 
470  /* Sanity-check the line pointer's offset and length values */
471  ctx.lp_len = ItemIdGetLength(ctx.itemid);
472  ctx.lp_off = ItemIdGetOffset(ctx.itemid);
473 
474  if (ctx.lp_off != MAXALIGN(ctx.lp_off))
475  {
476  report_corruption(&ctx,
477  psprintf("line pointer to page offset %u is not maximally aligned",
478  ctx.lp_off));
479  continue;
480  }
481  if (ctx.lp_len < MAXALIGN(SizeofHeapTupleHeader))
482  {
483  report_corruption(&ctx,
484  psprintf("line pointer length %u is less than the minimum tuple header size %u",
485  ctx.lp_len,
486  (unsigned) MAXALIGN(SizeofHeapTupleHeader)));
487  continue;
488  }
489  if (ctx.lp_off + ctx.lp_len > BLCKSZ)
490  {
491  report_corruption(&ctx,
492  psprintf("line pointer to page offset %u with length %u ends beyond maximum page offset %u",
493  ctx.lp_off,
494  ctx.lp_len,
495  (unsigned) BLCKSZ));
496  continue;
497  }
498 
499  /* It should be safe to examine the tuple's header, at least */
500  ctx.tuphdr = (HeapTupleHeader) PageGetItem(ctx.page, ctx.itemid);
501  ctx.natts = HeapTupleHeaderGetNatts(ctx.tuphdr);
502 
503  /* Ok, ready to check this next tuple */
504  check_tuple(&ctx);
505  }
506 
507  /* clean up */
508  UnlockReleaseBuffer(ctx.buffer);
509 
510  /*
511  * Check any toast pointers from the page whose lock we just released
512  */
513  if (ctx.toasted_attributes != NIL)
514  {
515  ListCell *cell;
516 
517  foreach(cell, ctx.toasted_attributes)
518  check_toasted_attribute(&ctx, lfirst(cell));
519  list_free_deep(ctx.toasted_attributes);
520  ctx.toasted_attributes = NIL;
521  }
522 
523  if (on_error_stop && ctx.is_corrupt)
524  break;
525  }
526 
527  if (vmbuffer != InvalidBuffer)
528  ReleaseBuffer(vmbuffer);
529 
530  /* Close the associated toast table and indexes, if any. */
531  if (ctx.toast_indexes)
532  toast_close_indexes(ctx.toast_indexes, ctx.num_toast_indexes,
534  if (ctx.toast_rel)
535  table_close(ctx.toast_rel, AccessShareLock);
536 
537  /* Close the main relation */
539 
540  PG_RETURN_NULL();
541 }
BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype)
Definition: freelist.c:542
int errdetail_relkind_not_supported(char relkind)
Definition: pg_class.c:24
#define NIL
Definition: pg_list.h:65
static void check_toasted_attribute(HeapCheckContext *ctx, ToastedAttribute *ta)
#define SizeofHeapTupleHeader
Definition: htup_details.h:184
#define IsA(nodeptr, _type_)
Definition: nodes.h:587
void table_close(Relation relation, LOCKMODE lockmode)
Definition: table.c:167
int errhint(const char *fmt,...)
Definition: elog.c:1156
#define ItemIdIsRedirected(itemId)
Definition: itemid.h:106
HeapTupleHeaderData * HeapTupleHeader
Definition: htup.h:23
#define ItemIdGetRedirect(itemId)
Definition: itemid.h:78
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:741
static const struct exclude_list_item skip[]
Definition: pg_checksums.c:116
#define ItemIdIsUsed(itemId)
Definition: itemid.h:92
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:109
#define AccessShareLock
Definition: lockdefs.h:36
#define InvalidBuffer
Definition: buf.h:25
int errcode(int sqlerrcode)
Definition: elog.c:698
static void report_corruption(HeapCheckContext *ctx, char *msg)
uint32 BlockNumber
Definition: block.h:31
void ReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3768
#define PG_GETARG_BOOL(n)
Definition: fmgr.h:274
int pg_strcasecmp(const char *s1, const char *s2)
Definition: pgstrcasecmp.c:36
unsigned int Oid
Definition: postgres_ext.h:31
static int fb(int x)
Definition: preproc-init.c:92
#define ItemIdIsDead(itemId)
Definition: itemid.h:113
Snapshot GetTransactionSnapshot(void)
Definition: snapmgr.c:250
#define PageGetMaxOffsetNumber(page)
Definition: bufpage.h:357
void list_free_deep(List *list)
Definition: list.c:1405
signed int int32
Definition: c.h:429
#define PG_GETARG_TEXT_PP(n)
Definition: fmgr.h:309
uint16 OffsetNumber
Definition: off.h:24
static void update_cached_xid_range(HeapCheckContext *ctx)
#define ItemIdGetLength(itemId)
Definition: itemid.h:59
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3791
#define ERROR
Definition: elog.h:46
#define HeapTupleHeaderGetNatts(tup)
Definition: htup_details.h:530
Relation relation_open(Oid relationId, LOCKMODE lockmode)
Definition: relation.c:48
#define PG_GETARG_OID(n)
Definition: fmgr.h:275
#define FirstOffsetNumber
Definition: off.h:27
void toast_close_indexes(Relation *toastidxs, int num_indexes, LOCKMODE lock)
#define InvalidTransactionId
Definition: transam.h:31
#define RelationGetRelationName(relation)
Definition: rel.h:511
#define ItemIdGetOffset(itemId)
Definition: itemid.h:65
TransactionId xmin
Definition: snapshot.h:157
static void update_cached_mxid_range(HeapCheckContext *ctx)
#define BufferGetPage(buffer)
Definition: bufmgr.h:169
static void check_tuple(HeapCheckContext *ctx)
#define PageGetItemId(page, offsetNumber)
Definition: bufpage.h:235
Tuplestorestate * tuplestore_begin_heap(bool randomAccess, bool interXact, int maxKBytes)
Definition: tuplestore.c:318
int toast_open_indexes(Relation toastrel, LOCKMODE lock, Relation **toastidxs, int *num_indexes)
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:4007
int work_mem
Definition: globals.c:124
#define RelationGetNumberOfBlocks(reln)
Definition: bufmgr.h:212
SkipPages
Definition: verify_heapam.c:54
#define ereport(elevel,...)
Definition: elog.h:157
int allowedModes
Definition: execnodes.h:305
SetFunctionReturnMode returnMode
Definition: execnodes.h:307
#define PG_ARGISNULL(n)
Definition: fmgr.h:209
void relation_close(Relation relation, LOCKMODE lockmode)
Definition: relation.c:206
#define lfirst(lc)
Definition: pg_list.h:169
static TupleDesc verify_heapam_tupdesc(void)
#define VISIBILITYMAP_ALL_FROZEN
#define OffsetNumberNext(offsetNumber)
Definition: off.h:52
#define MAXALIGN(LEN)
Definition: c.h:757
MemoryContext ecxt_per_query_memory
Definition: execnodes.h:233
Tuplestorestate * setResult
Definition: execnodes.h:310
char * text_to_cstring(const text *t)
Definition: varlena.c:222
ExprContext * econtext
Definition: execnodes.h:303
TupleDesc setDesc
Definition: execnodes.h:311
int errmsg(const char *fmt,...)
Definition: elog.c:909
uint8 visibilitymap_get_status(Relation rel, BlockNumber heapBlk, Buffer *buf)
#define BUFFER_LOCK_SHARE
Definition: bufmgr.h:97
#define CHECK_FOR_INTERRUPTS()
Definition: miscadmin.h:120
#define VISIBILITYMAP_ALL_VISIBLE
static FullTransactionId FullTransactionIdFromXidAndCtx(TransactionId xid, const HeapCheckContext *ctx)
#define TransactionIdIsNormal(xid)
Definition: transam.h:42
Relation table_open(Oid relationId, LOCKMODE lockmode)
Definition: table.c:39
#define PG_GETARG_INT64(n)
Definition: fmgr.h:283
int Buffer
Definition: buf.h:23
#define PG_RETURN_NULL()
Definition: fmgr.h:345
#define PageGetItem(page, itemId)
Definition: bufpage.h:340

◆ verify_heapam_tupdesc()

static TupleDesc verify_heapam_tupdesc ( void  )
static

Definition at line 615 of file verify_heapam.c.

References Assert, BlessTupleDesc(), CreateTemplateTupleDesc(), HEAPCHECK_RELATION_COLS, and TupleDescInitEntry().

Referenced by verify_heapam().

616 {
617  TupleDesc tupdesc;
618  AttrNumber a = 0;
619 
621  TupleDescInitEntry(tupdesc, ++a, "blkno", INT8OID, -1, 0);
622  TupleDescInitEntry(tupdesc, ++a, "offnum", INT4OID, -1, 0);
623  TupleDescInitEntry(tupdesc, ++a, "attnum", INT4OID, -1, 0);
624  TupleDescInitEntry(tupdesc, ++a, "msg", TEXTOID, -1, 0);
626 
627  return BlessTupleDesc(tupdesc);
628 }
TupleDesc CreateTemplateTupleDesc(int natts)
Definition: tupdesc.c:45
#define HEAPCHECK_RELATION_COLS
Definition: verify_heapam.c:31
TupleDesc BlessTupleDesc(TupleDesc tupdesc)
Definition: execTuples.c:2082
void TupleDescInitEntry(TupleDesc desc, AttrNumber attributeNumber, const char *attributeName, Oid oidtypeid, int32 typmod, int attdim)
Definition: tupdesc.c:583
#define Assert(condition)
Definition: c.h:804
int16 AttrNumber
Definition: attnum.h:21