1 /*-------------------------------------------------------------------------
3 * pg_xlogdump.c - decode and display WAL
5 * Copyright (c) 2013, PostgreSQL Global Development Group
8 * contrib/pg_xlogdump/pg_xlogdump.c
9 *-------------------------------------------------------------------------
18 #include "access/xlog.h"
19 #include "access/xlogreader.h"
20 #include "access/transam.h"
21 #include "common/fe_memutils.h"
22 #include "common/relpath.h"
23 #include "getopt_long.h"
27 static const char *progname;
29 typedef struct XLogDumpPrivate
37 typedef struct XLogDumpConfig
41 int stop_after_records;
42 int already_displayed_records;
46 TransactionId filter_by_xid;
47 bool filter_by_xid_enabled;
51 fatal_error(const char *fmt,...)
52 __attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2)));
55 * Big red button to push when things go horribly wrong.
58 fatal_error(const char *fmt,...)
64 fprintf(stderr, "%s: FATAL: ", progname);
66 vfprintf(stderr, fmt, args);
78 for (i = 0; i < RM_MAX_ID + 1; i++)
80 printf("%s\n", RmgrDescTable[i].rm_name);
85 * Check whether directory exists and whether we can open it. Keep errno set so
86 * that the caller can report errors somewhat more accurately.
89 verify_directory(const char *directory)
91 DIR *dir = opendir(directory);
99 * Split a pathname as dirname(1) and basename(1) would.
101 * XXX this probably doesn't do very well on Windows. We probably need to
102 * apply canonicalize_path(), at the very least.
105 split_path(const char *path, char **dir, char **fname)
109 /* split filepath into directory & filename */
110 sep = strrchr(path, '/');
115 *dir = pg_strdup(path);
116 (*dir)[(sep - path) + 1] = '\0'; /* no strndup */
117 *fname = pg_strdup(sep + 1);
119 /* local directory */
123 *fname = pg_strdup(path);
128 * Try to find the file in several places:
129 * if directory == NULL:
132 * $PGDATA / XLOGDIR / fname
135 * directory / XLOGDIR / fname
137 * return a read only fd
140 fuzzy_open_file(const char *directory, const char *fname)
143 char fpath[MAXPGPATH];
145 if (directory == NULL)
150 fd = open(fname, O_RDONLY | PG_BINARY, 0);
151 if (fd < 0 && errno != ENOENT)
156 /* XLOGDIR / fname */
157 snprintf(fpath, MAXPGPATH, "%s/%s",
159 fd = open(fpath, O_RDONLY | PG_BINARY, 0);
160 if (fd < 0 && errno != ENOENT)
165 datadir = getenv("PGDATA");
166 /* $PGDATA / XLOGDIR / fname */
169 snprintf(fpath, MAXPGPATH, "%s/%s/%s",
170 datadir, XLOGDIR, fname);
171 fd = open(fpath, O_RDONLY | PG_BINARY, 0);
172 if (fd < 0 && errno != ENOENT)
180 /* directory / fname */
181 snprintf(fpath, MAXPGPATH, "%s/%s",
183 fd = open(fpath, O_RDONLY | PG_BINARY, 0);
184 if (fd < 0 && errno != ENOENT)
189 /* directory / XLOGDIR / fname */
190 snprintf(fpath, MAXPGPATH, "%s/%s/%s",
191 directory, XLOGDIR, fname);
192 fd = open(fpath, O_RDONLY | PG_BINARY, 0);
193 if (fd < 0 && errno != ENOENT)
202 * Read count bytes from a segment file in the specified directory, for the
203 * given timeline, containing the specified record pointer; store the data in
207 XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
208 XLogRecPtr startptr, char *buf, Size count)
214 static int sendFile = -1;
215 static XLogSegNo sendSegNo = 0;
216 static uint32 sendOff = 0;
228 startoff = recptr % XLogSegSize;
230 if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo))
232 char fname[MAXFNAMELEN];
234 /* Switch to another logfile segment */
238 XLByteToSeg(recptr, sendSegNo);
240 XLogFileName(fname, timeline_id, sendSegNo);
242 sendFile = fuzzy_open_file(directory, fname);
245 fatal_error("could not find file \"%s\": %s",
246 fname, strerror(errno));
250 /* Need to seek in the file? */
251 if (sendOff != startoff)
253 if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
256 char fname[MAXPGPATH];
258 XLogFileName(fname, timeline_id, sendSegNo);
260 fatal_error("could not seek in log segment %s to offset %u: %s",
261 fname, startoff, strerror(err));
266 /* How many bytes are within this segment? */
267 if (nbytes > (XLogSegSize - startoff))
268 segbytes = XLogSegSize - startoff;
272 readbytes = read(sendFile, p, segbytes);
276 char fname[MAXPGPATH];
278 XLogFileName(fname, timeline_id, sendSegNo);
280 fatal_error("could not read from log segment %s, offset %d, length %d: %s",
281 fname, sendOff, segbytes, strerror(err));
284 /* Update state for read */
287 sendOff += readbytes;
294 * XLogReader read_page callback
297 XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
298 XLogRecPtr targetPtr, char *readBuff, TimeLineID *curFileTLI)
300 XLogDumpPrivate *private = state->private_data;
301 int count = XLOG_BLCKSZ;
303 if (private->endptr != InvalidXLogRecPtr)
305 if (targetPagePtr + XLOG_BLCKSZ <= private->endptr)
307 else if (targetPagePtr + reqLen <= private->endptr)
308 count = private->endptr - targetPagePtr;
313 XLogDumpXLogRead(private->inpath, private->timeline, targetPagePtr,
320 * Print a record to stdout
323 XLogDumpDisplayRecord(XLogDumpConfig *config, XLogRecPtr ReadRecPtr, XLogRecord *record)
325 const RmgrDescData *desc = &RmgrDescTable[record->xl_rmid];
327 if (config->filter_by_rmgr != -1 &&
328 config->filter_by_rmgr != record->xl_rmid)
331 if (config->filter_by_xid_enabled &&
332 config->filter_by_xid != record->xl_xid)
335 config->already_displayed_records++;
337 printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, bkp: %u%u%u%u, desc: ",
339 record->xl_len, record->xl_tot_len,
341 (uint32) (ReadRecPtr >> 32), (uint32) ReadRecPtr,
342 (uint32) (record->xl_prev >> 32), (uint32) record->xl_prev,
343 !!(XLR_BKP_BLOCK(0) & record->xl_info),
344 !!(XLR_BKP_BLOCK(1) & record->xl_info),
345 !!(XLR_BKP_BLOCK(2) & record->xl_info),
346 !!(XLR_BKP_BLOCK(3) & record->xl_info));
348 /* the desc routine will printf the description directly to stdout */
349 desc->rm_desc(NULL, record->xl_info, XLogRecGetData(record));
353 if (config->bkp_details)
356 char *blk = (char *) XLogRecGetData(record) + record->xl_len;
358 for (bkpnum = 0; bkpnum < XLR_MAX_BKP_BLOCKS; bkpnum++)
362 if (!(XLR_BKP_BLOCK(bkpnum) & record->xl_info))
365 memcpy(&bkpb, blk, sizeof(BkpBlock));
366 blk += sizeof(BkpBlock);
367 blk += BLCKSZ - bkpb.hole_length;
369 printf("\tbackup bkp #%u; rel %u/%u/%u; fork: %s; block: %u; hole: offset: %u, length: %u\n",
371 bkpb.node.spcNode, bkpb.node.dbNode, bkpb.node.relNode,
372 forkNames[bkpb.fork],
373 bkpb.block, bkpb.hole_offset, bkpb.hole_length);
381 printf("%s decodes and displays PostgreSQL transaction logs for debugging.\n\n",
384 printf(" %s [OPTION] [STARTSEG [ENDSEG]] \n", progname);
385 printf("\nGeneral options:\n");
386 printf(" -V, --version output version information, then exit\n");
387 printf(" -?, --help show this help, then exit\n");
388 printf("\nContent options:\n");
389 printf(" -b, --bkp-details output detailed information about backup blocks\n");
390 printf(" -e, --end=RECPTR stop reading at log position RECPTR\n");
391 printf(" -n, --limit=N number of records to display\n");
392 printf(" -p, --path=PATH directory in which to find log segment files\n");
393 printf(" (default: ./pg_xlog)\n");
394 printf(" -r, --rmgr=RMGR only show records generated by resource manager RMGR\n");
395 printf(" use --rmgr=list to list valid resource manager names\n");
396 printf(" -s, --start=RECPTR stop reading at log position RECPTR\n");
397 printf(" -t, --timeline=TLI timeline from which to read log records\n");
398 printf(" (default: 1 or the value used in STARTSEG)\n");
399 printf(" -x, --xid=XID only show records with TransactionId XID\n");
403 main(int argc, char **argv)
407 XLogReaderState *xlogreader_state;
408 XLogDumpPrivate private;
409 XLogDumpConfig config;
411 XLogRecPtr first_record;
414 static struct option long_options[] = {
415 {"bkp-details", no_argument, NULL, 'b'},
416 {"end", required_argument, NULL, 'e'},
417 {"help", no_argument, NULL, '?'},
418 {"limit", required_argument, NULL, 'n'},
419 {"path", required_argument, NULL, 'p'},
420 {"rmgr", required_argument, NULL, 'r'},
421 {"start", required_argument, NULL, 's'},
422 {"timeline", required_argument, NULL, 't'},
423 {"xid", required_argument, NULL, 'x'},
424 {"version", no_argument, NULL, 'V'},
431 progname = get_progname(argv[0]);
433 memset(&private, 0, sizeof(XLogDumpPrivate));
434 memset(&config, 0, sizeof(XLogDumpConfig));
436 private.timeline = 1;
437 private.startptr = InvalidXLogRecPtr;
438 private.endptr = InvalidXLogRecPtr;
440 config.bkp_details = false;
441 config.stop_after_records = -1;
442 config.already_displayed_records = 0;
443 config.filter_by_rmgr = -1;
444 config.filter_by_xid = InvalidTransactionId;
445 config.filter_by_xid_enabled = false;
449 fprintf(stderr, "%s: no arguments specified\n", progname);
453 while ((option = getopt_long(argc, argv, "be:?n:p:r:s:t:Vx:",
454 long_options, &optindex)) != -1)
459 config.bkp_details = true;
462 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
464 fprintf(stderr, "%s: could not parse end log position \"%s\"\n",
468 private.endptr = (uint64) xlogid << 32 | xrecoff;
475 if (sscanf(optarg, "%d", &config.stop_after_records) != 1)
477 fprintf(stderr, "%s: could not parse limit \"%s\"\n",
483 private.inpath = pg_strdup(optarg);
489 if (pg_strcasecmp(optarg, "list") == 0)
495 for (i = 0; i < RM_MAX_ID; i++)
497 if (pg_strcasecmp(optarg, RmgrDescTable[i].rm_name) == 0)
499 config.filter_by_rmgr = i;
504 if (config.filter_by_rmgr == -1)
506 fprintf(stderr, "%s: resource manager \"%s\" does not exist\n",
513 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
515 fprintf(stderr, "%s: could not parse end log position \"%s\"\n",
520 private.startptr = (uint64) xlogid << 32 | xrecoff;
523 if (sscanf(optarg, "%d", &private.timeline) != 1)
525 fprintf(stderr, "%s: could not parse timeline \"%s\"\n",
531 puts("pg_xlogdump (PostgreSQL) " PG_VERSION);
535 if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
537 fprintf(stderr, "%s: could not parse \"%s\" as a valid xid\n",
541 config.filter_by_xid_enabled = true;
548 if ((optind + 2) < argc)
551 "%s: too many command-line arguments (first is \"%s\")\n",
552 progname, argv[optind + 2]);
556 if (private.inpath != NULL)
558 /* validate path points to directory */
559 if (!verify_directory(private.inpath))
562 "%s: path \"%s\" cannot be opened: %s",
563 progname, private.inpath, strerror(errno));
568 /* parse files as start/end boundaries, extract path if not specified */
571 char *directory = NULL;
576 split_path(argv[optind], &directory, &fname);
578 if (private.inpath == NULL && directory != NULL)
580 private.inpath = directory;
582 if (!verify_directory(private.inpath))
583 fatal_error("cannot open directory \"%s\": %s",
584 private.inpath, strerror(errno));
587 fd = fuzzy_open_file(private.inpath, fname);
589 fatal_error("could not open file \"%s\"", fname);
592 /* parse position from file */
593 XLogFromFileName(fname, &private.timeline, &segno);
595 if (XLogRecPtrIsInvalid(private.startptr))
596 XLogSegNoOffsetToRecPtr(segno, 0, private.startptr);
597 else if (!XLByteInSeg(private.startptr, segno))
600 "%s: start log position %X/%X is not inside file \"%s\"\n",
602 (uint32) (private.startptr >> 32),
603 (uint32) private.startptr,
608 /* no second file specified, set end position */
609 if (!(optind + 1 < argc) && XLogRecPtrIsInvalid(private.endptr))
610 XLogSegNoOffsetToRecPtr(segno + 1, 0, private.endptr);
612 /* parse ENDSEG if passed */
613 if (optind + 1 < argc)
617 /* ignore directory, already have that */
618 split_path(argv[optind + 1], &directory, &fname);
620 fd = fuzzy_open_file(private.inpath, fname);
622 fatal_error("could not open file \"%s\"", fname);
625 /* parse position from file */
626 XLogFromFileName(fname, &private.timeline, &endsegno);
628 if (endsegno < segno)
629 fatal_error("ENDSEG %s is before STARTSEG %s",
630 argv[optind + 1], argv[optind]);
632 if (XLogRecPtrIsInvalid(private.endptr))
633 XLogSegNoOffsetToRecPtr(endsegno + 1, 0, private.endptr);
635 /* set segno to endsegno for check of --end */
640 if (!XLByteInSeg(private.endptr, segno) &&
641 private.endptr != (segno + 1) * XLogSegSize)
644 "%s: end log position %X/%X is not inside file \"%s\"\n",
646 (uint32) (private.endptr >> 32),
647 (uint32) private.endptr,
653 /* we don't know what to print */
654 if (XLogRecPtrIsInvalid(private.startptr))
656 fprintf(stderr, "%s: no start log position given in range mode.\n", progname);
660 /* done with argument parsing, do the actual work */
662 /* we have everything we need, start reading */
663 xlogreader_state = XLogReaderAllocate(XLogDumpReadPage, &private);
664 if (!xlogreader_state)
665 fatal_error("out of memory");
667 /* first find a valid recptr to start from */
668 first_record = XLogFindNextRecord(xlogreader_state, private.startptr);
670 if (first_record == InvalidXLogRecPtr)
671 fatal_error("could not find a valid record after %X/%X",
672 (uint32) (private.startptr >> 32),
673 (uint32) private.startptr);
676 * Display a message that we're skipping data if `from` wasn't a pointer to
677 * the start of a record and also wasn't a pointer to the beginning of a
678 * segment (e.g. we were used in file mode).
680 if (first_record != private.startptr && (private.startptr % XLogSegSize) != 0)
681 printf("first record is after %X/%X, at %X/%X, skipping over %u bytes\n",
682 (uint32) (private.startptr >> 32), (uint32) private.startptr,
683 (uint32) (first_record >> 32), (uint32) first_record,
684 (uint32) (first_record - private.startptr));
686 while ((record = XLogReadRecord(xlogreader_state, first_record, &errormsg)))
688 /* continue after the last record */
689 first_record = InvalidXLogRecPtr;
690 XLogDumpDisplayRecord(&config, xlogreader_state->ReadRecPtr, record);
692 /* check whether we printed enough */
693 if (config.stop_after_records > 0 &&
694 config.already_displayed_records >= config.stop_after_records)
699 fatal_error("error in WAL record at %X/%X: %s\n",
700 (uint32) (xlogreader_state->ReadRecPtr >> 32),
701 (uint32) xlogreader_state->ReadRecPtr,
704 XLogReaderFree(xlogreader_state);
709 fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);