Header And Logo

PostgreSQL
| The world's most advanced open source database.

pg_xlogdump.c

Go to the documentation of this file.
00001 /*-------------------------------------------------------------------------
00002  *
00003  * pg_xlogdump.c - decode and display WAL
00004  *
00005  * Copyright (c) 2013, PostgreSQL Global Development Group
00006  *
00007  * IDENTIFICATION
00008  *        contrib/pg_xlogdump/pg_xlogdump.c
00009  *-------------------------------------------------------------------------
00010  */
00011 
00012 #define FRONTEND 1
00013 #include "postgres.h"
00014 
00015 #include <dirent.h>
00016 #include <unistd.h>
00017 
00018 #include "access/xlog.h"
00019 #include "access/xlogreader.h"
00020 #include "access/transam.h"
00021 #include "common/fe_memutils.h"
00022 #include "common/relpath.h"
00023 #include "getopt_long.h"
00024 #include "rmgrdesc.h"
00025 
00026 
00027 static const char *progname;
00028 
00029 typedef struct XLogDumpPrivate
00030 {
00031     TimeLineID  timeline;
00032     char       *inpath;
00033     XLogRecPtr  startptr;
00034     XLogRecPtr  endptr;
00035 } XLogDumpPrivate;
00036 
00037 typedef struct XLogDumpConfig
00038 {
00039     /* display options */
00040     bool        bkp_details;
00041     int         stop_after_records;
00042     int         already_displayed_records;
00043 
00044     /* filter options */
00045     int         filter_by_rmgr;
00046     TransactionId filter_by_xid;
00047     bool        filter_by_xid_enabled;
00048 } XLogDumpConfig;
00049 
00050 static void
00051 fatal_error(const char *fmt,...)
00052 __attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2)));
00053 
00054 /*
00055  * Big red button to push when things go horribly wrong.
00056  */
00057 static void
00058 fatal_error(const char *fmt,...)
00059 {
00060     va_list     args;
00061 
00062     fflush(stdout);
00063 
00064     fprintf(stderr, "%s: FATAL:  ", progname);
00065     va_start(args, fmt);
00066     vfprintf(stderr, fmt, args);
00067     va_end(args);
00068     fputc('\n', stderr);
00069 
00070     exit(EXIT_FAILURE);
00071 }
00072 
00073 static void
00074 print_rmgr_list(void)
00075 {
00076     int     i;
00077 
00078     for (i = 0; i < RM_MAX_ID + 1; i++)
00079     {
00080         printf("%s\n", RmgrDescTable[i].rm_name);
00081     }
00082 }
00083 
00084 /*
00085  * Check whether directory exists and whether we can open it. Keep errno set so
00086  * that the caller can report errors somewhat more accurately.
00087  */
00088 static bool
00089 verify_directory(const char *directory)
00090 {
00091     DIR *dir = opendir(directory);
00092     if (dir == NULL)
00093         return false;
00094     closedir(dir);
00095     return true;
00096 }
00097 
00098 /*
00099  * Split a pathname as dirname(1) and basename(1) would.
00100  *
00101  * XXX this probably doesn't do very well on Windows.  We probably need to
00102  * apply canonicalize_path(), at the very least.
00103  */
00104 static void
00105 split_path(const char *path, char **dir, char **fname)
00106 {
00107     char       *sep;
00108 
00109     /* split filepath into directory & filename */
00110     sep = strrchr(path, '/');
00111 
00112     /* directory path */
00113     if (sep != NULL)
00114     {
00115         *dir = pg_strdup(path);
00116         (*dir)[(sep - path) + 1] = '\0';    /* no strndup */
00117         *fname = pg_strdup(sep + 1);
00118     }
00119     /* local directory */
00120     else
00121     {
00122         *dir = NULL;
00123         *fname = pg_strdup(path);
00124     }
00125 }
00126 
00127 /*
00128  * Try to find the file in several places:
00129  * if directory == NULL:
00130  *   fname
00131  *   XLOGDIR / fname
00132  *   $PGDATA / XLOGDIR / fname
00133  * else
00134  *   directory / fname
00135  *   directory / XLOGDIR / fname
00136  *
00137  * return a read only fd
00138  */
00139 static int
00140 fuzzy_open_file(const char *directory, const char *fname)
00141 {
00142     int         fd = -1;
00143     char        fpath[MAXPGPATH];
00144 
00145     if (directory == NULL)
00146     {
00147         const char *datadir;
00148 
00149         /* fname */
00150         fd = open(fname, O_RDONLY | PG_BINARY, 0);
00151         if (fd < 0 && errno != ENOENT)
00152             return -1;
00153         else if (fd > 0)
00154             return fd;
00155 
00156         /* XLOGDIR / fname */
00157         snprintf(fpath, MAXPGPATH, "%s/%s",
00158                  XLOGDIR, fname);
00159         fd = open(fpath, O_RDONLY | PG_BINARY, 0);
00160         if (fd < 0 && errno != ENOENT)
00161             return -1;
00162         else if (fd > 0)
00163             return fd;
00164 
00165         datadir = getenv("PGDATA");
00166         /* $PGDATA / XLOGDIR / fname */
00167         if (datadir != NULL)
00168         {
00169             snprintf(fpath, MAXPGPATH, "%s/%s/%s",
00170                      datadir, XLOGDIR, fname);
00171             fd = open(fpath, O_RDONLY | PG_BINARY, 0);
00172             if (fd < 0 && errno != ENOENT)
00173                 return -1;
00174             else if (fd > 0)
00175                 return fd;
00176         }
00177     }
00178     else
00179     {
00180         /* directory / fname */
00181         snprintf(fpath, MAXPGPATH, "%s/%s",
00182                  directory, fname);
00183         fd = open(fpath, O_RDONLY | PG_BINARY, 0);
00184         if (fd < 0 && errno != ENOENT)
00185             return -1;
00186         else if (fd > 0)
00187             return fd;
00188 
00189         /* directory / XLOGDIR / fname */
00190         snprintf(fpath, MAXPGPATH, "%s/%s/%s",
00191                  directory, XLOGDIR, fname);
00192         fd = open(fpath, O_RDONLY | PG_BINARY, 0);
00193         if (fd < 0 && errno != ENOENT)
00194             return -1;
00195         else if (fd > 0)
00196             return fd;
00197     }
00198     return -1;
00199 }
00200 
00201 /*
00202  * Read count bytes from a segment file in the specified directory, for the
00203  * given timeline, containing the specified record pointer; store the data in
00204  * the passed buffer.
00205  */
00206 static void
00207 XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
00208                  XLogRecPtr startptr, char *buf, Size count)
00209 {
00210     char       *p;
00211     XLogRecPtr  recptr;
00212     Size        nbytes;
00213 
00214     static int  sendFile = -1;
00215     static XLogSegNo sendSegNo = 0;
00216     static uint32 sendOff = 0;
00217 
00218     p = buf;
00219     recptr = startptr;
00220     nbytes = count;
00221 
00222     while (nbytes > 0)
00223     {
00224         uint32      startoff;
00225         int         segbytes;
00226         int         readbytes;
00227 
00228         startoff = recptr % XLogSegSize;
00229 
00230         if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo))
00231         {
00232             char        fname[MAXFNAMELEN];
00233 
00234             /* Switch to another logfile segment */
00235             if (sendFile >= 0)
00236                 close(sendFile);
00237 
00238             XLByteToSeg(recptr, sendSegNo);
00239 
00240             XLogFileName(fname, timeline_id, sendSegNo);
00241 
00242             sendFile = fuzzy_open_file(directory, fname);
00243 
00244             if (sendFile < 0)
00245                 fatal_error("could not find file \"%s\": %s",
00246                             fname, strerror(errno));
00247             sendOff = 0;
00248         }
00249 
00250         /* Need to seek in the file? */
00251         if (sendOff != startoff)
00252         {
00253             if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
00254             {
00255                 int         err = errno;
00256                 char        fname[MAXPGPATH];
00257 
00258                 XLogFileName(fname, timeline_id, sendSegNo);
00259 
00260                 fatal_error("could not seek in log segment %s to offset %u: %s",
00261                             fname, startoff, strerror(err));
00262             }
00263             sendOff = startoff;
00264         }
00265 
00266         /* How many bytes are within this segment? */
00267         if (nbytes > (XLogSegSize - startoff))
00268             segbytes = XLogSegSize - startoff;
00269         else
00270             segbytes = nbytes;
00271 
00272         readbytes = read(sendFile, p, segbytes);
00273         if (readbytes <= 0)
00274         {
00275             int         err = errno;
00276             char        fname[MAXPGPATH];
00277 
00278             XLogFileName(fname, timeline_id, sendSegNo);
00279 
00280             fatal_error("could not read from log segment %s, offset %d, length %d: %s",
00281                         fname, sendOff, segbytes, strerror(err));
00282         }
00283 
00284         /* Update state for read */
00285         recptr += readbytes;
00286 
00287         sendOff += readbytes;
00288         nbytes -= readbytes;
00289         p += readbytes;
00290     }
00291 }
00292 
00293 /*
00294  * XLogReader read_page callback
00295  */
00296 static int
00297 XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
00298                  XLogRecPtr targetPtr, char *readBuff, TimeLineID *curFileTLI)
00299 {
00300     XLogDumpPrivate *private = state->private_data;
00301     int         count = XLOG_BLCKSZ;
00302 
00303     if (private->endptr != InvalidXLogRecPtr)
00304     {
00305         if (targetPagePtr + XLOG_BLCKSZ <= private->endptr)
00306             count = XLOG_BLCKSZ;
00307         else if (targetPagePtr + reqLen <= private->endptr)
00308             count = private->endptr - targetPagePtr;
00309         else
00310             return -1;
00311     }
00312 
00313     XLogDumpXLogRead(private->inpath, private->timeline, targetPagePtr,
00314                      readBuff, count);
00315 
00316     return count;
00317 }
00318 
00319 /*
00320  * Print a record to stdout
00321  */
00322 static void
00323 XLogDumpDisplayRecord(XLogDumpConfig *config, XLogRecPtr ReadRecPtr, XLogRecord *record)
00324 {
00325     const RmgrDescData *desc = &RmgrDescTable[record->xl_rmid];
00326 
00327     if (config->filter_by_rmgr != -1 &&
00328         config->filter_by_rmgr != record->xl_rmid)
00329         return;
00330 
00331     if (config->filter_by_xid_enabled &&
00332         config->filter_by_xid != record->xl_xid)
00333         return;
00334 
00335     config->already_displayed_records++;
00336 
00337     printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, bkp: %u%u%u%u, desc: ",
00338            desc->rm_name,
00339            record->xl_len, record->xl_tot_len,
00340            record->xl_xid,
00341            (uint32) (ReadRecPtr >> 32), (uint32) ReadRecPtr,
00342            (uint32) (record->xl_prev >> 32), (uint32) record->xl_prev,
00343            !!(XLR_BKP_BLOCK(0) & record->xl_info),
00344            !!(XLR_BKP_BLOCK(1) & record->xl_info),
00345            !!(XLR_BKP_BLOCK(2) & record->xl_info),
00346            !!(XLR_BKP_BLOCK(3) & record->xl_info));
00347 
00348     /* the desc routine will printf the description directly to stdout */
00349     desc->rm_desc(NULL, record->xl_info, XLogRecGetData(record));
00350 
00351     putchar('\n');
00352 
00353     if (config->bkp_details)
00354     {
00355         int         bkpnum;
00356         char       *blk = (char *) XLogRecGetData(record) + record->xl_len;
00357 
00358         for (bkpnum = 0; bkpnum < XLR_MAX_BKP_BLOCKS; bkpnum++)
00359         {
00360             BkpBlock    bkpb;
00361 
00362             if (!(XLR_BKP_BLOCK(bkpnum) & record->xl_info))
00363                 continue;
00364 
00365             memcpy(&bkpb, blk, sizeof(BkpBlock));
00366             blk += sizeof(BkpBlock);
00367             blk += BLCKSZ - bkpb.hole_length;
00368 
00369             printf("\tbackup bkp #%u; rel %u/%u/%u; fork: %s; block: %u; hole: offset: %u, length: %u\n",
00370                    bkpnum,
00371                    bkpb.node.spcNode, bkpb.node.dbNode, bkpb.node.relNode,
00372                    forkNames[bkpb.fork],
00373                    bkpb.block, bkpb.hole_offset, bkpb.hole_length);
00374         }
00375     }
00376 }
00377 
00378 static void
00379 usage(void)
00380 {
00381     printf("%s decodes and displays PostgreSQL transaction logs for debugging.\n\n",
00382            progname);
00383     printf("Usage:\n");
00384     printf("  %s [OPTION] [STARTSEG [ENDSEG]] \n", progname);
00385     printf("\nGeneral options:\n");
00386     printf("  -V, --version          output version information, then exit\n");
00387     printf("  -?, --help             show this help, then exit\n");
00388     printf("\nContent options:\n");
00389     printf("  -b, --bkp-details      output detailed information about backup blocks\n");
00390     printf("  -e, --end=RECPTR       stop reading at log position RECPTR\n");
00391     printf("  -n, --limit=N          number of records to display\n");
00392     printf("  -p, --path=PATH        directory in which to find log segment files\n");
00393     printf("                         (default: ./pg_xlog)\n");
00394     printf("  -r, --rmgr=RMGR        only show records generated by resource manager RMGR\n");
00395     printf("                         use --rmgr=list to list valid resource manager names\n");
00396     printf("  -s, --start=RECPTR     start reading at log position RECPTR\n");
00397     printf("  -t, --timeline=TLI     timeline from which to read log records\n");
00398     printf("                         (default: 1 or the value used in STARTSEG)\n");
00399     printf("  -x, --xid=XID          only show records with TransactionId XID\n");
00400 }
00401 
00402 int
00403 main(int argc, char **argv)
00404 {
00405     uint32      xlogid;
00406     uint32      xrecoff;
00407     XLogReaderState *xlogreader_state;
00408     XLogDumpPrivate private;
00409     XLogDumpConfig config;
00410     XLogRecord *record;
00411     XLogRecPtr  first_record;
00412     char       *errormsg;
00413 
00414     static struct option long_options[] = {
00415         {"bkp-details", no_argument, NULL, 'b'},
00416         {"end", required_argument, NULL, 'e'},
00417         {"help", no_argument, NULL, '?'},
00418         {"limit", required_argument, NULL, 'n'},
00419         {"path", required_argument, NULL, 'p'},
00420         {"rmgr", required_argument, NULL, 'r'},
00421         {"start", required_argument, NULL, 's'},
00422         {"timeline", required_argument, NULL, 't'},
00423         {"xid", required_argument, NULL, 'x'},
00424         {"version", no_argument, NULL, 'V'},
00425         {NULL, 0, NULL, 0}
00426     };
00427 
00428     int         option;
00429     int         optindex = 0;
00430 
00431     progname = get_progname(argv[0]);
00432 
00433     memset(&private, 0, sizeof(XLogDumpPrivate));
00434     memset(&config, 0, sizeof(XLogDumpConfig));
00435 
00436     private.timeline = 1;
00437     private.startptr = InvalidXLogRecPtr;
00438     private.endptr = InvalidXLogRecPtr;
00439 
00440     config.bkp_details = false;
00441     config.stop_after_records = -1;
00442     config.already_displayed_records = 0;
00443     config.filter_by_rmgr = -1;
00444     config.filter_by_xid = InvalidTransactionId;
00445     config.filter_by_xid_enabled = false;
00446 
00447     if (argc <= 1)
00448     {
00449         fprintf(stderr, "%s: no arguments specified\n", progname);
00450         goto bad_argument;
00451     }
00452 
00453     while ((option = getopt_long(argc, argv, "be:?n:p:r:s:t:Vx:",
00454                                  long_options, &optindex)) != -1)
00455     {
00456         switch (option)
00457         {
00458             case 'b':
00459                 config.bkp_details = true;
00460                 break;
00461             case 'e':
00462                 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
00463                 {
00464                     fprintf(stderr, "%s: could not parse end log position \"%s\"\n",
00465                             progname, optarg);
00466                     goto bad_argument;
00467                 }
00468                 private.endptr = (uint64) xlogid << 32 | xrecoff;
00469                 break;
00470             case '?':
00471                 usage();
00472                 exit(EXIT_SUCCESS);
00473                 break;
00474             case 'n':
00475                 if (sscanf(optarg, "%d", &config.stop_after_records) != 1)
00476                 {
00477                     fprintf(stderr, "%s: could not parse limit \"%s\"\n",
00478                             progname, optarg);
00479                     goto bad_argument;
00480                 }
00481                 break;
00482             case 'p':
00483                 private.inpath = pg_strdup(optarg);
00484                 break;
00485             case 'r':
00486                 {
00487                     int         i;
00488 
00489                     if (pg_strcasecmp(optarg, "list") == 0)
00490                     {
00491                         print_rmgr_list();
00492                         exit(EXIT_SUCCESS);
00493                     }
00494 
00495                     for (i = 0; i < RM_MAX_ID; i++)
00496                     {
00497                         if (pg_strcasecmp(optarg, RmgrDescTable[i].rm_name) == 0)
00498                         {
00499                             config.filter_by_rmgr = i;
00500                             break;
00501                         }
00502                     }
00503 
00504                     if (config.filter_by_rmgr == -1)
00505                     {
00506                         fprintf(stderr, "%s: resource manager \"%s\" does not exist\n",
00507                                 progname, optarg);
00508                         goto bad_argument;
00509                     }
00510                 }
00511                 break;
00512             case 's':
00513                 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
00514                 {
00515                     fprintf(stderr, "%s: could not parse start log position \"%s\"\n",
00516                             progname, optarg);
00517                     goto bad_argument;
00518                 }
00519                 else
00520                     private.startptr = (uint64) xlogid << 32 | xrecoff;
00521                 break;
00522             case 't':
00523                 if (sscanf(optarg, "%d", &private.timeline) != 1)
00524                 {
00525                     fprintf(stderr, "%s: could not parse timeline \"%s\"\n",
00526                             progname, optarg);
00527                     goto bad_argument;
00528                 }
00529                 break;
00530             case 'V':
00531                 puts("pg_xlogdump (PostgreSQL) " PG_VERSION);
00532                 exit(EXIT_SUCCESS);
00533                 break;
00534             case 'x':
00535                 if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
00536                 {
00537                     fprintf(stderr, "%s: could not parse \"%s\" as a valid xid\n",
00538                             progname, optarg);
00539                     goto bad_argument;
00540                 }
00541                 config.filter_by_xid_enabled = true;
00542                 break;
00543             default:
00544                 goto bad_argument;
00545         }
00546     }
00547 
00548     if ((optind + 2) < argc)
00549     {
00550         fprintf(stderr,
00551                 "%s: too many command-line arguments (first is \"%s\")\n",
00552                 progname, argv[optind + 2]);
00553         goto bad_argument;
00554     }
00555 
00556     if (private.inpath != NULL)
00557     {
00558         /* validate path points to directory */
00559         if (!verify_directory(private.inpath))
00560         {
00561             fprintf(stderr,
00562                     "%s: path \"%s\" cannot be opened: %s",
00563                     progname, private.inpath, strerror(errno));
00564             goto bad_argument;
00565         }
00566     }
00567 
00568     /* parse files as start/end boundaries, extract path if not specified */
00569     if (optind < argc)
00570     {
00571         char       *directory = NULL;
00572         char       *fname = NULL;
00573         int         fd;
00574         XLogSegNo   segno;
00575 
00576         split_path(argv[optind], &directory, &fname);
00577 
00578         if (private.inpath == NULL && directory != NULL)
00579         {
00580             private.inpath = directory;
00581 
00582             if (!verify_directory(private.inpath))
00583                 fatal_error("cannot open directory \"%s\": %s",
00584                             private.inpath, strerror(errno));
00585         }
00586 
00587         fd = fuzzy_open_file(private.inpath, fname);
00588         if (fd < 0)
00589             fatal_error("could not open file \"%s\"", fname);
00590         close(fd);
00591 
00592         /* parse position from file */
00593         XLogFromFileName(fname, &private.timeline, &segno);
00594 
00595         if (XLogRecPtrIsInvalid(private.startptr))
00596             XLogSegNoOffsetToRecPtr(segno, 0, private.startptr);
00597         else if (!XLByteInSeg(private.startptr, segno))
00598         {
00599             fprintf(stderr,
00600                     "%s: start log position %X/%X is not inside file \"%s\"\n",
00601                     progname,
00602                     (uint32) (private.startptr >> 32),
00603                     (uint32) private.startptr,
00604                     fname);
00605             goto bad_argument;
00606         }
00607 
00608         /* no second file specified, set end position */
00609         if (!(optind + 1 < argc) && XLogRecPtrIsInvalid(private.endptr))
00610             XLogSegNoOffsetToRecPtr(segno + 1, 0, private.endptr);
00611 
00612         /* parse ENDSEG if passed */
00613         if (optind + 1 < argc)
00614         {
00615             XLogSegNo   endsegno;
00616 
00617             /* ignore directory, already have that */
00618             split_path(argv[optind + 1], &directory, &fname);
00619 
00620             fd = fuzzy_open_file(private.inpath, fname);
00621             if (fd < 0)
00622                 fatal_error("could not open file \"%s\"", fname);
00623             close(fd);
00624 
00625             /* parse position from file */
00626             XLogFromFileName(fname, &private.timeline, &endsegno);
00627 
00628             if (endsegno < segno)
00629                 fatal_error("ENDSEG %s is before STARTSEG %s",
00630                             argv[optind + 1], argv[optind]);
00631 
00632             if (XLogRecPtrIsInvalid(private.endptr))
00633                 XLogSegNoOffsetToRecPtr(endsegno + 1, 0, private.endptr);
00634 
00635             /* set segno to endsegno for check of --end */
00636             segno = endsegno;
00637         }
00638 
00639 
00640         if (!XLByteInSeg(private.endptr, segno) &&
00641             private.endptr != (segno + 1) * XLogSegSize)
00642         {
00643             fprintf(stderr,
00644                     "%s: end log position %X/%X is not inside file \"%s\"\n",
00645                     progname,
00646                     (uint32) (private.endptr >> 32),
00647                     (uint32) private.endptr,
00648                     argv[argc - 1]);
00649             goto bad_argument;
00650         }
00651     }
00652 
00653     /* we don't know what to print */
00654     if (XLogRecPtrIsInvalid(private.startptr))
00655     {
00656         fprintf(stderr, "%s: no start log position given in range mode.\n", progname);
00657         goto bad_argument;
00658     }
00659 
00660     /* done with argument parsing, do the actual work */
00661 
00662     /* we have everything we need, start reading */
00663     xlogreader_state = XLogReaderAllocate(XLogDumpReadPage, &private);
00664     if (!xlogreader_state)
00665         fatal_error("out of memory");
00666 
00667     /* first find a valid recptr to start from */
00668     first_record = XLogFindNextRecord(xlogreader_state, private.startptr);
00669 
00670     if (first_record == InvalidXLogRecPtr)
00671         fatal_error("could not find a valid record after %X/%X",
00672                     (uint32) (private.startptr >> 32),
00673                     (uint32) private.startptr);
00674 
00675     /*
00676      * Display a message that we're skipping data if `from` wasn't a pointer to
00677      * the start of a record and also wasn't a pointer to the beginning of a
00678      * segment (e.g. we were used in file mode).
00679      */
00680     if (first_record != private.startptr && (private.startptr % XLogSegSize) != 0)
00681         printf("first record is after %X/%X, at %X/%X, skipping over %u bytes\n",
00682                (uint32) (private.startptr >> 32), (uint32) private.startptr,
00683                (uint32) (first_record >> 32), (uint32) first_record,
00684                (uint32) (first_record - private.startptr));
00685 
00686     while ((record = XLogReadRecord(xlogreader_state, first_record, &errormsg)))
00687     {
00688         /* continue after the last record */
00689         first_record = InvalidXLogRecPtr;
00690         XLogDumpDisplayRecord(&config, xlogreader_state->ReadRecPtr, record);
00691 
00692         /* check whether we printed enough */
00693         if (config.stop_after_records > 0 &&
00694             config.already_displayed_records >= config.stop_after_records)
00695             break;
00696     }
00697 
00698     if (errormsg)
00699         fatal_error("error in WAL record at %X/%X: %s\n",
00700                     (uint32) (xlogreader_state->ReadRecPtr >> 32),
00701                     (uint32) xlogreader_state->ReadRecPtr,
00702                     errormsg);
00703 
00704     XLogReaderFree(xlogreader_state);
00705 
00706     return EXIT_SUCCESS;
00707 
00708 bad_argument:
00709     fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
00710     return EXIT_FAILURE;
00711 }