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