Coverage Report

Created: 2026-08-13 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/access/rmgrdesc/xlogdesc.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * xlogdesc.c
4
 *    rmgr descriptor routines for access/transam/xlog.c
5
 *
6
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/access/rmgrdesc/xlogdesc.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#include "access/transam.h"
18
#include "access/xlog.h"
19
#include "access/xlog_internal.h"
20
#include "catalog/pg_control.h"
21
#include "storage/checksum.h"
22
#include "utils/guc.h"
23
#include "utils/timestamp.h"
24
25
/*
26
 * GUC support
27
 */
28
const struct config_enum_entry wal_level_options[] = {
29
  {"minimal", WAL_LEVEL_MINIMAL, false},
30
  {"replica", WAL_LEVEL_REPLICA, false},
31
  {"archive", WAL_LEVEL_REPLICA, true}, /* deprecated */
32
  {"hot_standby", WAL_LEVEL_REPLICA, true}, /* deprecated */
33
  {"logical", WAL_LEVEL_LOGICAL, false},
34
  {NULL, 0, false}
35
};
36
37
/*
38
 * Find a string representation for wal_level
39
 */
40
static const char *
41
get_wal_level_string(int wal_level)
42
0
{
43
0
  const struct config_enum_entry *entry;
44
0
  const char *wal_level_str = "?";
45
46
0
  for (entry = wal_level_options; entry->name; entry++)
47
0
  {
48
0
    if (entry->val == wal_level)
49
0
    {
50
0
      wal_level_str = entry->name;
51
0
      break;
52
0
    }
53
0
  }
54
55
0
  return wal_level_str;
56
0
}
57
58
const char *
59
get_checksum_state_string(uint32 state)
60
0
{
61
0
  switch (state)
62
0
  {
63
0
    case PG_DATA_CHECKSUM_VERSION:
64
0
      return "on";
65
0
    case PG_DATA_CHECKSUM_INPROGRESS_OFF:
66
0
      return "inprogress-off";
67
0
    case PG_DATA_CHECKSUM_INPROGRESS_ON:
68
0
      return "inprogress-on";
69
0
    case PG_DATA_CHECKSUM_OFF:
70
0
      return "off";
71
0
  }
72
73
0
  Assert(false);
74
0
  return "?";
75
0
}
76
77
void
78
xlog2_desc(StringInfo buf, XLogReaderState *record)
79
0
{
80
0
  char     *rec = XLogRecGetData(record);
81
0
  uint8   info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
82
83
0
  if (info == XLOG2_CHECKSUMS)
84
0
  {
85
0
    xl_checksum_state xlrec;
86
87
0
    memcpy(&xlrec, rec, sizeof(xl_checksum_state));
88
0
    appendStringInfoString(buf, get_checksum_state_string(xlrec.new_checksum_state));
89
0
  }
90
0
}
91
92
void
93
xlog_desc(StringInfo buf, XLogReaderState *record)
94
0
{
95
0
  char     *rec = XLogRecGetData(record);
96
0
  uint8   info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
97
98
0
  if (info == XLOG_CHECKPOINT_SHUTDOWN ||
99
0
    info == XLOG_CHECKPOINT_ONLINE)
100
0
  {
101
0
    CheckPoint *checkpoint = (CheckPoint *) rec;
102
103
0
    appendStringInfo(buf, "redo %X/%08X; "
104
0
             "tli %u; prev tli %u; fpw %s; wal_level %s; logical decoding %s; xid %u:%u; oid %u; multi %u; offset %" PRIu64 "; "
105
0
             "oldest xid %u in DB %u; oldest multi %u in DB %u; "
106
0
             "oldest/newest commit timestamp xid: %u/%u; "
107
0
             "oldest running xid %u; "
108
0
             "checksums %s; %s",
109
0
             LSN_FORMAT_ARGS(checkpoint->redo),
110
0
             checkpoint->ThisTimeLineID,
111
0
             checkpoint->PrevTimeLineID,
112
0
             checkpoint->fullPageWrites ? "true" : "false",
113
0
             get_wal_level_string(checkpoint->wal_level),
114
0
             checkpoint->logicalDecodingEnabled ? "true" : "false",
115
0
             EpochFromFullTransactionId(checkpoint->nextXid),
116
0
             XidFromFullTransactionId(checkpoint->nextXid),
117
0
             checkpoint->nextOid,
118
0
             checkpoint->nextMulti,
119
0
             checkpoint->nextMultiOffset,
120
0
             checkpoint->oldestXid,
121
0
             checkpoint->oldestXidDB,
122
0
             checkpoint->oldestMulti,
123
0
             checkpoint->oldestMultiDB,
124
0
             checkpoint->oldestCommitTsXid,
125
0
             checkpoint->newestCommitTsXid,
126
0
             checkpoint->oldestActiveXid,
127
0
             get_checksum_state_string(checkpoint->dataChecksumState),
128
0
             (info == XLOG_CHECKPOINT_SHUTDOWN) ? "shutdown" : "online");
129
0
  }
130
0
  else if (info == XLOG_NEXTOID)
131
0
  {
132
0
    Oid     nextOid;
133
134
0
    memcpy(&nextOid, rec, sizeof(Oid));
135
0
    appendStringInfo(buf, "%u", nextOid);
136
0
  }
137
0
  else if (info == XLOG_RESTORE_POINT)
138
0
  {
139
0
    xl_restore_point *xlrec = (xl_restore_point *) rec;
140
141
0
    appendStringInfoString(buf, xlrec->rp_name);
142
0
  }
143
0
  else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT)
