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