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