1 /*-------------------------------------------------------------------------
3 * pg_waldump.c - decode and display WAL
5 * Copyright (c) 2013-2022, PostgreSQL Global Development Group
8 * src/bin/pg_waldump/pg_waldump.c
9 *-------------------------------------------------------------------------
20 #include "access/transam.h"
21 #include "access/xlog_internal.h"
22 #include "access/xlogreader.h"
23 #include "access/xlogrecord.h"
24 #include "common/fe_memutils.h"
25 #include "common/logging.h"
26 #include "getopt_long.h"
29 static const char *progname
;
32 static volatile sig_atomic_t time_to_stop
= false;
34 typedef struct XLogDumpPrivate
42 typedef struct XLogDumpConfig
47 int stop_after_records
;
48 int already_displayed_records
;
51 bool stats_per_record
;
54 bool filter_by_rmgr
[RM_MAX_ID
+ 1];
55 bool filter_by_rmgr_enabled
;
56 TransactionId filter_by_xid
;
57 bool filter_by_xid_enabled
;
67 #define MAX_XLINFO_TYPES 16
69 typedef struct XLogDumpStats
74 Stats rmgr_stats
[RM_NEXT_ID
];
75 Stats record_stats
[RM_NEXT_ID
][MAX_XLINFO_TYPES
];
78 #define fatal_error(...) do { pg_log_fatal(__VA_ARGS__); exit(EXIT_FAILURE); } while(0)
81 * When sigint is called, just tell the system to exit at the next possible
87 sigint_handler(int signum
)
98 for (i
= 0; i
<= RM_MAX_ID
; i
++)
100 printf("%s\n", RmgrDescTable
[i
].rm_name
);
105 * Check whether directory exists and whether we can open it. Keep errno set so
106 * that the caller can report errors somewhat more accurately.
109 verify_directory(const char *directory
)
111 DIR *dir
= opendir(directory
);
120 * Split a pathname as dirname(1) and basename(1) would.
122 * XXX this probably doesn't do very well on Windows. We probably need to
123 * apply canonicalize_path(), at the very least.
126 split_path(const char *path
, char **dir
, char **fname
)
130 /* split filepath into directory & filename */
131 sep
= strrchr(path
, '/');
136 *dir
= pnstrdup(path
, sep
- path
);
137 *fname
= pg_strdup(sep
+ 1);
139 /* local directory */
143 *fname
= pg_strdup(path
);
148 * Open the file in the valid target directory.
150 * return a read only fd
153 open_file_in_directory(const char *directory
, const char *fname
)
156 char fpath
[MAXPGPATH
];
158 Assert(directory
!= NULL
);
160 snprintf(fpath
, MAXPGPATH
, "%s/%s", directory
, fname
);
161 fd
= open(fpath
, O_RDONLY
| PG_BINARY
, 0);
163 if (fd
< 0 && errno
!= ENOENT
)
164 fatal_error("could not open file \"%s\": %m", fname
);
169 * Try to find fname in the given directory. Returns true if it is found,
170 * false otherwise. If fname is NULL, search the complete directory for any
171 * file with a valid WAL file name. If file is successfully opened, set the
175 search_directory(const char *directory
, const char *fname
)
180 /* open file if valid filename is provided */
182 fd
= open_file_in_directory(directory
, fname
);
185 * A valid file name is not passed, so search the complete directory. If
186 * we find any file whose name is a valid WAL file name then try to open
187 * it. If we cannot open it, bail out.
189 else if ((xldir
= opendir(directory
)) != NULL
)
193 while ((xlde
= readdir(xldir
)) != NULL
)
195 if (IsXLogFileName(xlde
->d_name
))
197 fd
= open_file_in_directory(directory
, xlde
->d_name
);
198 fname
= xlde
->d_name
;
206 /* set WalSegSz if file is successfully opened */
209 PGAlignedXLogBlock buf
;
212 r
= read(fd
, buf
.data
, XLOG_BLCKSZ
);
213 if (r
== XLOG_BLCKSZ
)
215 XLogLongPageHeader longhdr
= (XLogLongPageHeader
) buf
.data
;
217 WalSegSz
= longhdr
->xlp_seg_size
;
219 if (!IsValidWalSegSize(WalSegSz
))
220 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",
221 "WAL segment size must be a power of two between 1 MB and 1 GB, but the WAL file \"%s\" header specifies %d bytes",
226 fatal_error("could not read file \"%s\": %m",
229 fatal_error("could not read file \"%s\": read %d of %d",
230 fname
, r
, XLOG_BLCKSZ
);
239 * Identify the target directory.
241 * Try to find the file in several places:
242 * if directory != NULL:
244 * directory / XLOGDIR /
248 * $PGDATA / XLOGDIR /
250 * The valid target directory is returned.
253 identify_target_directory(char *directory
, char *fname
)
255 char fpath
[MAXPGPATH
];
257 if (directory
!= NULL
)
259 if (search_directory(directory
, fname
))
260 return pg_strdup(directory
);
262 /* directory / XLOGDIR */
263 snprintf(fpath
, MAXPGPATH
, "%s/%s", directory
, XLOGDIR
);
264 if (search_directory(fpath
, fname
))
265 return pg_strdup(fpath
);
271 /* current directory */
272 if (search_directory(".", fname
))
273 return pg_strdup(".");
275 if (search_directory(XLOGDIR
, fname
))
276 return pg_strdup(XLOGDIR
);
278 datadir
= getenv("PGDATA");
279 /* $PGDATA / XLOGDIR */
282 snprintf(fpath
, MAXPGPATH
, "%s/%s", datadir
, XLOGDIR
);
283 if (search_directory(fpath
, fname
))
284 return pg_strdup(fpath
);
288 /* could not locate WAL file */
290 fatal_error("could not locate WAL file \"%s\"", fname
);
292 fatal_error("could not find any WAL file");
294 return NULL
; /* not reached */
297 /* pg_waldump's XLogReaderRoutine->segment_open callback */
299 WALDumpOpenSegment(XLogReaderState
*state
, XLogSegNo nextSegNo
,
302 TimeLineID tli
= *tli_p
;
303 char fname
[MAXPGPATH
];
306 XLogFileName(fname
, tli
, nextSegNo
, state
->segcxt
.ws_segsize
);
309 * In follow mode there is a short period of time after the server has
310 * written the end of the previous file before the new file is available.
311 * So we loop for 5 seconds looking for the file to appear before giving
314 for (tries
= 0; tries
< 10; tries
++)
316 state
->seg
.ws_file
= open_file_in_directory(state
->segcxt
.ws_dir
, fname
);
317 if (state
->seg
.ws_file
>= 0)
321 int save_errno
= errno
;
323 /* File not there yet, try again */
324 pg_usleep(500 * 1000);
329 /* Any other error, fall through and fail */
333 fatal_error("could not find file \"%s\": %m", fname
);
337 * pg_waldump's XLogReaderRoutine->segment_close callback. Same as
341 WALDumpCloseSegment(XLogReaderState
*state
)
343 close(state
->seg
.ws_file
);
344 /* need to check errno? */
345 state
->seg
.ws_file
= -1;
348 /* pg_waldump's XLogReaderRoutine->page_read callback */
350 WALDumpReadPage(XLogReaderState
*state
, XLogRecPtr targetPagePtr
, int reqLen
,
351 XLogRecPtr targetPtr
, char *readBuff
)
353 XLogDumpPrivate
*private = state
->private_data
;
354 int count
= XLOG_BLCKSZ
;
355 WALReadError errinfo
;
357 if (private->endptr
!= InvalidXLogRecPtr
)
359 if (targetPagePtr
+ XLOG_BLCKSZ
<= private->endptr
)
361 else if (targetPagePtr
+ reqLen
<= private->endptr
)
362 count
= private->endptr
- targetPagePtr
;
365 private->endptr_reached
= true;
370 if (!WALRead(state
, readBuff
, targetPagePtr
, count
, private->timeline
,
373 WALOpenSegment
*seg
= &errinfo
.wre_seg
;
374 char fname
[MAXPGPATH
];
376 XLogFileName(fname
, seg
->ws_tli
, seg
->ws_segno
,
377 state
->segcxt
.ws_segsize
);
379 if (errinfo
.wre_errno
!= 0)
381 errno
= errinfo
.wre_errno
;
382 fatal_error("could not read from file %s, offset %d: %m",
383 fname
, errinfo
.wre_off
);
386 fatal_error("could not read from file %s, offset %d: read %d of %d",
387 fname
, errinfo
.wre_off
, errinfo
.wre_read
,
395 * Calculate the size of a record, split into !FPI and FPI parts.
398 XLogDumpRecordLen(XLogReaderState
*record
, uint32
*rec_len
, uint32
*fpi_len
)
403 * Calculate the amount of FPI data in the record.
405 * XXX: We peek into xlogreader's private decoded backup blocks for the
406 * bimg_len indicating the length of FPI data.
409 for (block_id
= 0; block_id
<= XLogRecMaxBlockId(record
); block_id
++)
411 if (XLogRecHasBlockImage(record
, block_id
))
412 *fpi_len
+= XLogRecGetBlock(record
, block_id
)->bimg_len
;
416 * Calculate the length of the record as the total length - the length of
417 * all the block images.
419 *rec_len
= XLogRecGetTotalLen(record
) - *fpi_len
;
423 * Store per-rmgr and per-record statistics for a given record.
426 XLogDumpCountRecord(XLogDumpConfig
*config
, XLogDumpStats
*stats
,
427 XLogReaderState
*record
)
436 rmid
= XLogRecGetRmid(record
);
438 XLogDumpRecordLen(record
, &rec_len
, &fpi_len
);
440 /* Update per-rmgr statistics */
442 stats
->rmgr_stats
[rmid
].count
++;
443 stats
->rmgr_stats
[rmid
].rec_len
+= rec_len
;
444 stats
->rmgr_stats
[rmid
].fpi_len
+= fpi_len
;
447 * Update per-record statistics, where the record is identified by a
448 * combination of the RmgrId and the four bits of the xl_info field that
449 * are the rmgr's domain (resulting in sixteen possible entries per
453 recid
= XLogRecGetInfo(record
) >> 4;
456 * XACT records need to be handled differently. Those records use the
457 * first bit of those four bits for an optional flag variable and the
458 * following three bits for the opcode. We filter opcode out of xl_info
459 * and use it as the identifier of the record.
461 if (rmid
== RM_XACT_ID
)
464 stats
->record_stats
[rmid
][recid
].count
++;
465 stats
->record_stats
[rmid
][recid
].rec_len
+= rec_len
;
466 stats
->record_stats
[rmid
][recid
].fpi_len
+= fpi_len
;
470 * Print a record to stdout
473 XLogDumpDisplayRecord(XLogDumpConfig
*config
, XLogReaderState
*record
)
476 const RmgrDescData
*desc
= &RmgrDescTable
[XLogRecGetRmid(record
)];
483 uint8 info
= XLogRecGetInfo(record
);
484 XLogRecPtr xl_prev
= XLogRecGetPrev(record
);
487 XLogDumpRecordLen(record
, &rec_len
, &fpi_len
);
489 printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, ",
491 rec_len
, XLogRecGetTotalLen(record
),
492 XLogRecGetXid(record
),
493 LSN_FORMAT_ARGS(record
->ReadRecPtr
),
494 LSN_FORMAT_ARGS(xl_prev
));
496 id
= desc
->rm_identify(info
);
498 printf("desc: UNKNOWN (%x) ", info
& ~XLR_INFO_MASK
);
500 printf("desc: %s ", id
);
503 desc
->rm_desc(&s
, record
);
504 printf("%s", s
.data
);
507 if (!config
->bkp_details
)
509 /* print block references (short format) */
510 for (block_id
= 0; block_id
<= XLogRecMaxBlockId(record
); block_id
++)
512 if (!XLogRecHasBlockRef(record
, block_id
))
515 XLogRecGetBlockTag(record
, block_id
, &rnode
, &forknum
, &blk
);
516 if (forknum
!= MAIN_FORKNUM
)
517 printf(", blkref #%d: rel %u/%u/%u fork %s blk %u",
519 rnode
.spcNode
, rnode
.dbNode
, rnode
.relNode
,
523 printf(", blkref #%d: rel %u/%u/%u blk %u",
525 rnode
.spcNode
, rnode
.dbNode
, rnode
.relNode
,
527 if (XLogRecHasBlockImage(record
, block_id
))
529 if (XLogRecBlockImageApply(record
, block_id
))
532 printf(" FPW for WAL verification");
539 /* print block references (detailed format) */
541 for (block_id
= 0; block_id
<= XLogRecMaxBlockId(record
); block_id
++)
543 if (!XLogRecHasBlockRef(record
, block_id
))
546 XLogRecGetBlockTag(record
, block_id
, &rnode
, &forknum
, &blk
);
547 printf("\tblkref #%d: rel %u/%u/%u fork %s blk %u",
549 rnode
.spcNode
, rnode
.dbNode
, rnode
.relNode
,
552 if (XLogRecHasBlockImage(record
, block_id
))
554 uint8 bimg_info
= XLogRecGetBlock(record
, block_id
)->bimg_info
;
556 if (BKPIMAGE_COMPRESSED(bimg_info
))
560 if ((bimg_info
& BKPIMAGE_COMPRESS_PGLZ
) != 0)
562 else if ((bimg_info
& BKPIMAGE_COMPRESS_LZ4
) != 0)
564 else if ((bimg_info
& BKPIMAGE_COMPRESS_ZSTD
) != 0)
569 printf(" (FPW%s); hole: offset: %u, length: %u, "
570 "compression saved: %u, method: %s",
571 XLogRecBlockImageApply(record
, block_id
) ?
572 "" : " for WAL verification",
573 XLogRecGetBlock(record
, block_id
)->hole_offset
,
574 XLogRecGetBlock(record
, block_id
)->hole_length
,
576 XLogRecGetBlock(record
, block_id
)->hole_length
-
577 XLogRecGetBlock(record
, block_id
)->bimg_len
,
582 printf(" (FPW%s); hole: offset: %u, length: %u",
583 XLogRecBlockImageApply(record
, block_id
) ?
584 "" : " for WAL verification",
585 XLogRecGetBlock(record
, block_id
)->hole_offset
,
586 XLogRecGetBlock(record
, block_id
)->hole_length
);
595 * Display a single row of record counts and sizes for an rmgr or record.
598 XLogDumpStatsRow(const char *name
,
599 uint64 n
, uint64 total_count
,
600 uint64 rec_len
, uint64 total_rec_len
,
601 uint64 fpi_len
, uint64 total_fpi_len
,
602 uint64 tot_len
, uint64 total_len
)
610 if (total_count
!= 0)
611 n_pct
= 100 * (double) n
/ total_count
;
614 if (total_rec_len
!= 0)
615 rec_len_pct
= 100 * (double) rec_len
/ total_rec_len
;
618 if (total_fpi_len
!= 0)
619 fpi_len_pct
= 100 * (double) fpi_len
/ total_fpi_len
;
623 tot_len_pct
= 100 * (double) tot_len
/ total_len
;
626 "%20" INT64_MODIFIER
"u (%6.02f) "
627 "%20" INT64_MODIFIER
"u (%6.02f) "
628 "%20" INT64_MODIFIER
"u (%6.02f) "
629 "%20" INT64_MODIFIER
"u (%6.02f)\n",
630 name
, n
, n_pct
, rec_len
, rec_len_pct
, fpi_len
, fpi_len_pct
,
631 tot_len
, tot_len_pct
);
636 * Display summary statistics about the records seen so far.
639 XLogDumpDisplayStats(XLogDumpConfig
*config
, XLogDumpStats
*stats
)
643 uint64 total_count
= 0;
644 uint64 total_rec_len
= 0;
645 uint64 total_fpi_len
= 0;
646 uint64 total_len
= 0;
651 * Leave if no stats have been computed yet, as tracked by the end LSN.
653 if (XLogRecPtrIsInvalid(stats
->endptr
))
657 * Each row shows its percentages of the total, so make a first pass to
658 * calculate column totals.
661 for (ri
= 0; ri
< RM_NEXT_ID
; ri
++)
663 total_count
+= stats
->rmgr_stats
[ri
].count
;
664 total_rec_len
+= stats
->rmgr_stats
[ri
].rec_len
;
665 total_fpi_len
+= stats
->rmgr_stats
[ri
].fpi_len
;
667 total_len
= total_rec_len
+ total_fpi_len
;
669 printf("WAL statistics between %X/%X and %X/%X:\n",
670 LSN_FORMAT_ARGS(stats
->startptr
), LSN_FORMAT_ARGS(stats
->endptr
));
673 * 27 is strlen("Transaction/COMMIT_PREPARED"), 20 is strlen(2^64), 8 is
674 * strlen("(100.00%)")
677 printf("%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n"
678 "%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n",
679 "Type", "N", "(%)", "Record size", "(%)", "FPI size", "(%)", "Combined size", "(%)",
680 "----", "-", "---", "-----------", "---", "--------", "---", "-------------", "---");
682 for (ri
= 0; ri
< RM_NEXT_ID
; ri
++)
688 const RmgrDescData
*desc
= &RmgrDescTable
[ri
];
690 if (!config
->stats_per_record
)
692 count
= stats
->rmgr_stats
[ri
].count
;
693 rec_len
= stats
->rmgr_stats
[ri
].rec_len
;
694 fpi_len
= stats
->rmgr_stats
[ri
].fpi_len
;
695 tot_len
= rec_len
+ fpi_len
;
697 XLogDumpStatsRow(desc
->rm_name
,
698 count
, total_count
, rec_len
, total_rec_len
,
699 fpi_len
, total_fpi_len
, tot_len
, total_len
);
703 for (rj
= 0; rj
< MAX_XLINFO_TYPES
; rj
++)
707 count
= stats
->record_stats
[ri
][rj
].count
;
708 rec_len
= stats
->record_stats
[ri
][rj
].rec_len
;
709 fpi_len
= stats
->record_stats
[ri
][rj
].fpi_len
;
710 tot_len
= rec_len
+ fpi_len
;
712 /* Skip undefined combinations and ones that didn't occur */
716 /* the upper four bits in xl_info are the rmgr's */
717 id
= desc
->rm_identify(rj
<< 4);
719 id
= psprintf("UNKNOWN (%x)", rj
<< 4);
721 XLogDumpStatsRow(psprintf("%s/%s", desc
->rm_name
, id
),
722 count
, total_count
, rec_len
, total_rec_len
,
723 fpi_len
, total_fpi_len
, tot_len
, total_len
);
728 printf("%-27s %20s %8s %20s %8s %20s %8s %20s\n",
729 "", "--------", "", "--------", "", "--------", "", "--------");
732 * The percentages in earlier rows were calculated against the column
733 * total, but the ones that follow are against the row total. Note that
734 * these are displayed with a % symbol to differentiate them from the
735 * earlier ones, and are thus up to 9 characters long.
740 rec_len_pct
= 100 * (double) total_rec_len
/ total_len
;
744 fpi_len_pct
= 100 * (double) total_fpi_len
/ total_len
;
747 "%20" INT64_MODIFIER
"u %-9s"
748 "%20" INT64_MODIFIER
"u %-9s"
749 "%20" INT64_MODIFIER
"u %-9s"
750 "%20" INT64_MODIFIER
"u %-6s\n",
751 "Total", stats
->count
, "",
752 total_rec_len
, psprintf("[%.02f%%]", rec_len_pct
),
753 total_fpi_len
, psprintf("[%.02f%%]", fpi_len_pct
),
754 total_len
, "[100%]");
760 printf(_("%s decodes and displays PostgreSQL write-ahead logs for debugging.\n\n"),
762 printf(_("Usage:\n"));
763 printf(_(" %s [OPTION]... [STARTSEG [ENDSEG]]\n"), progname
);
764 printf(_("\nOptions:\n"));
765 printf(_(" -b, --bkp-details output detailed information about backup blocks\n"));
766 printf(_(" -e, --end=RECPTR stop reading at WAL location RECPTR\n"));
767 printf(_(" -f, --follow keep retrying after reaching end of WAL\n"));
768 printf(_(" -n, --limit=N number of records to display\n"));
769 printf(_(" -p, --path=PATH directory in which to find log segment files or a\n"
770 " directory with a ./pg_wal that contains such files\n"
771 " (default: current directory, ./pg_wal, $PGDATA/pg_wal)\n"));
772 printf(_(" -q, --quiet do not print any output, except for errors\n"));
773 printf(_(" -r, --rmgr=RMGR only show records generated by resource manager RMGR;\n"
774 " use --rmgr=list to list valid resource manager names\n"));
775 printf(_(" -s, --start=RECPTR start reading at WAL location RECPTR\n"));
776 printf(_(" -t, --timeline=TLI timeline from which to read log records\n"
777 " (default: 1 or the value used in STARTSEG)\n"));
778 printf(_(" -V, --version output version information, then exit\n"));
779 printf(_(" -x, --xid=XID only show records with transaction ID XID\n"));
780 printf(_(" -z, --stats[=record] show statistics instead of records\n"
781 " (optionally, show per-record statistics)\n"));
782 printf(_(" -?, --help show this help, then exit\n"));
783 printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT
);
784 printf(_("%s home page: <%s>\n"), PACKAGE_NAME
, PACKAGE_URL
);
788 main(int argc
, char **argv
)
792 XLogReaderState
*xlogreader_state
;
793 XLogDumpPrivate
private;
794 XLogDumpConfig config
;
797 XLogRecPtr first_record
;
801 static struct option long_options
[] = {
802 {"bkp-details", no_argument
, NULL
, 'b'},
803 {"end", required_argument
, NULL
, 'e'},
804 {"follow", no_argument
, NULL
, 'f'},
805 {"help", no_argument
, NULL
, '?'},
806 {"limit", required_argument
, NULL
, 'n'},
807 {"path", required_argument
, NULL
, 'p'},
808 {"quiet", no_argument
, NULL
, 'q'},
809 {"rmgr", required_argument
, NULL
, 'r'},
810 {"start", required_argument
, NULL
, 's'},
811 {"timeline", required_argument
, NULL
, 't'},
812 {"xid", required_argument
, NULL
, 'x'},
813 {"version", no_argument
, NULL
, 'V'},
814 {"stats", optional_argument
, NULL
, 'z'},
822 pqsignal(SIGINT
, sigint_handler
);
825 pg_logging_init(argv
[0]);
826 set_pglocale_pgservice(argv
[0], PG_TEXTDOMAIN("pg_waldump"));
827 progname
= get_progname(argv
[0]);
831 if (strcmp(argv
[1], "--help") == 0 || strcmp(argv
[1], "-?") == 0)
836 if (strcmp(argv
[1], "--version") == 0 || strcmp(argv
[1], "-V") == 0)
838 puts("pg_waldump (PostgreSQL) " PG_VERSION
);
843 memset(&private, 0, sizeof(XLogDumpPrivate
));
844 memset(&config
, 0, sizeof(XLogDumpConfig
));
845 memset(&stats
, 0, sizeof(XLogDumpStats
));
847 private.timeline
= 1;
848 private.startptr
= InvalidXLogRecPtr
;
849 private.endptr
= InvalidXLogRecPtr
;
850 private.endptr_reached
= false;
852 config
.quiet
= false;
853 config
.bkp_details
= false;
854 config
.stop_after_records
= -1;
855 config
.already_displayed_records
= 0;
856 config
.follow
= false;
857 /* filter_by_rmgr array was zeroed by memset above */
858 config
.filter_by_rmgr_enabled
= false;
859 config
.filter_by_xid
= InvalidTransactionId
;
860 config
.filter_by_xid_enabled
= false;
861 config
.stats
= false;
862 config
.stats_per_record
= false;
864 stats
.startptr
= InvalidXLogRecPtr
;
865 stats
.endptr
= InvalidXLogRecPtr
;
869 pg_log_error("no arguments specified");
873 while ((option
= getopt_long(argc
, argv
, "be:fn:p:qr:s:t:x:z",
874 long_options
, &optindex
)) != -1)
879 config
.bkp_details
= true;
882 if (sscanf(optarg
, "%X/%X", &xlogid
, &xrecoff
) != 2)
884 pg_log_error("could not parse end WAL location \"%s\"",
888 private.endptr
= (uint64
) xlogid
<< 32 | xrecoff
;
891 config
.follow
= true;
894 if (sscanf(optarg
, "%d", &config
.stop_after_records
) != 1)
896 pg_log_error("could not parse limit \"%s\"", optarg
);
901 waldir
= pg_strdup(optarg
);
910 if (pg_strcasecmp(optarg
, "list") == 0)
916 for (i
= 0; i
<= RM_MAX_ID
; i
++)
918 if (pg_strcasecmp(optarg
, RmgrDescTable
[i
].rm_name
) == 0)
920 config
.filter_by_rmgr
[i
] = true;
921 config
.filter_by_rmgr_enabled
= true;
927 pg_log_error("resource manager \"%s\" does not exist",
934 if (sscanf(optarg
, "%X/%X", &xlogid
, &xrecoff
) != 2)
936 pg_log_error("could not parse start WAL location \"%s\"",
941 private.startptr
= (uint64
) xlogid
<< 32 | xrecoff
;
944 if (sscanf(optarg
, "%u", &private.timeline
) != 1)
946 pg_log_error("could not parse timeline \"%s\"", optarg
);
951 if (sscanf(optarg
, "%u", &config
.filter_by_xid
) != 1)
953 pg_log_error("could not parse \"%s\" as a transaction ID",
957 config
.filter_by_xid_enabled
= true;
961 config
.stats_per_record
= false;
964 if (strcmp(optarg
, "record") == 0)
965 config
.stats_per_record
= true;
966 else if (strcmp(optarg
, "rmgr") != 0)
968 pg_log_error("unrecognized argument to --stats: %s",
979 if ((optind
+ 2) < argc
)
981 pg_log_error("too many command-line arguments (first is \"%s\")",
988 /* validate path points to directory */
989 if (!verify_directory(waldir
))
991 pg_log_error("could not open directory \"%s\": %m", waldir
);
996 /* parse files as start/end boundaries, extract path if not specified */
999 char *directory
= NULL
;
1004 split_path(argv
[optind
], &directory
, &fname
);
1006 if (waldir
== NULL
&& directory
!= NULL
)
1010 if (!verify_directory(waldir
))
1011 fatal_error("could not open directory \"%s\": %m", waldir
);
1014 waldir
= identify_target_directory(waldir
, fname
);
1015 fd
= open_file_in_directory(waldir
, fname
);
1017 fatal_error("could not open file \"%s\"", fname
);
1020 /* parse position from file */
1021 XLogFromFileName(fname
, &private.timeline
, &segno
, WalSegSz
);
1023 if (XLogRecPtrIsInvalid(private.startptr
))
1024 XLogSegNoOffsetToRecPtr(segno
, 0, WalSegSz
, private.startptr
);
1025 else if (!XLByteInSeg(private.startptr
, segno
, WalSegSz
))
1027 pg_log_error("start WAL location %X/%X is not inside file \"%s\"",
1028 LSN_FORMAT_ARGS(private.startptr
),
1033 /* no second file specified, set end position */
1034 if (!(optind
+ 1 < argc
) && XLogRecPtrIsInvalid(private.endptr
))
1035 XLogSegNoOffsetToRecPtr(segno
+ 1, 0, WalSegSz
, private.endptr
);
1037 /* parse ENDSEG if passed */
1038 if (optind
+ 1 < argc
)
1042 /* ignore directory, already have that */
1043 split_path(argv
[optind
+ 1], &directory
, &fname
);
1045 fd
= open_file_in_directory(waldir
, fname
);
1047 fatal_error("could not open file \"%s\"", fname
);
1050 /* parse position from file */
1051 XLogFromFileName(fname
, &private.timeline
, &endsegno
, WalSegSz
);
1053 if (endsegno
< segno
)
1054 fatal_error("ENDSEG %s is before STARTSEG %s",
1055 argv
[optind
+ 1], argv
[optind
]);
1057 if (XLogRecPtrIsInvalid(private.endptr
))
1058 XLogSegNoOffsetToRecPtr(endsegno
+ 1, 0, WalSegSz
,
1061 /* set segno to endsegno for check of --end */
1066 if (!XLByteInSeg(private.endptr
, segno
, WalSegSz
) &&
1067 private.endptr
!= (segno
+ 1) * WalSegSz
)
1069 pg_log_error("end WAL location %X/%X is not inside file \"%s\"",
1070 LSN_FORMAT_ARGS(private.endptr
),
1076 waldir
= identify_target_directory(waldir
, NULL
);
1078 /* we don't know what to print */
1079 if (XLogRecPtrIsInvalid(private.startptr
))
1081 pg_log_error("no start WAL location given");
1085 /* done with argument parsing, do the actual work */
1087 /* we have everything we need, start reading */
1089 XLogReaderAllocate(WalSegSz
, waldir
,
1090 XL_ROUTINE(.page_read
= WALDumpReadPage
,
1091 .segment_open
= WALDumpOpenSegment
,
1092 .segment_close
= WALDumpCloseSegment
),
1094 if (!xlogreader_state
)
1095 fatal_error("out of memory while allocating a WAL reading processor");
1097 /* first find a valid recptr to start from */
1098 first_record
= XLogFindNextRecord(xlogreader_state
, private.startptr
);
1100 if (first_record
== InvalidXLogRecPtr
)
1101 fatal_error("could not find a valid record after %X/%X",
1102 LSN_FORMAT_ARGS(private.startptr
));
1105 * Display a message that we're skipping data if `from` wasn't a pointer
1106 * to the start of a record and also wasn't a pointer to the beginning of
1107 * a segment (e.g. we were used in file mode).
1109 if (first_record
!= private.startptr
&&
1110 XLogSegmentOffset(private.startptr
, WalSegSz
) != 0)
1111 printf(ngettext("first record is after %X/%X, at %X/%X, skipping over %u byte\n",
1112 "first record is after %X/%X, at %X/%X, skipping over %u bytes\n",
1113 (first_record
- private.startptr
)),
1114 LSN_FORMAT_ARGS(private.startptr
),
1115 LSN_FORMAT_ARGS(first_record
),
1116 (uint32
) (first_record
- private.startptr
));
1118 if (config
.stats
== true && !config
.quiet
)
1119 stats
.startptr
= first_record
;
1125 /* We've been Ctrl-C'ed, so leave */
1129 /* try to read the next record */
1130 record
= XLogReadRecord(xlogreader_state
, &errormsg
);
1133 if (!config
.follow
|| private.endptr_reached
)
1137 pg_usleep(1000000L); /* 1 second */
1142 /* apply all specified filters */
1143 if (config
.filter_by_rmgr_enabled
&&
1144 !config
.filter_by_rmgr
[record
->xl_rmid
])
1147 if (config
.filter_by_xid_enabled
&&
1148 config
.filter_by_xid
!= record
->xl_xid
)
1151 /* perform any per-record work */
1154 if (config
.stats
== true)
1156 XLogDumpCountRecord(&config
, &stats
, xlogreader_state
);
1157 stats
.endptr
= xlogreader_state
->EndRecPtr
;
1160 XLogDumpDisplayRecord(&config
, xlogreader_state
);
1163 /* check whether we printed enough */
1164 config
.already_displayed_records
++;
1165 if (config
.stop_after_records
> 0 &&
1166 config
.already_displayed_records
>= config
.stop_after_records
)
1170 if (config
.stats
== true && !config
.quiet
)
1171 XLogDumpDisplayStats(&config
, &stats
);
1177 fatal_error("error in WAL record at %X/%X: %s",
1178 LSN_FORMAT_ARGS(xlogreader_state
->ReadRecPtr
),
1181 XLogReaderFree(xlogreader_state
);
1183 return EXIT_SUCCESS
;
1186 fprintf(stderr
, _("Try \"%s --help\" for more information.\n"), progname
);
1187 return EXIT_FAILURE
;