144
0
  {
145
    /* no further information to print */
146
0
  }
147
0
  else if (info == XLOG_BACKUP_END)
148
0
  {
149
0
    XLogRecPtr  startpoint;
150
151
0
    memcpy(&startpoint, rec, sizeof(XLogRecPtr));
152
0
    appendStringInfo(buf, "%X/%08X", LSN_FORMAT_ARGS(startpoint));
153
0
  }
154
0
  else if (info == XLOG_PARAMETER_CHANGE)
155
0
  {
156
0
    xl_parameter_change xlrec;
157
0
    const char *wal_level_str;
158
159
0
    memcpy(&xlrec, rec, sizeof(xl_parameter_change));
160
0
    wal_level_str = get_wal_level_string(xlrec.wal_level);
161
162
0
    appendStringInfo(buf, "max_connections=%d max_worker_processes=%d "
163
0
             "max_wal_senders=%d max_prepared_xacts=%d "
164
0
             "max_locks_per_xact=%d wal_level=%s "
165
0
             "wal_log_hints=%s track_commit_timestamp=%s",
166
0
             xlrec.MaxConnections,
167
0
             xlrec.max_worker_processes,
168
0
             xlrec.max_wal_senders,
169
0
             xlrec.max_prepared_xacts,
170
0
             xlrec.max_locks_per_xact,
171
0
             wal_level_str,
172
0
             xlrec.wal_log_hints ? "on" : "off",
173
0
             xlrec.track_commit_timestamp ? "on" : "off");
174
0
  }
175
0
  else if (info == XLOG_FPW_CHANGE)
176
0
  {
177
0
    bool    fpw;
178
179
0
    memcpy(&fpw, rec, sizeof(bool));
180
0
    appendStringInfoString(buf, fpw ? "true" : "false");
181
0
  }
182
0
  else if (info == XLOG_END_OF_RECOVERY)
183
0
  {
184
0
    xl_end_of_recovery xlrec;
185
186
0
    memcpy(&xlrec, rec, sizeof(xl_end_of_recovery));
187
0
    appendStringInfo(buf, "tli %u; prev tli %u; time %s; wal_level %s",
188
0
             xlrec.ThisTimeLineID, xlrec.PrevTimeLineID,
189
0
             timestamptz_to_str(xlrec.end_time),
190
0
             get_wal_level_string(xlrec.wal_level));
191
0
  }
192
0
  else if (info == XLOG_OVERWRITE_CONTRECORD)
193
0
  {
194
0
    xl_overwrite_contrecord xlrec;
195
196
0
    memcpy(&xlrec, rec, sizeof(xl_overwrite_contrecord));
197
0
    appendStringInfo(buf, "lsn %X/%08X; time %s",
198
0
             LSN_FORMAT_ARGS(xlrec.overwritten_lsn),
199
0
             timestamptz_to_str(xlrec.overwrite_time));
200
0
  }
201
0
  else if (info == XLOG_CHECKPOINT_REDO)
