PostgreSQL Source Code  git master
pg_waldump.c
Go to the documentation of this file.
1 /*-------------------------------------------------------------------------
2  *
3  * pg_waldump.c - decode and display WAL
4  *
5  * Copyright (c) 2013-2021, PostgreSQL Global Development Group
6  *
7  * IDENTIFICATION
8  * src/bin/pg_waldump/pg_waldump.c
9  *-------------------------------------------------------------------------
10  */
11 
12 #define FRONTEND 1
13 #include "postgres.h"
14 
15 #include <dirent.h>
16 #include <sys/stat.h>
17 #include <unistd.h>
18 
19 #include "access/transam.h"
20 #include "access/xlog_internal.h"
21 #include "access/xlogreader.h"
22 #include "access/xlogrecord.h"
23 #include "common/fe_memutils.h"
24 #include "common/logging.h"
25 #include "getopt_long.h"
26 #include "rmgrdesc.h"
27 
28 static const char *progname;
29 
30 static int WalSegSz;
31 
32 typedef struct XLogDumpConfig
33 {
34  /* display options */
35  bool quiet;
39  bool follow;
40  bool stats;
42 
43  /* filter options */
48 
49 typedef struct Stats
50 {
51  uint64 count;
52  uint64 rec_len;
53  uint64 fpi_len;
54 } Stats;
55 
56 #define MAX_XLINFO_TYPES 16
57 
58 typedef struct XLogDumpStats
59 {
60  uint64 count;
61  Stats rmgr_stats[RM_NEXT_ID];
64 
65 #define fatal_error(...) do { pg_log_fatal(__VA_ARGS__); exit(EXIT_FAILURE); } while(0)
66 
67 static void
69 {
70  int i;
71 
72  for (i = 0; i <= RM_MAX_ID; i++)
73  {
74  printf("%s\n", RmgrDescTable[i].rm_name);
75  }
76 }
77 
78 /*
79  * Check whether directory exists and whether we can open it. Keep errno set so
80  * that the caller can report errors somewhat more accurately.
81  */
82 static bool
84 {
85  DIR *dir = opendir(directory);
86 
87  if (dir == NULL)
88  return false;
89  closedir(dir);
90  return true;
91 }
92 
93 /*
94  * Split a pathname as dirname(1) and basename(1) would.
95  *
96  * XXX this probably doesn't do very well on Windows. We probably need to
97  * apply canonicalize_path(), at the very least.
98  */
99 static void
100 split_path(const char *path, char **dir, char **fname)
101 {
102  char *sep;
103 
104  /* split filepath into directory & filename */
105  sep = strrchr(path, '/');
106 
107  /* directory path */
108  if (sep != NULL)
109  {
110  *dir = pnstrdup(path, sep - path);
111  *fname = pg_strdup(sep + 1);
112  }
113  /* local directory */
114  else
115  {
116  *dir = NULL;
117  *fname = pg_strdup(path);
118  }
119 }
120 
121 /*
122  * Open the file in the valid target directory.
123  *
124  * return a read only fd
125  */
126 static int
127 open_file_in_directory(const char *directory, const char *fname)
128 {
129  int fd = -1;
130  char fpath[MAXPGPATH];
131 
132  Assert(directory != NULL);
133 
134  snprintf(fpath, MAXPGPATH, "%s/%s", directory, fname);
135  fd = open(fpath, O_RDONLY | PG_BINARY, 0);
136 
137  if (fd < 0 && errno != ENOENT)
138  fatal_error("could not open file \"%s\": %m", fname);
139  return fd;
140 }
141 
142 /*
143  * Try to find fname in the given directory. Returns true if it is found,
144  * false otherwise. If fname is NULL, search the complete directory for any
145  * file with a valid WAL file name. If file is successfully opened, set the
146  * wal segment size.
147  */
148 static bool
149 search_directory(const char *directory, const char *fname)
150 {
151  int fd = -1;
152  DIR *xldir;
153 
154  /* open file if valid filename is provided */
155  if (fname != NULL)
156  fd = open_file_in_directory(directory, fname);
157 
158  /*
159  * A valid file name is not passed, so search the complete directory. If
160  * we find any file whose name is a valid WAL file name then try to open
161  * it. If we cannot open it, bail out.
162  */
163  else if ((xldir = opendir(directory)) != NULL)
164  {
165  struct dirent *xlde;
166 
167  while ((xlde = readdir(xldir)) != NULL)
168  {
169  if (IsXLogFileName(xlde->d_name))
170  {
171  fd = open_file_in_directory(directory, xlde->d_name);
172  fname = xlde->d_name;
173  break;
174  }
175  }
176 
177  closedir(xldir);
178  }
179 
180  /* set WalSegSz if file is successfully opened */
181  if (fd >= 0)
182  {
184  int r;
185 
186  r = read(fd, buf.data, XLOG_BLCKSZ);
187  if (r == XLOG_BLCKSZ)
188  {
190 
191  WalSegSz = longhdr->xlp_seg_size;
192 
194  fatal_error(ngettext("WAL segment size must be a power of two between 1 MB and 1 GB, but the WAL file \"%s\" header specifies %d byte",
195  "WAL segment size must be a power of two between 1 MB and 1 GB, but the WAL file \"%s\" header specifies %d bytes",
196  WalSegSz),
197  fname, WalSegSz);
198  }
199  else
200  {
201  if (errno != 0)
202  fatal_error("could not read file \"%s\": %m",
203  fname);
204  else
205  fatal_error("could not read file \"%s\": read %d of %zu",
206  fname, r, (Size) XLOG_BLCKSZ);
207  }
208  close(fd);
209  return true;
210  }
211 
212  return false;
213 }
214 
215 /*
216  * Identify the target directory.
217  *
218  * Try to find the file in several places:
219  * if directory != NULL:
220  * directory /
221  * directory / XLOGDIR /
222  * else
223  * .
224  * XLOGDIR /
225  * $PGDATA / XLOGDIR /
226  *
227  * The valid target directory is returned.
228  */
229 static char *
231 {
232  char fpath[MAXPGPATH];
233 
234  if (directory != NULL)
235  {
236  if (search_directory(directory, fname))
237  return pg_strdup(directory);
238 
239  /* directory / XLOGDIR */
240  snprintf(fpath, MAXPGPATH, "%s/%s", directory, XLOGDIR);
241  if (search_directory(fpath, fname))
242  return pg_strdup(fpath);
243  }
244  else
245  {
246  const char *datadir;
247 
248  /* current directory */
249  if (search_directory(".", fname))
250  return pg_strdup(".");
251  /* XLOGDIR */
252  if (search_directory(XLOGDIR, fname))
253  return pg_strdup(XLOGDIR);
254 
255  datadir = getenv("PGDATA");
256  /* $PGDATA / XLOGDIR */
257  if (datadir != NULL)
258  {
259  snprintf(fpath, MAXPGPATH, "%s/%s", datadir, XLOGDIR);
260  if (search_directory(fpath, fname))
261  return pg_strdup(fpath);
262  }
263  }
264 
265  /* could not locate WAL file */
266  if (fname)
267  fatal_error("could not locate WAL file \"%s\"", fname);
268  else
269  fatal_error("could not find any WAL file");
270 
271  return NULL; /* not reached */
272 }
273 
274 /* pg_waldump's XLogReaderRoutine->segment_open callback */
275 static void
277  TimeLineID *tli_p)
278 {
279  TimeLineID tli = *tli_p;
280  char fname[MAXPGPATH];
281  int tries;
282 
283  XLogFileName(fname, tli, nextSegNo, state->segcxt.ws_segsize);
284 
285  /*
286  * In follow mode there is a short period of time after the server has
287  * written the end of the previous file before the new file is available.
288  * So we loop for 5 seconds looking for the file to appear before giving
289  * up.
290  */
291  for (tries = 0; tries < 10; tries++)
292  {
293  state->seg.ws_file = open_file_in_directory(state->segcxt.ws_dir, fname);
294  if (state->seg.ws_file >= 0)
295  return;
296  if (errno == ENOENT)
297  {
298  int save_errno = errno;
299 
300  /* File not there yet, try again */
301  pg_usleep(500 * 1000);
302 
303  errno = save_errno;
304  continue;
305  }
306  /* Any other error, fall through and fail */
307  break;
308  }
309 
310  fatal_error("could not find file \"%s\": %m", fname);
311 }
312 
313 /*
314  * pg_waldump's XLogReaderRoutine->segment_close callback. Same as
315  * wal_segment_close
316  */
317 static void
319 {
320  close(state->seg.ws_file);
321  /* need to check errno? */
322  state->seg.ws_file = -1;
323 }
324 
325 /*
326  * pg_waldump's WAL page reader
327  *
328  * timeline and startptr specifies the LSN, and reads up to endptr.
329  */
330 static bool
333 {
334  XLogRecPtr targetPagePtr = state->readPagePtr;
335  int reqLen = state->reqLen;
336  char *readBuff = state->readBuf;
337  int count = XLOG_BLCKSZ;
338  WALReadError errinfo;
339 
340  /* determine the number of bytes to read on the page */
341  if (endptr != InvalidXLogRecPtr)
342  {
343  if (targetPagePtr + XLOG_BLCKSZ <= endptr)
344  count = XLOG_BLCKSZ;
345  else if (targetPagePtr + reqLen <= endptr)
346  count = endptr - targetPagePtr;
347  else
348  {
349  /* Notify xlogreader that we didn't read at all */
350  XLogReaderSetInputData(state, -1);
351  return false;
352  }
353  }
354 
355  /* We should read more than requested by xlogreader */
356  Assert(count >= state->readLen);
357 
359  readBuff, targetPagePtr, count, timeline, &errinfo))
360  {
361  WALOpenSegment *seg = &errinfo.wre_seg;
362  char fname[MAXPGPATH];
363 
364  XLogFileName(fname, seg->ws_tli, seg->ws_segno,
365  state->segcxt.ws_segsize);
366 
367  if (errinfo.wre_errno != 0)
368  {
369  errno = errinfo.wre_errno;
370  fatal_error("could not read from file %s, offset %u: %m",
371  fname, errinfo.wre_off);
372  }
373  else
374  fatal_error("could not read from file %s, offset %u: read %d of %zu",
375  fname, errinfo.wre_off, errinfo.wre_read,
376  (Size) errinfo.wre_req);
377  }
378 
379  /* Notify xlogreader of how many bytes we have read */
380  XLogReaderSetInputData(state, count);
381  return true;
382 }
383 
384 /*
385  * Calculate the size of a record, split into !FPI and FPI parts.
386  */
387 static void
388 XLogDumpRecordLen(XLogReaderState *record, uint32 *rec_len, uint32 *fpi_len)
389 {
390  int block_id;
391 
392  /*
393  * Calculate the amount of FPI data in the record.
394  *
395  * XXX: We peek into xlogreader's private decoded backup blocks for the
396  * bimg_len indicating the length of FPI data. It doesn't seem worth it to
397  * add an accessor macro for this.
398  */
399  *fpi_len = 0;
400  for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
401  {
402  if (XLogRecHasBlockImage(record, block_id))
403  *fpi_len += record->record->blocks[block_id].bimg_len;
404  }
405 
406  /*
407  * Calculate the length of the record as the total length - the length of
408  * all the block images.
409  */
410  *rec_len = XLogRecGetTotalLen(record) - *fpi_len;
411 }
412 
413 /*
414  * Store per-rmgr and per-record statistics for a given record.
415  */
416 static void
418  XLogReaderState *record)
419 {
420  RmgrId rmid;
421  uint8 recid;
422  uint32 rec_len;
423  uint32 fpi_len;
424 
425  stats->count++;
426 
427  rmid = XLogRecGetRmid(record);
428 
429  XLogDumpRecordLen(record, &rec_len, &fpi_len);
430 
431  /* Update per-rmgr statistics */
432 
433  stats->rmgr_stats[rmid].count++;
434  stats->rmgr_stats[rmid].rec_len += rec_len;
435  stats->rmgr_stats[rmid].fpi_len += fpi_len;
436 
437  /*
438  * Update per-record statistics, where the record is identified by a
439  * combination of the RmgrId and the four bits of the xl_info field that
440  * are the rmgr's domain (resulting in sixteen possible entries per
441  * RmgrId).
442  */
443 
444  recid = XLogRecGetInfo(record) >> 4;
445 
446  /*
447  * XACT records need to be handled differently. Those records use the
448  * first bit of those four bits for an optional flag variable and the
449  * following three bits for the opcode. We filter opcode out of xl_info
450  * and use it as the identifier of the record.
451  */
452  if (rmid == RM_XACT_ID)
453  recid &= 0x07;
454 
455  stats->record_stats[rmid][recid].count++;
456  stats->record_stats[rmid][recid].rec_len += rec_len;
457  stats->record_stats[rmid][recid].fpi_len += fpi_len;
458 }
459 
460 /*
461  * Print a record to stdout
462  */
463 static void
465 {
466  const char *id;
467  const RmgrDescData *desc = &RmgrDescTable[XLogRecGetRmid(record)];
468  uint32 rec_len;
469  uint32 fpi_len;
470  RelFileNode rnode;
471  ForkNumber forknum;
472  BlockNumber blk;
473  int block_id;
474  uint8 info = XLogRecGetInfo(record);
475  XLogRecPtr xl_prev = XLogRecGetPrev(record);
476  StringInfoData s;
477 
478  XLogDumpRecordLen(record, &rec_len, &fpi_len);
479 
480  printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, ",
481  desc->rm_name,
482  rec_len, XLogRecGetTotalLen(record),
483  XLogRecGetXid(record),
484  LSN_FORMAT_ARGS(record->ReadRecPtr),
485  LSN_FORMAT_ARGS(xl_prev));
486 
487  id = desc->rm_identify(info);
488  if (id == NULL)
489  printf("desc: UNKNOWN (%x) ", info & ~XLR_INFO_MASK);
490  else
491  printf("desc: %s ", id);
492 
493  initStringInfo(&s);
494  desc->rm_desc(&s, record);
495  printf("%s", s.data);
496  pfree(s.data);
497 
498  if (!config->bkp_details)
499  {
500  /* print block references (short format) */
501  for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
502  {
503  if (!XLogRecHasBlockRef(record, block_id))
504  continue;
505 
506  XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk);
507  if (forknum != MAIN_FORKNUM)
508  printf(", blkref #%u: rel %u/%u/%u fork %s blk %u",
509  block_id,
510  rnode.spcNode, rnode.dbNode, rnode.relNode,
511  forkNames[forknum],
512  blk);
513  else
514  printf(", blkref #%u: rel %u/%u/%u blk %u",
515  block_id,
516  rnode.spcNode, rnode.dbNode, rnode.relNode,
517  blk);
518  if (XLogRecHasBlockImage(record, block_id))
519  {
520  if (XLogRecBlockImageApply(record, block_id))
521  printf(" FPW");
522  else
523  printf(" FPW for WAL verification");
524  }
525  }
526  putchar('\n');
527  }
528  else
529  {
530  /* print block references (detailed format) */
531  putchar('\n');
532  for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
533  {
534  if (!XLogRecHasBlockRef(record, block_id))
535  continue;
536 
537  XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk);
538  printf("\tblkref #%u: rel %u/%u/%u fork %s blk %u",
539  block_id,
540  rnode.spcNode, rnode.dbNode, rnode.relNode,
541  forkNames[forknum],
542  blk);
543  if (XLogRecHasBlockImage(record, block_id))
544  {
545  if (record->record->blocks[block_id].bimg_info &
547  {
548  printf(" (FPW%s); hole: offset: %u, length: %u, "
549  "compression saved: %u",
550  XLogRecBlockImageApply(record, block_id) ?
551  "" : " for WAL verification",
552  record->record->blocks[block_id].hole_offset,
553  record->record->blocks[block_id].hole_length,
554  BLCKSZ -
555  record->record->blocks[block_id].hole_length -
556  record->record->blocks[block_id].bimg_len);
557  }
558  else
559  {
560  printf(" (FPW%s); hole: offset: %u, length: %u",
561  XLogRecBlockImageApply(record, block_id) ?
562  "" : " for WAL verification",
563  record->record->blocks[block_id].hole_offset,
564  record->record->blocks[block_id].hole_length);
565  }
566  }
567  putchar('\n');
568  }
569  }
570 }
571 
572 /*
573  * Display a single row of record counts and sizes for an rmgr or record.
574  */
575 static void
576 XLogDumpStatsRow(const char *name,
577  uint64 n, uint64 total_count,
578  uint64 rec_len, uint64 total_rec_len,
579  uint64 fpi_len, uint64 total_fpi_len,
580  uint64 tot_len, uint64 total_len)
581 {
582  double n_pct,
583  rec_len_pct,
584  fpi_len_pct,
585  tot_len_pct;
586 
587  n_pct = 0;
588  if (total_count != 0)
589  n_pct = 100 * (double) n / total_count;
590 
591  rec_len_pct = 0;
592  if (total_rec_len != 0)
593  rec_len_pct = 100 * (double) rec_len / total_rec_len;
594 
595  fpi_len_pct = 0;
596  if (total_fpi_len != 0)
597  fpi_len_pct = 100 * (double) fpi_len / total_fpi_len;
598 
599  tot_len_pct = 0;
600  if (total_len != 0)
601  tot_len_pct = 100 * (double) tot_len / total_len;
602 
603  printf("%-27s "
604  "%20" INT64_MODIFIER "u (%6.02f) "
605  "%20" INT64_MODIFIER "u (%6.02f) "
606  "%20" INT64_MODIFIER "u (%6.02f) "
607  "%20" INT64_MODIFIER "u (%6.02f)\n",
608  name, n, n_pct, rec_len, rec_len_pct, fpi_len, fpi_len_pct,
609  tot_len, tot_len_pct);
610 }
611 
612 
613 /*
614  * Display summary statistics about the records seen so far.
615  */
616 static void
618 {
619  int ri,
620  rj;
621  uint64 total_count = 0;
622  uint64 total_rec_len = 0;
623  uint64 total_fpi_len = 0;
624  uint64 total_len = 0;
625  double rec_len_pct,
626  fpi_len_pct;
627 
628  /*
629  * Each row shows its percentages of the total, so make a first pass to
630  * calculate column totals.
631  */
632 
633  for (ri = 0; ri < RM_NEXT_ID; ri++)
634  {
635  total_count += stats->rmgr_stats[ri].count;
636  total_rec_len += stats->rmgr_stats[ri].rec_len;
637  total_fpi_len += stats->rmgr_stats[ri].fpi_len;
638  }
639  total_len = total_rec_len + total_fpi_len;
640 
641  /*
642  * 27 is strlen("Transaction/COMMIT_PREPARED"), 20 is strlen(2^64), 8 is
643  * strlen("(100.00%)")
644  */
645 
646  printf("%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n"
647  "%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n",
648  "Type", "N", "(%)", "Record size", "(%)", "FPI size", "(%)", "Combined size", "(%)",
649  "----", "-", "---", "-----------", "---", "--------", "---", "-------------", "---");
650 
651  for (ri = 0; ri < RM_NEXT_ID; ri++)
652  {
653  uint64 count,
654  rec_len,
655  fpi_len,
656  tot_len;
657  const RmgrDescData *desc = &RmgrDescTable[ri];
658 
659  if (!config->stats_per_record)
660  {
661  count = stats->rmgr_stats[ri].count;
662  rec_len = stats->rmgr_stats[ri].rec_len;
663  fpi_len = stats->rmgr_stats[ri].fpi_len;
664  tot_len = rec_len + fpi_len;
665 
667  count, total_count, rec_len, total_rec_len,
668  fpi_len, total_fpi_len, tot_len, total_len);
669  }
670  else
671  {
672  for (rj = 0; rj < MAX_XLINFO_TYPES; rj++)
673  {
674  const char *id;
675 
676  count = stats->record_stats[ri][rj].count;
677  rec_len = stats->record_stats[ri][rj].rec_len;
678  fpi_len = stats->record_stats[ri][rj].fpi_len;
679  tot_len = rec_len + fpi_len;
680 
681  /* Skip undefined combinations and ones that didn't occur */
682  if (count == 0)
683  continue;
684 
685  /* the upper four bits in xl_info are the rmgr's */
686  id = desc->rm_identify(rj << 4);
687  if (id == NULL)
688  id = psprintf("UNKNOWN (%x)", rj << 4);
689 
690  XLogDumpStatsRow(psprintf("%s/%s", desc->rm_name, id),
691  count, total_count, rec_len, total_rec_len,
692  fpi_len, total_fpi_len, tot_len, total_len);
693  }
694  }
695  }
696 
697  printf("%-27s %20s %8s %20s %8s %20s %8s %20s\n",
698  "", "--------", "", "--------", "", "--------", "", "--------");
699 
700  /*
701  * The percentages in earlier rows were calculated against the column
702  * total, but the ones that follow are against the row total. Note that
703  * these are displayed with a % symbol to differentiate them from the
704  * earlier ones, and are thus up to 9 characters long.
705  */
706 
707  rec_len_pct = 0;
708  if (total_len != 0)
709  rec_len_pct = 100 * (double) total_rec_len / total_len;
710 
711  fpi_len_pct = 0;
712  if (total_len != 0)
713  fpi_len_pct = 100 * (double) total_fpi_len / total_len;
714 
715  printf("%-27s "
716  "%20" INT64_MODIFIER "u %-9s"
717  "%20" INT64_MODIFIER "u %-9s"
718  "%20" INT64_MODIFIER "u %-9s"
719  "%20" INT64_MODIFIER "u %-6s\n",
720  "Total", stats->count, "",
721  total_rec_len, psprintf("[%.02f%%]", rec_len_pct),
722  total_fpi_len, psprintf("[%.02f%%]", fpi_len_pct),
723  total_len, "[100%]");
724 }
725 
726 static void
727 usage(void)
728 {
729  printf(_("%s decodes and displays PostgreSQL write-ahead logs for debugging.\n\n"),
730  progname);
731  printf(_("Usage:\n"));
732  printf(_(" %s [OPTION]... [STARTSEG [ENDSEG]]\n"), progname);
733  printf(_("\nOptions:\n"));
734  printf(_(" -b, --bkp-details output detailed information about backup blocks\n"));
735  printf(_(" -e, --end=RECPTR stop reading at WAL location RECPTR\n"));
736  printf(_(" -f, --follow keep retrying after reaching end of WAL\n"));
737  printf(_(" -n, --limit=N number of records to display\n"));
738  printf(_(" -p, --path=PATH directory in which to find log segment files or a\n"
739  " directory with a ./pg_wal that contains such files\n"
740  " (default: current directory, ./pg_wal, $PGDATA/pg_wal)\n"));
741  printf(_(" -q, --quiet do not print any output, except for errors\n"));
742  printf(_(" -r, --rmgr=RMGR only show records generated by resource manager RMGR;\n"
743  " use --rmgr=list to list valid resource manager names\n"));
744  printf(_(" -s, --start=RECPTR start reading at WAL location RECPTR\n"));
745  printf(_(" -t, --timeline=TLI timeline from which to read log records\n"
746  " (default: 1 or the value used in STARTSEG)\n"));
747  printf(_(" -V, --version output version information, then exit\n"));
748  printf(_(" -x, --xid=XID only show records with transaction ID XID\n"));
749  printf(_(" -z, --stats[=record] show statistics instead of records\n"
750  " (optionally, show per-record statistics)\n"));
751  printf(_(" -?, --help show this help, then exit\n"));
752  printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
753  printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
754 }
755 
756 int
757 main(int argc, char **argv)
758 {
759  uint32 xlogid;
760  uint32 xrecoff;
761  XLogReaderState *xlogreader_state;
762  XLogFindNextRecordState *findnext_state;
763  TimeLineID timeline;
765  XLogRecPtr endptr;
766  XLogDumpConfig config;
768  XLogRecord *record;
769  XLogRecPtr first_record;
770  char *waldir = NULL;
771  char *errormsg;
772 
773  static struct option long_options[] = {
774  {"bkp-details", no_argument, NULL, 'b'},
775  {"end", required_argument, NULL, 'e'},
776  {"follow", no_argument, NULL, 'f'},
777  {"help", no_argument, NULL, '?'},
778  {"limit", required_argument, NULL, 'n'},
779  {"path", required_argument, NULL, 'p'},
780  {"quiet", no_argument, NULL, 'q'},
781  {"rmgr", required_argument, NULL, 'r'},
782  {"start", required_argument, NULL, 's'},
783  {"timeline", required_argument, NULL, 't'},
784  {"xid", required_argument, NULL, 'x'},
785  {"version", no_argument, NULL, 'V'},
786  {"stats", optional_argument, NULL, 'z'},
787  {NULL, 0, NULL, 0}
788  };
789 
790  int option;
791  int optindex = 0;
792 
793  pg_logging_init(argv[0]);
794  set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_waldump"));
795  progname = get_progname(argv[0]);
796 
797  if (argc > 1)
798  {
799  if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
800  {
801  usage();
802  exit(0);
803  }
804  if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
805  {
806  puts("pg_waldump (PostgreSQL) " PG_VERSION);
807  exit(0);
808  }
809  }
810 
811  memset(&config, 0, sizeof(XLogDumpConfig));
812  memset(&stats, 0, sizeof(XLogDumpStats));
813 
814  timeline = 1;
815  startptr = InvalidXLogRecPtr;
816  endptr = InvalidXLogRecPtr;
817 
818  config.quiet = false;
819  config.bkp_details = false;
820  config.stop_after_records = -1;
821  config.already_displayed_records = 0;
822  config.follow = false;
823  config.filter_by_rmgr = -1;
825  config.filter_by_xid_enabled = false;
826  config.stats = false;
827  config.stats_per_record = false;
828 
829  if (argc <= 1)
830  {
831  pg_log_error("no arguments specified");
832  goto bad_argument;
833  }
834 
835  while ((option = getopt_long(argc, argv, "be:fn:p:qr:s:t:x:z",
836  long_options, &optindex)) != -1)
837  {
838  switch (option)
839  {
840  case 'b':
841  config.bkp_details = true;
842  break;
843  case 'e':
844  if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
845  {
846  pg_log_error("could not parse end WAL location \"%s\"",
847  optarg);
848  goto bad_argument;
849  }
850  endptr = (uint64) xlogid << 32 | xrecoff;
851  break;
852  case 'f':
853  config.follow = true;
854  break;
855  case 'n':
856  if (sscanf(optarg, "%d", &config.stop_after_records) != 1)
857  {
858  pg_log_error("could not parse limit \"%s\"", optarg);
859  goto bad_argument;
860  }
861  break;
862  case 'p':
863  waldir = pg_strdup(optarg);
864  break;
865  case 'q':
866  config.quiet = true;
867  break;
868  case 'r':
869  {
870  int i;
871 
872  if (pg_strcasecmp(optarg, "list") == 0)
873  {
874  print_rmgr_list();
875  exit(EXIT_SUCCESS);
876  }
877 
878  for (i = 0; i <= RM_MAX_ID; i++)
879  {
880  if (pg_strcasecmp(optarg, RmgrDescTable[i].rm_name) == 0)
881  {
882  config.filter_by_rmgr = i;
883  break;
884  }
885  }
886 
887  if (config.filter_by_rmgr == -1)
888  {
889  pg_log_error("resource manager \"%s\" does not exist",
890  optarg);
891  goto bad_argument;
892  }
893  }
894  break;
895  case 's':
896  if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
897  {
898  pg_log_error("could not parse start WAL location \"%s\"",
899  optarg);
900  goto bad_argument;
901  }
902  else
903  startptr = (uint64) xlogid << 32 | xrecoff;
904  break;
905  case 't':
906  if (sscanf(optarg, "%d", &timeline) != 1)
907  {
908  pg_log_error("could not parse timeline \"%s\"", optarg);
909  goto bad_argument;
910  }
911  break;
912  case 'x':
913  if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
914  {
915  pg_log_error("could not parse \"%s\" as a transaction ID",
916  optarg);
917  goto bad_argument;
918  }
919  config.filter_by_xid_enabled = true;
920  break;
921  case 'z':
922  config.stats = true;
923  config.stats_per_record = false;
924  if (optarg)
925  {
926  if (strcmp(optarg, "record") == 0)
927  config.stats_per_record = true;
928  else if (strcmp(optarg, "rmgr") != 0)
929  {
930  pg_log_error("unrecognized argument to --stats: %s",
931  optarg);
932  goto bad_argument;
933  }
934  }
935  break;
936  default:
937  goto bad_argument;
938  }
939  }
940 
941  if ((optind + 2) < argc)
942  {
943  pg_log_error("too many command-line arguments (first is \"%s\")",
944  argv[optind + 2]);
945  goto bad_argument;
946  }
947 
948  if (waldir != NULL)
949  {
950  /* validate path points to directory */
951  if (!verify_directory(waldir))
952  {
953  pg_log_error("could not open directory \"%s\": %m", waldir);
954  goto bad_argument;
955  }
956  }
957 
958  /* parse files as start/end boundaries, extract path if not specified */
959  if (optind < argc)
960  {
961  char *directory = NULL;
962  char *fname = NULL;
963  int fd;
964  XLogSegNo segno;
965 
966  split_path(argv[optind], &directory, &fname);
967 
968  if (waldir == NULL && directory != NULL)
969  {
970  waldir = directory;
971 
972  if (!verify_directory(waldir))
973  fatal_error("could not open directory \"%s\": %m", waldir);
974  }
975 
976  waldir = identify_target_directory(waldir, fname);
977  fd = open_file_in_directory(waldir, fname);
978  if (fd < 0)
979  fatal_error("could not open file \"%s\"", fname);
980  close(fd);
981 
982  /* parse position from file */
983  XLogFromFileName(fname, &timeline, &segno, WalSegSz);
984 
985  if (XLogRecPtrIsInvalid(startptr))
986  XLogSegNoOffsetToRecPtr(segno, 0, WalSegSz, startptr);
987  else if (!XLByteInSeg(startptr, segno, WalSegSz))
988  {
989  pg_log_error("start WAL location %X/%X is not inside file \"%s\"",
990  LSN_FORMAT_ARGS(startptr),
991  fname);
992  goto bad_argument;
993  }
994 
995  /* no second file specified, set end position */
996  if (!(optind + 1 < argc) && XLogRecPtrIsInvalid(endptr))
997  XLogSegNoOffsetToRecPtr(segno + 1, 0, WalSegSz, endptr);
998 
999  /* parse ENDSEG if passed */
1000  if (optind + 1 < argc)
1001  {
1002  XLogSegNo endsegno;
1003 
1004  /* ignore directory, already have that */
1005  split_path(argv[optind + 1], &directory, &fname);
1006 
1007  fd = open_file_in_directory(waldir, fname);
1008  if (fd < 0)
1009  fatal_error("could not open file \"%s\"", fname);
1010  close(fd);
1011 
1012  /* parse position from file */
1013  XLogFromFileName(fname, &timeline, &endsegno, WalSegSz);
1014 
1015  if (endsegno < segno)
1016  fatal_error("ENDSEG %s is before STARTSEG %s",
1017  argv[optind + 1], argv[optind]);
1018 
1019  if (XLogRecPtrIsInvalid(endptr))
1020  XLogSegNoOffsetToRecPtr(endsegno + 1, 0, WalSegSz,
1021  endptr);
1022 
1023  /* set segno to endsegno for check of --end */
1024  segno = endsegno;
1025  }
1026 
1027 
1028  if (!XLByteInSeg(endptr, segno, WalSegSz) &&
1029  endptr != (segno + 1) * WalSegSz)
1030  {
1031  pg_log_error("end WAL location %X/%X is not inside file \"%s\"",
1032  LSN_FORMAT_ARGS(endptr),
1033  argv[argc - 1]);
1034  goto bad_argument;
1035  }
1036  }
1037  else
1038  waldir = identify_target_directory(waldir, NULL);
1039 
1040  /* we don't know what to print */
1041  if (XLogRecPtrIsInvalid(startptr))
1042  {
1043  pg_log_error("no start WAL location given");
1044  goto bad_argument;
1045  }
1046 
1047  /* done with argument parsing, do the actual work */
1048 
1049  /* we have everything we need, start reading */
1050  xlogreader_state =
1052 
1053  if (!xlogreader_state)
1054  fatal_error("out of memory");
1055 
1056  findnext_state =
1057  InitXLogFindNextRecord(xlogreader_state, startptr);
1058 
1059  if (!findnext_state)
1060  fatal_error("out of memory");
1061 
1062  /* first find a valid recptr to start from */
1063  while (XLogFindNextRecord(findnext_state))
1064  {
1065  if (!WALDumpReadPage(xlogreader_state, timeline, startptr, endptr))
1066  break;
1067  }
1068 
1069  first_record = findnext_state->currRecPtr;
1070  if (first_record == InvalidXLogRecPtr)
1071  fatal_error("could not find a valid record after %X/%X",
1072  LSN_FORMAT_ARGS(startptr));
1073 
1074  /*
1075  * Display a message that we're skipping data if `from` wasn't a pointer
1076  * to the start of a record and also wasn't a pointer to the beginning of
1077  * a segment (e.g. we were used in file mode).
1078  */
1079  if (first_record != startptr &&
1080  XLogSegmentOffset(startptr, WalSegSz) != 0)
1081  printf(ngettext("first record is after %X/%X, at %X/%X, skipping over %u byte\n",
1082  "first record is after %X/%X, at %X/%X, skipping over %u bytes\n",
1083  (first_record - startptr)),
1084  LSN_FORMAT_ARGS(startptr),
1085  LSN_FORMAT_ARGS(first_record),
1086  (uint32) (first_record - startptr));
1087 
1088  for (;;)
1089  {
1090  /* try to read the next record */
1091  while (XLogReadRecord(xlogreader_state, &record, &errormsg) ==
1093  {
1094  if (!WALDumpReadPage(xlogreader_state, timeline, startptr, endptr))
1095  break;
1096  }
1097 
1098  if (!record)
1099  {
1100  if (!config.follow)
1101  break;
1102  else
1103  {
1104  pg_usleep(1000000L); /* 1 second */
1105  continue;
1106  }
1107  }
1108 
1109  /* apply all specified filters */
1110  if (config.filter_by_rmgr != -1 &&
1111  config.filter_by_rmgr != record->xl_rmid)
1112  continue;
1113 
1114  if (config.filter_by_xid_enabled &&
1115  config.filter_by_xid != record->xl_xid)
1116  continue;
1117 
1118  /* perform any per-record work */
1119  if (!config.quiet)
1120  {
1121  if (config.stats == true)
1122  XLogDumpCountRecord(&config, &stats, xlogreader_state);
1123  else
1124  XLogDumpDisplayRecord(&config, xlogreader_state);
1125  }
1126 
1127  /* check whether we printed enough */
1128  config.already_displayed_records++;
1129  if (config.stop_after_records > 0 &&
1131  break;
1132  }
1133 
1134  if (config.stats == true && !config.quiet)
1135  XLogDumpDisplayStats(&config, &stats);
1136 
1137  if (errormsg)
1138  fatal_error("error in WAL record at %X/%X: %s",
1139  LSN_FORMAT_ARGS(xlogreader_state->ReadRecPtr),
1140  errormsg);
1141 
1142  XLogReaderFree(xlogreader_state);
1143 
1144  return EXIT_SUCCESS;
1145 
1146 bad_argument:
1147  fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
1148  return EXIT_FAILURE;
1149 }
#define fatal_error(...)
Definition: pg_waldump.c:65
WALOpenSegment wre_seg
Definition: xlogreader.h:358
static bool WALDumpReadPage(XLogReaderState *state, TimeLineID timeline, XLogRecPtr startptr, XLogRecPtr endptr)
Definition: pg_waldump.c:331
#define IsValidWalSegSize(size)
Definition: xlog_internal.h:94
static void usage(void)
Definition: pg_waldump.c:727
static void WALDumpOpenSegment(XLogReaderState *state, XLogSegNo nextSegNo, TimeLineID *tli_p)
Definition: pg_waldump.c:276
char ws_dir[MAXPGPATH]
Definition: xlogreader.h:55
static void XLogDumpDisplayStats(XLogDumpConfig *config, XLogDumpStats *stats)
Definition: pg_waldump.c:617
#define InvalidXLogRecPtr
Definition: xlogdefs.h:28
int already_displayed_records
Definition: pg_waldump.c:38
char * pnstrdup(const char *in, Size len)
Definition: mcxt.c:1310
uint32 TimeLineID
Definition: xlogdefs.h:59
#define EXIT_SUCCESS
Definition: settings.h:158
static bool search_directory(const char *directory, const char *fname)
Definition: pg_waldump.c:149
uint32 TransactionId
Definition: c.h:587
XLogReaderState * XLogReaderAllocate(int wal_segment_size, const char *waldir, WALSegmentCleanupCB cleanup_cb)
Definition: xlogreader.c:82
uint64 count
Definition: pg_waldump.c:51
#define XLogRecHasBlockImage(decoder, block_id)
Definition: xlogreader.h:390
int filter_by_rmgr
Definition: pg_waldump.c:44
const char * get_progname(const char *argv0)
Definition: path.c:453
#define pg_log_error(...)
Definition: logging.h:80
int getopt_long(int argc, char *const argv[], const char *optstring, const struct option *longopts, int *longindex)
Definition: getopt_long.c:57
void pg_logging_init(const char *argv0)
Definition: logging.c:81
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
uint64 count
Definition: pg_waldump.c:60
uint16 hole_offset
Definition: xlogreader.h:91
struct Stats Stats
unsigned char uint8
Definition: c.h:439
int closedir(DIR *)
Definition: dirent.c:123
#define XLogRecHasBlockRef(decoder, block_id)
Definition: xlogreader.h:387
Stats record_stats[RM_NEXT_ID][MAX_XLINFO_TYPES]
Definition: pg_waldump.c:62
RmgrId xl_rmid
Definition: xlogrecord.h:47
#define printf(...)
Definition: port.h:222
uint32 BlockNumber
Definition: block.h:31
TransactionId filter_by_xid
Definition: pg_waldump.c:45
int pg_strcasecmp(const char *s1, const char *s2)
Definition: pgstrcasecmp.c:36
bool bkp_details
Definition: pg_waldump.c:36
uint16 bimg_len
Definition: xlogreader.h:93
#define fprintf
Definition: port.h:220
XLogReadRecordResult XLogReadRecord(XLogReaderState *state, XLogRecord **record, char **errormsg)
Definition: xlogreader.c:346
Definition: dirent.h:9
static char * identify_target_directory(char *directory, char *fname)
Definition: pg_waldump.c:230
static int fd(const char *x, int i)
Definition: preproc-init.c:105
#define PG_BINARY
Definition: c.h:1271
#define XLogRecGetTotalLen(decoder)
Definition: xlogreader.h:375
#define XLByteInSeg(xlrp, logSegNo, wal_segsz_bytes)
XLogLongPageHeaderData * XLogLongPageHeader
Definition: xlog_internal.h:71
Stats rmgr_stats[RM_NEXT_ID]
Definition: pg_waldump.c:61
WALOpenSegment seg
Definition: xlogreader.h:243
bool filter_by_xid_enabled
Definition: pg_waldump.c:46
void pg_usleep(long microsec)
Definition: signal.c:53
#define LSN_FORMAT_ARGS(lsn)
Definition: xlogdefs.h:43
#define required_argument
Definition: getopt_long.h:25
void pfree(void *pointer)
Definition: mcxt.c:1169
int optind
Definition: getopt.c:50
Definition: dirent.c:25
uint16 hole_length
Definition: xlogreader.h:92
#define IsXLogFileName(fname)
#define MAXPGPATH
const RmgrDescData RmgrDescTable[RM_MAX_ID+1]
Definition: rmgrdesc.c:38
DIR * opendir(const char *)
Definition: dirent.c:33
#define XLogRecGetPrev(decoder)
Definition: xlogreader.h:376
#define XLogFromFileName(fname, tli, logSegNo, wal_segsz_bytes)
static void WALDumpCloseSegment(XLogReaderState *state)
Definition: pg_waldump.c:318
static void XLogDumpCountRecord(XLogDumpConfig *config, XLogDumpStats *stats, XLogReaderState *record)
Definition: pg_waldump.c:417
static char * buf
Definition: pg_test_fsync.c:68
XLogRecPtr readPagePtr
Definition: xlogreader.h:187
uint64 XLogSegNo
Definition: xlogdefs.h:48
static void XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
Definition: pg_waldump.c:464
XLogRecPtr ReadRecPtr
Definition: xlogreader.h:178
XLogSegNo ws_segno
Definition: xlogreader.h:48
char * datadir
char * pg_strdup(const char *in)
Definition: fe_memutils.c:85
#define InvalidTransactionId
Definition: transam.h:31
static void split_path(const char *path, char **dir, char **fname)
Definition: pg_waldump.c:100
unsigned int uint32
Definition: c.h:441
struct XLogDumpConfig XLogDumpConfig
#define XLogRecMaxBlockId(decoder)
Definition: xlogreader.h:385
#define XLogRecGetInfo(decoder)
Definition: xlogreader.h:377
ForkNumber
Definition: relpath.h:40
const char * rm_name
Definition: rmgrdesc.h:16
void initStringInfo(StringInfo str)
Definition: stringinfo.c:59
#define XLogRecPtrIsInvalid(r)
Definition: xlogdefs.h:29
DecodedBkpBlock blocks[FLEXIBLE_ARRAY_MEMBER]
Definition: xlogreader.h:149
void XLogReaderFree(XLogReaderState *state)
Definition: xlogreader.c:142
#define RM_MAX_ID
Definition: rmgr.h:33
bool XLogRecGetBlockTag(XLogReaderState *record, uint8 block_id, RelFileNode *rnode, ForkNumber *forknum, BlockNumber *blknum)
Definition: xlogreader.c:2132
#define XLogRecGetXid(decoder)
Definition: xlogreader.h:379
#define no_argument
Definition: getopt_long.h:24
#define ngettext(s, p, n)
Definition: c.h:1182
#define PG_TEXTDOMAIN(domain)
Definition: c.h:1215
#define BKPIMAGE_IS_COMPRESSED
Definition: xlogrecord.h:147
static int WalSegSz
Definition: pg_waldump.c:30
#define XLOGDIR
#define XLogSegmentOffset(xlogptr, wal_segsz_bytes)
const char *(* rm_identify)(uint8 info)
Definition: rmgrdesc.h:18
DecodedXLogRecord * record
Definition: xlogreader.h:214
uint8 RmgrId
Definition: rmgr.h:11
bool WALRead(XLogReaderState *state, WALSegmentOpenCB segopenfn, WALSegmentCloseCB segclosefn, char *buf, XLogRecPtr startptr, Size count, TimeLineID tli, WALReadError *errinfo)
Definition: xlogreader.c:1660
#define XLogSegNoOffsetToRecPtr(segno, offset, wal_segsz_bytes, dest)
uint64 XLogRecPtr
Definition: xlogdefs.h:21
#define Assert(condition)
Definition: c.h:804
#define XLR_INFO_MASK
Definition: xlogrecord.h:62
#define MAX_XLINFO_TYPES
Definition: pg_waldump.c:56
Definition: regguts.h:317
size_t Size
Definition: c.h:540
#define XLogFileName(fname, tli, logSegNo, wal_segsz_bytes)
static const char * directory
Definition: zic.c:632
static int open_file_in_directory(const char *directory, const char *fname)
Definition: pg_waldump.c:127
#define optional_argument
Definition: getopt_long.h:26
struct dirent * readdir(DIR *)
Definition: dirent.c:78
static bool verify_directory(const char *directory)
Definition: pg_waldump.c:83
const char * name
Definition: encode.c:515
TimeLineID ws_tli
Definition: xlogreader.h:49
uint64 fpi_len
Definition: pg_waldump.c:53
uint64 rec_len
Definition: pg_waldump.c:52
TransactionId xl_xid
Definition: xlogrecord.h:44
void set_pglocale_pgservice(const char *argv0, const char *app)
Definition: exec.c:433
static const char * progname
Definition: pg_waldump.c:28
static void XLogDumpStatsRow(const char *name, uint64 n, uint64 total_count, uint64 rec_len, uint64 total_rec_len, uint64 fpi_len, uint64 total_fpi_len, uint64 tot_len, uint64 total_len)
Definition: pg_waldump.c:576
char * optarg
Definition: getopt.c:52
int i
int stop_after_records
Definition: pg_waldump.c:37
#define EXIT_FAILURE
Definition: settings.h:162
struct XLogDumpStats XLogDumpStats
WALSegmentContext segcxt
Definition: xlogreader.h:242
void(* rm_desc)(StringInfo buf, XLogReaderState *record)
Definition: rmgrdesc.h:17
char d_name[MAX_PATH]
Definition: dirent.h:15
#define XLogRecBlockImageApply(decoder, block_id)
Definition: xlogreader.h:392
static void print_rmgr_list(void)
Definition: pg_waldump.c:68
bool stats_per_record
Definition: pg_waldump.c:41
#define close(a)
Definition: win32.h:12
const char *const forkNames[]
Definition: relpath.c:33
char data[XLOG_BLCKSZ]
Definition: c.h:1149
#define snprintf
Definition: port.h:216
#define _(x)
Definition: elog.c:89
static void XLogReaderSetInputData(XLogReaderState *state, int32 len)
Definition: xlogreader.h:304
int main(int argc, char **argv)
Definition: pg_waldump.c:757
#define read(a, b, c)
Definition: win32.h:13
#define XLogRecGetRmid(decoder)
Definition: xlogreader.h:378
static XLogRecPtr startptr
Definition: basebackup.c:116
static void XLogDumpRecordLen(XLogReaderState *record, uint32 *rec_len, uint32 *fpi_len)
Definition: pg_waldump.c:388