LCOV - differential code coverage report
Current view: top level - src/bin/pg_waldump - pg_waldump.c (source / functions) Coverage Total Hit UNC LBC UIC UBC GBC GIC GNC CBC EUB ECB DUB DCB
Current: Differential Code Coverage HEAD vs 15 Lines: 49.8 % 532 265 18 51 144 54 47 116 47 55 157 157 9 2
Current Date: 2023-04-08 15:15:32 Functions: 68.4 % 19 13 1 5 11 2 6 13
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

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

Generated by: LCOV version v1.16-55-g56c0a2a