LCOV - code coverage report
Current view: top level - src/bin/pg_waldump - pg_waldump.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13devel Lines: 53 429 12.4 %
Date: 2019-09-22 08:06:49 Functions: 2 15 13.3 %
Legend: Lines: hit not hit

          Line data    Source code
       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             : 
      29             : static const char *progname;
      30             : 
      31             : static int  WalSegSz;
      32             : 
      33             : typedef struct XLogDumpPrivate
      34             : {
      35             :     TimeLineID  timeline;
      36             :     char       *inpath;
      37             :     XLogRecPtr  startptr;
      38             :     XLogRecPtr  endptr;
      39             :     bool        endptr_reached;
      40             : } XLogDumpPrivate;
      41             : 
      42             : typedef 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             : 
      58             : typedef struct Stats
      59             : {
      60             :     uint64      count;
      61             :     uint64      rec_len;
      62             :     uint64      fpi_len;
      63             : } Stats;
      64             : 
      65             : #define MAX_XLINFO_TYPES 16
      66             : 
      67             : typedef 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             : 
      76             : static void
      77           0 : print_rmgr_list(void)
      78             : {
      79             :     int         i;
      80             : 
      81           0 :     for (i = 0; i <= RM_MAX_ID; i++)
      82             :     {
      83           0 :         printf("%s\n", RmgrDescTable[i].rm_name);
      84             :     }
      85           0 : }
      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             :  */
      91             : static bool
      92           0 : verify_directory(const char *directory)
      93             : {
      94           0 :     DIR        *dir = opendir(directory);
      95             : 
      96           0 :     if (dir == NULL)
      97           0 :         return false;
      98           0 :     closedir(dir);
      99           0 :     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             :  */
     108             : static void
     109           0 : split_path(const char *path, char **dir, char **fname)
     110             : {
     111             :     char       *sep;
     112             : 
     113             :     /* split filepath into directory & filename */
     114           0 :     sep = strrchr(path, '/');
     115             : 
     116             :     /* directory path */
     117           0 :     if (sep != NULL)
     118             :     {
     119           0 :         *dir = pg_strdup(path);
     120           0 :         (*dir)[(sep - path) + 1] = '\0';    /* no strndup */
     121           0 :         *fname = pg_strdup(sep + 1);
     122             :     }
     123             :     /* local directory */
     124             :     else
     125             :     {
     126           0 :         *dir = NULL;
     127           0 :         *fname = pg_strdup(path);
     128             :     }
     129           0 : }
     130             : 
     131             : /*
     132             :  * Open the file in the valid target directory.
     133             :  *
     134             :  * return a read only fd
     135             :  */
     136             : static int
     137           0 : open_file_in_directory(const char *directory, const char *fname)
     138             : {
     139           0 :     int         fd = -1;
     140             :     char        fpath[MAXPGPATH];
     141             : 
     142             :     Assert(directory != NULL);
     143             : 
     144           0 :     snprintf(fpath, MAXPGPATH, "%s/%s", directory, fname);
     145           0 :     fd = open(fpath, O_RDONLY | PG_BINARY, 0);
     146             : 
     147           0 :     if (fd < 0 && errno != ENOENT)
     148           0 :         fatal_error("could not open file \"%s\": %s",
     149             :                     fname, strerror(errno));
     150           0 :     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             :  */
     159             : static bool
     160           0 : search_directory(const char *directory, const char *fname)
     161             : {
     162           0 :     int         fd = -1;
     163             :     DIR        *xldir;
     164             : 
     165             :     /* open file if valid filename is provided */
     166           0 :     if (fname != NULL)
     167           0 :         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           0 :     else if ((xldir = opendir(directory)) != NULL)
     175             :     {
     176             :         struct dirent *xlde;
     177             : 
     178           0 :         while ((xlde = readdir(xldir)) != NULL)
     179             :         {
     180           0 :             if (IsXLogFileName(xlde->d_name))
     181             :             {
     182           0 :                 fd = open_file_in_directory(directory, xlde->d_name);
     183           0 :                 fname = xlde->d_name;
     184           0 :                 break;
     185             :             }
     186             :         }
     187             : 
     188           0 :         closedir(xldir);
     189             :     }
     190             : 
     191             :     /* set WalSegSz if file is successfully opened */
     192           0 :     if (fd >= 0)
     193             :     {
     194             :         PGAlignedXLogBlock buf;
     195             :         int         r;
     196             : 
     197           0 :         r = read(fd, buf.data, XLOG_BLCKSZ);
     198           0 :         if (r == XLOG_BLCKSZ)
     199             :         {
     200           0 :             XLogLongPageHeader longhdr = (XLogLongPageHeader) buf.data;
     201             : 
     202           0 :             WalSegSz = longhdr->xlp_seg_size;
     203             : 
     204           0 :             if (!IsValidWalSegSize(WalSegSz))
     205           0 :                 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           0 :             if (errno != 0)
     213           0 :                 fatal_error("could not read file \"%s\": %s",
     214             :                             fname, strerror(errno));
     215             :             else
     216           0 :                 fatal_error("could not read file \"%s\": read %d of %zu",
     217             :                             fname, r, (Size) XLOG_BLCKSZ);
     218             :         }
     219           0 :         close(fd);
     220           0 :         return true;
     221             :     }
     222             : 
     223           0 :     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             :  */
     240             : static void
     241           0 : identify_target_directory(XLogDumpPrivate *private, char *directory,
     242             :                           char *fname)
     243             : {
     244             :     char        fpath[MAXPGPATH];
     245             : 
     246           0 :     if (directory != NULL)
     247             :     {
     248           0 :         if (search_directory(directory, fname))
     249             :         {
     250           0 :             private->inpath = pg_strdup(directory);
     251           0 :             return;
     252             :         }
     253             : 
     254             :         /* directory / XLOGDIR */
     255           0 :         snprintf(fpath, MAXPGPATH, "%s/%s", directory, XLOGDIR);
     256           0 :         if (search_directory(fpath, fname))
     257             :         {
     258           0 :             private->inpath = pg_strdup(fpath);
     259           0 :             return;
     260             :         }
     261             :     }
     262             :     else
     263             :     {
     264             :         const char *datadir;
     265             : 
     266             :         /* current directory */
     267           0 :         if (search_directory(".", fname))
     268             :         {
     269           0 :             private->inpath = pg_strdup(".");
     270           0 :             return;
     271             :         }
     272             :         /* XLOGDIR */
     273           0 :         if (search_directory(XLOGDIR, fname))
     274             :         {
     275           0 :             private->inpath = pg_strdup(XLOGDIR);
     276           0 :             return;
     277             :         }
     278             : 
     279           0 :         datadir = getenv("PGDATA");
     280             :         /* $PGDATA / XLOGDIR */
     281           0 :         if (datadir != NULL)
     282             :         {
     283           0 :             snprintf(fpath, MAXPGPATH, "%s/%s", datadir, XLOGDIR);
     284           0 :             if (search_directory(fpath, fname))
     285             :             {
     286           0 :                 private->inpath = pg_strdup(fpath);
     287           0 :                 return;
     288             :             }
     289             :         }
     290             :     }
     291             : 
     292             :     /* could not locate WAL file */
     293           0 :     if (fname)
     294           0 :         fatal_error("could not locate WAL file \"%s\"", fname);
     295             :     else
     296           0 :         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             :  */
     304             : static void
     305           0 : XLogDumpXLogRead(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           0 :     p = buf;
     317           0 :     recptr = startptr;
     318           0 :     nbytes = count;
     319             : 
     320           0 :     while (nbytes > 0)
     321             :     {
     322             :         uint32      startoff;
     323             :         int         segbytes;
     324             :         int         readbytes;
     325             : 
     326           0 :         startoff = XLogSegmentOffset(recptr, WalSegSz);
     327             : 
     328           0 :         if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, WalSegSz))
     329             :         {
     330             :             char        fname[MAXFNAMELEN];
     331             :             int         tries;
     332             : 
     333             :             /* Switch to another logfile segment */
     334           0 :             if (sendFile >= 0)
     335           0 :                 close(sendFile);
     336             : 
     337           0 :             XLByteToSeg(recptr, sendSegNo, WalSegSz);
     338             : 
     339           0 :             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           0 :             for (tries = 0; tries < 10; tries++)
     348             :             {
     349           0 :                 sendFile = open_file_in_directory(directory, fname);
     350           0 :                 if (sendFile >= 0)
     351           0 :                     break;
     352           0 :                 if (errno == ENOENT)
     353             :                 {
     354           0 :                     int         save_errno = errno;
     355             : 
     356             :                     /* File not there yet, try again */
     357           0 :                     pg_usleep(500 * 1000);
     358             : 
     359           0 :                     errno = save_errno;
     360           0 :                     continue;
     361             :                 }
     362             :                 /* Any other error, fall through and fail */
     363           0 :                 break;
     364             :             }
     365             : 
     366           0 :             if (sendFile < 0)
     367           0 :                 fatal_error("could not find file \"%s\": %s",
     368             :                             fname, strerror(errno));
     369           0 :             sendOff = 0;
     370             :         }
     371             : 
     372             :         /* Need to seek in the file? */
     373           0 :         if (sendOff != startoff)
     374             :         {
     375           0 :             if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
     376             :             {
     377           0 :                 int         err = errno;
     378             :                 char        fname[MAXPGPATH];
     379             : 
     380           0 :                 XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
     381             : 
     382           0 :                 fatal_error("could not seek in log file %s to offset %u: %s",
     383             :                             fname, startoff, strerror(err));
     384             :             }
     385           0 :             sendOff = startoff;
     386             :         }
     387             : 
     388             :         /* How many bytes are within this segment? */
     389           0 :         if (nbytes > (WalSegSz - startoff))
     390           0 :             segbytes = WalSegSz - startoff;
     391             :         else
     392           0 :             segbytes = nbytes;
     393             : 
     394           0 :         readbytes = read(sendFile, p, segbytes);
     395           0 :         if (readbytes <= 0)
     396             :         {
     397           0 :             int         err = errno;
     398             :             char        fname[MAXPGPATH];
     399           0 :             int         save_errno = errno;
     400             : 
     401           0 :             XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
     402           0 :             errno = save_errno;
     403             : 
     404           0 :             if (readbytes < 0)
     405           0 :                 fatal_error("could not read from log file %s, offset %u, length %d: %s",
     406             :                             fname, sendOff, segbytes, strerror(err));
     407           0 :             else if (readbytes == 0)
     408           0 :                 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           0 :         recptr += readbytes;
     414             : 
     415           0 :         sendOff += readbytes;
     416           0 :         nbytes -= readbytes;
     417           0 :         p += readbytes;
     418             :     }
     419           0 : }
     420             : 
     421             : /*
     422             :  * XLogReader read_page callback
     423             :  */
     424             : static int
     425           0 : XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
     426             :                  XLogRecPtr targetPtr, char *readBuff, TimeLineID *curFileTLI)
     427             : {
     428           0 :     XLogDumpPrivate *private = state->private_data;
     429           0 :     int         count = XLOG_BLCKSZ;
     430             : 
     431           0 :     if (private->endptr != InvalidXLogRecPtr)
     432             :     {
     433           0 :         if (targetPagePtr + XLOG_BLCKSZ <= private->endptr)
     434           0 :             count = XLOG_BLCKSZ;
     435           0 :         else if (targetPagePtr + reqLen <= private->endptr)
     436           0 :             count = private->endptr - targetPagePtr;
     437             :         else
     438             :         {
     439           0 :             private->endptr_reached = true;
     440           0 :             return -1;
     441             :         }
     442             :     }
     443             : 
     444           0 :     XLogDumpXLogRead(private->inpath, private->timeline, targetPagePtr,
     445             :                      readBuff, count);
     446             : 
     447           0 :     return count;
     448             : }
     449             : 
     450             : /*
     451             :  * Calculate the size of a record, split into !FPI and FPI parts.
     452             :  */
     453             : static void
     454           0 : XLogDumpRecordLen(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           0 :     *fpi_len = 0;
     466           0 :     for (block_id = 0; block_id <= record->max_block_id; block_id++)
     467             :     {
     468           0 :         if (XLogRecHasBlockImage(record, block_id))
     469           0 :             *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           0 :     *rec_len = XLogRecGetTotalLen(record) - *fpi_len;
     477           0 : }
     478             : 
     479             : /*
     480             :  * Store per-rmgr and per-record statistics for a given record.
     481             :  */
     482             : static void
     483           0 : XLogDumpCountRecord(XLogDumpConfig *config, XLogDumpStats *stats,
     484             :                     XLogReaderState *record)
     485             : {
     486             :     RmgrId      rmid;
     487             :     uint8       recid;
     488             :     uint32      rec_len;
     489             :     uint32      fpi_len;
     490             : 
     491           0 :     stats->count++;
     492             : 
     493           0 :     rmid = XLogRecGetRmid(record);
     494             : 
     495           0 :     XLogDumpRecordLen(record, &rec_len, &fpi_len);
     496             : 
     497             :     /* Update per-rmgr statistics */
     498             : 
     499           0 :     stats->rmgr_stats[rmid].count++;
     500           0 :     stats->rmgr_stats[rmid].rec_len += rec_len;
     501           0 :     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           0 :     recid = XLogRecGetInfo(record) >> 4;
     511             : 
     512           0 :     stats->record_stats[rmid][recid].count++;
     513           0 :     stats->record_stats[rmid][recid].rec_len += rec_len;
     514           0 :     stats->record_stats[rmid][recid].fpi_len += fpi_len;
     515           0 : }
     516             : 
     517             : /*
     518             :  * Print a record to stdout
     519             :  */
     520             : static void
     521           0 : XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
     522             : {
     523             :     const char *id;
     524           0 :     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           0 :     uint8       info = XLogRecGetInfo(record);
     532           0 :     XLogRecPtr  xl_prev = XLogRecGetPrev(record);
     533             : 
     534           0 :     XLogDumpRecordLen(record, &rec_len, &fpi_len);
     535             : 
     536           0 :     id = desc->rm_identify(info);
     537           0 :     if (id == NULL)
     538           0 :         id = psprintf("UNKNOWN (%x)", info & ~XLR_INFO_MASK);
     539             : 
     540           0 :     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           0 :     printf("desc: %s ", id);
     547             : 
     548             :     /* the desc routine will printf the description directly to stdout */
     549           0 :     desc->rm_desc(NULL, record);
     550             : 
     551           0 :     if (!config->bkp_details)
     552             :     {
     553             :         /* print block references (short format) */
     554           0 :         for (block_id = 0; block_id <= record->max_block_id; block_id++)
     555             :         {
     556           0 :             if (!XLogRecHasBlockRef(record, block_id))
     557           0 :                 continue;
     558             : 
     559           0 :             XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk);
     560           0 :             if (forknum != MAIN_FORKNUM)
     561           0 :                 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           0 :                 printf(", blkref #%u: rel %u/%u/%u blk %u",
     568             :                        block_id,
     569             :                        rnode.spcNode, rnode.dbNode, rnode.relNode,
     570             :                        blk);
     571           0 :             if (XLogRecHasBlockImage(record, block_id))
     572             :             {
     573           0 :                 if (XLogRecBlockImageApply(record, block_id))
     574           0 :                     printf(" FPW");
     575             :                 else
     576           0 :                     printf(" FPW for WAL verification");
     577             :             }
     578             :         }
     579           0 :         putchar('\n');
     580             :     }
     581             :     else
     582             :     {
     583             :         /* print block references (detailed format) */
     584           0 :         putchar('\n');
     585           0 :         for (block_id = 0; block_id <= record->max_block_id; block_id++)
     586             :         {
     587           0 :             if (!XLogRecHasBlockRef(record, block_id))
     588           0 :                 continue;
     589             : 
     590           0 :             XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk);
     591           0 :             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           0 :             if (XLogRecHasBlockImage(record, block_id))
     597             :             {
     598           0 :                 if (record->blocks[block_id].bimg_info &
     599             :                     BKPIMAGE_IS_COMPRESSED)
     600             :                 {
     601           0 :                     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           0 :                     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           0 :             putchar('\n');
     621             :         }
     622             :     }
     623           0 : }
     624             : 
     625             : /*
     626             :  * Display a single row of record counts and sizes for an rmgr or record.
     627             :  */
     628             : static void
     629           0 : XLogDumpStatsRow(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           0 :     n_pct = 0;
     641           0 :     if (total_count != 0)
     642           0 :         n_pct = 100 * (double) n / total_count;
     643             : 
     644           0 :     rec_len_pct = 0;
     645           0 :     if (total_rec_len != 0)
     646           0 :         rec_len_pct = 100 * (double) rec_len / total_rec_len;
     647             : 
     648           0 :     fpi_len_pct = 0;
     649           0 :     if (total_fpi_len != 0)
     650           0 :         fpi_len_pct = 100 * (double) fpi_len / total_fpi_len;
     651             : 
     652           0 :     tot_len_pct = 0;
     653           0 :     if (total_len != 0)
     654           0 :         tot_len_pct = 100 * (double) tot_len / total_len;
     655             : 
     656           0 :     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           0 : }
     664             : 
     665             : 
     666             : /*
     667             :  * Display summary statistics about the records seen so far.
     668             :  */
     669             : static void
     670           0 : XLogDumpDisplayStats(XLogDumpConfig *config, XLogDumpStats *stats)
     671             : {
     672             :     int         ri,
     673             :                 rj;
     674           0 :     uint64      total_count = 0;
     675           0 :     uint64      total_rec_len = 0;
     676           0 :     uint64      total_fpi_len = 0;
     677           0 :     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           0 :     for (ri = 0; ri < RM_NEXT_ID; ri++)
     692             :     {
     693           0 :         total_count += stats->rmgr_stats[ri].count;
     694           0 :         total_rec_len += stats->rmgr_stats[ri].rec_len;
     695           0 :         total_fpi_len += stats->rmgr_stats[ri].fpi_len;
     696             :     }
     697           0 :     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           0 :     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           0 :     for (ri = 0; ri < RM_NEXT_ID; ri++)
     710             :     {
     711             :         uint64      count,
     712             :                     rec_len,
     713             :                     fpi_len,
     714             :                     tot_len;
     715           0 :         const RmgrDescData *desc = &RmgrDescTable[ri];
     716             : 
     717           0 :         if (!config->stats_per_record)
     718             :         {
     719           0 :             count = stats->rmgr_stats[ri].count;
     720           0 :             rec_len = stats->rmgr_stats[ri].rec_len;
     721           0 :             fpi_len = stats->rmgr_stats[ri].fpi_len;
     722           0 :             tot_len = rec_len + fpi_len;
     723             : 
     724           0 :             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           0 :             for (rj = 0; rj < MAX_XLINFO_TYPES; rj++)
     731             :             {
     732             :                 const char *id;
     733             : 
     734           0 :                 count = stats->record_stats[ri][rj].count;
     735           0 :                 rec_len = stats->record_stats[ri][rj].rec_len;
     736           0 :                 fpi_len = stats->record_stats[ri][rj].fpi_len;
     737           0 :                 tot_len = rec_len + fpi_len;
     738             : 
     739             :                 /* Skip undefined combinations and ones that didn't occur */
     740           0 :                 if (count == 0)
     741           0 :                     continue;
     742             : 
     743             :                 /* the upper four bits in xl_info are the rmgr's */
     744           0 :                 id = desc->rm_identify(rj << 4);
     745           0 :                 if (id == NULL)
     746           0 :                     id = psprintf("UNKNOWN (%x)", rj << 4);
     747             : 
     748           0 :                 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           0 :     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           0 :     rec_len_pct = 0;
     766           0 :     if (total_len != 0)
     767           0 :         rec_len_pct = 100 * (double) total_rec_len / total_len;
     768             : 
     769           0 :     fpi_len_pct = 0;
     770           0 :     if (total_len != 0)
     771           0 :         fpi_len_pct = 100 * (double) total_fpi_len / total_len;
     772             : 
     773           0 :     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           0 : }
     783             : 
     784             : static void
     785           2 : usage(void)
     786             : {
     787           2 :     printf(_("%s decodes and displays PostgreSQL write-ahead logs for debugging.\n\n"),
     788             :            progname);
     789           2 :     printf(_("Usage:\n"));
     790           2 :     printf(_("  %s [OPTION]... [STARTSEG [ENDSEG]]\n"), progname);
     791           2 :     printf(_("\nOptions:\n"));
     792           2 :     printf(_("  -b, --bkp-details      output detailed information about backup blocks\n"));
     793           2 :     printf(_("  -e, --end=RECPTR       stop reading at WAL location RECPTR\n"));
     794           2 :     printf(_("  -f, --follow           keep retrying after reaching end of WAL\n"));
     795           2 :     printf(_("  -n, --limit=N          number of records to display\n"));
     796           2 :     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           2 :     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           2 :     printf(_("  -s, --start=RECPTR     start reading at WAL location RECPTR\n"));
     802           2 :     printf(_("  -t, --timeline=TLI     timeline from which to read log records\n"
     803             :              "                         (default: 1 or the value used in STARTSEG)\n"));
     804           2 :     printf(_("  -V, --version          output version information, then exit\n"));
     805           2 :     printf(_("  -x, --xid=XID          only show records with transaction ID XID\n"));
     806           2 :     printf(_("  -z, --stats[=record]   show statistics instead of records\n"
     807             :              "                         (optionally, show per-record statistics)\n"));
     808           2 :     printf(_("  -?, --help             show this help, then exit\n"));
     809           2 :     printf(_("\nReport bugs to <pgsql-bugs@lists.postgresql.org>.\n"));
     810           2 : }
     811             : 
     812             : int
     813           6 : main(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           6 :     int         optindex = 0;
     843             : 
     844           6 :     pg_logging_init(argv[0]);
     845           6 :     set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_waldump"));
     846           6 :     progname = get_progname(argv[0]);
     847             : 
     848           6 :     if (argc > 1)
     849             :     {
     850           6 :         if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
     851             :         {
     852           2 :             usage();
     853           2 :             exit(0);
     854             :         }
     855           4 :         if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
     856             :         {
     857           2 :             puts("pg_waldump (PostgreSQL) " PG_VERSION);
     858           2 :             exit(0);
     859             :         }
     860             :     }
     861             : 
     862           2 :     memset(&private, 0, sizeof(XLogDumpPrivate));
     863           2 :     memset(&config, 0, sizeof(XLogDumpConfig));
     864           2 :     memset(&stats, 0, sizeof(XLogDumpStats));
     865             : 
     866           2 :     private.timeline = 1;
     867           2 :     private.startptr = InvalidXLogRecPtr;
     868           2 :     private.endptr = InvalidXLogRecPtr;
     869           2 :     private.endptr_reached = false;
     870             : 
     871           2 :     config.bkp_details = false;
     872           2 :     config.stop_after_records = -1;
     873           2 :     config.already_displayed_records = 0;
     874           2 :     config.follow = false;
     875           2 :     config.filter_by_rmgr = -1;
     876           2 :     config.filter_by_xid = InvalidTransactionId;
     877           2 :     config.filter_by_xid_enabled = false;
     878           2 :     config.stats = false;
     879           2 :     config.stats_per_record = false;
     880             : 
     881           2 :     if (argc <= 1)
     882             :     {
     883           0 :         pg_log_error("no arguments specified");
     884           0 :         goto bad_argument;
     885             :     }
     886             : 
     887           4 :     while ((option = getopt_long(argc, argv, "be:fn:p:r:s:t:x:z",
     888             :                                  long_options, &optindex)) != -1)
     889             :     {
     890           2 :         switch (option)
     891             :         {
     892             :             case 'b':
     893           0 :                 config.bkp_details = true;
     894           0 :                 break;
     895             :             case 'e':
     896           0 :                 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
     897             :                 {
     898           0 :                     pg_log_error("could not parse end WAL location \"%s\"",
     899             :                                  optarg);
     900           0 :                     goto bad_argument;
     901             :                 }
     902           0 :                 private.endptr = (uint64) xlogid << 32 | xrecoff;
     903           0 :                 break;
     904             :             case 'f':
     905           0 :                 config.follow = true;
     906           0 :                 break;
     907             :             case 'n':
     908           0 :                 if (sscanf(optarg, "%d", &config.stop_after_records) != 1)
     909             :                 {
     910           0 :                     pg_log_error("could not parse limit \"%s\"", optarg);
     911           0 :                     goto bad_argument;
     912             :                 }
     913           0 :                 break;
     914             :             case 'p':
     915           0 :                 private.inpath = pg_strdup(optarg);
     916           0 :                 break;
     917             :             case 'r':
     918             :                 {
     919             :                     int         i;
     920             : 
     921           0 :                     if (pg_strcasecmp(optarg, "list") == 0)
     922             :                     {
     923           0 :                         print_rmgr_list();
     924           0 :                         exit(EXIT_SUCCESS);
     925             :                     }
     926             : 
     927           0 :                     for (i = 0; i <= RM_MAX_ID; i++)
     928             :                     {
     929           0 :                         if (pg_strcasecmp(optarg, RmgrDescTable[i].rm_name) == 0)
     930             :                         {
     931           0 :                             config.filter_by_rmgr = i;
     932           0 :                             break;
     933             :                         }
     934             :                     }
     935             : 
     936           0 :                     if (config.filter_by_rmgr == -1)
     937             :                     {
     938           0 :                         pg_log_error("resource manager \"%s\" does not exist",
     939             :                                      optarg);
     940           0 :                         goto bad_argument;
     941             :                     }
     942             :                 }
     943           0 :                 break;
     944             :             case 's':
     945           0 :                 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
     946             :                 {
     947           0 :                     pg_log_error("could not parse start WAL location \"%s\"",
     948             :                                  optarg);
     949           0 :                     goto bad_argument;
     950             :                 }
     951             :                 else
     952           0 :                     private.startptr = (uint64) xlogid << 32 | xrecoff;
     953           0 :                 break;
     954             :             case 't':
     955           0 :                 if (sscanf(optarg, "%d", &private.timeline) != 1)
     956             :                 {
     957           0 :                     pg_log_error("could not parse timeline \"%s\"", optarg);
     958           0 :                     goto bad_argument;
     959             :                 }
     960           0 :                 break;
     961             :             case 'x':
     962           0 :                 if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
     963             :                 {
     964           0 :                     pg_log_error("could not parse \"%s\" as a transaction ID",
     965             :                                  optarg);
     966           0 :                     goto bad_argument;
     967             :                 }
     968           0 :                 config.filter_by_xid_enabled = true;
     969           0 :                 break;
     970             :             case 'z':
     971           0 :                 config.stats = true;
     972           0 :                 config.stats_per_record = false;
     973           0 :                 if (optarg)
     974             :                 {
     975           0 :                     if (strcmp(optarg, "record") == 0)
     976           0 :                         config.stats_per_record = true;
     977           0 :                     else if (strcmp(optarg, "rmgr") != 0)
     978             :                     {
     979           0 :                         pg_log_error("unrecognized argument to --stats: %s",
     980             :                                      optarg);
     981           0 :                         goto bad_argument;
     982             :                     }
     983             :                 }
     984           0 :                 break;
     985             :             default:
     986           2 :                 goto bad_argument;
     987             :         }
     988             :     }
     989             : 
     990           0 :     if ((optind + 2) < argc)
     991             :     {
     992           0 :         pg_log_error("too many command-line arguments (first is \"%s\")",
     993             :                      argv[optind + 2]);
     994           0 :         goto bad_argument;
     995             :     }
     996             : 
     997           0 :     if (private.inpath != NULL)
     998             :     {
     999             :         /* validate path points to directory */
    1000           0 :         if (!verify_directory(private.inpath))
    1001             :         {
    1002           0 :             pg_log_error("path \"%s\" could not be opened: %s",
    1003             :                          private.inpath, strerror(errno));
    1004           0 :             goto bad_argument;
    1005             :         }
    1006             :     }
    1007             : 
    1008             :     /* parse files as start/end boundaries, extract path if not specified */
    1009           0 :     if (optind < argc)
    1010             :     {
    1011           0 :         char       *directory = NULL;
    1012           0 :         char       *fname = NULL;
    1013             :         int         fd;
    1014             :         XLogSegNo   segno;
    1015             : 
    1016           0 :         split_path(argv[optind], &directory, &fname);
    1017             : 
    1018           0 :         if (private.inpath == NULL && directory != NULL)
    1019             :         {
    1020           0 :             private.inpath = directory;
    1021             : 
    1022           0 :             if (!verify_directory(private.inpath))
    1023           0 :                 fatal_error("could not open directory \"%s\": %s",
    1024             :                             private.inpath, strerror(errno));
    1025             :         }
    1026             : 
    1027           0 :         identify_target_directory(&private, private.inpath, fname);
    1028           0 :         fd = open_file_in_directory(private.inpath, fname);
    1029           0 :         if (fd < 0)
    1030           0 :             fatal_error("could not open file \"%s\"", fname);
    1031           0 :         close(fd);
    1032             : 
    1033             :         /* parse position from file */
    1034           0 :         XLogFromFileName(fname, &private.timeline, &segno, WalSegSz);
    1035             : 
    1036           0 :         if (XLogRecPtrIsInvalid(private.startptr))
    1037           0 :             XLogSegNoOffsetToRecPtr(segno, 0, WalSegSz, private.startptr);
    1038           0 :         else if (!XLByteInSeg(private.startptr, segno, WalSegSz))
    1039             :         {
    1040           0 :             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           0 :             goto bad_argument;
    1045             :         }
    1046             : 
    1047             :         /* no second file specified, set end position */
    1048           0 :         if (!(optind + 1 < argc) && XLogRecPtrIsInvalid(private.endptr))
    1049           0 :             XLogSegNoOffsetToRecPtr(segno + 1, 0, WalSegSz, private.endptr);
    1050             : 
    1051             :         /* parse ENDSEG if passed */
    1052           0 :         if (optind + 1 < argc)
    1053             :         {
    1054             :             XLogSegNo   endsegno;
    1055             : 
    1056             :             /* ignore directory, already have that */
    1057           0 :             split_path(argv[optind + 1], &directory, &fname);
    1058             : 
    1059           0 :             fd = open_file_in_directory(private.inpath, fname);
    1060           0 :             if (fd < 0)
    1061           0 :                 fatal_error("could not open file \"%s\"", fname);
    1062           0 :             close(fd);
    1063             : 
    1064             :             /* parse position from file */
    1065           0 :             XLogFromFileName(fname, &private.timeline, &endsegno, WalSegSz);
    1066             : 
    1067           0 :             if (endsegno < segno)
    1068           0 :                 fatal_error("ENDSEG %s is before STARTSEG %s",
    1069             :                             argv[optind + 1], argv[optind]);
    1070             : 
    1071           0 :             if (XLogRecPtrIsInvalid(private.endptr))
    1072           0 :                 XLogSegNoOffsetToRecPtr(endsegno + 1, 0, WalSegSz,
    1073             :                                         private.endptr);
    1074             : 
    1075             :             /* set segno to endsegno for check of --end */
    1076           0 :             segno = endsegno;
    1077             :         }
    1078             : 
    1079             : 
    1080           0 :         if (!XLByteInSeg(private.endptr, segno, WalSegSz) &&
    1081           0 :             private.endptr != (segno + 1) * WalSegSz)
    1082             :         {
    1083           0 :             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           0 :             goto bad_argument;
    1088             :         }
    1089             :     }
    1090             :     else
    1091           0 :         identify_target_directory(&private, private.inpath, NULL);
    1092             : 
    1093             :     /* we don't know what to print */
    1094           0 :     if (XLogRecPtrIsInvalid(private.startptr))
    1095             :     {
    1096           0 :         pg_log_error("no start WAL location given");
    1097           0 :         goto bad_argument;
    1098             :     }
    1099             : 
    1100             :     /* done with argument parsing, do the actual work */
    1101             : 
    1102             :     /* we have everything we need, start reading */
    1103           0 :     xlogreader_state = XLogReaderAllocate(WalSegSz, XLogDumpReadPage,
    1104             :                                           &private);
    1105           0 :     if (!xlogreader_state)
    1106           0 :         fatal_error("out of memory");
    1107             : 
    1108             :     /* first find a valid recptr to start from */
    1109           0 :     first_record = XLogFindNextRecord(xlogreader_state, private.startptr);
    1110             : 
    1111           0 :     if (first_record == InvalidXLogRecPtr)
    1112           0 :         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           0 :     if (first_record != private.startptr &&
    1122           0 :         XLogSegmentOffset(private.startptr, WalSegSz) != 0)
    1123           0 :         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           0 :         record = XLogReadRecord(xlogreader_state, first_record, &errormsg);
    1134           0 :         if (!record)
    1135             :         {
    1136           0 :             if (!config.follow || private.endptr_reached)
    1137             :                 break;
    1138             :             else
    1139             :             {
    1140           0 :                 pg_usleep(1000000L);    /* 1 second */
    1141           0 :                 continue;
    1142             :             }
    1143             :         }
    1144             : 
    1145             :         /* after reading the first record, continue at next one */
    1146           0 :         first_record = InvalidXLogRecPtr;
    1147             : 
    1148             :         /* apply all specified filters */
    1149           0 :         if (config.filter_by_rmgr != -1 &&
    1150           0 :             config.filter_by_rmgr != record->xl_rmid)
    1151           0 :             continue;
    1152             : 
    1153           0 :         if (config.filter_by_xid_enabled &&
    1154           0 :             config.filter_by_xid != record->xl_xid)
    1155           0 :             continue;
    1156             : 
    1157             :         /* process the record */
    1158           0 :         if (config.stats == true)
    1159           0 :             XLogDumpCountRecord(&config, &stats, xlogreader_state);
    1160             :         else
    1161           0 :             XLogDumpDisplayRecord(&config, xlogreader_state);
    1162             : 
    1163             :         /* check whether we printed enough */
    1164           0 :         config.already_displayed_records++;
    1165           0 :         if (config.stop_after_records > 0 &&
    1166           0 :             config.already_displayed_records >= config.stop_after_records)
    1167           0 :             break;
    1168             :     }
    1169             : 
    1170           0 :     if (config.stats == true)
    1171           0 :         XLogDumpDisplayStats(&config, &stats);
    1172             : 
    1173           0 :     if (errormsg)
    1174           0 :         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           0 :     XLogReaderFree(xlogreader_state);
    1180             : 
    1181           0 :     return EXIT_SUCCESS;
    1182             : 
    1183             : bad_argument:
    1184           2 :     fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
    1185           2 :     return EXIT_FAILURE;
    1186             : }

Generated by: LCOV version 1.13