202
0
  {
203
0
    xl_checkpoint_redo xlrec;
204
205
0
    memcpy(&xlrec, rec, sizeof(xl_checkpoint_redo));
206
0
    appendStringInfo(buf, "wal_level %s; checksums %s",
207
0
             get_wal_level_string(xlrec.wal_level),
208
0
             get_checksum_state_string(xlrec.data_checksum_version));
209
0
  }
210
0
  else if (info == XLOG_LOGICAL_DECODING_STATUS_CHANGE)
211
0
  {
212
0
    bool    enabled;
213
214
0
    memcpy(&enabled, rec, sizeof(bool));
215
0
    appendStringInfoString(buf, enabled ? "true" : "false");
216
0
  }
217
0
  else if (info == XLOG_ASSIGN_LSN)
218
0
  {
219
    /* no further information to print */
220
0
  }
221
0
}
222
223
const char *
224
xlog_identify(uint8 info)
225
0
{
226
0
  const char *id = NULL;
227
228
0
  switch (info & ~XLR_INFO_MASK)
229
0
  {
230
0
    case XLOG_CHECKPOINT_SHUTDOWN:
231
0
      id = "CHECKPOINT_SHUTDOWN";
232
0
      break;
233
0
    case XLOG_CHECKPOINT_ONLINE:
234
0
      id = "CHECKPOINT_ONLINE";
235
0
      break;
236
0
    case XLOG_NOOP:
237
0
      id = "NOOP";
238
0
      break;
239
0
    case XLOG_NEXTOID:
240
0
      id = "NEXTOID";
241
0
      break;
242
0
    case XLOG_SWITCH:
243
0
      id = "SWITCH";
244
0
      break;
245
0
    case XLOG_BACKUP_END:
246
0
      id = "BACKUP_END";
247
0
      break;
248
0
    case XLOG_PARAMETER_CHANGE:
249
0
      id = "PARAMETER_CHANGE";
250
0
      break;
251
0
    case XLOG_RESTORE_POINT:
252
0
      id = "RESTORE_POINT";
253
0
      break;
254
0
    case XLOG_FPW_CHANGE:
255
0
      id = "FPW_CHANGE";
256
0
      break;
257
0
    case XLOG_END_OF_RECOVERY:
258
0
      id = "END_OF_RECOVERY";
259
0
      break;
260
0
    case XLOG_OVERWRITE_CONTRECORD:
261
0
      id = "OVERWRITE_CONTRECORD";
262
0
      break;
263
0
    case XLOG_FPI:
264
0
      id = "FPI";
265
0
      break;
266
0
    case XLOG_FPI_FOR_HINT:
267
0
      id = "FPI_FOR_HINT";
268
0
      break;
269
0
    case XLOG_CHECKPOINT_REDO:
270
0
      id = "CHECKPOINT_REDO";
271
0
      break;
272
0
    case XLOG_LOGICAL_DECODING_STATUS_CHANGE:
273
0
      id = "LOGICAL_DECODING_STATUS_CHANGE";
274
0
      break;
275
0
    case XLOG_ASSIGN_LSN:
276
0
      id = "ASSIGN_LSN";
277
0
      break;
278
0
  }
279
280
0
  return id;
281
0
}
282
283
const char *
284
xlog2_identify(uint8 info)
285
0
{
286
0
  const char *id = NULL;
287
288
0
  switch (info & ~XLR_INFO_MASK)
289
0
  {
290
0
    case XLOG2_CHECKSUMS:
291
0
      id = "CHECKSUMS";
292
0
      break;
293
0
  }
294
295
0
  return id;
296
0
}
297
298
/*
299
 * Returns a string giving information about all the blocks in an
300
 * XLogRecord.
301
 */
302
void
303
XLogRecGetBlockRefInfo(XLogReaderState *record, bool pretty,
304
             bool detailed_format, StringInfo buf,
305
             uint32 *fpi_len)
