LCOV - code coverage report
Current view: top level - src/bin/pg_waldump - pg_waldump.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 84.0 % 607 510
Test Date: 2026-07-15 11:15:40 Functions: 87.5 % 24 21
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 74.0 % 389 288

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * pg_waldump.c - decode and display WAL
       4                 :             :  *
       5                 :             :  * Copyright (c) 2013-2026, 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 <limits.h>
      17                 :             : #include <signal.h>
      18                 :             : #include <sys/stat.h>
      19                 :             : #include <unistd.h>
      20                 :             : 
      21                 :             : #include "access/transam.h"
      22                 :             : #include "access/xlog_internal.h"
      23                 :             : #include "access/xlogreader.h"
      24                 :             : #include "access/xlogrecord.h"
      25                 :             : #include "access/xlogstats.h"
      26                 :             : #include "common/fe_memutils.h"
      27                 :             : #include "common/file_perm.h"
      28                 :             : #include "common/file_utils.h"
      29                 :             : #include "common/logging.h"
      30                 :             : #include "common/relpath.h"
      31                 :             : #include "getopt_long.h"
      32                 :             : #include "pg_waldump.h"
      33                 :             : #include "rmgrdesc.h"
      34                 :             : #include "storage/bufpage.h"
      35                 :             : 
      36                 :             : /*
      37                 :             :  * NOTE: For any code change or issue fix here, it is highly recommended to
      38                 :             :  * give a thought about doing the same in pg_walinspect contrib module as well.
      39                 :             :  */
      40                 :             : 
      41                 :             : static const char *progname;
      42                 :             : 
      43                 :             : static volatile sig_atomic_t time_to_stop = false;
      44                 :             : 
      45                 :             : static XLogReaderState *xlogreader_state_cleanup = NULL;
      46                 :             : 
      47                 :             : static const RelFileLocator emptyRelFileLocator = {0, 0, 0};
      48                 :             : 
      49                 :             : typedef struct XLogDumpConfig
      50                 :             : {
      51                 :             :     /* display options */
      52                 :             :     bool        quiet;
      53                 :             :     bool        bkp_details;
      54                 :             :     int         stop_after_records;
      55                 :             :     int         already_displayed_records;
      56                 :             :     bool        follow;
      57                 :             :     bool        stats;
      58                 :             :     bool        stats_per_record;
      59                 :             : 
      60                 :             :     /* filter options */
      61                 :             :     bool        filter_by_rmgr[RM_MAX_ID + 1];
      62                 :             :     bool        filter_by_rmgr_enabled;
      63                 :             :     TransactionId filter_by_xid;
      64                 :             :     bool        filter_by_xid_enabled;
      65                 :             :     RelFileLocator filter_by_relation;
      66                 :             :     bool        filter_by_extended;
      67                 :             :     bool        filter_by_relation_enabled;
      68                 :             :     BlockNumber filter_by_relation_block;
      69                 :             :     bool        filter_by_relation_block_enabled;
      70                 :             :     ForkNumber  filter_by_relation_forknum;
      71                 :             :     bool        filter_by_fpw;
      72                 :             : 
      73                 :             :     /* save options */
      74                 :             :     char       *save_fullpage_path;
      75                 :             : } XLogDumpConfig;
      76                 :             : 
      77                 :             : 
      78                 :             : /*
      79                 :             :  * When sigint is called, just tell the system to exit at the next possible
      80                 :             :  * moment.
      81                 :             :  */
      82                 :             : #ifndef WIN32
      83                 :             : 
      84                 :             : static void
      85                 :           0 : sigint_handler(SIGNAL_ARGS)
      86                 :             : {
      87                 :           0 :     time_to_stop = true;
      88                 :           0 : }
      89                 :             : #endif
      90                 :             : 
      91                 :             : static void
      92                 :           1 : print_rmgr_list(void)
      93                 :             : {
      94                 :             :     int         i;
      95                 :             : 
      96         [ +  + ]:          24 :     for (i = 0; i <= RM_MAX_BUILTIN_ID; i++)
      97                 :             :     {
      98                 :          23 :         printf("%s\n", GetRmgrDesc(i)->rm_name);
      99                 :             :     }
     100                 :           1 : }
     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                 :          72 : verify_directory(const char *directory)
     108                 :             : {
     109                 :          72 :     DIR        *dir = opendir(directory);
     110                 :             : 
     111         [ +  + ]:          72 :     if (dir == NULL)
     112                 :           1 :         return false;
     113                 :          71 :     closedir(dir);
     114                 :          71 :     return true;
     115                 :             : }
     116                 :             : 
     117                 :             : /*
     118                 :             :  * Create if necessary the directory storing the full-page images extracted
     119                 :             :  * from the WAL records read.
     120                 :             :  */
     121                 :             : static void
     122                 :           1 : create_fullpage_directory(char *path)
     123                 :             : {
     124                 :             :     int         ret;
     125                 :             : 
     126   [ +  -  -  - ]:           1 :     switch ((ret = pg_check_dir(path)))
     127                 :             :     {
     128                 :           1 :         case 0:
     129                 :             :             /* Does not exist, so create it */
     130         [ -  + ]:           1 :             if (pg_mkdir_p(path, pg_dir_create_mode) < 0)
     131                 :           0 :                 pg_fatal("could not create directory \"%s\": %m", path);
     132                 :           1 :             break;
     133                 :           0 :         case 1:
     134                 :             :             /* Present and empty, so do nothing */
     135                 :           0 :             break;
     136                 :           0 :         case 2:
     137                 :             :         case 3:
     138                 :             :         case 4:
     139                 :             :             /* Exists and not empty */
     140                 :           0 :             pg_fatal("directory \"%s\" exists but is not empty", path);
     141                 :             :             break;
     142                 :           0 :         default:
     143                 :             :             /* Trouble accessing directory */
     144                 :           0 :             pg_fatal("could not access directory \"%s\": %m", path);
     145                 :             :     }
     146                 :           1 : }
     147                 :             : 
     148                 :             : /*
     149                 :             :  * Split a pathname as dirname(1) and basename(1) would.
     150                 :             :  *
     151                 :             :  * XXX this probably doesn't do very well on Windows.  We probably need to
     152                 :             :  * apply canonicalize_path(), at the very least.
     153                 :             :  */
     154                 :             : static void
     155                 :          63 : split_path(const char *path, char **dir, char **fname)
     156                 :             : {
     157                 :             :     const char *sep;
     158                 :             : 
     159                 :             :     /* split filepath into directory & filename */
     160                 :          63 :     sep = strrchr(path, '/');
     161                 :             : 
     162                 :             :     /* directory path */
     163         [ +  + ]:          63 :     if (sep != NULL)
     164                 :             :     {
     165                 :          60 :         *dir = pnstrdup(path, sep - path);
     166                 :          60 :         *fname = pg_strdup(sep + 1);
     167                 :             :     }
     168                 :             :     /* local directory */
     169                 :             :     else
     170                 :             :     {
     171                 :           3 :         *dir = NULL;
     172                 :           3 :         *fname = pg_strdup(path);
     173                 :             :     }
     174                 :          63 : }
     175                 :             : 
     176                 :             : /*
     177                 :             :  * Open the file in the valid target directory.
     178                 :             :  *
     179                 :             :  * return a read only fd
     180                 :             :  */
     181                 :             : int
     182                 :         222 : open_file_in_directory(const char *directory, const char *fname)
     183                 :             : {
     184                 :         222 :     int         fd = -1;
     185                 :             :     char        fpath[MAXPGPATH];
     186                 :             : 
     187                 :             :     Assert(directory != NULL);
     188                 :             : 
     189                 :         222 :     snprintf(fpath, MAXPGPATH, "%s/%s", directory, fname);
     190                 :         222 :     fd = open(fpath, O_RDONLY | PG_BINARY, 0);
     191                 :             : 
     192   [ +  +  -  + ]:         222 :     if (fd < 0 && errno != ENOENT)
     193                 :           0 :         pg_fatal("could not open file \"%s\": %m", fname);
     194                 :         222 :     return fd;
     195                 :             : }
     196                 :             : 
     197                 :             : /*
     198                 :             :  * Try to find fname in the given directory. Returns true if it is found,
     199                 :             :  * false otherwise. If fname is NULL, search the complete directory for any
     200                 :             :  * file with a valid WAL file name. If file is successfully opened, set
     201                 :             :  * *WaSegSz to the WAL segment size.
     202                 :             :  */
     203                 :             : static bool
     204                 :          89 : search_directory(const char *directory, const char *fname, int *WalSegSz)
     205                 :             : {
     206                 :          89 :     int         fd = -1;
     207                 :             :     DIR        *xldir;
     208                 :             : 
     209                 :             :     /* open file if valid filename is provided */
     210         [ +  + ]:          89 :     if (fname != NULL)
     211                 :           8 :         fd = open_file_in_directory(directory, fname);
     212                 :             : 
     213                 :             :     /*
     214                 :             :      * A valid file name is not passed, so search the complete directory.  If
     215                 :             :      * we find any file whose name is a valid WAL file name then try to open
     216                 :             :      * it.  If we cannot open it, bail out.
     217                 :             :      */
     218         [ +  - ]:          81 :     else if ((xldir = opendir(directory)) != NULL)
     219                 :             :     {
     220                 :             :         struct dirent *xlde;
     221                 :             : 
     222         [ +  + ]:         654 :         while ((xlde = readdir(xldir)) != NULL)
     223                 :             :         {
     224         [ +  + ]:         638 :             if (IsXLogFileName(xlde->d_name))
     225                 :             :             {
     226                 :          65 :                 fd = open_file_in_directory(directory, xlde->d_name);
     227                 :          65 :                 fname = pg_strdup(xlde->d_name);
     228                 :          65 :                 break;
     229                 :             :             }
     230                 :             :         }
     231                 :             : 
     232                 :          81 :         closedir(xldir);
     233                 :             :     }
     234                 :             : 
     235                 :             :     /* set WalSegSz if file is successfully opened */
     236         [ +  + ]:          89 :     if (fd >= 0)
     237                 :             :     {
     238                 :             :         PGAlignedXLogBlock buf;
     239                 :             :         ssize_t     r;
     240                 :             : 
     241                 :          71 :         r = read(fd, buf.data, XLOG_BLCKSZ);
     242         [ +  - ]:          71 :         if (r == XLOG_BLCKSZ)
     243                 :             :         {
     244                 :          71 :             XLogLongPageHeader longhdr = (XLogLongPageHeader) buf.data;
     245                 :             : 
     246   [ +  -  +  +  :          71 :             if (!IsValidWalSegSize(longhdr->xlp_seg_size))
             +  -  -  + ]
     247                 :             :             {
     248                 :           1 :                 pg_log_error(ngettext("invalid WAL segment size in WAL file \"%s\" (%u byte)",
     249                 :             :                                       "invalid WAL segment size in WAL file \"%s\" (%u bytes)",
     250                 :             :                                       longhdr->xlp_seg_size),
     251                 :             :                              fname, longhdr->xlp_seg_size);
     252                 :           1 :                 pg_log_error_detail("The WAL segment size must be a power of two between 1 MB and 1 GB.");
     253                 :           1 :                 exit(1);
     254                 :             :             }
     255                 :             : 
     256                 :          70 :             *WalSegSz = longhdr->xlp_seg_size;
     257                 :             :         }
     258         [ #  # ]:           0 :         else if (r < 0)
     259                 :           0 :             pg_fatal("could not read file \"%s\": %m",
     260                 :             :                      fname);
     261                 :             :         else
     262                 :           0 :             pg_fatal("could not read file \"%s\": read %zd of %zu",
     263                 :             :                      fname, r, (size_t) XLOG_BLCKSZ);
     264                 :          70 :         close(fd);
     265                 :          70 :         return true;
     266                 :             :     }
     267                 :             : 
     268                 :          18 :     return false;
     269                 :             : }
     270                 :             : 
     271                 :             : /*
     272                 :             :  * Identify the target directory.
     273                 :             :  *
     274                 :             :  * Try to find the file in several places:
     275                 :             :  * if directory != NULL:
     276                 :             :  *   directory /
     277                 :             :  *   directory / XLOGDIR /
     278                 :             :  * else
     279                 :             :  *   .
     280                 :             :  *   XLOGDIR /
     281                 :             :  *   $PGDATA / XLOGDIR /
     282                 :             :  *
     283                 :             :  * The valid target directory is returned, and *WalSegSz is set to the
     284                 :             :  * size of the WAL segment found in that directory.
     285                 :             :  */
     286                 :             : static char *
     287                 :          72 : identify_target_directory(char *directory, char *fname, int *WalSegSz)
     288                 :             : {
     289                 :             :     char        fpath[MAXPGPATH];
     290                 :             : 
     291         [ +  + ]:          72 :     if (directory != NULL)
     292                 :             :     {
     293         [ +  + ]:          71 :         if (search_directory(directory, fname, WalSegSz))
     294                 :          54 :             return pg_strdup(directory);
     295                 :             : 
     296                 :             :         /* directory / XLOGDIR */
     297                 :          16 :         snprintf(fpath, MAXPGPATH, "%s/%s", directory, XLOGDIR);
     298         [ +  - ]:          16 :         if (search_directory(fpath, fname, WalSegSz))
     299                 :          16 :             return pg_strdup(fpath);
     300                 :             :     }
     301                 :             :     else
     302                 :             :     {
     303                 :             :         const char *datadir;
     304                 :             : 
     305                 :             :         /* current directory */
     306         [ -  + ]:           1 :         if (search_directory(".", fname, WalSegSz))
     307                 :           0 :             return pg_strdup(".");
     308                 :             :         /* XLOGDIR */
     309         [ -  + ]:           1 :         if (search_directory(XLOGDIR, fname, WalSegSz))
     310                 :           0 :             return pg_strdup(XLOGDIR);
     311                 :             : 
     312                 :           1 :         datadir = getenv("PGDATA");
     313                 :             :         /* $PGDATA / XLOGDIR */
     314         [ -  + ]:           1 :         if (datadir != NULL)
     315                 :             :         {
     316                 :           0 :             snprintf(fpath, MAXPGPATH, "%s/%s", datadir, XLOGDIR);
     317         [ #  # ]:           0 :             if (search_directory(fpath, fname, WalSegSz))
     318                 :           0 :                 return pg_strdup(fpath);
     319                 :             :         }
     320                 :             :     }
     321                 :             : 
     322                 :             :     /* could not locate WAL file */
     323         [ +  - ]:           1 :     if (fname)
     324                 :           1 :         pg_fatal("could not locate WAL file \"%s\"", fname);
     325                 :             :     else
     326                 :           0 :         pg_fatal("could not find any WAL file");
     327                 :             : 
     328                 :             :     return NULL;                /* not reached */
     329                 :             : }
     330                 :             : 
     331                 :             : /*
     332                 :             :  * Returns the number of bytes to read for the given page.  Returns -1 if
     333                 :             :  * the requested range has already been reached or exceeded.
     334                 :             :  */
     335                 :             : static inline int
     336                 :       49989 : required_read_len(XLogDumpPrivate *private, XLogRecPtr targetPagePtr,
     337                 :             :                   int reqLen)
     338                 :             : {
     339                 :       49989 :     int         count = XLOG_BLCKSZ;
     340                 :             : 
     341         [ +  + ]:       49989 :     if (XLogRecPtrIsValid(private->endptr))
     342                 :             :     {
     343         [ +  + ]:       42999 :         if (targetPagePtr + XLOG_BLCKSZ <= private->endptr)
     344                 :       42780 :             count = XLOG_BLCKSZ;
     345         [ +  + ]:         219 :         else if (targetPagePtr + reqLen <= private->endptr)
     346                 :         109 :             count = private->endptr - targetPagePtr;
     347                 :             :         else
     348                 :             :         {
     349                 :         110 :             private->endptr_reached = true;
     350                 :         110 :             return -1;
     351                 :             :         }
     352                 :             :     }
     353                 :             : 
     354                 :       49879 :     return count;
     355                 :             : }
     356                 :             : 
     357                 :             : /* pg_waldump's XLogReaderRoutine->segment_open callback */
     358                 :             : static void
     359                 :          87 : WALDumpOpenSegment(XLogReaderState *state, XLogSegNo nextSegNo,
     360                 :             :                    TimeLineID *tli_p)
     361                 :             : {
     362                 :          87 :     TimeLineID  tli = *tli_p;
     363                 :             :     char        fname[MAXPGPATH];
     364                 :             :     int         tries;
     365                 :             : 
     366                 :          87 :     XLogFileName(fname, tli, nextSegNo, state->segcxt.ws_segsize);
     367                 :             : 
     368                 :             :     /*
     369                 :             :      * In follow mode there is a short period of time after the server has
     370                 :             :      * written the end of the previous file before the new file is available.
     371                 :             :      * So we loop for 5 seconds looking for the file to appear before giving
     372                 :             :      * up.
     373                 :             :      */
     374         [ +  - ]:          87 :     for (tries = 0; tries < 10; tries++)
     375                 :             :     {
     376                 :          87 :         state->seg.ws_file = open_file_in_directory(state->segcxt.ws_dir, fname);
     377         [ +  - ]:          87 :         if (state->seg.ws_file >= 0)
     378                 :          87 :             return;
     379         [ #  # ]:           0 :         if (errno == ENOENT)
     380                 :           0 :         {
     381                 :           0 :             int         save_errno = errno;
     382                 :             : 
     383                 :             :             /* File not there yet, try again */
     384                 :           0 :             pg_usleep(500 * 1000);
     385                 :             : 
     386                 :           0 :             errno = save_errno;
     387                 :           0 :             continue;
     388                 :             :         }
     389                 :             :         /* Any other error, fall through and fail */
     390                 :           0 :         break;
     391                 :             :     }
     392                 :             : 
     393                 :           0 :     pg_fatal("could not find file \"%s\": %m", fname);
     394                 :             : }
     395                 :             : 
     396                 :             : /*
     397                 :             :  * pg_waldump's XLogReaderRoutine->segment_close callback.  Same as
     398                 :             :  * wal_segment_close
     399                 :             :  */
     400                 :             : static void
     401                 :          87 : WALDumpCloseSegment(XLogReaderState *state)
     402                 :             : {
     403                 :          87 :     close(state->seg.ws_file);
     404                 :             :     /* need to check errno? */
     405                 :          87 :     state->seg.ws_file = -1;
     406                 :          87 : }
     407                 :             : 
     408                 :             : /* pg_waldump's XLogReaderRoutine->page_read callback */
     409                 :             : static int
     410                 :       21513 : WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
     411                 :             :                 XLogRecPtr targetPtr, char *readBuff)
     412                 :             : {
     413                 :       21513 :     XLogDumpPrivate *private = state->private_data;
     414                 :       21513 :     int         count = required_read_len(private, targetPagePtr, reqLen);
     415                 :             :     WALReadError errinfo;
     416                 :             : 
     417                 :             :     /* Bail out if the end of the requested range has already been reached */
     418         [ +  + ]:       21513 :     if (count < 0)
     419                 :          64 :         return -1;
     420                 :             : 
     421         [ -  + ]:       21449 :     if (!WALRead(state, readBuff, targetPagePtr, count, private->timeline,
     422                 :             :                  &errinfo))
     423                 :             :     {
     424                 :           0 :         WALOpenSegment *seg = &errinfo.wre_seg;
     425                 :             :         char        fname[MAXPGPATH];
     426                 :             : 
     427                 :           0 :         XLogFileName(fname, seg->ws_tli, seg->ws_segno,
     428                 :             :                      state->segcxt.ws_segsize);
     429                 :             : 
     430         [ #  # ]:           0 :         if (errinfo.wre_errno != 0)
     431                 :             :         {
     432                 :           0 :             errno = errinfo.wre_errno;
     433                 :           0 :             pg_fatal("could not read from file \"%s\", offset %u: %m",
     434                 :             :                      fname, errinfo.wre_off);
     435                 :             :         }
     436                 :             :         else
     437                 :           0 :             pg_fatal("could not read from file \"%s\", offset %u: read %zd of %zu",
     438                 :             :                      fname, errinfo.wre_off, errinfo.wre_read,
     439                 :             :                      errinfo.wre_req);
     440                 :             :     }
     441                 :             : 
     442                 :       21449 :     return count;
     443                 :             : }
     444                 :             : 
     445                 :             : /*
     446                 :             :  * pg_waldump's XLogReaderRoutine->segment_open callback to support dumping WAL
     447                 :             :  * files from tar archives.  Segment tracking is handled by
     448                 :             :  * TarWALDumpReadPage, so no action is needed here.
     449                 :             :  */
     450                 :             : static void
     451                 :           0 : TarWALDumpOpenSegment(XLogReaderState *state, XLogSegNo nextSegNo,
     452                 :             :                       TimeLineID *tli_p)
     453                 :             : {
     454                 :             :     /* No action needed */
     455                 :           0 : }
     456                 :             : 
     457                 :             : /*
     458                 :             :  * pg_waldump's XLogReaderRoutine->segment_close callback to support dumping
     459                 :             :  * WAL files from tar archives.  Same as wal_segment_close.
     460                 :             :  */
     461                 :             : static void
     462                 :           0 : TarWALDumpCloseSegment(XLogReaderState *state)
     463                 :             : {
     464                 :           0 :     close(state->seg.ws_file);
     465                 :             :     /* need to check errno? */
     466                 :           0 :     state->seg.ws_file = -1;
     467                 :           0 : }
     468                 :             : 
     469                 :             : /*
     470                 :             :  * pg_waldump's XLogReaderRoutine->page_read callback to support dumping WAL
     471                 :             :  * files from tar archives.
     472                 :             :  */
     473                 :             : static int
     474                 :       28476 : TarWALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
     475                 :             :                    XLogRecPtr targetPtr, char *readBuff)
     476                 :             : {
     477                 :       28476 :     XLogDumpPrivate *private = state->private_data;
     478                 :       28476 :     int         count = required_read_len(private, targetPagePtr, reqLen);
     479                 :       28476 :     int         segsize = state->segcxt.ws_segsize;
     480                 :             :     XLogSegNo   curSegNo;
     481                 :             : 
     482                 :             :     /* Bail out if the end of the requested range has already been reached */
     483         [ +  + ]:       28476 :     if (count < 0)
     484                 :          46 :         return -1;
     485                 :             : 
     486                 :             :     /*
     487                 :             :      * If the target page is in a different segment, release the hash entry
     488                 :             :      * buffer and remove any spilled temporary file for the previous segment.
     489                 :             :      * Since pg_waldump never requests the same WAL bytes twice, moving to a
     490                 :             :      * new segment means the previous segment's data will not be needed again.
     491                 :             :      *
     492                 :             :      * Afterward, check whether the next required WAL segment was already
     493                 :             :      * spilled to the temporary directory before invoking the archive
     494                 :             :      * streamer.
     495                 :             :      */
     496                 :       28430 :     curSegNo = state->seg.ws_segno;
     497         [ +  + ]:       28430 :     if (!XLByteInSeg(targetPagePtr, curSegNo, segsize))
     498                 :             :     {
     499                 :             :         char        fname[MAXFNAMELEN];
     500                 :             :         XLogSegNo   nextSegNo;
     501                 :             : 
     502                 :             :         /*
     503                 :             :          * Calculate the next WAL segment to be decoded from the given page
     504                 :             :          * pointer.
     505                 :             :          */
     506                 :          88 :         XLByteToSeg(targetPagePtr, nextSegNo, segsize);
     507                 :          88 :         state->seg.ws_tli = private->timeline;
     508                 :          88 :         state->seg.ws_segno = nextSegNo;
     509                 :             : 
     510                 :             :         /* Close the WAL segment file if it is currently open */
     511         [ +  + ]:          88 :         if (state->seg.ws_file >= 0)
     512                 :             :         {
     513                 :           1 :             close(state->seg.ws_file);
     514                 :           1 :             state->seg.ws_file = -1;
     515                 :             :         }
     516                 :             : 
     517                 :             :         /*
     518                 :             :          * If in pre-reading mode (prior to actual decoding), do not delete
     519                 :             :          * any entries that might be requested again once the decoding loop
     520                 :             :          * starts. For more details, see the comments in
     521                 :             :          * read_archive_wal_page().
     522                 :             :          */
     523   [ +  +  +  + ]:          88 :         if (private->decoding_started && curSegNo < nextSegNo)
     524                 :             :         {
     525                 :          30 :             XLogFileName(fname, state->seg.ws_tli, curSegNo, segsize);
     526                 :          30 :             free_archive_wal_entry(fname, private);
     527                 :             :         }
     528                 :             : 
     529                 :             :         /*
     530                 :             :          * If the next segment exists in the temporary spill directory, open
     531                 :             :          * it and continue reading from there.
     532                 :             :          */
     533         [ +  + ]:          88 :         if (TmpWalSegDir != NULL)
     534                 :             :         {
     535                 :           2 :             XLogFileName(fname, state->seg.ws_tli, nextSegNo, segsize);
     536                 :           2 :             state->seg.ws_file = open_file_in_directory(TmpWalSegDir, fname);
     537                 :             :         }
     538                 :             :     }
     539                 :             : 
     540                 :             :     /* Continue reading from the open WAL segment, if any */
     541         [ +  + ]:       28430 :     if (state->seg.ws_file >= 0)
     542                 :           3 :         return WALDumpReadPage(state, targetPagePtr, count, targetPtr,
     543                 :             :                                readBuff);
     544                 :             : 
     545                 :             :     /* Otherwise, read the WAL page from the archive streamer */
     546                 :       28427 :     return read_archive_wal_page(private, targetPagePtr, count, readBuff);
     547                 :             : }
     548                 :             : 
     549                 :             : /*
     550                 :             :  * Boolean to return whether the given WAL record matches a specific relation
     551                 :             :  * and optionally block.
     552                 :             :  */
     553                 :             : static bool
     554                 :      365338 : XLogRecordMatchesRelationBlock(XLogReaderState *record,
     555                 :             :                                RelFileLocator matchRlocator,
     556                 :             :                                BlockNumber matchBlock,
     557                 :             :                                ForkNumber matchFork)
     558                 :             : {
     559                 :             :     int         block_id;
     560                 :             : 
     561         [ +  + ]:      781590 :     for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
     562                 :             :     {
     563                 :             :         RelFileLocator rlocator;
     564                 :             :         ForkNumber  forknum;
     565                 :             :         BlockNumber blk;
     566                 :             : 
     567         [ +  + ]:      416474 :         if (!XLogRecGetBlockTagExtended(record, block_id,
     568                 :             :                                         &rlocator, &forknum, &blk, NULL))
     569                 :          76 :             continue;
     570                 :             : 
     571   [ +  +  +  + ]:      416398 :         if ((matchFork == InvalidForkNumber || matchFork == forknum) &&
     572   [ +  +  +  -  :      286156 :             (RelFileLocatorEquals(matchRlocator, emptyRelFileLocator) ||
                   -  + ]
     573   [ +  +  +  -  :      286156 :              RelFileLocatorEquals(matchRlocator, rlocator)) &&
             +  -  +  + ]
     574         [ +  + ]:          12 :             (matchBlock == InvalidBlockNumber || matchBlock == blk))
     575                 :         222 :             return true;
     576                 :             :     }
     577                 :             : 
     578                 :      365116 :     return false;
     579                 :             : }
     580                 :             : 
     581                 :             : /*
     582                 :             :  * Boolean to return whether the given WAL record contains a full page write.
     583                 :             :  */
     584                 :             : static bool
     585                 :      113163 : XLogRecordHasFPW(XLogReaderState *record)
     586                 :             : {
     587                 :             :     int         block_id;
     588                 :             : 
     589         [ +  + ]:      240093 :     for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
     590                 :             :     {
     591   [ +  -  +  + ]:      130236 :         if (!XLogRecHasBlockRef(record, block_id))
     592                 :          21 :             continue;
     593                 :             : 
     594         [ +  + ]:      130215 :         if (XLogRecHasBlockImage(record, block_id))
     595                 :        3306 :             return true;
     596                 :             :     }
     597                 :             : 
     598                 :      109857 :     return false;
     599                 :             : }
     600                 :             : 
     601                 :             : /*
     602                 :             :  * Function to externally save all FPWs stored in the given WAL record.
     603                 :             :  * Decompression is applied to all the blocks saved, if necessary.
     604                 :             :  */
     605                 :             : static void
     606                 :         201 : XLogRecordSaveFPWs(XLogReaderState *record, const char *savepath)
     607                 :             : {
     608                 :             :     int         block_id;
     609                 :             : 
     610         [ +  + ]:         402 :     for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
     611                 :             :     {
     612                 :             :         PGAlignedBlock buf;
     613                 :             :         Page        page;
     614                 :             :         char        filename[MAXPGPATH];
     615                 :             :         char        forkname[FORKNAMECHARS + 2];    /* _ + terminating zero */
     616                 :             :         FILE       *file;
     617                 :             :         BlockNumber blk;
     618                 :             :         RelFileLocator rnode;
     619                 :             :         ForkNumber  fork;
     620                 :             : 
     621   [ +  -  -  + ]:         201 :         if (!XLogRecHasBlockRef(record, block_id))
     622                 :         200 :             continue;
     623                 :             : 
     624         [ +  + ]:         201 :         if (!XLogRecHasBlockImage(record, block_id))
     625                 :         200 :             continue;
     626                 :             : 
     627                 :           1 :         page = (Page) buf.data;
     628                 :             : 
     629                 :             :         /* Full page exists, so let's save it */
     630         [ -  + ]:           1 :         if (!RestoreBlockImage(record, block_id, page))
     631                 :           0 :             pg_fatal("%s", record->errormsg_buf);
     632                 :             : 
     633                 :           1 :         (void) XLogRecGetBlockTagExtended(record, block_id,
     634                 :             :                                           &rnode, &fork, &blk, NULL);
     635                 :             : 
     636   [ +  -  +  - ]:           1 :         if (fork >= 0 && fork <= MAX_FORKNUM)
     637                 :           1 :             sprintf(forkname, "_%s", forkNames[fork]);
     638                 :             :         else
     639                 :           0 :             pg_fatal("invalid fork number: %u", fork);
     640                 :             : 
     641                 :           1 :         snprintf(filename, MAXPGPATH, "%s/%08X-%08X-%08X.%u.%u.%u.%u%s", savepath,
     642                 :             :                  record->seg.ws_tli,
     643                 :           1 :                  LSN_FORMAT_ARGS(record->ReadRecPtr),
     644                 :             :                  rnode.spcOid, rnode.dbOid, rnode.relNumber, blk, forkname);
     645                 :             : 
     646                 :           1 :         file = fopen(filename, PG_BINARY_W);
     647         [ -  + ]:           1 :         if (!file)
     648                 :           0 :             pg_fatal("could not open file \"%s\": %m", filename);
     649                 :             : 
     650         [ -  + ]:           1 :         if (fwrite(page, BLCKSZ, 1, file) != 1)
     651                 :           0 :             pg_fatal("could not write file \"%s\": %m", filename);
     652                 :             : 
     653         [ -  + ]:           1 :         if (fclose(file) != 0)
     654                 :           0 :             pg_fatal("could not close file \"%s\": %m", filename);
     655                 :             :     }
     656                 :         201 : }
     657                 :             : 
     658                 :             : /*
     659                 :             :  * Print a record to stdout
     660                 :             :  */
     661                 :             : static void
     662                 :      588788 : XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
     663                 :             : {
     664                 :             :     const char *id;
     665                 :      588788 :     const RmgrDescData *desc = GetRmgrDesc(XLogRecGetRmid(record));
     666                 :             :     uint32      rec_len;
     667                 :             :     uint32      fpi_len;
     668                 :      588788 :     uint8       info = XLogRecGetInfo(record);
     669                 :      588788 :     XLogRecPtr  xl_prev = XLogRecGetPrev(record);
     670                 :             :     StringInfoData s;
     671                 :             : 
     672                 :      588788 :     XLogRecGetLen(record, &rec_len, &fpi_len);
     673                 :             : 
     674                 :      588788 :     printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, ",
     675                 :             :            desc->rm_name,
     676                 :             :            rec_len, XLogRecGetTotalLen(record),
     677                 :             :            XLogRecGetXid(record),
     678                 :             :            LSN_FORMAT_ARGS(record->ReadRecPtr),
     679                 :             :            LSN_FORMAT_ARGS(xl_prev));
     680                 :             : 
     681                 :      588788 :     id = desc->rm_identify(info);
     682         [ -  + ]:      588788 :     if (id == NULL)
     683                 :           0 :         printf("desc: UNKNOWN (%x) ", info & ~XLR_INFO_MASK);
     684                 :             :     else
     685                 :      588788 :         printf("desc: %s ", id);
     686                 :             : 
     687                 :      588788 :     initStringInfo(&s);
     688                 :      588788 :     desc->rm_desc(&s, record);
     689                 :      588788 :     printf("%s", s.data);
     690                 :             : 
     691                 :      588788 :     resetStringInfo(&s);
     692                 :      588788 :     XLogRecGetBlockRefInfo(record, true, config->bkp_details, &s, NULL);
     693                 :      588788 :     printf("%s", s.data);
     694                 :      588788 :     pfree(s.data);
     695                 :      588788 : }
     696                 :             : 
     697                 :             : /*
     698                 :             :  * Display a single row of record counts and sizes for an rmgr or record.
     699                 :             :  */
     700                 :             : static void
     701                 :         240 : XLogDumpStatsRow(const char *name,
     702                 :             :                  uint64 n, uint64 total_count,
     703                 :             :                  uint64 rec_len, uint64 total_rec_len,
     704                 :             :                  uint64 fpi_len, uint64 total_fpi_len,
     705                 :             :                  uint64 tot_len, uint64 total_len)
     706                 :             : {
     707                 :             :     double      n_pct,
     708                 :             :                 rec_len_pct,
     709                 :             :                 fpi_len_pct,
     710                 :             :                 tot_len_pct;
     711                 :             : 
     712                 :         240 :     n_pct = 0;
     713         [ +  - ]:         240 :     if (total_count != 0)
     714                 :         240 :         n_pct = 100 * (double) n / total_count;
     715                 :             : 
     716                 :         240 :     rec_len_pct = 0;
     717         [ +  - ]:         240 :     if (total_rec_len != 0)
     718                 :         240 :         rec_len_pct = 100 * (double) rec_len / total_rec_len;
     719                 :             : 
     720                 :         240 :     fpi_len_pct = 0;
     721         [ +  - ]:         240 :     if (total_fpi_len != 0)
     722                 :         240 :         fpi_len_pct = 100 * (double) fpi_len / total_fpi_len;
     723                 :             : 
     724                 :         240 :     tot_len_pct = 0;
     725         [ +  - ]:         240 :     if (total_len != 0)
     726                 :         240 :         tot_len_pct = 100 * (double) tot_len / total_len;
     727                 :             : 
     728                 :         240 :     printf("%-27s "
     729                 :             :            "%20" PRIu64 " (%6.02f) "
     730                 :             :            "%20" PRIu64 " (%6.02f) "
     731                 :             :            "%20" PRIu64 " (%6.02f) "
     732                 :             :            "%20" PRIu64 " (%6.02f)\n",
     733                 :             :            name, n, n_pct, rec_len, rec_len_pct, fpi_len, fpi_len_pct,
     734                 :             :            tot_len, tot_len_pct);
     735                 :         240 : }
     736                 :             : 
     737                 :             : 
     738                 :             : /*
     739                 :             :  * Display summary statistics about the records seen so far.
     740                 :             :  */
     741                 :             : static void
     742                 :           6 : XLogDumpDisplayStats(XLogDumpConfig *config, XLogStats *stats)
     743                 :             : {
     744                 :             :     int         ri,
     745                 :             :                 rj;
     746                 :           6 :     uint64      total_count = 0;
     747                 :           6 :     uint64      total_rec_len = 0;
     748                 :           6 :     uint64      total_fpi_len = 0;
     749                 :           6 :     uint64      total_len = 0;
     750                 :             :     double      rec_len_pct,
     751                 :             :                 fpi_len_pct;
     752                 :             : 
     753                 :             :     /*
     754                 :             :      * Leave if no stats have been computed yet, as tracked by the end LSN.
     755                 :             :      */
     756         [ -  + ]:           6 :     if (!XLogRecPtrIsValid(stats->endptr))
     757                 :           0 :         return;
     758                 :             : 
     759                 :             :     /*
     760                 :             :      * Each row shows its percentages of the total, so make a first pass to
     761                 :             :      * calculate column totals.
     762                 :             :      */
     763                 :             : 
     764         [ +  + ]:        1542 :     for (ri = 0; ri <= RM_MAX_ID; ri++)
     765                 :             :     {
     766   [ +  +  +  + ]:        1536 :         if (!RmgrIdIsValid(ri))
     767                 :         630 :             continue;
     768                 :             : 
     769                 :         906 :         total_count += stats->rmgr_stats[ri].count;
     770                 :         906 :         total_rec_len += stats->rmgr_stats[ri].rec_len;
     771                 :         906 :         total_fpi_len += stats->rmgr_stats[ri].fpi_len;
     772                 :             :     }
     773                 :           6 :     total_len = total_rec_len + total_fpi_len;
     774                 :             : 
     775                 :           6 :     printf("WAL statistics between %X/%08X and %X/%08X:\n",
     776                 :             :            LSN_FORMAT_ARGS(stats->startptr), LSN_FORMAT_ARGS(stats->endptr));
     777                 :             : 
     778                 :             :     /*
     779                 :             :      * 27 is strlen("Transaction/COMMIT_PREPARED"), 20 is strlen(2^64), 8 is
     780                 :             :      * strlen("(100.00%)")
     781                 :             :      */
     782                 :             : 
     783                 :           6 :     printf("%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n"
     784                 :             :            "%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n",
     785                 :             :            "Type", "N", "(%)", "Record size", "(%)", "FPI size", "(%)", "Combined size", "(%)",
     786                 :             :            "----", "-", "---", "-----------", "---", "--------", "---", "-------------", "---");
     787                 :             : 
     788         [ +  + ]:        1542 :     for (ri = 0; ri <= RM_MAX_ID; ri++)
     789                 :             :     {
     790                 :             :         uint64      count,
     791                 :             :                     rec_len,
     792                 :             :                     fpi_len,
     793                 :             :                     tot_len;
     794                 :             :         const RmgrDescData *desc;
     795                 :             : 
     796   [ +  +  +  + ]:        1536 :         if (!RmgrIdIsValid(ri))
     797                 :         630 :             continue;
     798                 :             : 
     799                 :         906 :         desc = GetRmgrDesc(ri);
     800                 :             : 
     801         [ +  + ]:         906 :         if (!config->stats_per_record)
     802                 :             :         {
     803                 :         453 :             count = stats->rmgr_stats[ri].count;
     804                 :         453 :             rec_len = stats->rmgr_stats[ri].rec_len;
     805                 :         453 :             fpi_len = stats->rmgr_stats[ri].fpi_len;
     806                 :         453 :             tot_len = rec_len + fpi_len;
     807                 :             : 
     808   [ +  +  +  - ]:         453 :             if (RmgrIdIsCustom(ri) && count == 0)
     809                 :         384 :                 continue;
     810                 :             : 
     811                 :          69 :             XLogDumpStatsRow(desc->rm_name,
     812                 :             :                              count, total_count, rec_len, total_rec_len,
     813                 :             :                              fpi_len, total_fpi_len, tot_len, total_len);
     814                 :             :         }
     815                 :             :         else
     816                 :             :         {
     817         [ +  + ]:        7701 :             for (rj = 0; rj < MAX_XLINFO_TYPES; rj++)
     818                 :             :             {
     819                 :             :                 const char *id;
     820                 :             : 
     821                 :        7248 :                 count = stats->record_stats[ri][rj].count;
     822                 :        7248 :                 rec_len = stats->record_stats[ri][rj].rec_len;
     823                 :        7248 :                 fpi_len = stats->record_stats[ri][rj].fpi_len;
     824                 :        7248 :                 tot_len = rec_len + fpi_len;
     825                 :             : 
     826                 :             :                 /* Skip undefined combinations and ones that didn't occur */
     827         [ +  + ]:        7248 :                 if (count == 0)
     828                 :        7077 :                     continue;
     829                 :             : 
     830                 :             :                 /* the upper four bits in xl_info are the rmgr's */
     831                 :         171 :                 id = desc->rm_identify(rj << 4);
     832         [ -  + ]:         171 :                 if (id == NULL)
     833                 :           0 :                     id = psprintf("UNKNOWN (%x)", rj << 4);
     834                 :             : 
     835                 :         171 :                 XLogDumpStatsRow(psprintf("%s/%s", desc->rm_name, id),
     836                 :             :                                  count, total_count, rec_len, total_rec_len,
     837                 :             :                                  fpi_len, total_fpi_len, tot_len, total_len);
     838                 :             :             }
     839                 :             :         }
     840                 :             :     }
     841                 :             : 
     842                 :           6 :     printf("%-27s %20s %8s %20s %8s %20s %8s %20s\n",
     843                 :             :            "", "--------", "", "--------", "", "--------", "", "--------");
     844                 :             : 
     845                 :             :     /*
     846                 :             :      * The percentages in earlier rows were calculated against the column
     847                 :             :      * total, but the ones that follow are against the row total. Note that
     848                 :             :      * these are displayed with a % symbol to differentiate them from the
     849                 :             :      * earlier ones, and are thus up to 9 characters long.
     850                 :             :      */
     851                 :             : 
     852                 :           6 :     rec_len_pct = 0;
     853         [ +  - ]:           6 :     if (total_len != 0)
     854                 :           6 :         rec_len_pct = 100 * (double) total_rec_len / total_len;
     855                 :             : 
     856                 :           6 :     fpi_len_pct = 0;
     857         [ +  - ]:           6 :     if (total_len != 0)
     858                 :           6 :         fpi_len_pct = 100 * (double) total_fpi_len / total_len;
     859                 :             : 
     860                 :           6 :     printf("%-27s "
     861                 :             :            "%20" PRIu64 " %-9s"
     862                 :             :            "%20" PRIu64 " %-9s"
     863                 :             :            "%20" PRIu64 " %-9s"
     864                 :             :            "%20" PRIu64 " %-6s\n",
     865                 :             :            "Total", stats->count, "",
     866                 :             :            total_rec_len, psprintf("[%.02f%%]", rec_len_pct),
     867                 :             :            total_fpi_len, psprintf("[%.02f%%]", fpi_len_pct),
     868                 :             :            total_len, "[100%]");
     869                 :             : }
     870                 :             : 
     871                 :             : /*
     872                 :             :  * Remove temporary directory at exit, if any.
     873                 :             :  */
     874                 :             : static void
     875                 :         120 : cleanup_tmpwal_dir_atexit(void)
     876                 :             : {
     877                 :             :     /*
     878                 :             :      * Before calling rmtree, we must close any open file we have in the temp
     879                 :             :      * directory; else rmdir fails on Windows.
     880                 :             :      */
     881         [ +  + ]:         120 :     if (xlogreader_state_cleanup != NULL &&
     882         [ +  + ]:           7 :         xlogreader_state_cleanup->seg.ws_file >= 0)
     883                 :           3 :         WALDumpCloseSegment(xlogreader_state_cleanup);
     884                 :             : 
     885         [ +  + ]:         120 :     if (TmpWalSegDir != NULL)
     886                 :             :     {
     887                 :           2 :         rmtree(TmpWalSegDir, true);
     888                 :           2 :         TmpWalSegDir = NULL;
     889                 :             :     }
     890                 :         120 : }
     891                 :             : 
     892                 :             : static void
     893                 :           1 : usage(void)
     894                 :             : {
     895                 :           1 :     printf(_("%s decodes and displays PostgreSQL write-ahead logs for debugging.\n\n"),
     896                 :             :            progname);
     897                 :           1 :     printf(_("Usage:\n"));
     898                 :           1 :     printf(_("  %s [OPTION]... [STARTSEG [ENDSEG]]\n"), progname);
     899                 :           1 :     printf(_("\nOptions:\n"));
     900                 :           1 :     printf(_("  -b, --bkp-details      output detailed information about backup blocks\n"));
     901                 :           1 :     printf(_("  -B, --block=N          with --relation, only show records that modify block N\n"));
     902                 :           1 :     printf(_("  -e, --end=RECPTR       stop reading at WAL location RECPTR\n"));
     903                 :           1 :     printf(_("  -f, --follow           keep retrying after reaching end of WAL\n"));
     904                 :           1 :     printf(_("  -F, --fork=FORK        only show records that modify blocks in fork FORK;\n"
     905                 :             :              "                         valid names are main, fsm, vm, init\n"));
     906                 :           1 :     printf(_("  -n, --limit=N          number of records to display\n"));
     907                 :           1 :     printf(_("  -p, --path=PATH        a tar archive or a directory in which to find WAL segment files or\n"
     908                 :             :              "                         a directory with a pg_wal subdirectory containing such files\n"
     909                 :             :              "                         (default: current directory, ./pg_wal, $PGDATA/pg_wal)\n"));
     910                 :           1 :     printf(_("  -q, --quiet            do not print any output, except for errors\n"));
     911                 :           1 :     printf(_("  -r, --rmgr=RMGR        only show records generated by resource manager RMGR;\n"
     912                 :             :              "                         use --rmgr=list to list valid resource manager names\n"));
     913                 :           1 :     printf(_("  -R, --relation=T/D/R   only show records that modify blocks in relation T/D/R\n"));
     914                 :           1 :     printf(_("  -s, --start=RECPTR     start reading at WAL location RECPTR\n"));
     915                 :           1 :     printf(_("  -t, --timeline=TLI     timeline from which to read WAL records\n"
     916                 :             :              "                         (default: 1 or the value used in STARTSEG)\n"));
     917                 :           1 :     printf(_("  -V, --version          output version information, then exit\n"));
     918                 :           1 :     printf(_("  -w, --fullpage         only show records with a full page write\n"));
     919                 :           1 :     printf(_("  -x, --xid=XID          only show records with transaction ID XID\n"));
     920                 :           1 :     printf(_("  -z, --stats[=record]   show statistics instead of records\n"
     921                 :             :              "                         (optionally, show per-record statistics)\n"));
     922                 :           1 :     printf(_("  --save-fullpage=DIR    save full page images to DIR\n"));
     923                 :           1 :     printf(_("  -?, --help             show this help, then exit\n"));
     924                 :           1 :     printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
     925                 :           1 :     printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
     926                 :           1 : }
     927                 :             : 
     928                 :             : int
     929                 :         251 : main(int argc, char **argv)
     930                 :             : {
     931                 :             :     uint32      xlogid;
     932                 :             :     uint32      xrecoff;
     933                 :             :     XLogReaderState *xlogreader_state;
     934                 :             :     XLogDumpPrivate private;
     935                 :             :     XLogDumpConfig config;
     936                 :             :     XLogStats   stats;
     937                 :             :     XLogRecord *record;
     938                 :             :     XLogRecPtr  first_record;
     939                 :         251 :     char       *waldir = NULL;
     940                 :             :     char       *errormsg;
     941                 :         251 :     pg_compress_algorithm compression = PG_COMPRESSION_NONE;
     942                 :             : 
     943                 :             :     static struct option long_options[] = {
     944                 :             :         {"bkp-details", no_argument, NULL, 'b'},
     945                 :             :         {"block", required_argument, NULL, 'B'},
     946                 :             :         {"end", required_argument, NULL, 'e'},
     947                 :             :         {"follow", no_argument, NULL, 'f'},
     948                 :             :         {"fork", required_argument, NULL, 'F'},
     949                 :             :         {"fullpage", no_argument, NULL, 'w'},
     950                 :             :         {"help", no_argument, NULL, '?'},
     951                 :             :         {"limit", required_argument, NULL, 'n'},
     952                 :             :         {"path", required_argument, NULL, 'p'},
     953                 :             :         {"quiet", no_argument, NULL, 'q'},
     954                 :             :         {"relation", required_argument, NULL, 'R'},
     955                 :             :         {"rmgr", required_argument, NULL, 'r'},
     956                 :             :         {"start", required_argument, NULL, 's'},
     957                 :             :         {"timeline", required_argument, NULL, 't'},
     958                 :             :         {"xid", required_argument, NULL, 'x'},
     959                 :             :         {"version", no_argument, NULL, 'V'},
     960                 :             :         {"stats", optional_argument, NULL, 'z'},
     961                 :             :         {"save-fullpage", required_argument, NULL, 1},
     962                 :             :         {NULL, 0, NULL, 0}
     963                 :             :     };
     964                 :             : 
     965                 :             :     int         option;
     966                 :         251 :     int         optindex = 0;
     967                 :             : 
     968                 :             : #ifndef WIN32
     969                 :         251 :     pqsignal(SIGINT, sigint_handler);
     970                 :             : #endif
     971                 :             : 
     972                 :         251 :     pg_logging_init(argv[0]);
     973                 :         251 :     set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_waldump"));
     974                 :         251 :     progname = get_progname(argv[0]);
     975                 :             : 
     976         [ +  + ]:         251 :     if (argc > 1)
     977                 :             :     {
     978   [ +  +  -  + ]:         250 :         if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
     979                 :             :         {
     980                 :           1 :             usage();
     981                 :           1 :             exit(0);
     982                 :             :         }
     983   [ +  +  +  + ]:         249 :         if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
     984                 :             :         {
     985                 :         112 :             puts("pg_waldump (PostgreSQL) " PG_VERSION);
     986                 :         112 :             exit(0);
     987                 :             :         }
     988                 :             :     }
     989                 :             : 
     990                 :         138 :     memset(&private, 0, sizeof(XLogDumpPrivate));
     991                 :         138 :     memset(&config, 0, sizeof(XLogDumpConfig));
     992                 :         138 :     memset(&stats, 0, sizeof(XLogStats));
     993                 :             : 
     994                 :         138 :     private.timeline = 1;
     995                 :         138 :     private.segsize = 0;
     996                 :         138 :     private.startptr = InvalidXLogRecPtr;
     997                 :         138 :     private.endptr = InvalidXLogRecPtr;
     998                 :         138 :     private.endptr_reached = false;
     999                 :         138 :     private.decoding_started = false;
    1000                 :         138 :     private.archive_name = NULL;
    1001                 :         138 :     private.start_segno = 0;
    1002                 :         138 :     private.end_segno = UINT64_MAX;
    1003                 :             : 
    1004                 :         138 :     config.quiet = false;
    1005                 :         138 :     config.bkp_details = false;
    1006                 :         138 :     config.stop_after_records = -1;
    1007                 :         138 :     config.already_displayed_records = 0;
    1008                 :         138 :     config.follow = false;
    1009                 :             :     /* filter_by_rmgr array was zeroed by memset above */
    1010                 :         138 :     config.filter_by_rmgr_enabled = false;
    1011                 :         138 :     config.filter_by_xid = InvalidTransactionId;
    1012                 :         138 :     config.filter_by_xid_enabled = false;
    1013                 :         138 :     config.filter_by_extended = false;
    1014                 :         138 :     config.filter_by_relation_enabled = false;
    1015                 :         138 :     config.filter_by_relation_block_enabled = false;
    1016                 :         138 :     config.filter_by_relation_forknum = InvalidForkNumber;
    1017                 :         138 :     config.filter_by_fpw = false;
    1018                 :         138 :     config.save_fullpage_path = NULL;
    1019                 :         138 :     config.stats = false;
    1020                 :         138 :     config.stats_per_record = false;
    1021                 :             : 
    1022                 :         138 :     stats.startptr = InvalidXLogRecPtr;
    1023                 :         138 :     stats.endptr = InvalidXLogRecPtr;
    1024                 :             : 
    1025         [ +  + ]:         138 :     if (argc <= 1)
    1026                 :             :     {
    1027                 :           1 :         pg_log_error("no arguments specified");
    1028                 :           1 :         goto bad_argument;
    1029                 :             :     }
    1030                 :             : 
    1031                 :         664 :     while ((option = getopt_long(argc, argv, "bB:e:fF:n:p:qr:R:s:t:wx:z",
    1032         [ +  + ]:         664 :                                  long_options, &optindex)) != -1)
    1033                 :             :     {
    1034   [ -  +  +  -  :         536 :         switch (option)
          +  +  +  +  +  
          +  +  +  +  -  
                +  +  + ]
    1035                 :             :         {
    1036                 :           0 :             case 'b':
    1037                 :           0 :                 config.bkp_details = true;
    1038                 :           0 :                 break;
    1039                 :           4 :             case 'B':
    1040         [ +  + ]:           4 :                 if (sscanf(optarg, "%u", &config.filter_by_relation_block) != 1 ||
    1041         [ -  + ]:           3 :                     !BlockNumberIsValid(config.filter_by_relation_block))
    1042                 :             :                 {
    1043                 :           1 :                     pg_log_error("invalid block number: \"%s\"", optarg);
    1044                 :           1 :                     goto bad_argument;
    1045                 :             :                 }
    1046                 :           3 :                 config.filter_by_relation_block_enabled = true;
    1047                 :           3 :                 config.filter_by_extended = true;
    1048                 :           3 :                 break;
    1049                 :         112 :             case 'e':
    1050         [ +  + ]:         112 :                 if (sscanf(optarg, "%X/%08X", &xlogid, &xrecoff) != 2)
    1051                 :             :                 {
    1052                 :           1 :                     pg_log_error("invalid WAL location: \"%s\"",
    1053                 :             :                                  optarg);
    1054                 :           1 :                     goto bad_argument;
    1055                 :             :                 }
    1056                 :         111 :                 private.endptr = (uint64) xlogid << 32 | xrecoff;
    1057                 :         111 :                 break;
    1058                 :           0 :             case 'f':
    1059                 :           0 :                 config.follow = true;
    1060                 :           0 :                 break;
    1061                 :           4 :             case 'F':
    1062                 :           4 :                 config.filter_by_relation_forknum = forkname_to_number(optarg);
    1063         [ +  + ]:           4 :                 if (config.filter_by_relation_forknum == InvalidForkNumber)
    1064                 :             :                 {
    1065                 :           1 :                     pg_log_error("invalid fork name: \"%s\"", optarg);
    1066                 :           1 :                     goto bad_argument;
    1067                 :             :                 }
    1068                 :           3 :                 config.filter_by_extended = true;
    1069                 :           3 :                 break;
    1070                 :           4 :             case 'n':
    1071         [ +  + ]:           4 :                 if (sscanf(optarg, "%d", &config.stop_after_records) != 1)
    1072                 :             :                 {
    1073                 :           1 :                     pg_log_error("invalid value \"%s\" for option %s", optarg, "-n/--limit");
    1074                 :           1 :                     goto bad_argument;
    1075                 :             :                 }
    1076                 :           3 :                 break;
    1077                 :         121 :             case 'p':
    1078                 :         121 :                 waldir = pg_strdup(optarg);
    1079                 :         121 :                 break;
    1080                 :          77 :             case 'q':
    1081                 :          77 :                 config.quiet = true;
    1082                 :          77 :                 break;
    1083                 :           5 :             case 'r':
    1084                 :             :                 {
    1085                 :             :                     int         rmid;
    1086                 :             : 
    1087         [ +  + ]:           5 :                     if (pg_strcasecmp(optarg, "list") == 0)
    1088                 :             :                     {
    1089                 :           1 :                         print_rmgr_list();
    1090                 :           1 :                         exit(EXIT_SUCCESS);
    1091                 :             :                     }
    1092                 :             : 
    1093                 :             :                     /*
    1094                 :             :                      * First look for the generated name of a custom rmgr, of
    1095                 :             :                      * the form "custom###". We accept this form, because the
    1096                 :             :                      * custom rmgr module is not loaded, so there's no way to
    1097                 :             :                      * know the real name. This convention should be
    1098                 :             :                      * consistent with that in rmgrdesc.c.
    1099                 :             :                      */
    1100         [ -  + ]:           4 :                     if (sscanf(optarg, "custom%03d", &rmid) == 1)
    1101                 :             :                     {
    1102         [ #  # ]:           0 :                         if (!RmgrIdIsCustom(rmid))
    1103                 :             :                         {
    1104                 :           0 :                             pg_log_error("custom resource manager \"%s\" does not exist",
    1105                 :             :                                          optarg);
    1106                 :           1 :                             goto bad_argument;
    1107                 :             :                         }
    1108                 :           0 :                         config.filter_by_rmgr[rmid] = true;
    1109                 :           0 :                         config.filter_by_rmgr_enabled = true;
    1110                 :             :                     }
    1111                 :             :                     else
    1112                 :             :                     {
    1113                 :             :                         /* then look for builtin rmgrs */
    1114         [ +  + ]:          60 :                         for (rmid = 0; rmid <= RM_MAX_BUILTIN_ID; rmid++)
    1115                 :             :                         {
    1116         [ +  + ]:          59 :                             if (pg_strcasecmp(optarg, GetRmgrDesc(rmid)->rm_name) == 0)
    1117                 :             :                             {
    1118                 :           3 :                                 config.filter_by_rmgr[rmid] = true;
    1119                 :           3 :                                 config.filter_by_rmgr_enabled = true;
    1120                 :           3 :                                 break;
    1121                 :             :                             }
    1122                 :             :                         }
    1123         [ +  + ]:           4 :                         if (rmid > RM_MAX_BUILTIN_ID)
    1124                 :             :                         {
    1125                 :           1 :                             pg_log_error("resource manager \"%s\" does not exist",
    1126                 :             :                                          optarg);
    1127                 :           1 :                             goto bad_argument;
    1128                 :             :                         }
    1129                 :             :                     }
    1130                 :             :                 }
    1131                 :           3 :                 break;
    1132                 :           8 :             case 'R':
    1133         [ +  + ]:           8 :                 if (sscanf(optarg, "%u/%u/%u",
    1134                 :             :                            &config.filter_by_relation.spcOid,
    1135                 :             :                            &config.filter_by_relation.dbOid,
    1136                 :           7 :                            &config.filter_by_relation.relNumber) != 3 ||
    1137         [ +  - ]:           7 :                     !OidIsValid(config.filter_by_relation.spcOid) ||
    1138         [ -  + ]:           7 :                     !RelFileNumberIsValid(config.filter_by_relation.relNumber))
    1139                 :             :                 {
    1140                 :           1 :                     pg_log_error("invalid relation specification: \"%s\"", optarg);
    1141                 :           1 :                     pg_log_error_detail("Expecting \"tablespace OID/database OID/relation filenode\".");
    1142                 :           1 :                     goto bad_argument;
    1143                 :             :                 }
    1144                 :           7 :                 config.filter_by_relation_enabled = true;
    1145                 :           7 :                 config.filter_by_extended = true;
    1146                 :           7 :                 break;
    1147                 :         118 :             case 's':
    1148         [ +  + ]:         118 :                 if (sscanf(optarg, "%X/%08X", &xlogid, &xrecoff) != 2)
    1149                 :             :                 {
    1150                 :           1 :                     pg_log_error("invalid WAL location: \"%s\"",
    1151                 :             :                                  optarg);
    1152                 :           1 :                     goto bad_argument;
    1153                 :             :                 }
    1154                 :             :                 else
    1155                 :         117 :                     private.startptr = (uint64) xlogid << 32 | xrecoff;
    1156                 :         117 :                 break;
    1157                 :          72 :             case 't':
    1158                 :             : 
    1159                 :             :                 /*
    1160                 :             :                  * This is like option_parse_int() but needs to handle
    1161                 :             :                  * unsigned 32-bit int.  Also, we accept both decimal and
    1162                 :             :                  * hexadecimal specifications here.
    1163                 :             :                  */
    1164                 :             :                 {
    1165                 :             :                     char       *endptr;
    1166                 :             :                     unsigned long val;
    1167                 :             : 
    1168                 :          72 :                     errno = 0;
    1169                 :          72 :                     val = strtoul(optarg, &endptr, 0);
    1170                 :             : 
    1171   [ -  +  -  - ]:          72 :                     while (*endptr != '\0' && isspace((unsigned char) *endptr))
    1172                 :           0 :                         endptr++;
    1173                 :             : 
    1174         [ -  + ]:          72 :                     if (*endptr != '\0')
    1175                 :             :                     {
    1176                 :           0 :                         pg_log_error("invalid value \"%s\" for option %s",
    1177                 :             :                                      optarg, "-t/--timeline");
    1178                 :           0 :                         goto bad_argument;
    1179                 :             :                     }
    1180                 :             : 
    1181   [ +  -  +  -  :          72 :                     if (errno == ERANGE || val < 1 || val > UINT_MAX)
                   -  + ]
    1182                 :             :                     {
    1183                 :           0 :                         pg_log_error("%s must be in range %u..%u",
    1184                 :             :                                      "-t/--timeline", 1, UINT_MAX);
    1185                 :           0 :                         goto bad_argument;
    1186                 :             :                     }
    1187                 :             : 
    1188                 :          72 :                     private.timeline = val;
    1189                 :             : 
    1190                 :          72 :                     break;
    1191                 :             :                 }
    1192                 :           3 :             case 'w':
    1193                 :           3 :                 config.filter_by_fpw = true;
    1194                 :           3 :                 break;
    1195                 :           0 :             case 'x':
    1196         [ #  # ]:           0 :                 if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
    1197                 :             :                 {
    1198                 :           0 :                     pg_log_error("invalid transaction ID specification: \"%s\"",
    1199                 :             :                                  optarg);
    1200                 :           0 :                     goto bad_argument;
    1201                 :             :                 }
    1202                 :           0 :                 config.filter_by_xid_enabled = true;
    1203                 :           0 :                 break;
    1204                 :           6 :             case 'z':
    1205                 :           6 :                 config.stats = true;
    1206                 :           6 :                 config.stats_per_record = false;
    1207         [ +  + ]:           6 :                 if (optarg)
    1208                 :             :                 {
    1209         [ +  - ]:           3 :                     if (strcmp(optarg, "record") == 0)
    1210                 :           3 :                         config.stats_per_record = true;
    1211         [ #  # ]:           0 :                     else if (strcmp(optarg, "rmgr") != 0)
    1212                 :             :                     {
    1213                 :           0 :                         pg_log_error("unrecognized value for option %s: %s",
    1214                 :             :                                      "--stats", optarg);
    1215                 :           0 :                         goto bad_argument;
    1216                 :             :                     }
    1217                 :             :                 }
    1218                 :           6 :                 break;
    1219                 :           1 :             case 1:
    1220                 :           1 :                 config.save_fullpage_path = pg_strdup(optarg);
    1221                 :           1 :                 break;
    1222                 :           1 :             default:
    1223                 :           1 :                 goto bad_argument;
    1224                 :             :         }
    1225                 :             :     }
    1226                 :             : 
    1227         [ +  + ]:         128 :     if (config.filter_by_relation_block_enabled &&
    1228         [ -  + ]:           3 :         !config.filter_by_relation_enabled)
    1229                 :             :     {
    1230                 :           0 :         pg_log_error("option %s requires option %s to be specified",
    1231                 :             :                      "-B/--block", "-R/--relation");
    1232                 :           0 :         goto bad_argument;
    1233                 :             :     }
    1234                 :             : 
    1235         [ +  + ]:         128 :     if ((optind + 2) < argc)
    1236                 :             :     {
    1237                 :           1 :         pg_log_error("too many command-line arguments (first is \"%s\")",
    1238                 :             :                      argv[optind + 2]);
    1239                 :           1 :         goto bad_argument;
    1240                 :             :     }
    1241                 :             : 
    1242         [ +  + ]:         127 :     if (waldir != NULL)
    1243                 :             :     {
    1244                 :             :         /* Check whether the path looks like a tar archive by its extension */
    1245         [ +  + ]:         121 :         if (parse_tar_compress_algorithm(waldir, &compression))
    1246                 :             :         {
    1247                 :          54 :             split_path(waldir, &private.archive_dir, &private.archive_name);
    1248                 :             :         }
    1249                 :             :         /* Otherwise it must be a directory */
    1250         [ +  + ]:          67 :         else if (!verify_directory(waldir))
    1251                 :             :         {
    1252                 :           1 :             pg_log_error("could not open directory \"%s\": %m", waldir);
    1253                 :           1 :             goto bad_argument;
    1254                 :             :         }
    1255                 :             :     }
    1256                 :             : 
    1257         [ +  + ]:         126 :     if (config.save_fullpage_path != NULL)
    1258                 :           1 :         create_fullpage_directory(config.save_fullpage_path);
    1259                 :             : 
    1260                 :             :     /* parse files as start/end boundaries, extract path if not specified */
    1261         [ +  + ]:         126 :     if (optind < argc)
    1262                 :             :     {
    1263                 :           7 :         char       *directory = NULL;
    1264                 :           7 :         char       *fname = NULL;
    1265                 :             :         int         fd;
    1266                 :             :         XLogSegNo   segno;
    1267                 :             : 
    1268                 :             :         /*
    1269                 :             :          * If a tar archive is passed using the --path option, all other
    1270                 :             :          * arguments become unnecessary.
    1271                 :             :          */
    1272         [ -  + ]:           7 :         if (private.archive_name)
    1273                 :             :         {
    1274                 :           0 :             pg_log_error("unnecessary command-line arguments specified with tar archive (first is \"%s\")",
    1275                 :             :                          argv[optind]);
    1276                 :           0 :             goto bad_argument;
    1277                 :             :         }
    1278                 :             : 
    1279                 :           7 :         split_path(argv[optind], &directory, &fname);
    1280                 :             : 
    1281   [ +  +  +  + ]:           7 :         if (waldir == NULL && directory != NULL)
    1282                 :             :         {
    1283                 :           5 :             waldir = directory;
    1284                 :             : 
    1285         [ -  + ]:           5 :             if (!verify_directory(waldir))
    1286                 :           0 :                 pg_fatal("could not open directory \"%s\": %m", waldir);
    1287                 :             :         }
    1288                 :             : 
    1289   [ +  -  -  + ]:           7 :         if (fname != NULL && parse_tar_compress_algorithm(fname, &compression))
    1290                 :             :         {
    1291                 :           0 :             private.archive_dir = waldir;
    1292                 :           0 :             private.archive_name = fname;
    1293                 :             :         }
    1294                 :             :         else
    1295                 :             :         {
    1296                 :           7 :             waldir = identify_target_directory(waldir, fname, &private.segsize);
    1297                 :           6 :             fd = open_file_in_directory(waldir, fname);
    1298         [ -  + ]:           6 :             if (fd < 0)
    1299                 :           0 :                 pg_fatal("could not open file \"%s\"", fname);
    1300                 :           6 :             close(fd);
    1301                 :             : 
    1302                 :             :             /* parse position from file */
    1303                 :           6 :             XLogFromFileName(fname, &private.timeline, &segno, private.segsize);
    1304                 :             : 
    1305         [ +  - ]:           6 :             if (!XLogRecPtrIsValid(private.startptr))
    1306                 :           6 :                 XLogSegNoOffsetToRecPtr(segno, 0, private.segsize, private.startptr);
    1307         [ #  # ]:           0 :             else if (!XLByteInSeg(private.startptr, segno, private.segsize))
    1308                 :             :             {
    1309                 :           0 :                 pg_log_error("start WAL location %X/%08X is not inside file \"%s\"",
    1310                 :             :                              LSN_FORMAT_ARGS(private.startptr),
    1311                 :             :                              fname);
    1312                 :           0 :                 goto bad_argument;
    1313                 :             :             }
    1314                 :             : 
    1315                 :             :             /* no second file specified, set end position */
    1316   [ +  +  +  - ]:           6 :             if (!(optind + 1 < argc) && !XLogRecPtrIsValid(private.endptr))
    1317                 :           4 :                 XLogSegNoOffsetToRecPtr(segno + 1, 0, private.segsize, private.endptr);
    1318                 :             : 
    1319                 :             :             /* parse ENDSEG if passed */
    1320         [ +  + ]:           6 :             if (optind + 1 < argc)
    1321                 :             :             {
    1322                 :             :                 XLogSegNo   endsegno;
    1323                 :             : 
    1324                 :             :                 /* ignore directory, already have that */
    1325                 :           2 :                 split_path(argv[optind + 1], &directory, &fname);
    1326                 :             : 
    1327                 :           2 :                 fd = open_file_in_directory(waldir, fname);
    1328         [ +  + ]:           2 :                 if (fd < 0)
    1329                 :           1 :                     pg_fatal("could not open file \"%s\"", fname);
    1330                 :           1 :                 close(fd);
    1331                 :             : 
    1332                 :             :                 /* parse position from file */
    1333                 :           1 :                 XLogFromFileName(fname, &private.timeline, &endsegno, private.segsize);
    1334                 :             : 
    1335         [ -  + ]:           1 :                 if (endsegno < segno)
    1336                 :           0 :                     pg_fatal("ENDSEG %s is before STARTSEG %s",
    1337                 :             :                              argv[optind + 1], argv[optind]);
    1338                 :             : 
    1339         [ +  - ]:           1 :                 if (!XLogRecPtrIsValid(private.endptr))
    1340                 :           1 :                     XLogSegNoOffsetToRecPtr(endsegno + 1, 0, private.segsize,
    1341                 :             :                                             private.endptr);
    1342                 :             : 
    1343                 :             :                 /* set segno to endsegno for check of --end */
    1344                 :           1 :                 segno = endsegno;
    1345                 :             :             }
    1346                 :             : 
    1347         [ +  - ]:           5 :             if (!XLByteInSeg(private.endptr, segno, private.segsize) &&
    1348         [ -  + ]:           5 :                 private.endptr != (segno + 1) * private.segsize)
    1349                 :             :             {
    1350                 :           0 :                 pg_log_error("end WAL location %X/%08X is not inside file \"%s\"",
    1351                 :             :                              LSN_FORMAT_ARGS(private.endptr),
    1352                 :             :                              argv[argc - 1]);
    1353                 :           0 :                 goto bad_argument;
    1354                 :             :             }
    1355                 :             :         }
    1356                 :             :     }
    1357         [ +  + ]:         119 :     else if (!private.archive_name)
    1358                 :          65 :         waldir = identify_target_directory(waldir, NULL, &private.segsize);
    1359                 :             : 
    1360                 :             :     /* we don't know what to print */
    1361         [ +  + ]:         123 :     if (!XLogRecPtrIsValid(private.startptr))
    1362                 :             :     {
    1363                 :           3 :         pg_log_error("no start WAL location given");
    1364                 :           3 :         goto bad_argument;
    1365                 :             :     }
    1366                 :             : 
    1367                 :             :     /* --follow is not supported with tar archives */
    1368   [ -  +  -  - ]:         120 :     if (config.follow && private.archive_name)
    1369                 :             :     {
    1370                 :           0 :         pg_log_error("--follow is not supported when reading from a tar archive");
    1371                 :           0 :         goto bad_argument;
    1372                 :             :     }
    1373                 :             : 
    1374                 :             :     /* done with argument parsing, do the actual work */
    1375                 :             : 
    1376                 :             :     /* we have everything we need, start reading */
    1377         [ +  + ]:         120 :     if (private.archive_name)
    1378                 :             :     {
    1379                 :             :         /*
    1380                 :             :          * A NULL directory indicates that the archive file is located in the
    1381                 :             :          * current working directory.
    1382                 :             :          */
    1383         [ -  + ]:          52 :         if (private.archive_dir == NULL)
    1384                 :           0 :             private.archive_dir = pg_strdup(".");
    1385                 :             : 
    1386                 :             :         /* Set up for reading tar file */
    1387                 :          52 :         init_archive_reader(&private, compression);
    1388                 :             : 
    1389                 :             :         /* Routine to decode WAL files in tar archive */
    1390                 :             :         xlogreader_state =
    1391                 :          52 :             XLogReaderAllocate(private.segsize, private.archive_dir,
    1392                 :          52 :                                XL_ROUTINE(.page_read = TarWALDumpReadPage,
    1393                 :             :                                           .segment_open = TarWALDumpOpenSegment,
    1394                 :             :                                           .segment_close = TarWALDumpCloseSegment),
    1395                 :             :                                &private);
    1396                 :             :     }
    1397                 :             :     else
    1398                 :             :     {
    1399                 :             :         xlogreader_state =
    1400                 :          68 :             XLogReaderAllocate(private.segsize, waldir,
    1401                 :          68 :                                XL_ROUTINE(.page_read = WALDumpReadPage,
    1402                 :             :                                           .segment_open = WALDumpOpenSegment,
    1403                 :             :                                           .segment_close = WALDumpCloseSegment),
    1404                 :             :                                &private);
    1405                 :             :     }
    1406                 :             : 
    1407         [ -  + ]:         120 :     if (!xlogreader_state)
    1408                 :           0 :         pg_fatal("out of memory while allocating a WAL reading processor");
    1409                 :             : 
    1410                 :             :     /*
    1411                 :             :      * Set up atexit cleanup of temporary directory.  This must happen before
    1412                 :             :      * archive_waldump.c could possibly create the temporary directory.  Also
    1413                 :             :      * arm the callback to cleanup the xlogreader state.
    1414                 :             :      */
    1415                 :         120 :     atexit(cleanup_tmpwal_dir_atexit);
    1416                 :         120 :     xlogreader_state_cleanup = xlogreader_state;
    1417                 :             : 
    1418                 :             :     /* first find a valid recptr to start from */
    1419                 :         120 :     first_record = XLogFindNextRecord(xlogreader_state, private.startptr, &errormsg);
    1420                 :             : 
    1421         [ +  + ]:         120 :     if (!XLogRecPtrIsValid(first_record))
    1422                 :             :     {
    1423         [ +  - ]:           1 :         if (errormsg)
    1424                 :           1 :             pg_fatal("could not find a valid record after %X/%08X: %s",
    1425                 :             :                      LSN_FORMAT_ARGS(private.startptr), errormsg);
    1426                 :             :         else
    1427                 :           0 :             pg_fatal("could not find a valid record after %X/%08X",
    1428                 :             :                      LSN_FORMAT_ARGS(private.startptr));
    1429                 :             :     }
    1430                 :             : 
    1431                 :             :     /*
    1432                 :             :      * Display a message that we're skipping data if `from` wasn't a pointer
    1433                 :             :      * to the start of a record and also wasn't a pointer to the beginning of
    1434                 :             :      * a segment (e.g. we were used in file mode).
    1435                 :             :      */
    1436         [ +  + ]:         119 :     if (first_record != private.startptr &&
    1437         [ +  + ]:          10 :         XLogSegmentOffset(private.startptr, private.segsize) != 0)
    1438                 :           6 :         pg_log_info(ngettext("first record is after %X/%08X, at %X/%08X, skipping over %u byte",
    1439                 :             :                              "first record is after %X/%08X, at %X/%08X, skipping over %u bytes",
    1440                 :             :                              (first_record - private.startptr)),
    1441                 :             :                     LSN_FORMAT_ARGS(private.startptr),
    1442                 :             :                     LSN_FORMAT_ARGS(first_record),
    1443                 :             :                     (uint32) (first_record - private.startptr));
    1444                 :             : 
    1445   [ +  +  +  - ]:         119 :     if (config.stats == true && !config.quiet)
    1446                 :           6 :         stats.startptr = first_record;
    1447                 :             : 
    1448                 :             :     /* Flag indicating that the decoding loop has been entered */
    1449                 :         119 :     private.decoding_started = true;
    1450                 :             : 
    1451                 :             :     for (;;)
    1452                 :             :     {
    1453         [ -  + ]:     1574206 :         if (time_to_stop)
    1454                 :             :         {
    1455                 :             :             /* We've been Ctrl-C'ed, so leave */
    1456                 :           0 :             break;
    1457                 :             :         }
    1458                 :             : 
    1459                 :             :         /* try to read the next record */
    1460                 :     1574206 :         record = XLogReadRecord(xlogreader_state, &errormsg);
    1461         [ +  + ]:     1574206 :         if (!record)
    1462                 :             :         {
    1463   [ -  +  -  - ]:         116 :             if (!config.follow || private.endptr_reached)
    1464                 :             :                 break;
    1465                 :             :             else
    1466                 :             :             {
    1467                 :           0 :                 pg_usleep(1000000L);    /* 1 second */
    1468                 :           0 :                 continue;
    1469                 :             :             }
    1470                 :             :         }
    1471                 :             : 
    1472                 :             :         /* apply all specified filters */
    1473         [ +  + ]:     1574090 :         if (config.filter_by_rmgr_enabled &&
    1474         [ +  + ]:      113163 :             !config.filter_by_rmgr[record->xl_rmid])
    1475                 :      107031 :             continue;
    1476                 :             : 
    1477         [ -  + ]:     1467059 :         if (config.filter_by_xid_enabled &&
    1478         [ #  # ]:           0 :             config.filter_by_xid != record->xl_xid)
    1479                 :           0 :             continue;
    1480                 :             : 
    1481                 :             :         /* check for extended filtering */
    1482         [ +  + ]:     1467059 :         if (config.filter_by_extended &&
    1483         [ +  + ]:      730676 :             !XLogRecordMatchesRelationBlock(xlogreader_state,
    1484         [ +  + ]:      365338 :                                             config.filter_by_relation_enabled ?
    1485                 :             :                                             config.filter_by_relation :
    1486                 :             :                                             emptyRelFileLocator,
    1487         [ +  + ]:      365338 :                                             config.filter_by_relation_block_enabled ?
    1488                 :             :                                             config.filter_by_relation_block :
    1489                 :             :                                             InvalidBlockNumber,
    1490                 :             :                                             config.filter_by_relation_forknum))
    1491                 :      365116 :             continue;
    1492                 :             : 
    1493   [ +  +  +  + ]:     1101943 :         if (config.filter_by_fpw && !XLogRecordHasFPW(xlogreader_state))
    1494                 :      109857 :             continue;
    1495                 :             : 
    1496                 :             :         /* perform any per-record work */
    1497         [ +  + ]:      992086 :         if (!config.quiet)
    1498                 :             :         {
    1499         [ +  + ]:      815114 :             if (config.stats == true)
    1500                 :             :             {
    1501                 :      226326 :                 XLogRecStoreStats(&stats, xlogreader_state);
    1502                 :      226326 :                 stats.endptr = xlogreader_state->EndRecPtr;
    1503                 :             :             }
    1504                 :             :             else
    1505                 :      588788 :                 XLogDumpDisplayRecord(&config, xlogreader_state);
    1506                 :             :         }
    1507                 :             : 
    1508                 :             :         /* save full pages if requested */
    1509         [ +  + ]:      992086 :         if (config.save_fullpage_path != NULL)
    1510                 :         201 :             XLogRecordSaveFPWs(xlogreader_state, config.save_fullpage_path);
    1511                 :             : 
    1512                 :             :         /* check whether we printed enough */
    1513                 :      992086 :         config.already_displayed_records++;
    1514         [ +  + ]:      992086 :         if (config.stop_after_records > 0 &&
    1515         [ +  + ]:          18 :             config.already_displayed_records >= config.stop_after_records)
    1516                 :           3 :             break;
    1517                 :             :     }
    1518                 :             : 
    1519   [ +  +  +  - ]:         119 :     if (config.stats == true && !config.quiet)
    1520                 :           6 :         XLogDumpDisplayStats(&config, &stats);
    1521                 :             : 
    1522         [ -  + ]:         119 :     if (time_to_stop)
    1523                 :           0 :         exit(0);
    1524                 :             : 
    1525         [ +  + ]:         119 :     if (errormsg)
    1526                 :           6 :         pg_fatal("error in WAL record at %X/%08X: %s",
    1527                 :             :                  LSN_FORMAT_ARGS(xlogreader_state->ReadRecPtr),
    1528                 :             :                  errormsg);
    1529                 :             : 
    1530                 :             :     /*
    1531                 :             :      * Disarm atexit cleanup of open WAL file; XLogReaderFree will close it,
    1532                 :             :      * and we don't want the atexit callback trying to touch freed memory.
    1533                 :             :      */
    1534                 :         113 :     xlogreader_state_cleanup = NULL;
    1535                 :             : 
    1536                 :         113 :     XLogReaderFree(xlogreader_state);
    1537                 :             : 
    1538         [ +  + ]:         113 :     if (private.archive_name)
    1539                 :          48 :         free_archive_reader(&private);
    1540                 :             : 
    1541                 :         113 :     return EXIT_SUCCESS;
    1542                 :             : 
    1543                 :          14 : bad_argument:
    1544                 :          14 :     pg_log_error_hint("Try \"%s --help\" for more information.", progname);
    1545                 :          14 :     return EXIT_FAILURE;
    1546                 :             : }
        

Generated by: LCOV version 2.0-1