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