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