306
0
{
307
0
  int     block_id;
308
309
0
  Assert(record != NULL);
310
311
0
  if (detailed_format && pretty)
312
0
    appendStringInfoChar(buf, '\n');
313
314
0
  for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
315
0
  {
316
0
    RelFileLocator rlocator;
317
0
    ForkNumber  forknum;
318
0
    BlockNumber blk;
319
320
0
    if (!XLogRecGetBlockTagExtended(record, block_id,
321
0
                    &rlocator, &forknum, &blk, NULL))
322
0
      continue;
323
324
0
    if (detailed_format)
325
0
    {
326
      /* Get block references in detailed format. */
327
328
0
      if (pretty)
329
0
        appendStringInfoChar(buf, '\t');
330
0
      else if (block_id > 0)
331
0
        appendStringInfoChar(buf, ' ');
332
333
0
      appendStringInfo(buf,
334
0
               "blkref #%d: rel %u/%u/%u fork %s blk %u",
335
0
               block_id,
336
0
               rlocator.spcOid, rlocator.dbOid, rlocator.relNumber,
337
0
               forkNames[forknum],
338
0
               blk);
339
340
0
      if (XLogRecHasBlockImage(record, block_id))
341
0
      {
342
0
        uint8   bimg_info = XLogRecGetBlock(record, block_id)->bimg_info;
343
344
        /* Calculate the amount of FPI data in the record. */
345
0
        if (fpi_len)
346
0
          *fpi_len += XLogRecGetBlock(record, block_id)->bimg_len;
347
348
0
        if (BKPIMAGE_COMPRESSED(bimg_info))
349
0
        {
350
0
          const char *method;
351
352
0
          if ((bimg_info & BKPIMAGE_COMPRESS_PGLZ) != 0)
353
0
            method = "pglz";
354
0
          else if ((bimg_info & BKPIMAGE_COMPRESS_LZ4) != 0)
355
0
            method = "lz4";
356
0
          else if ((bimg_info & BKPIMAGE_COMPRESS_ZSTD) != 0)
357
0
            method = "zstd";
358
0
          else
359
0
            method = "unknown";
360
361
0
          appendStringInfo(buf,
362
0
                   " (FPW%s); hole: offset: %u, length: %u, "
363
0
                   "compression saved: %u, method: %s",
364
0
                   XLogRecBlockImageApply(record, block_id) ?
365
0
                   "" : " for WAL verification",
366
0
                   XLogRecGetBlock(record, block_id)->hole_offset,
367
0
                   XLogRecGetBlock(record, block_id)->hole_length,
368
0
                   BLCKSZ -
369
0
                   XLogRecGetBlock(record, block_id)->hole_length -
370
0
                   XLogRecGetBlock(record, block_id)->bimg_len,
371
0
                   method);
372
0
        }
373
0
        else
374
0
        {
375
0
          appendStringInfo(buf,
376
0
                   " (FPW%s); hole: offset: %u, length: %u",
377
0
                   XLogRecBlockImageApply(record, block_id) ?
378
0
                   "" : " for WAL verification",
379
0
                   XLogRecGetBlock(record, block_id)->hole_offset,
380
0
                   XLogRecGetBlock(record, block_id)->hole_length);
381
0
        }
382
0
      }
383
384
0
      if (pretty)
385
0
        appendStringInfoChar(buf, '\n');
386
0
    }
387
0
    else
388
0
    {
389
      /* Get block references in short format. */
390
391
0
      if (forknum != MAIN_FORKNUM)
392
0
      {
393
0
        appendStringInfo(buf,
394
0
                 ", blkref #%d: rel %u/%u/%u fork %s blk %u",
395
0
                 block_id,
396
0
                 rlocator.spcOid, rlocator.dbOid, rlocator.relNumber,
397
0
                 forkNames[forknum],
398
0
                 blk);
399
0
      }
400
0
      else
401
0
      {
402
0
        appendStringInfo(buf,
403
0
                 ", blkref #%d: rel %u/%u/%u blk %u",
404
0
                 block_id,
405
0
                 rlocator.spcOid, rlocator.dbOid, rlocator.relNumber,
406
0
                 blk);
407
0
      }
408
409
0
      if (XLogRecHasBlockImage(record, block_id))
410
0
      {
411
        /* Calculate the amount of FPI data in the record. */
412
0
        if (fpi_len)
413
0
          *fpi_len += XLogRecGetBlock(record, block_id)->bimg_len;
414
415
0
        if (XLogRecBlockImageApply(record, block_id))
416
0
          appendStringInfoString(buf, " FPW");
417
0
        else
418
0
          appendStringInfoString(buf, " FPW for WAL verification");
419
0
      }
420
0
    }
421
0
  }
422
423
0
  if (!detailed_format && pretty)
424
0
    appendStringInfoChar(buf, '\n');
425
0
}