]> granicus.if.org Git - postgresql/blob - contrib/pg_xlogdump/pg_xlogdump.c
Fix some typos and grammatical mistakes
[postgresql] / contrib / pg_xlogdump / pg_xlogdump.c
1 /*-------------------------------------------------------------------------
2  *
3  * pg_xlogdump.c - decode and display WAL
4  *
5  * Copyright (c) 2013, PostgreSQL Global Development Group
6  *
7  * IDENTIFICATION
8  *                contrib/pg_xlogdump/pg_xlogdump.c
9  *-------------------------------------------------------------------------
10  */
11
12 #define FRONTEND 1
13 #include "postgres.h"
14
15 #include <dirent.h>
16 #include <unistd.h>
17
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"
24 #include "rmgrdesc.h"
25
26
27 static const char *progname;
28
29 typedef struct XLogDumpPrivate
30 {
31         TimeLineID      timeline;
32         char       *inpath;
33         XLogRecPtr      startptr;
34         XLogRecPtr      endptr;
35 } XLogDumpPrivate;
36
37 typedef struct XLogDumpConfig
38 {
39         /* display options */
40         bool            bkp_details;
41         int                     stop_after_records;
42         int                     already_displayed_records;
43
44         /* filter options */
45         int                     filter_by_rmgr;
46         TransactionId filter_by_xid;
47         bool            filter_by_xid_enabled;
48 } XLogDumpConfig;
49
50 static void
51 fatal_error(const char *fmt,...)
52 __attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2)));
53
54 /*
55  * Big red button to push when things go horribly wrong.
56  */
57 static void
58 fatal_error(const char *fmt,...)
59 {
60         va_list         args;
61
62         fflush(stdout);
63
64         fprintf(stderr, "%s: FATAL:  ", progname);
65         va_start(args, fmt);
66         vfprintf(stderr, fmt, args);
67         va_end(args);
68         fputc('\n', stderr);
69
70         exit(EXIT_FAILURE);
71 }
72
73 static void
74 print_rmgr_list(void)
75 {
76         int             i;
77
78         for (i = 0; i < RM_MAX_ID + 1; i++)
79         {
80                 printf("%s\n", RmgrDescTable[i].rm_name);
81         }
82 }
83
84 /*
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.
87  */
88 static bool
89 verify_directory(const char *directory)
90 {
91         DIR *dir = opendir(directory);
92         if (dir == NULL)
93                 return false;
94         closedir(dir);
95         return true;
96 }
97
98 /*
99  * Split a pathname as dirname(1) and basename(1) would.
100  *
101  * XXX this probably doesn't do very well on Windows.  We probably need to
102  * apply canonicalize_path(), at the very least.
103  */
104 static void
105 split_path(const char *path, char **dir, char **fname)
106 {
107         char       *sep;
108
109         /* split filepath into directory & filename */
110         sep = strrchr(path, '/');
111
112         /* directory path */
113         if (sep != NULL)
114         {
115                 *dir = pg_strdup(path);
116                 (*dir)[(sep - path) + 1] = '\0';        /* no strndup */
117                 *fname = pg_strdup(sep + 1);
118         }
119         /* local directory */
120         else
121         {
122                 *dir = NULL;
123                 *fname = pg_strdup(path);
124         }
125 }
126
127 /*
128  * Try to find the file in several places:
129  * if directory == NULL:
130  *       fname
131  *       XLOGDIR / fname
132  *       $PGDATA / XLOGDIR / fname
133  * else
134  *       directory / fname
135  *       directory / XLOGDIR / fname
136  *
137  * return a read only fd
138  */
139 static int
140 fuzzy_open_file(const char *directory, const char *fname)
141 {
142         int                     fd = -1;
143         char            fpath[MAXPGPATH];
144
145         if (directory == NULL)
146         {
147                 const char *datadir;
148
149                 /* fname */
150                 fd = open(fname, O_RDONLY | PG_BINARY, 0);
151                 if (fd < 0 && errno != ENOENT)
152                         return -1;
153                 else if (fd > 0)
154                         return fd;
155
156                 /* XLOGDIR / fname */
157                 snprintf(fpath, MAXPGPATH, "%s/%s",
158                                  XLOGDIR, fname);
159                 fd = open(fpath, O_RDONLY | PG_BINARY, 0);
160                 if (fd < 0 && errno != ENOENT)
161                         return -1;
162                 else if (fd > 0)
163                         return fd;
164
165                 datadir = getenv("PGDATA");
166                 /* $PGDATA / XLOGDIR / fname */
167                 if (datadir != NULL)
168                 {
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)
173                                 return -1;
174                         else if (fd > 0)
175                                 return fd;
176                 }
177         }
178         else
179         {
180                 /* directory / fname */
181                 snprintf(fpath, MAXPGPATH, "%s/%s",
182                                  directory, fname);
183                 fd = open(fpath, O_RDONLY | PG_BINARY, 0);
184                 if (fd < 0 && errno != ENOENT)
185                         return -1;
186                 else if (fd > 0)
187                         return fd;
188
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)
194                         return -1;
195                 else if (fd > 0)
196                         return fd;
197         }
198         return -1;
199 }
200
201 /*
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
204  * the passed buffer.
205  */
206 static void
207 XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
208                                  XLogRecPtr startptr, char *buf, Size count)
209 {
210         char       *p;
211         XLogRecPtr      recptr;
212         Size            nbytes;
213
214         static int      sendFile = -1;
215         static XLogSegNo sendSegNo = 0;
216         static uint32 sendOff = 0;
217
218         p = buf;
219         recptr = startptr;
220         nbytes = count;
221
222         while (nbytes > 0)
223         {
224                 uint32          startoff;
225                 int                     segbytes;
226                 int                     readbytes;
227
228                 startoff = recptr % XLogSegSize;
229
230                 if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo))
231                 {
232                         char            fname[MAXFNAMELEN];
233
234                         /* Switch to another logfile segment */
235                         if (sendFile >= 0)
236                                 close(sendFile);
237
238                         XLByteToSeg(recptr, sendSegNo);
239
240                         XLogFileName(fname, timeline_id, sendSegNo);
241
242                         sendFile = fuzzy_open_file(directory, fname);
243
244                         if (sendFile < 0)
245                                 fatal_error("could not find file \"%s\": %s",
246                                                         fname, strerror(errno));
247                         sendOff = 0;
248                 }
249
250                 /* Need to seek in the file? */
251                 if (sendOff != startoff)
252                 {
253                         if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
254                         {
255                                 int                     err = errno;
256                                 char            fname[MAXPGPATH];
257
258                                 XLogFileName(fname, timeline_id, sendSegNo);
259
260                                 fatal_error("could not seek in log segment %s to offset %u: %s",
261                                                         fname, startoff, strerror(err));
262                         }
263                         sendOff = startoff;
264                 }
265
266                 /* How many bytes are within this segment? */
267                 if (nbytes > (XLogSegSize - startoff))
268                         segbytes = XLogSegSize - startoff;
269                 else
270                         segbytes = nbytes;
271
272                 readbytes = read(sendFile, p, segbytes);
273                 if (readbytes <= 0)
274                 {
275                         int                     err = errno;
276                         char            fname[MAXPGPATH];
277
278                         XLogFileName(fname, timeline_id, sendSegNo);
279
280                         fatal_error("could not read from log segment %s, offset %d, length %d: %s",
281                                                 fname, sendOff, segbytes, strerror(err));
282                 }
283
284                 /* Update state for read */
285                 recptr += readbytes;
286
287                 sendOff += readbytes;
288                 nbytes -= readbytes;
289                 p += readbytes;
290         }
291 }
292
293 /*
294  * XLogReader read_page callback
295  */
296 static int
297 XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
298                                  XLogRecPtr targetPtr, char *readBuff, TimeLineID *curFileTLI)
299 {
300         XLogDumpPrivate *private = state->private_data;
301         int                     count = XLOG_BLCKSZ;
302
303         if (private->endptr != InvalidXLogRecPtr)
304         {
305                 if (targetPagePtr + XLOG_BLCKSZ <= private->endptr)
306                         count = XLOG_BLCKSZ;
307                 else if (targetPagePtr + reqLen <= private->endptr)
308                         count = private->endptr - targetPagePtr;
309                 else
310                         return -1;
311         }
312
313         XLogDumpXLogRead(private->inpath, private->timeline, targetPagePtr,
314                                          readBuff, count);
315
316         return count;
317 }
318
319 /*
320  * Print a record to stdout
321  */
322 static void
323 XLogDumpDisplayRecord(XLogDumpConfig *config, XLogRecPtr ReadRecPtr, XLogRecord *record)
324 {
325         const RmgrDescData *desc = &RmgrDescTable[record->xl_rmid];
326
327         if (config->filter_by_rmgr != -1 &&
328                 config->filter_by_rmgr != record->xl_rmid)
329                 return;
330
331         if (config->filter_by_xid_enabled &&
332                 config->filter_by_xid != record->xl_xid)
333                 return;
334
335         config->already_displayed_records++;
336
337         printf("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, bkp: %u%u%u%u, desc: ",
338                    desc->rm_name,
339                    record->xl_len, record->xl_tot_len,
340                    record->xl_xid,
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));
347
348         /* the desc routine will printf the description directly to stdout */
349         desc->rm_desc(NULL, record->xl_info, XLogRecGetData(record));
350
351         putchar('\n');
352
353         if (config->bkp_details)
354         {
355                 int                     bkpnum;
356                 char       *blk = (char *) XLogRecGetData(record) + record->xl_len;
357
358                 for (bkpnum = 0; bkpnum < XLR_MAX_BKP_BLOCKS; bkpnum++)
359                 {
360                         BkpBlock        bkpb;
361
362                         if (!(XLR_BKP_BLOCK(bkpnum) & record->xl_info))
363                                 continue;
364
365                         memcpy(&bkpb, blk, sizeof(BkpBlock));
366                         blk += sizeof(BkpBlock);
367                         blk += BLCKSZ - bkpb.hole_length;
368
369                         printf("\tbackup bkp #%u; rel %u/%u/%u; fork: %s; block: %u; hole: offset: %u, length: %u\n",
370                                    bkpnum,
371                                    bkpb.node.spcNode, bkpb.node.dbNode, bkpb.node.relNode,
372                                    forkNames[bkpb.fork],
373                                    bkpb.block, bkpb.hole_offset, bkpb.hole_length);
374                 }
375         }
376 }
377
378 static void
379 usage(void)
380 {
381         printf("%s decodes and displays PostgreSQL transaction logs for debugging.\n\n",
382                    progname);
383         printf("Usage:\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");
400 }
401
402 int
403 main(int argc, char **argv)
404 {
405         uint32          xlogid;
406         uint32          xrecoff;
407         XLogReaderState *xlogreader_state;
408         XLogDumpPrivate private;
409         XLogDumpConfig config;
410         XLogRecord *record;
411         XLogRecPtr      first_record;
412         char       *errormsg;
413
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'},
425                 {NULL, 0, NULL, 0}
426         };
427
428         int                     option;
429         int                     optindex = 0;
430
431         progname = get_progname(argv[0]);
432
433         memset(&private, 0, sizeof(XLogDumpPrivate));
434         memset(&config, 0, sizeof(XLogDumpConfig));
435
436         private.timeline = 1;
437         private.startptr = InvalidXLogRecPtr;
438         private.endptr = InvalidXLogRecPtr;
439
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;
446
447         if (argc <= 1)
448         {
449                 fprintf(stderr, "%s: no arguments specified\n", progname);
450                 goto bad_argument;
451         }
452
453         while ((option = getopt_long(argc, argv, "be:?n:p:r:s:t:Vx:",
454                                                                  long_options, &optindex)) != -1)
455         {
456                 switch (option)
457                 {
458                         case 'b':
459                                 config.bkp_details = true;
460                                 break;
461                         case 'e':
462                                 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
463                                 {
464                                         fprintf(stderr, "%s: could not parse end log position \"%s\"\n",
465                                                         progname, optarg);
466                                         goto bad_argument;
467                                 }
468                                 private.endptr = (uint64) xlogid << 32 | xrecoff;
469                                 break;
470                         case '?':
471                                 usage();
472                                 exit(EXIT_SUCCESS);
473                                 break;
474                         case 'n':
475                                 if (sscanf(optarg, "%d", &config.stop_after_records) != 1)
476                                 {
477                                         fprintf(stderr, "%s: could not parse limit \"%s\"\n",
478                                                         progname, optarg);
479                                         goto bad_argument;
480                                 }
481                                 break;
482                         case 'p':
483                                 private.inpath = pg_strdup(optarg);
484                                 break;
485                         case 'r':
486                                 {
487                                         int                     i;
488
489                                         if (pg_strcasecmp(optarg, "list") == 0)
490                                         {
491                                                 print_rmgr_list();
492                                                 exit(EXIT_SUCCESS);
493                                         }
494
495                                         for (i = 0; i < RM_MAX_ID; i++)
496                                         {
497                                                 if (pg_strcasecmp(optarg, RmgrDescTable[i].rm_name) == 0)
498                                                 {
499                                                         config.filter_by_rmgr = i;
500                                                         break;
501                                                 }
502                                         }
503
504                                         if (config.filter_by_rmgr == -1)
505                                         {
506                                                 fprintf(stderr, "%s: resource manager \"%s\" does not exist\n",
507                                                                 progname, optarg);
508                                                 goto bad_argument;
509                                         }
510                                 }
511                                 break;
512                         case 's':
513                                 if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2)
514                                 {
515                                         fprintf(stderr, "%s: could not parse end log position \"%s\"\n",
516                                                         progname, optarg);
517                                         goto bad_argument;
518                                 }
519                                 else
520                                         private.startptr = (uint64) xlogid << 32 | xrecoff;
521                                 break;
522                         case 't':
523                                 if (sscanf(optarg, "%d", &private.timeline) != 1)
524                                 {
525                                         fprintf(stderr, "%s: could not parse timeline \"%s\"\n",
526                                                         progname, optarg);
527                                         goto bad_argument;
528                                 }
529                                 break;
530                         case 'V':
531                                 puts("pg_xlogdump (PostgreSQL) " PG_VERSION);
532                                 exit(EXIT_SUCCESS);
533                                 break;
534                         case 'x':
535                                 if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
536                                 {
537                                         fprintf(stderr, "%s: could not parse \"%s\" as a valid xid\n",
538                                                         progname, optarg);
539                                         goto bad_argument;
540                                 }
541                                 config.filter_by_xid_enabled = true;
542                                 break;
543                         default:
544                                 goto bad_argument;
545                 }
546         }
547
548         if ((optind + 2) < argc)
549         {
550                 fprintf(stderr,
551                                 "%s: too many command-line arguments (first is \"%s\")\n",
552                                 progname, argv[optind + 2]);
553                 goto bad_argument;
554         }
555
556         if (private.inpath != NULL)
557         {
558                 /* validate path points to directory */
559                 if (!verify_directory(private.inpath))
560                 {
561                         fprintf(stderr,
562                                         "%s: path \"%s\" cannot be opened: %s",
563                                         progname, private.inpath, strerror(errno));
564                         goto bad_argument;
565                 }
566         }
567
568         /* parse files as start/end boundaries, extract path if not specified */
569         if (optind < argc)
570         {
571                 char       *directory = NULL;
572                 char       *fname = NULL;
573                 int                     fd;
574                 XLogSegNo       segno;
575
576                 split_path(argv[optind], &directory, &fname);
577
578                 if (private.inpath == NULL && directory != NULL)
579                 {
580                         private.inpath = directory;
581
582                         if (!verify_directory(private.inpath))
583                                 fatal_error("cannot open directory \"%s\": %s",
584                                                         private.inpath, strerror(errno));
585                 }
586
587                 fd = fuzzy_open_file(private.inpath, fname);
588                 if (fd < 0)
589                         fatal_error("could not open file \"%s\"", fname);
590                 close(fd);
591
592                 /* parse position from file */
593                 XLogFromFileName(fname, &private.timeline, &segno);
594
595                 if (XLogRecPtrIsInvalid(private.startptr))
596                         XLogSegNoOffsetToRecPtr(segno, 0, private.startptr);
597                 else if (!XLByteInSeg(private.startptr, segno))
598                 {
599                         fprintf(stderr,
600                                         "%s: start log position %X/%X is not inside file \"%s\"\n",
601                                         progname,
602                                         (uint32) (private.startptr >> 32),
603                                         (uint32) private.startptr,
604                                         fname);
605                         goto bad_argument;
606                 }
607
608                 /* no second file specified, set end position */
609                 if (!(optind + 1 < argc) && XLogRecPtrIsInvalid(private.endptr))
610                         XLogSegNoOffsetToRecPtr(segno + 1, 0, private.endptr);
611
612                 /* parse ENDSEG if passed */
613                 if (optind + 1 < argc)
614                 {
615                         XLogSegNo       endsegno;
616
617                         /* ignore directory, already have that */
618                         split_path(argv[optind + 1], &directory, &fname);
619
620                         fd = fuzzy_open_file(private.inpath, fname);
621                         if (fd < 0)
622                                 fatal_error("could not open file \"%s\"", fname);
623                         close(fd);
624
625                         /* parse position from file */
626                         XLogFromFileName(fname, &private.timeline, &endsegno);
627
628                         if (endsegno < segno)
629                                 fatal_error("ENDSEG %s is before STARTSEG %s",
630                                                         argv[optind + 1], argv[optind]);
631
632                         if (XLogRecPtrIsInvalid(private.endptr))
633                                 XLogSegNoOffsetToRecPtr(endsegno + 1, 0, private.endptr);
634
635                         /* set segno to endsegno for check of --end */
636                         segno = endsegno;
637                 }
638
639
640                 if (!XLByteInSeg(private.endptr, segno) &&
641                         private.endptr != (segno + 1) * XLogSegSize)
642                 {
643                         fprintf(stderr,
644                                         "%s: end log position %X/%X is not inside file \"%s\"\n",
645                                         progname,
646                                         (uint32) (private.endptr >> 32),
647                                         (uint32) private.endptr,
648                                         argv[argc - 1]);
649                         goto bad_argument;
650                 }
651         }
652
653         /* we don't know what to print */
654         if (XLogRecPtrIsInvalid(private.startptr))
655         {
656                 fprintf(stderr, "%s: no start log position given in range mode.\n", progname);
657                 goto bad_argument;
658         }
659
660         /* done with argument parsing, do the actual work */
661
662         /* we have everything we need, start reading */
663         xlogreader_state = XLogReaderAllocate(XLogDumpReadPage, &private);
664         if (!xlogreader_state)
665                 fatal_error("out of memory");
666
667         /* first find a valid recptr to start from */
668         first_record = XLogFindNextRecord(xlogreader_state, private.startptr);
669
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);
674
675         /*
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).
679          */
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));
685
686         while ((record = XLogReadRecord(xlogreader_state, first_record, &errormsg)))
687         {
688                 /* continue after the last record */
689                 first_record = InvalidXLogRecPtr;
690                 XLogDumpDisplayRecord(&config, xlogreader_state->ReadRecPtr, record);
691
692                 /* check whether we printed enough */
693                 if (config.stop_after_records > 0 &&
694                         config.already_displayed_records >= config.stop_after_records)
695                         break;
696         }
697
698         if (errormsg)
699                 fatal_error("error in WAL record at %X/%X: %s\n",
700                                         (uint32) (xlogreader_state->ReadRecPtr >> 32),
701                                         (uint32) xlogreader_state->ReadRecPtr,
702                                         errormsg);
703
704         XLogReaderFree(xlogreader_state);
705
706         return EXIT_SUCCESS;
707
708 bad_argument:
709         fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
710         return EXIT_FAILURE;
711 }