]> granicus.if.org Git - postgresql/blob - src/bin/pg_waldump/pg_waldump.c
Message translatability fixes
[postgresql] / src / bin / pg_waldump / pg_waldump.c
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 {
31         TimeLineID      timeline;
32         char       *inpath;
33         XLogRecPtr      startptr;
34         XLogRecPtr      endptr;
35         bool            endptr_reached;
36 } XLogDumpPrivate;
37
38 typedef struct XLogDumpConfig
39 {
40         /* display options */
41         bool            bkp_details;
42         int                     stop_after_records;
43         int                     already_displayed_records;
44         bool            follow;
45         bool            stats;
46         bool            stats_per_record;
47
48         /* filter options */
49         int                     filter_by_rmgr;
50         TransactionId filter_by_xid;
51         bool            filter_by_xid_enabled;
52 } XLogDumpConfig;
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;
66         Stats           rmgr_stats[RM_NEXT_ID];
67         Stats           record_stats[RM_NEXT_ID][MAX_XLINFO_TYPES];
68 } XLogDumpStats;
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
92 print_rmgr_list(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
107 verify_directory(const char *directory)
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 server
264                          * has written the end of the previous file before the new file is
265                          * available. So we loop for 5 seconds looking for the file to
266                          * 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 file %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 file %s, offset %u, 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  * Calculate the size of a record, split into !FPI and FPI parts.
367  */
368 static void
369 XLogDumpRecordLen(XLogReaderState *record, uint32 *rec_len, uint32 *fpi_len)
370 {
371         int                     block_id;
372
373         /*
374          * Calculate the amount of FPI data in the record.
375          *
376          * XXX: We peek into xlogreader's private decoded backup blocks for the
377          * bimg_len indicating the length of FPI data. It doesn't seem worth it to
378          * add an accessor macro for this.
379          */
380         *fpi_len = 0;
381         for (block_id = 0; block_id <= record->max_block_id; block_id++)
382         {
383                 if (XLogRecHasBlockImage(record, block_id))
384                         *fpi_len += record->blocks[block_id].bimg_len;
385         }
386
387         /*
388          * Calculate the length of the record as the total length - the length of
389          * all the block images.
390          */
391         *rec_len = XLogRecGetTotalLen(record) - *fpi_len;
392 }
393
394 /*
395  * Store per-rmgr and per-record statistics for a given record.
396  */
397 static void
398 XLogDumpCountRecord(XLogDumpConfig *config, XLogDumpStats *stats,
399                                         XLogReaderState *record)
400 {
401         RmgrId          rmid;
402         uint8           recid;
403         uint32          rec_len;
404         uint32          fpi_len;
405
406         stats->count++;
407
408         rmid = XLogRecGetRmid(record);
409
410         XLogDumpRecordLen(record, &rec_len, &fpi_len);
411
412         /* Update per-rmgr statistics */
413
414         stats->rmgr_stats[rmid].count++;
415         stats->rmgr_stats[rmid].rec_len += rec_len;
416         stats->rmgr_stats[rmid].fpi_len += fpi_len;
417
418         /*
419          * Update per-record statistics, where the record is identified by a
420          * combination of the RmgrId and the four bits of the xl_info field that
421          * are the rmgr's domain (resulting in sixteen possible entries per
422          * RmgrId).
423          */
424
425         recid = XLogRecGetInfo(record) >> 4;
426
427         stats->record_stats[rmid][recid].count++;
428         stats->record_stats[rmid][recid].rec_len += rec_len;
429         stats->record_stats[rmid][recid].fpi_len += fpi_len;
430 }
431
432 /*
433  * Print a record to stdout
434  */
435 static void
436 XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
437 {
438         const char *id;
439         const RmgrDescData *desc = &RmgrDescTable[XLogRecGetRmid(record)];
440         uint32          rec_len;
441         uint32          fpi_len;
442         RelFileNode rnode;
443         ForkNumber      forknum;
444         BlockNumber blk;
445         int                     block_id;
446         uint8           info = XLogRecGetInfo(record);
447         XLogRecPtr      xl_prev = XLogRecGetPrev(record);
448
449         XLogDumpRecordLen(record, &rec_len, &fpi_len);
450
451         id = desc->rm_identify(info);
452         if (id == NULL)
453                 id = psprintf("UNKNOWN (%x)", info & ~XLR_INFO_MASK);
454
455         printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, ",
456                    desc->rm_name,
457                    rec_len, XLogRecGetTotalLen(record),
458                    XLogRecGetXid(record),
459                    (uint32) (record->ReadRecPtr >> 32), (uint32) record->ReadRecPtr,
460                    (uint32) (xl_prev >> 32), (uint32) xl_prev);
461         printf("desc: %s ", id);
462
463         /* the desc routine will printf the description directly to stdout */
464         desc->rm_desc(NULL, record);
465
466         if (!config->bkp_details)
467         {
468                 /* print block references (short format) */
469                 for (block_id = 0; block_id <= record->max_block_id; block_id++)
470                 {
471                         if (!XLogRecHasBlockRef(record, block_id))
472                                 continue;
473
474                         XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk);
475                         if (forknum != MAIN_FORKNUM)
476                                 printf(", blkref #%u: rel %u/%u/%u fork %s blk %u",
477                                            block_id,
478                                            rnode.spcNode, rnode.dbNode, rnode.relNode,
479                                            forkNames[forknum],
480                                            blk);
481                         else
482                                 printf(", blkref #%u: rel %u/%u/%u blk %u",
483                                            block_id,
484                                            rnode.spcNode, rnode.dbNode, rnode.relNode,
485                                            blk);
486                         if (XLogRecHasBlockImage(record, block_id))
487                         {
488                                 if (XLogRecBlockImageApply(record, block_id))
489                                         printf(" FPW");
490                                 else
491                                         printf(" FPW for WAL verification");
492                         }
493                 }
494                 putchar('\n');
495         }
496         else
497         {
498                 /* print block references (detailed format) */
499                 putchar('\n');
500                 for (block_id = 0; block_id <= record->max_block_id; block_id++)
501                 {
502                         if (!XLogRecHasBlockRef(record, block_id))
503                                 continue;
504
505                         XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk);
506                         printf("\tblkref #%u: rel %u/%u/%u fork %s blk %u",
507                                    block_id,
508                                    rnode.spcNode, rnode.dbNode, rnode.relNode,
509                                    forkNames[forknum],
510                                    blk);
511                         if (XLogRecHasBlockImage(record, block_id))
512                         {
513                                 if (record->blocks[block_id].bimg_info &
514                                         BKPIMAGE_IS_COMPRESSED)
515                                 {
516                                         printf(" (FPW%s); hole: offset: %u, length: %u, "
517                                                    "compression saved: %u\n",
518                                                    XLogRecBlockImageApply(record, block_id) ?
519                                                    "" : " for WAL verification",
520                                                    record->blocks[block_id].hole_offset,
521                                                    record->blocks[block_id].hole_length,
522                                                    BLCKSZ -
523                                                    record->blocks[block_id].hole_length -
524                                                    record->blocks[block_id].bimg_len);
525                                 }
526                                 else
527                                 {
528                                         printf(" (FPW%s); hole: offset: %u, length: %u\n",
529                                                    XLogRecBlockImageApply(record, block_id) ?
530                                                    "" : " for WAL verification",
531                                                    record->blocks[block_id].hole_offset,
532                                                    record->blocks[block_id].hole_length);
533                                 }
534                         }
535                         putchar('\n');
536                 }
537         }
538 }
539
540 /*
541  * Display a single row of record counts and sizes for an rmgr or record.
542  */
543 static void
544 XLogDumpStatsRow(const char *name,
545                                  uint64 n, uint64 total_count,
546                                  uint64 rec_len, uint64 total_rec_len,
547                                  uint64 fpi_len, uint64 total_fpi_len,
548                                  uint64 tot_len, uint64 total_len)
549 {
550         double          n_pct,
551                                 rec_len_pct,
552                                 fpi_len_pct,
553                                 tot_len_pct;
554
555         n_pct = 0;
556         if (total_count != 0)
557                 n_pct = 100 * (double) n / total_count;
558
559         rec_len_pct = 0;
560         if (total_rec_len != 0)
561                 rec_len_pct = 100 * (double) rec_len / total_rec_len;
562
563         fpi_len_pct = 0;
564         if (total_fpi_len != 0)
565                 fpi_len_pct = 100 * (double) fpi_len / total_fpi_len;
566
567         tot_len_pct = 0;
568         if (total_len != 0)
569                 tot_len_pct = 100 * (double) tot_len / total_len;
570
571         printf("%-27s "
572                    "%20" INT64_MODIFIER "u (%6.02f) "
573                    "%20" INT64_MODIFIER "u (%6.02f) "
574                    "%20" INT64_MODIFIER "u (%6.02f) "
575                    "%20" INT64_MODIFIER "u (%6.02f)\n",
576                    name, n, n_pct, rec_len, rec_len_pct, fpi_len, fpi_len_pct,
577                    tot_len, tot_len_pct);
578 }
579
580
581 /*
582  * Display summary statistics about the records seen so far.
583  */
584 static void
585 XLogDumpDisplayStats(XLogDumpConfig *config, XLogDumpStats *stats)
586 {
587         int                     ri,
588                                 rj;
589         uint64          total_count = 0;
590         uint64          total_rec_len = 0;
591         uint64          total_fpi_len = 0;
592         uint64          total_len = 0;
593         double          rec_len_pct,
594                                 fpi_len_pct;
595
596         /* ---
597          * Make a first pass to calculate column totals:
598          * count(*),
599          * sum(xl_len+SizeOfXLogRecord),
600          * sum(xl_tot_len-xl_len-SizeOfXLogRecord), and
601          * sum(xl_tot_len).
602          * These are used to calculate percentages for each record type.
603          * ---
604          */
605
606         for (ri = 0; ri < RM_NEXT_ID; ri++)
607         {
608                 total_count += stats->rmgr_stats[ri].count;
609                 total_rec_len += stats->rmgr_stats[ri].rec_len;
610                 total_fpi_len += stats->rmgr_stats[ri].fpi_len;
611         }
612         total_len = total_rec_len + total_fpi_len;
613
614         /*
615          * 27 is strlen("Transaction/COMMIT_PREPARED"), 20 is strlen(2^64), 8 is
616          * strlen("(100.00%)")
617          */
618
619         printf("%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n"
620                    "%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n",
621                    "Type", "N", "(%)", "Record size", "(%)", "FPI size", "(%)", "Combined size", "(%)",
622                    "----", "-", "---", "-----------", "---", "--------", "---", "-------------", "---");
623
624         for (ri = 0; ri < RM_NEXT_ID; ri++)
625         {
626                 uint64          count,
627                                         rec_len,
628                                         fpi_len,
629                                         tot_len;
630                 const RmgrDescData *desc = &RmgrDescTable[ri];
631
632                 if (!config->stats_per_record)
633                 {
634                         count = stats->rmgr_stats[ri].count;
635                         rec_len = stats->rmgr_stats[ri].rec_len;
636                         fpi_len = stats->rmgr_stats[ri].fpi_len;
637                         tot_len = rec_len + fpi_len;
638
639                         XLogDumpStatsRow(desc->rm_name,
640                                                          count, total_count, rec_len, total_rec_len,
641                                                          fpi_len, total_fpi_len, tot_len, total_len);
642                 }
643                 else
644                 {
645                         for (rj = 0; rj < MAX_XLINFO_TYPES; rj++)
646                         {
647                                 const char *id;
648
649                                 count = stats->record_stats[ri][rj].count;
650                                 rec_len = stats->record_stats[ri][rj].rec_len;
651                                 fpi_len = stats->record_stats[ri][rj].fpi_len;
652                                 tot_len = rec_len + fpi_len;
653
654                                 /* Skip undefined combinations and ones that didn't occur */
655                                 if (count == 0)
656                                         continue;
657
658                                 /* the upper four bits in xl_info are the rmgr's */
659                                 id = desc->rm_identify(rj << 4);
660                                 if (id == NULL)
661                                         id = psprintf("UNKNOWN (%x)", rj << 4);
662
663                                 XLogDumpStatsRow(psprintf("%s/%s", desc->rm_name, id),
664                                                                  count, total_count, rec_len, total_rec_len,
665                                                                  fpi_len, total_fpi_len, tot_len, total_len);
666                         }
667                 }
668         }
669
670         printf("%-27s %20s %8s %20s %8s %20s %8s %20s\n",
671                    "", "--------", "", "--------", "", "--------", "", "--------");
672
673         /*
674          * The percentages in earlier rows were calculated against the column
675          * total, but the ones that follow are against the row total. Note that
676          * these are displayed with a % symbol to differentiate them from the
677          * earlier ones, and are thus up to 9 characters long.
678          */
679
680         rec_len_pct = 0;
681         if (total_len != 0)
682                 rec_len_pct = 100 * (double) total_rec_len / total_len;
683
684         fpi_len_pct = 0;
685         if (total_len != 0)
686                 fpi_len_pct = 100 * (double) total_fpi_len / total_len;
687
688         printf("%-27s "
689                    "%20" INT64_MODIFIER "u %-9s"
690                    "%20" INT64_MODIFIER "u %-9s"
691                    "%20" INT64_MODIFIER "u %-9s"
692                    "%20" INT64_MODIFIER "u %-6s\n",
693                    "Total", stats->count, "",
694                    total_rec_len, psprintf("[%.02f%%]", rec_len_pct),
695                    total_fpi_len, psprintf("[%.02f%%]", fpi_len_pct),
696                    total_len, "[100%]");
697 }
698
699 static void
700 usage(void)
701 {
702         printf(_("%s decodes and displays PostgreSQL write-ahead logs for debugging.\n\n"),
703                    progname);
704         printf(_("Usage:\n"));
705         printf(_("  %s [OPTION]... [STARTSEG [ENDSEG]]\n"), progname);
706         printf(_("\nOptions:\n"));
707         printf(_("  -b, --bkp-details      output detailed information about backup blocks\n"));
708         printf(_("  -e, --end=RECPTR       stop reading at WAL location RECPTR\n"));
709         printf(_("  -f, --follow           keep retrying after reaching end of WAL\n"));
710         printf(_("  -n, --limit=N          number of records to display\n"));
711         printf(_("  -p, --path=PATH        directory in which to find log segment files or a\n"
712                          "                         directory with a ./pg_wal that contains such files\n"
713                          "                         (default: current directory, ./pg_wal, $PGDATA/pg_wal)\n"));
714         printf(_("  -r, --rmgr=RMGR        only show records generated by resource manager RMGR;\n"
715                          "                         use --rmgr=list to list valid resource manager names\n"));
716         printf(_("  -s, --start=RECPTR     start reading at WAL location RECPTR\n"));
717         printf(_("  -t, --timeline=TLI     timeline from which to read log records\n"
718                          "                         (default: 1 or the value used in STARTSEG)\n"));
719         printf(_("  -V, --version          output version information, then exit\n"));
720         printf(_("  -x, --xid=XID          only show records with transaction ID XID\n"));
721         printf(_("  -z, --stats[=record]   show statistics instead of records\n"
722                          "                         (optionally, show per-record statistics)\n"));
723         printf(_("  -?, --help             show this help, then exit\n"));
724 }
725
726 int
727 main(int argc, char **argv)
728 {
729         uint32          xlogid;
730         uint32          xrecoff;
731         XLogReaderState *xlogreader_state;
732         XLogDumpPrivate private;
733         XLogDumpConfig config;
734         XLogDumpStats stats;
735         XLogRecord *record;
736         XLogRecPtr      first_record;
737         char       *errormsg;
738
739         static struct option long_options[] = {
740                 {"bkp-details", no_argument, NULL, 'b'},
741                 {"end", required_argument, NULL, 'e'},
742                 {"follow", no_argument, NULL, 'f'},
743                 {"help", no_argument, NULL, '?'},
744                 {"limit", required_argument, NULL, 'n'},
745                 {"path", required_argument, NULL, 'p'},
746                 {"rmgr", required_argument, NULL, 'r'},
747                 {"start", required_argument, NULL, 's'},
748                 {"timeline", required_argument, NULL, 't'},
749                 {"xid", required_argument, NULL, 'x'},
750                 {"version", no_argument, NULL, 'V'},
751                 {"stats", optional_argument, NULL, 'z'},
752                 {NULL, 0, NULL, 0}
753         };
754
755         int                     option;
756         int                     optindex = 0;
757
758         set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_waldump"));
759         progname = get_progname(argv[0]);
760
761         memset(&private, 0, sizeof(XLogDumpPrivate));
762         memset(&config, 0, sizeof(XLogDumpConfig));
763         memset(&stats, 0, sizeof(XLogDumpStats));
764
765         private.timeline = 1;
766         private.startptr = InvalidXLogRecPtr;
767         private.endptr = InvalidXLogRecPtr;
768         private.endptr_reached = false;
769
770         config.bkp_details = false;
771         config.stop_after_records = -1;
772         config.already_displayed_records = 0;
773         config.follow = false;
774         config.filter_by_rmgr = -1;
775         config.filter_by_xid = InvalidTransactionId;
776         config.filter_by_xid_enabled = false;
777         config.stats = false;
778         config.stats_per_record = false;
779
780         if (argc <= 1)
781         {
782                 fprintf(stderr, _("%s: no arguments specified\n"), progname);
783                 goto bad_argument;
784         }
785
786         while ((option = getopt_long(argc, argv, "be:?fn:p:r:s:t:Vx:z",
787                                                                  long_options, &optindex)) != -1)
788         {
789                 switch (option)
790                 {
791                         case 'b':
792                                 config.bkp_details = true;
793                                 break;
794                         case 'e':
795                                 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
796                                 {
797                                         fprintf(stderr, _("%s: could not parse end WAL location \"%s\"\n"),
798                                                         progname, optarg);
799                                         goto bad_argument;
800                                 }
801                                 private.endptr = (uint64) xlogid << 32 | xrecoff;
802                                 break;
803                         case 'f':
804                                 config.follow = true;
805                                 break;
806                         case '?':
807                                 usage();
808                                 exit(EXIT_SUCCESS);
809                                 break;
810                         case 'n':
811                                 if (sscanf(optarg, "%d", &config.stop_after_records) != 1)
812                                 {
813                                         fprintf(stderr, _("%s: could not parse limit \"%s\"\n"),
814                                                         progname, optarg);
815                                         goto bad_argument;
816                                 }
817                                 break;
818                         case 'p':
819                                 private.inpath = pg_strdup(optarg);
820                                 break;
821                         case 'r':
822                                 {
823                                         int                     i;
824
825                                         if (pg_strcasecmp(optarg, "list") == 0)
826                                         {
827                                                 print_rmgr_list();
828                                                 exit(EXIT_SUCCESS);
829                                         }
830
831                                         for (i = 0; i <= RM_MAX_ID; i++)
832                                         {
833                                                 if (pg_strcasecmp(optarg, RmgrDescTable[i].rm_name) == 0)
834                                                 {
835                                                         config.filter_by_rmgr = i;
836                                                         break;
837                                                 }
838                                         }
839
840                                         if (config.filter_by_rmgr == -1)
841                                         {
842                                                 fprintf(stderr, _("%s: resource manager \"%s\" does not exist\n"),
843                                                                 progname, optarg);
844                                                 goto bad_argument;
845                                         }
846                                 }
847                                 break;
848                         case 's':
849                                 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
850                                 {
851                                         fprintf(stderr, _("%s: could not parse start WAL location \"%s\"\n"),
852                                                         progname, optarg);
853                                         goto bad_argument;
854                                 }
855                                 else
856                                         private.startptr = (uint64) xlogid << 32 | xrecoff;
857                                 break;
858                         case 't':
859                                 if (sscanf(optarg, "%d", &private.timeline) != 1)
860                                 {
861                                         fprintf(stderr, _("%s: could not parse timeline \"%s\"\n"),
862                                                         progname, optarg);
863                                         goto bad_argument;
864                                 }
865                                 break;
866                         case 'V':
867                                 puts("pg_waldump (PostgreSQL) " PG_VERSION);
868                                 exit(EXIT_SUCCESS);
869                                 break;
870                         case 'x':
871                                 if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
872                                 {
873                                         fprintf(stderr, _("%s: could not parse \"%s\" as a transaction ID\n"),
874                                                         progname, optarg);
875                                         goto bad_argument;
876                                 }
877                                 config.filter_by_xid_enabled = true;
878                                 break;
879                         case 'z':
880                                 config.stats = true;
881                                 config.stats_per_record = false;
882                                 if (optarg)
883                                 {
884                                         if (strcmp(optarg, "record") == 0)
885                                                 config.stats_per_record = true;
886                                         else if (strcmp(optarg, "rmgr") != 0)
887                                         {
888                                                 fprintf(stderr, _("%s: unrecognized argument to --stats: %s\n"),
889                                                                 progname, optarg);
890                                                 goto bad_argument;
891                                         }
892                                 }
893                                 break;
894                         default:
895                                 goto bad_argument;
896                 }
897         }
898
899         if ((optind + 2) < argc)
900         {
901                 fprintf(stderr,
902                                 _("%s: too many command-line arguments (first is \"%s\")\n"),
903                                 progname, argv[optind + 2]);
904                 goto bad_argument;
905         }
906
907         if (private.inpath != NULL)
908         {
909                 /* validate path points to directory */
910                 if (!verify_directory(private.inpath))
911                 {
912                         fprintf(stderr,
913                                         _("%s: path \"%s\" could not be opened: %s\n"),
914                                         progname, private.inpath, strerror(errno));
915                         goto bad_argument;
916                 }
917         }
918
919         /* parse files as start/end boundaries, extract path if not specified */
920         if (optind < argc)
921         {
922                 char       *directory = NULL;
923                 char       *fname = NULL;
924                 int                     fd;
925                 XLogSegNo       segno;
926
927                 split_path(argv[optind], &directory, &fname);
928
929                 if (private.inpath == NULL && directory != NULL)
930                 {
931                         private.inpath = directory;
932
933                         if (!verify_directory(private.inpath))
934                                 fatal_error("could not open directory \"%s\": %s",
935                                                         private.inpath, strerror(errno));
936                 }
937
938                 fd = fuzzy_open_file(private.inpath, fname);
939                 if (fd < 0)
940                         fatal_error("could not open file \"%s\"", fname);
941                 close(fd);
942
943                 /* parse position from file */
944                 XLogFromFileName(fname, &private.timeline, &segno);
945
946                 if (XLogRecPtrIsInvalid(private.startptr))
947                         XLogSegNoOffsetToRecPtr(segno, 0, private.startptr);
948                 else if (!XLByteInSeg(private.startptr, segno))
949                 {
950                         fprintf(stderr,
951                                         _("%s: start WAL location %X/%X is not inside file \"%s\"\n"),
952                                         progname,
953                                         (uint32) (private.startptr >> 32),
954                                         (uint32) private.startptr,
955                                         fname);
956                         goto bad_argument;
957                 }
958
959                 /* no second file specified, set end position */
960                 if (!(optind + 1 < argc) && XLogRecPtrIsInvalid(private.endptr))
961                         XLogSegNoOffsetToRecPtr(segno + 1, 0, private.endptr);
962
963                 /* parse ENDSEG if passed */
964                 if (optind + 1 < argc)
965                 {
966                         XLogSegNo       endsegno;
967
968                         /* ignore directory, already have that */
969                         split_path(argv[optind + 1], &directory, &fname);
970
971                         fd = fuzzy_open_file(private.inpath, 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, &endsegno);
978
979                         if (endsegno < segno)
980                                 fatal_error("ENDSEG %s is before STARTSEG %s",
981                                                         argv[optind + 1], argv[optind]);
982
983                         if (XLogRecPtrIsInvalid(private.endptr))
984                                 XLogSegNoOffsetToRecPtr(endsegno + 1, 0, private.endptr);
985
986                         /* set segno to endsegno for check of --end */
987                         segno = endsegno;
988                 }
989
990
991                 if (!XLByteInSeg(private.endptr, segno) &&
992                         private.endptr != (segno + 1) * XLogSegSize)
993                 {
994                         fprintf(stderr,
995                                         _("%s: end WAL location %X/%X is not inside file \"%s\"\n"),
996                                         progname,
997                                         (uint32) (private.endptr >> 32),
998                                         (uint32) private.endptr,
999                                         argv[argc - 1]);
1000                         goto bad_argument;
1001                 }
1002         }
1003
1004         /* we don't know what to print */
1005         if (XLogRecPtrIsInvalid(private.startptr))
1006         {
1007                 fprintf(stderr, _("%s: no start WAL location given\n"), progname);
1008                 goto bad_argument;
1009         }
1010
1011         /* done with argument parsing, do the actual work */
1012
1013         /* we have everything we need, start reading */
1014         xlogreader_state = XLogReaderAllocate(XLogDumpReadPage, &private);
1015         if (!xlogreader_state)
1016                 fatal_error("out of memory");
1017
1018         /* first find a valid recptr to start from */
1019         first_record = XLogFindNextRecord(xlogreader_state, private.startptr);
1020
1021         if (first_record == InvalidXLogRecPtr)
1022                 fatal_error("could not find a valid record after %X/%X",
1023                                         (uint32) (private.startptr >> 32),
1024                                         (uint32) private.startptr);
1025
1026         /*
1027          * Display a message that we're skipping data if `from` wasn't a pointer
1028          * to the start of a record and also wasn't a pointer to the beginning of
1029          * a segment (e.g. we were used in file mode).
1030          */
1031         if (first_record != private.startptr && (private.startptr % XLogSegSize) != 0)
1032                 printf(ngettext("first record is after %X/%X, at %X/%X, skipping over %u byte\n",
1033                                                 "first record is after %X/%X, at %X/%X, skipping over %u bytes\n",
1034                                                 (first_record - private.startptr)),
1035                            (uint32) (private.startptr >> 32), (uint32) private.startptr,
1036                            (uint32) (first_record >> 32), (uint32) first_record,
1037                            (uint32) (first_record - private.startptr));
1038
1039         for (;;)
1040         {
1041                 /* try to read the next record */
1042                 record = XLogReadRecord(xlogreader_state, first_record, &errormsg);
1043                 if (!record)
1044                 {
1045                         if (!config.follow || private.endptr_reached)
1046                                 break;
1047                         else
1048                         {
1049                                 pg_usleep(1000000L);    /* 1 second */
1050                                 continue;
1051                         }
1052                 }
1053
1054                 /* after reading the first record, continue at next one */
1055                 first_record = InvalidXLogRecPtr;
1056
1057                 /* apply all specified filters */
1058                 if (config.filter_by_rmgr != -1 &&
1059                         config.filter_by_rmgr != record->xl_rmid)
1060                         continue;
1061
1062                 if (config.filter_by_xid_enabled &&
1063                         config.filter_by_xid != record->xl_xid)
1064                         continue;
1065
1066                 /* process the record */
1067                 if (config.stats == true)
1068                         XLogDumpCountRecord(&config, &stats, xlogreader_state);
1069                 else
1070                         XLogDumpDisplayRecord(&config, xlogreader_state);
1071
1072                 /* check whether we printed enough */
1073                 config.already_displayed_records++;
1074                 if (config.stop_after_records > 0 &&
1075                         config.already_displayed_records >= config.stop_after_records)
1076                         break;
1077         }
1078
1079         if (config.stats == true)
1080                 XLogDumpDisplayStats(&config, &stats);
1081
1082         if (errormsg)
1083                 fatal_error("error in WAL record at %X/%X: %s",
1084                                         (uint32) (xlogreader_state->ReadRecPtr >> 32),
1085                                         (uint32) xlogreader_state->ReadRecPtr,
1086                                         errormsg);
1087
1088         XLogReaderFree(xlogreader_state);
1089
1090         return EXIT_SUCCESS;
1091
1092 bad_argument:
1093         fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
1094         return EXIT_FAILURE;
1095 }