Age Owner Branch data TLA 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
1329 tgl@sss.pgh.pa.us 85 :UBC 0 : sigint_handler(SIGNAL_ARGS)
86 : : {
1615 michael@paquier.xyz 87 : 0 : time_to_stop = true;
88 : 0 : }
89 : : #endif
90 : :
91 : : static void
4820 alvherre@alvh.no-ip. 92 :CBC 1 : print_rmgr_list(void)
93 : : {
94 : : int i;
95 : :
1490 jdavis@postgresql.or 96 [ + + ]: 24 : for (i = 0; i <= RM_MAX_BUILTIN_ID; i++)
97 : : {
98 : 23 : printf("%s\n", GetRmgrDesc(i)->rm_name);
99 : : }
4820 alvherre@alvh.no-ip. 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 : 74 : verify_directory(const char *directory)
108 : : {
4724 bruce@momjian.us 109 : 74 : DIR *dir = opendir(directory);
110 : :
4820 alvherre@alvh.no-ip. 111 [ + + ]: 74 : if (dir == NULL)
112 : 1 : return false;
113 : 73 : closedir(dir);
114 : 73 : 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
1225 michael@paquier.xyz 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)
1225 michael@paquier.xyz 131 :UBC 0 : pg_fatal("could not create directory \"%s\": %m", path);
1225 michael@paquier.xyz 132 :CBC 1 : break;
1225 michael@paquier.xyz 133 :UBC 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 : : }
1225 michael@paquier.xyz 146 :CBC 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
4820 alvherre@alvh.no-ip. 155 : 68 : split_path(const char *path, char **dir, char **fname)
156 : : {
157 : : const char *sep;
158 : :
159 : : /* split filepath into directory & filename */
160 : 68 : sep = strrchr(path, '/');
161 : :
162 : : /* directory path */
163 [ + + ]: 68 : if (sep != NULL)
164 : : {
2344 165 : 65 : *dir = pnstrdup(path, sep - path);
4820 166 : 65 : *fname = pg_strdup(sep + 1);
167 : : }
168 : : /* local directory */
169 : : else
170 : : {
171 : 3 : *dir = NULL;
172 : 3 : *fname = pg_strdup(path);
173 : : }
174 : 68 : }
175 : :
176 : : /*
177 : : * Open the file in the valid target directory.
178 : : *
179 : : * return a read only fd
180 : : */
181 : : int
3150 andres@anarazel.de 182 : 229 : open_file_in_directory(const char *directory, const char *fname)
183 : : {
4820 alvherre@alvh.no-ip. 184 : 229 : int fd = -1;
185 : : char fpath[MAXPGPATH];
186 : :
3150 andres@anarazel.de 187 [ - + ]: 229 : Assert(directory != NULL);
188 : :
189 : 229 : snprintf(fpath, MAXPGPATH, "%s/%s", directory, fname);
190 : 229 : fd = open(fpath, O_RDONLY | PG_BINARY, 0);
191 : :
192 [ + + - + ]: 229 : if (fd < 0 && errno != ENOENT)
1488 tgl@sss.pgh.pa.us 193 :UBC 0 : pg_fatal("could not open file \"%s\": %m", fname);
3150 andres@anarazel.de 194 :CBC 229 : 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
98 rhaas@postgresql.org 204 :GNC 91 : search_directory(const char *directory, const char *fname, int *WalSegSz)
205 : : {
3150 andres@anarazel.de 206 :CBC 91 : int fd = -1;
207 : : DIR *xldir;
208 : :
209 : : /* open file if valid filename is provided */
210 [ + + ]: 91 : 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 [ + - ]: 83 : else if ((xldir = opendir(directory)) != NULL)
219 : : {
220 : : struct dirent *xlde;
221 : :
222 [ + + ]: 558 : while ((xlde = readdir(xldir)) != NULL)
223 : : {
224 [ + + ]: 542 : if (IsXLogFileName(xlde->d_name))
225 : : {
226 : 67 : fd = open_file_in_directory(directory, xlde->d_name);
1504 227 : 67 : fname = pg_strdup(xlde->d_name);
3150 228 : 67 : break;
229 : : }
230 : : }
231 : :
232 : 83 : closedir(xldir);
233 : : }
234 : :
235 : : /* set WalSegSz if file is successfully opened */
236 [ + + ]: 91 : if (fd >= 0)
237 : : {
238 : : PGAlignedXLogBlock buf;
239 : : int r;
240 : :
2803 tgl@sss.pgh.pa.us 241 : 73 : r = read(fd, buf.data, XLOG_BLCKSZ);
2848 michael@paquier.xyz 242 [ + - ]: 73 : if (r == XLOG_BLCKSZ)
243 : : {
2803 tgl@sss.pgh.pa.us 244 : 73 : XLogLongPageHeader longhdr = (XLogLongPageHeader) buf.data;
245 : :
98 rhaas@postgresql.org 246 [ + - + + :GNC 73 : if (!IsValidWalSegSize(longhdr->xlp_seg_size))
+ - - + ]
247 : : {
15 peter@eisentraut.org 248 : 1 : pg_log_error(ngettext("invalid WAL segment size in WAL file \"%s\" (%u byte)",
249 : : "invalid WAL segment size in WAL file \"%s\" (%u bytes)",
250 : : longhdr->xlp_seg_size),
251 : : fname, longhdr->xlp_seg_size);
981 peter@eisentraut.org 252 :CBC 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 : :
98 rhaas@postgresql.org 256 :GNC 72 : *WalSegSz = longhdr->xlp_seg_size;
257 : : }
1530 andres@anarazel.de 258 [ # # ]:UBC 0 : else if (r < 0)
1488 tgl@sss.pgh.pa.us 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);
3150 andres@anarazel.de 264 :CBC 72 : close(fd);
265 : 72 : 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 *
98 rhaas@postgresql.org 287 :GNC 74 : identify_target_directory(char *directory, char *fname, int *WalSegSz)
288 : : {
289 : : char fpath[MAXPGPATH];
290 : :
3150 andres@anarazel.de 291 [ + + ]:CBC 74 : if (directory != NULL)
292 : : {
98 rhaas@postgresql.org 293 [ + + ]:GNC 73 : if (search_directory(directory, fname, WalSegSz))
2415 alvherre@alvh.no-ip. 294 :CBC 56 : return pg_strdup(directory);
295 : :
296 : : /* directory / XLOGDIR */
3150 andres@anarazel.de 297 : 16 : snprintf(fpath, MAXPGPATH, "%s/%s", directory, XLOGDIR);
98 rhaas@postgresql.org 298 [ + - ]:GNC 16 : if (search_directory(fpath, fname, WalSegSz))
2415 alvherre@alvh.no-ip. 299 :CBC 16 : return pg_strdup(fpath);
300 : : }
301 : : else
302 : : {
303 : : const char *datadir;
304 : :
305 : : /* current directory */
98 rhaas@postgresql.org 306 [ - + ]:GNC 1 : if (search_directory(".", fname, WalSegSz))
2415 alvherre@alvh.no-ip. 307 :UBC 0 : return pg_strdup(".");
308 : : /* XLOGDIR */
98 rhaas@postgresql.org 309 [ - + ]:GNC 1 : if (search_directory(XLOGDIR, fname, WalSegSz))
2415 alvherre@alvh.no-ip. 310 :UBC 0 : return pg_strdup(XLOGDIR);
311 : :
4820 alvherre@alvh.no-ip. 312 :CBC 1 : datadir = getenv("PGDATA");
313 : : /* $PGDATA / XLOGDIR */
314 [ - + ]: 1 : if (datadir != NULL)
315 : : {
3150 andres@anarazel.de 316 :UBC 0 : snprintf(fpath, MAXPGPATH, "%s/%s", datadir, XLOGDIR);
98 rhaas@postgresql.org 317 [ # # ]:UNC 0 : if (search_directory(fpath, fname, WalSegSz))
2415 alvherre@alvh.no-ip. 318 :UBC 0 : return pg_strdup(fpath);
319 : : }
320 : : }
321 : :
322 : : /* could not locate WAL file */
3150 andres@anarazel.de 323 [ + - ]:CBC 1 : if (fname)
1488 tgl@sss.pgh.pa.us 324 : 1 : pg_fatal("could not locate WAL file \"%s\"", fname);
325 : : else
1488 tgl@sss.pgh.pa.us 326 :UBC 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
46 andrew@dunslane.net 336 :GNC 49031 : required_read_len(XLogDumpPrivate *private, XLogRecPtr targetPagePtr,
337 : : int reqLen)
338 : : {
339 : 49031 : int count = XLOG_BLCKSZ;
340 : :
341 [ + + ]: 49031 : if (XLogRecPtrIsValid(private->endptr))
342 : : {
343 [ + + ]: 42209 : if (targetPagePtr + XLOG_BLCKSZ <= private->endptr)
344 : 41976 : count = XLOG_BLCKSZ;
345 [ + + ]: 233 : else if (targetPagePtr + reqLen <= private->endptr)
346 : 116 : count = private->endptr - targetPagePtr;
347 : : else
348 : : {
349 : 117 : private->endptr_reached = true;
350 : 117 : return -1;
351 : : }
352 : : }
353 : :
354 : 48914 : return count;
355 : : }
356 : :
357 : : /* pg_waldump's XLogReaderRoutine->segment_open callback */
358 : : static void
2183 alvherre@alvh.no-ip. 359 :CBC 89 : WALDumpOpenSegment(XLogReaderState *state, XLogSegNo nextSegNo,
360 : : TimeLineID *tli_p)
361 : : {
2353 362 : 89 : TimeLineID tli = *tli_p;
363 : : char fname[MAXPGPATH];
364 : : int tries;
365 : :
2183 366 : 89 : 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 : : */
2353 374 [ + - ]: 89 : for (tries = 0; tries < 10; tries++)
375 : : {
2183 376 : 89 : state->seg.ws_file = open_file_in_directory(state->segcxt.ws_dir, fname);
377 [ + - ]: 89 : if (state->seg.ws_file >= 0)
378 : 89 : return;
2353 alvherre@alvh.no-ip. 379 [ # # ]:UBC 0 : if (errno == ENOENT)
4820 380 : 0 : {
2848 michael@paquier.xyz 381 : 0 : int save_errno = errno;
382 : :
383 : : /* File not there yet, try again */
2353 alvherre@alvh.no-ip. 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 : :
1488 tgl@sss.pgh.pa.us 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
2188 alvherre@alvh.no-ip. 401 :CBC 89 : WALDumpCloseSegment(XLogReaderState *state)
402 : : {
403 : 89 : close(state->seg.ws_file);
404 : : /* need to check errno? */
405 : 89 : state->seg.ws_file = -1;
406 : 89 : }
407 : :
408 : : /* pg_waldump's XLogReaderRoutine->page_read callback */
409 : : static int
1821 tmunro@postgresql.or 410 : 21211 : WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
411 : : XLogRecPtr targetPtr, char *readBuff)
412 : : {
413 : 21211 : XLogDumpPrivate *private = state->private_data;
46 andrew@dunslane.net 414 :GNC 21211 : 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 [ + + ]: 21211 : if (count < 0)
419 : 66 : return -1;
420 : :
1821 tmunro@postgresql.or 421 [ - + ]:CBC 21145 : if (!WALRead(state, readBuff, targetPagePtr, count, private->timeline,
422 : : &errinfo))
423 : : {
2353 alvherre@alvh.no-ip. 424 :UBC 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;
964 dgustafsson@postgres 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 : :
1821 tmunro@postgresql.or 442 :CBC 21145 : 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
46 andrew@dunslane.net 451 :UNC 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 : : {
41 tgl@sss.pgh.pa.us 464 : 0 : close(state->seg.ws_file);
465 : : /* need to check errno? */
466 : 0 : state->seg.ws_file = -1;
46 andrew@dunslane.net 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
46 andrew@dunslane.net 474 :GNC 27820 : TarWALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
475 : : XLogRecPtr targetPtr, char *readBuff)
476 : : {
477 : 27820 : XLogDumpPrivate *private = state->private_data;
478 : 27820 : int count = required_read_len(private, targetPagePtr, reqLen);
479 : 27820 : 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 [ + + ]: 27820 : if (count < 0)
484 : 51 : 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 : 27769 : curSegNo = state->seg.ws_segno;
497 [ + + ]: 27769 : 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 : 93 : XLByteToSeg(targetPagePtr, nextSegNo, segsize);
507 : 93 : state->seg.ws_tli = private->timeline;
508 : 93 : state->seg.ws_segno = nextSegNo;
509 : :
510 : : /* Close the WAL segment file if it is currently open */
511 [ - + ]: 93 : if (state->seg.ws_file >= 0)
512 : : {
46 andrew@dunslane.net 513 :UNC 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 : : */
46 andrew@dunslane.net 523 [ + + + + ]:GNC 93 : 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 [ - + ]: 93 : if (TmpWalSegDir != NULL)
534 : : {
46 andrew@dunslane.net 535 :UNC 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 */
46 andrew@dunslane.net 541 [ - + ]:GNC 27769 : if (state->seg.ws_file >= 0)
46 andrew@dunslane.net 542 :UNC 0 : return WALDumpReadPage(state, targetPagePtr, count, targetPtr,
543 : : readBuff);
544 : :
545 : : /* Otherwise, read the WAL page from the archive streamer */
46 andrew@dunslane.net 546 :GNC 27769 : 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
1503 tmunro@postgresql.or 554 :CBC 352924 : XLogRecordMatchesRelationBlock(XLogReaderState *record,
555 : : RelFileLocator matchRlocator,
556 : : BlockNumber matchBlock,
557 : : ForkNumber matchFork)
558 : : {
559 : : int block_id;
560 : :
561 [ + + ]: 752056 : for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
562 : : {
563 : : RelFileLocator rlocator;
564 : : ForkNumber forknum;
565 : : BlockNumber blk;
566 : :
1485 tgl@sss.pgh.pa.us 567 [ + + ]: 399354 : if (!XLogRecGetBlockTagExtended(record, block_id,
568 : : &rlocator, &forknum, &blk, NULL))
1503 tmunro@postgresql.or 569 : 76 : continue;
570 : :
571 [ + + + + ]: 399278 : if ((matchFork == InvalidForkNumber || matchFork == forknum) &&
1399 rhaas@postgresql.org 572 [ + + + - : 275372 : (RelFileLocatorEquals(matchRlocator, emptyRelFileLocator) ||
- + ]
573 [ + + + - : 275372 : RelFileLocatorEquals(matchRlocator, rlocator)) &&
+ - + + ]
1503 tmunro@postgresql.or 574 [ + + ]: 12 : (matchBlock == InvalidBlockNumber || matchBlock == blk))
575 : 222 : return true;
576 : : }
577 : :
578 : 352702 : return false;
579 : : }
580 : :
581 : : /*
582 : : * Boolean to return whether the given WAL record contains a full page write.
583 : : */
584 : : static bool
585 : 108411 : XLogRecordHasFPW(XLogReaderState *record)
586 : : {
587 : : int block_id;
588 : :
589 [ + + ]: 228933 : for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
590 : : {
591 [ + - + + ]: 123897 : if (!XLogRecHasBlockRef(record, block_id))
592 : 21 : continue;
593 : :
594 [ + + ]: 123876 : if (XLogRecHasBlockImage(record, block_id))
595 : 3375 : return true;
596 : : }
597 : :
598 : 105036 : 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
1225 michael@paquier.xyz 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))
1225 michael@paquier.xyz 631 :UBC 0 : pg_fatal("%s", record->errormsg_buf);
632 : :
1225 michael@paquier.xyz 633 :CBC 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
1225 michael@paquier.xyz 639 :UBC 0 : pg_fatal("invalid fork number: %u", fork);
640 : :
1042 michael@paquier.xyz 641 :CBC 1 : snprintf(filename, MAXPGPATH, "%s/%08X-%08X-%08X.%u.%u.%u.%u%s", savepath,
642 : : record->seg.ws_tli,
1225 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)
1225 michael@paquier.xyz 648 :UBC 0 : pg_fatal("could not open file \"%s\": %m", filename);
649 : :
1225 michael@paquier.xyz 650 [ - + ]:CBC 1 : if (fwrite(page, BLCKSZ, 1, file) != 1)
1225 michael@paquier.xyz 651 :UBC 0 : pg_fatal("could not write file \"%s\": %m", filename);
652 : :
1225 michael@paquier.xyz 653 [ - + ]:CBC 1 : if (fclose(file) != 0)
1225 michael@paquier.xyz 654 :UBC 0 : pg_fatal("could not close file \"%s\": %m", filename);
655 : : }
1225 michael@paquier.xyz 656 :CBC 201 : }
657 : :
658 : : /*
659 : : * Print a record to stdout
660 : : */
661 : : static void
4184 heikki.linnakangas@i 662 : 568772 : XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
663 : : {
664 : : const char *id;
1490 jdavis@postgresql.or 665 : 568772 : const RmgrDescData *desc = GetRmgrDesc(XLogRecGetRmid(record));
666 : : uint32 rec_len;
667 : : uint32 fpi_len;
4184 heikki.linnakangas@i 668 : 568772 : uint8 info = XLogRecGetInfo(record);
669 : 568772 : XLogRecPtr xl_prev = XLogRecGetPrev(record);
670 : : StringInfoData s;
671 : :
1488 jdavis@postgresql.or 672 : 568772 : XLogRecGetLen(record, &rec_len, &fpi_len);
673 : :
4184 heikki.linnakangas@i 674 : 568772 : 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 : :
2380 andres@anarazel.de 681 : 568772 : id = desc->rm_identify(info);
682 [ - + ]: 568772 : if (id == NULL)
2380 andres@anarazel.de 683 :UBC 0 : printf("desc: UNKNOWN (%x) ", info & ~XLR_INFO_MASK);
684 : : else
2380 andres@anarazel.de 685 :CBC 568772 : printf("desc: %s ", id);
686 : :
2373 687 : 568772 : initStringInfo(&s);
688 : 568772 : desc->rm_desc(&s, record);
689 : 568772 : printf("%s", s.data);
690 : :
1488 jdavis@postgresql.or 691 : 568772 : resetStringInfo(&s);
692 : 568772 : XLogRecGetBlockRefInfo(record, true, config->bkp_details, &s, NULL);
693 : 568772 : printf("%s", s.data);
694 : 568772 : pfree(s.data);
4820 alvherre@alvh.no-ip. 695 : 568772 : }
696 : :
697 : : /*
698 : : * Display a single row of record counts and sizes for an rmgr or record.
699 : : */
700 : : static void
4246 andres@anarazel.de 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 : :
4027 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 : :
4246 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
1488 jdavis@postgresql.or 742 : 6 : XLogDumpDisplayStats(XLogDumpConfig *config, XLogStats *stats)
743 : : {
744 : : int ri,
745 : : rj;
4246 andres@anarazel.de 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 : : */
180 alvherre@kurilemu.de 756 [ - + ]:GNC 6 : if (!XLogRecPtrIsValid(stats->endptr))
1615 michael@paquier.xyz 757 :UBC 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 : :
1489 jdavis@postgresql.or 764 [ + + ]:CBC 1542 : for (ri = 0; ri <= RM_MAX_ID; ri++)
765 : : {
1488 766 [ + + + + ]: 1536 : if (!RmgrIdIsValid(ri))
767 : 630 : continue;
768 : :
4246 andres@anarazel.de 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 : : }
4000 bruce@momjian.us 773 : 6 : total_len = total_rec_len + total_fpi_len;
774 : :
302 alvherre@kurilemu.de 775 :GNC 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 : :
4246 andres@anarazel.de 783 :CBC 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 : :
1490 jdavis@postgresql.or 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 : :
1489 796 [ + + + + ]: 1536 : if (!RmgrIdIsValid(ri))
1490 797 : 630 : continue;
798 : :
799 : 906 : desc = GetRmgrDesc(ri);
800 : :
4246 andres@anarazel.de 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 : :
1489 jdavis@postgresql.or 808 [ + + + - ]: 453 : if (RmgrIdIsCustom(ri) && count == 0)
1490 809 : 384 : continue;
810 : :
4246 andres@anarazel.de 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)
4246 andres@anarazel.de 833 :UBC 0 : id = psprintf("UNKNOWN (%x)", rj << 4);
834 : :
4246 andres@anarazel.de 835 :CBC 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 : :
4027 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 : :
4246 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
41 tgl@sss.pgh.pa.us 875 :GNC 127 : 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 [ + + ]: 127 : if (xlogreader_state_cleanup != NULL &&
882 [ + + ]: 7 : xlogreader_state_cleanup->seg.ws_file >= 0)
883 : 3 : WALDumpCloseSegment(xlogreader_state_cleanup);
884 : :
885 [ - + ]: 127 : if (TmpWalSegDir != NULL)
886 : : {
41 tgl@sss.pgh.pa.us 887 :UNC 0 : rmtree(TmpWalSegDir, true);
888 : 0 : TmpWalSegDir = NULL;
889 : : }
41 tgl@sss.pgh.pa.us 890 :GNC 127 : }
891 : :
892 : : static void
4820 alvherre@alvh.no-ip. 893 :CBC 1 : usage(void)
894 : : {
3280 peter_e@gmx.net 895 : 1 : printf(_("%s decodes and displays PostgreSQL write-ahead logs for debugging.\n\n"),
896 : : progname);
3489 897 : 1 : printf(_("Usage:\n"));
3309 898 : 1 : printf(_(" %s [OPTION]... [STARTSEG [ENDSEG]]\n"), progname);
3489 899 : 1 : printf(_("\nOptions:\n"));
900 : 1 : printf(_(" -b, --bkp-details output detailed information about backup blocks\n"));
1502 tmunro@postgresql.or 901 : 1 : printf(_(" -B, --block=N with --relation, only show records that modify block N\n"));
3280 peter_e@gmx.net 902 : 1 : printf(_(" -e, --end=RECPTR stop reading at WAL location RECPTR\n"));
3489 903 : 1 : printf(_(" -f, --follow keep retrying after reaching end of WAL\n"));
1502 tmunro@postgresql.or 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"));
3489 peter_e@gmx.net 906 : 1 : printf(_(" -n, --limit=N number of records to display\n"));
46 andrew@dunslane.net 907 :GNC 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"));
2224 rhaas@postgresql.org 910 :CBC 1 : printf(_(" -q, --quiet do not print any output, except for errors\n"));
3175 peter_e@gmx.net 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"));
1502 tmunro@postgresql.or 913 : 1 : printf(_(" -R, --relation=T/D/R only show records that modify blocks in relation T/D/R\n"));
3280 peter_e@gmx.net 914 : 1 : printf(_(" -s, --start=RECPTR start reading at WAL location RECPTR\n"));
1329 tgl@sss.pgh.pa.us 915 : 1 : printf(_(" -t, --timeline=TLI timeline from which to read WAL records\n"
916 : : " (default: 1 or the value used in STARTSEG)\n"));
3489 peter_e@gmx.net 917 : 1 : printf(_(" -V, --version output version information, then exit\n"));
1503 tmunro@postgresql.or 918 : 1 : printf(_(" -w, --fullpage only show records with a full page write\n"));
1502 919 : 1 : printf(_(" -x, --xid=XID only show records with transaction ID XID\n"));
3275 tgl@sss.pgh.pa.us 920 : 1 : printf(_(" -z, --stats[=record] show statistics instead of records\n"
921 : : " (optionally, show per-record statistics)\n"));
1114 peter@eisentraut.org 922 : 1 : printf(_(" --save-fullpage=DIR save full page images to DIR\n"));
3489 peter_e@gmx.net 923 : 1 : printf(_(" -?, --help show this help, then exit\n"));
2258 peter@eisentraut.org 924 : 1 : printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
925 : 1 : printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
4820 alvherre@alvh.no-ip. 926 : 1 : }
927 : :
928 : : int
929 : 265 : 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;
2415 939 : 265 : char *waldir = NULL;
940 : : char *errormsg;
46 andrew@dunslane.net 941 :GNC 265 : 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;
4820 alvherre@alvh.no-ip. 966 :CBC 265 : int optindex = 0;
967 : :
968 : : #ifndef WIN32
1615 michael@paquier.xyz 969 : 265 : pqsignal(SIGINT, sigint_handler);
970 : : #endif
971 : :
2591 peter@eisentraut.org 972 : 265 : pg_logging_init(argv[0]);
3372 rhaas@postgresql.org 973 : 265 : set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_waldump"));
4820 alvherre@alvh.no-ip. 974 : 265 : progname = get_progname(argv[0]);
975 : :
2526 peter@eisentraut.org 976 [ + + ]: 265 : if (argc > 1)
977 : : {
978 [ + + - + ]: 264 : if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
979 : : {
980 : 1 : usage();
981 : 1 : exit(0);
982 : : }
983 [ + + + + ]: 263 : if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
984 : : {
985 : 119 : puts("pg_waldump (PostgreSQL) " PG_VERSION);
986 : 119 : exit(0);
987 : : }
988 : : }
989 : :
1821 tmunro@postgresql.or 990 : 145 : memset(&private, 0, sizeof(XLogDumpPrivate));
4820 alvherre@alvh.no-ip. 991 : 145 : memset(&config, 0, sizeof(XLogDumpConfig));
1488 jdavis@postgresql.or 992 : 145 : memset(&stats, 0, sizeof(XLogStats));
993 : :
1821 tmunro@postgresql.or 994 : 145 : private.timeline = 1;
46 andrew@dunslane.net 995 :GNC 145 : private.segsize = 0;
1821 tmunro@postgresql.or 996 :CBC 145 : private.startptr = InvalidXLogRecPtr;
997 : 145 : private.endptr = InvalidXLogRecPtr;
998 : 145 : private.endptr_reached = false;
46 andrew@dunslane.net 999 :GNC 145 : private.decoding_started = false;
1000 : 145 : private.archive_name = NULL;
1001 : 145 : private.start_segno = 0;
1002 : 145 : private.end_segno = UINT64_MAX;
1003 : :
2224 rhaas@postgresql.org 1004 :CBC 145 : config.quiet = false;
4820 alvherre@alvh.no-ip. 1005 : 145 : config.bkp_details = false;
1006 : 145 : config.stop_after_records = -1;
1007 : 145 : config.already_displayed_records = 0;
4423 heikki.linnakangas@i 1008 : 145 : config.follow = false;
1009 : : /* filter_by_rmgr array was zeroed by memset above */
1769 1010 : 145 : config.filter_by_rmgr_enabled = false;
4820 alvherre@alvh.no-ip. 1011 : 145 : config.filter_by_xid = InvalidTransactionId;
1012 : 145 : config.filter_by_xid_enabled = false;
1503 tmunro@postgresql.or 1013 : 145 : config.filter_by_extended = false;
1014 : 145 : config.filter_by_relation_enabled = false;
1015 : 145 : config.filter_by_relation_block_enabled = false;
1016 : 145 : config.filter_by_relation_forknum = InvalidForkNumber;
1017 : 145 : config.filter_by_fpw = false;
1225 michael@paquier.xyz 1018 : 145 : config.save_fullpage_path = NULL;
4246 andres@anarazel.de 1019 : 145 : config.stats = false;
1020 : 145 : config.stats_per_record = false;
1021 : :
1615 michael@paquier.xyz 1022 : 145 : stats.startptr = InvalidXLogRecPtr;
1023 : 145 : stats.endptr = InvalidXLogRecPtr;
1024 : :
4820 alvherre@alvh.no-ip. 1025 [ + + ]: 145 : if (argc <= 1)
1026 : : {
2591 peter@eisentraut.org 1027 : 1 : pg_log_error("no arguments specified");
4820 alvherre@alvh.no-ip. 1028 : 1 : goto bad_argument;
1029 : : }
1030 : :
1502 tmunro@postgresql.or 1031 : 706 : while ((option = getopt_long(argc, argv, "bB:e:fF:n:p:qr:R:s:t:wx:z",
4820 alvherre@alvh.no-ip. 1032 [ + + ]: 706 : long_options, &optindex)) != -1)
1033 : : {
1034 [ - + + - : 571 : switch (option)
+ + + + +
+ + + + -
+ + + ]
1035 : : {
4820 alvherre@alvh.no-ip. 1036 :UBC 0 : case 'b':
1037 : 0 : config.bkp_details = true;
1038 : 0 : break;
1502 tmunro@postgresql.or 1039 :CBC 4 : case 'B':
1040 [ + + ]: 4 : if (sscanf(optarg, "%u", &config.filter_by_relation_block) != 1 ||
1041 [ - + ]: 3 : !BlockNumberIsValid(config.filter_by_relation_block))
1042 : : {
1446 peter@eisentraut.org 1043 : 1 : pg_log_error("invalid block number: \"%s\"", optarg);
1502 tmunro@postgresql.or 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;
4820 alvherre@alvh.no-ip. 1049 : 119 : case 'e':
302 alvherre@kurilemu.de 1050 [ + + ]:GNC 119 : if (sscanf(optarg, "%X/%08X", &xlogid, &xrecoff) != 2)
1051 : : {
1446 peter@eisentraut.org 1052 :CBC 1 : pg_log_error("invalid WAL location: \"%s\"",
1053 : : optarg);
4820 alvherre@alvh.no-ip. 1054 : 1 : goto bad_argument;
1055 : : }
1821 tmunro@postgresql.or 1056 : 118 : private.endptr = (uint64) xlogid << 32 | xrecoff;
4820 alvherre@alvh.no-ip. 1057 : 118 : break;
4423 heikki.linnakangas@i 1058 :UBC 0 : case 'f':
1059 : 0 : config.follow = true;
1060 : 0 : break;
1503 tmunro@postgresql.or 1061 :CBC 4 : case 'F':
1502 1062 : 4 : config.filter_by_relation_forknum = forkname_to_number(optarg);
1063 [ + + ]: 4 : if (config.filter_by_relation_forknum == InvalidForkNumber)
1064 : : {
1446 peter@eisentraut.org 1065 : 1 : pg_log_error("invalid fork name: \"%s\"", optarg);
1503 tmunro@postgresql.or 1066 : 1 : goto bad_argument;
1067 : : }
1068 : 3 : config.filter_by_extended = true;
1069 : 3 : break;
4820 alvherre@alvh.no-ip. 1070 : 4 : case 'n':
1071 [ + + ]: 4 : if (sscanf(optarg, "%d", &config.stop_after_records) != 1)
1072 : : {
1446 peter@eisentraut.org 1073 : 1 : pg_log_error("invalid value \"%s\" for option %s", optarg, "-n/--limit");
4820 alvherre@alvh.no-ip. 1074 : 1 : goto bad_argument;
1075 : : }
1076 : 3 : break;
1077 : 128 : case 'p':
2415 1078 : 128 : waldir = pg_strdup(optarg);
4820 1079 : 128 : break;
2224 rhaas@postgresql.org 1080 : 84 : case 'q':
1081 : 84 : config.quiet = true;
1082 : 84 : break;
4820 alvherre@alvh.no-ip. 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 : : */
1490 jdavis@postgresql.or 1100 [ - + ]: 4 : if (sscanf(optarg, "custom%03d", &rmid) == 1)
1101 : : {
1489 jdavis@postgresql.or 1102 [ # # ]:UBC 0 : if (!RmgrIdIsCustom(rmid))
1103 : : {
1490 1104 : 0 : pg_log_error("custom resource manager \"%s\" does not exist",
1105 : : optarg);
1490 jdavis@postgresql.or 1106 :CBC 1 : goto bad_argument;
1107 : : }
1490 jdavis@postgresql.or 1108 :UBC 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 */
1490 jdavis@postgresql.or 1114 [ + + ]:CBC 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 : : }
4820 alvherre@alvh.no-ip. 1131 : 3 : break;
1502 tmunro@postgresql.or 1132 : 8 : case 'R':
1315 rhaas@postgresql.org 1133 [ + + ]: 8 : if (sscanf(optarg, "%u/%u/%u",
1134 : : &config.filter_by_relation.spcOid,
1135 : : &config.filter_by_relation.dbOid,
1399 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 : : {
1446 peter@eisentraut.org 1140 : 1 : pg_log_error("invalid relation specification: \"%s\"", optarg);
1141 : 1 : pg_log_error_detail("Expecting \"tablespace OID/database OID/relation filenode\".");
1502 tmunro@postgresql.or 1142 : 1 : goto bad_argument;
1143 : : }
1144 : 7 : config.filter_by_relation_enabled = true;
1145 : 7 : config.filter_by_extended = true;
1146 : 7 : break;
4820 alvherre@alvh.no-ip. 1147 : 125 : case 's':
302 alvherre@kurilemu.de 1148 [ + + ]:GNC 125 : if (sscanf(optarg, "%X/%08X", &xlogid, &xrecoff) != 2)
1149 : : {
1446 peter@eisentraut.org 1150 :CBC 1 : pg_log_error("invalid WAL location: \"%s\"",
1151 : : optarg);
4820 alvherre@alvh.no-ip. 1152 : 1 : goto bad_argument;
1153 : : }
1154 : : else
1821 tmunro@postgresql.or 1155 : 124 : private.startptr = (uint64) xlogid << 32 | xrecoff;
4820 alvherre@alvh.no-ip. 1156 : 124 : break;
1157 : 79 : 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 : :
1141 peter@eisentraut.org 1168 : 79 : errno = 0;
1169 : 79 : val = strtoul(optarg, &endptr, 0);
1170 : :
1171 [ - + - - ]: 79 : while (*endptr != '\0' && isspace((unsigned char) *endptr))
1141 peter@eisentraut.org 1172 :UBC 0 : endptr++;
1173 : :
1141 peter@eisentraut.org 1174 [ - + ]:CBC 79 : if (*endptr != '\0')
1175 : : {
1141 peter@eisentraut.org 1176 :UBC 0 : pg_log_error("invalid value \"%s\" for option %s",
1177 : : optarg, "-t/--timeline");
1178 : 0 : goto bad_argument;
1179 : : }
1180 : :
1141 peter@eisentraut.org 1181 [ + - + - :CBC 79 : if (errno == ERANGE || val < 1 || val > UINT_MAX)
- + ]
1182 : : {
1141 peter@eisentraut.org 1183 :UBC 0 : pg_log_error("%s must be in range %u..%u",
1184 : : "-t/--timeline", 1, UINT_MAX);
1185 : 0 : goto bad_argument;
1186 : : }
1187 : :
1141 peter@eisentraut.org 1188 :CBC 79 : private.timeline = val;
1189 : :
1190 : 79 : break;
1191 : : }
1503 tmunro@postgresql.or 1192 : 3 : case 'w':
1193 : 3 : config.filter_by_fpw = true;
1194 : 3 : break;
4820 alvherre@alvh.no-ip. 1195 :UBC 0 : case 'x':
1196 [ # # ]: 0 : if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
1197 : : {
1446 peter@eisentraut.org 1198 : 0 : pg_log_error("invalid transaction ID specification: \"%s\"",
1199 : : optarg);
4820 alvherre@alvh.no-ip. 1200 : 0 : goto bad_argument;
1201 : : }
1202 : 0 : config.filter_by_xid_enabled = true;
1203 : 0 : break;
4246 andres@anarazel.de 1204 :CBC 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;
4246 andres@anarazel.de 1211 [ # # ]:UBC 0 : else if (strcmp(optarg, "rmgr") != 0)
1212 : : {
1446 peter@eisentraut.org 1213 : 0 : pg_log_error("unrecognized value for option %s: %s",
1214 : : "--stats", optarg);
4246 andres@anarazel.de 1215 : 0 : goto bad_argument;
1216 : : }
1217 : : }
4246 andres@anarazel.de 1218 :CBC 6 : break;
1225 michael@paquier.xyz 1219 : 1 : case 1:
1220 : 1 : config.save_fullpage_path = pg_strdup(optarg);
1221 : 1 : break;
4820 alvherre@alvh.no-ip. 1222 : 1 : default:
1223 : 1 : goto bad_argument;
1224 : : }
1225 : : }
1226 : :
1503 tmunro@postgresql.or 1227 [ + + ]: 135 : if (config.filter_by_relation_block_enabled &&
1228 [ - + ]: 3 : !config.filter_by_relation_enabled)
1229 : : {
1446 peter@eisentraut.org 1230 :UBC 0 : pg_log_error("option %s requires option %s to be specified",
1231 : : "-B/--block", "-R/--relation");
1503 tmunro@postgresql.or 1232 : 0 : goto bad_argument;
1233 : : }
1234 : :
4820 alvherre@alvh.no-ip. 1235 [ + + ]:CBC 135 : if ((optind + 2) < argc)
1236 : : {
2591 peter@eisentraut.org 1237 : 1 : pg_log_error("too many command-line arguments (first is \"%s\")",
1238 : : argv[optind + 2]);
4820 alvherre@alvh.no-ip. 1239 : 1 : goto bad_argument;
1240 : : }
1241 : :
2415 1242 [ + + ]: 134 : if (waldir != NULL)
1243 : : {
1244 : : /* Check whether the path looks like a tar archive by its extension */
46 andrew@dunslane.net 1245 [ + + ]:GNC 128 : if (parse_tar_compress_algorithm(waldir, &compression))
1246 : : {
1247 : 59 : split_path(waldir, &private.archive_dir, &private.archive_name);
1248 : : }
1249 : : /* Otherwise it must be a directory */
1250 [ + + ]: 69 : else if (!verify_directory(waldir))
1251 : : {
2324 michael@paquier.xyz 1252 :CBC 1 : pg_log_error("could not open directory \"%s\": %m", waldir);
4820 alvherre@alvh.no-ip. 1253 : 1 : goto bad_argument;
1254 : : }
1255 : : }
1256 : :
1225 michael@paquier.xyz 1257 [ + + ]: 133 : 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 */
4820 alvherre@alvh.no-ip. 1261 [ + + ]: 133 : 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 : : */
46 andrew@dunslane.net 1272 [ - + ]:GNC 7 : if (private.archive_name)
1273 : : {
46 andrew@dunslane.net 1274 :UNC 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 : :
4820 alvherre@alvh.no-ip. 1279 :CBC 7 : split_path(argv[optind], &directory, &fname);
1280 : :
2415 1281 [ + + + + ]: 7 : if (waldir == NULL && directory != NULL)
1282 : : {
1283 : 5 : waldir = directory;
1284 : :
1285 [ - + ]: 5 : if (!verify_directory(waldir))
1488 tgl@sss.pgh.pa.us 1286 :UBC 0 : pg_fatal("could not open directory \"%s\": %m", waldir);
1287 : : }
1288 : :
46 andrew@dunslane.net 1289 [ + - - + ]:GNC 7 : if (fname != NULL && parse_tar_compress_algorithm(fname, &compression))
1290 : : {
46 andrew@dunslane.net 1291 :UNC 0 : private.archive_dir = waldir;
1292 : 0 : private.archive_name = fname;
1293 : : }
1294 : : else
1295 : : {
46 andrew@dunslane.net 1296 :GNC 7 : waldir = identify_target_directory(waldir, fname, &private.segsize);
2415 alvherre@alvh.no-ip. 1297 :CBC 6 : fd = open_file_in_directory(waldir, fname);
4820 1298 [ - + ]: 6 : if (fd < 0)
1488 tgl@sss.pgh.pa.us 1299 :LBC (1) : pg_fatal("could not open file \"%s\"", fname);
4820 alvherre@alvh.no-ip. 1300 :CBC 6 : close(fd);
1301 : :
1302 : : /* parse position from file */
46 andrew@dunslane.net 1303 :GNC 6 : XLogFromFileName(fname, &private.timeline, &segno, private.segsize);
1304 : :
1305 [ + - ]: 6 : if (!XLogRecPtrIsValid(private.startptr))
1306 : 6 : XLogSegNoOffsetToRecPtr(segno, 0, private.segsize, private.startptr);
46 andrew@dunslane.net 1307 [ # # ]:UNC 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 */
46 andrew@dunslane.net 1316 [ + + + - ]:GNC 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)
46 andrew@dunslane.net 1336 :UNC 0 : pg_fatal("ENDSEG %s is before STARTSEG %s",
1337 : : argv[optind + 1], argv[optind]);
1338 : :
46 andrew@dunslane.net 1339 [ + - ]:GNC 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 : : {
46 andrew@dunslane.net 1350 :UNC 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 : : }
46 andrew@dunslane.net 1357 [ + + ]:GNC 126 : else if (!private.archive_name)
1358 : 67 : waldir = identify_target_directory(waldir, NULL, &private.segsize);
1359 : :
1360 : : /* we don't know what to print */
180 alvherre@kurilemu.de 1361 [ + + ]: 130 : if (!XLogRecPtrIsValid(private.startptr))
1362 : : {
2591 peter@eisentraut.org 1363 :CBC 3 : pg_log_error("no start WAL location given");
4820 alvherre@alvh.no-ip. 1364 : 3 : goto bad_argument;
1365 : : }
1366 : :
1367 : : /* --follow is not supported with tar archives */
46 andrew@dunslane.net 1368 [ - + - - ]:GNC 127 : if (config.follow && private.archive_name)
1369 : : {
46 andrew@dunslane.net 1370 :UNC 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 */
46 andrew@dunslane.net 1377 [ + + ]:GNC 127 : 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 [ - + ]: 57 : if (private.archive_dir == NULL)
46 andrew@dunslane.net 1384 :UNC 0 : private.archive_dir = pg_strdup(".");
1385 : :
1386 : : /* Set up for reading tar file */
46 andrew@dunslane.net 1387 :GNC 57 : init_archive_reader(&private, compression);
1388 : :
1389 : : /* Routine to decode WAL files in tar archive */
1390 : : xlogreader_state =
1391 : 57 : XLogReaderAllocate(private.segsize, private.archive_dir,
1392 : 57 : XL_ROUTINE(.page_read = TarWALDumpReadPage,
1393 : : .segment_open = TarWALDumpOpenSegment,
1394 : : .segment_close = TarWALDumpCloseSegment),
1395 : : &private);
1396 : : }
1397 : : else
1398 : : {
1399 : : xlogreader_state =
1400 : 70 : XLogReaderAllocate(private.segsize, waldir,
1401 : 70 : XL_ROUTINE(.page_read = WALDumpReadPage,
1402 : : .segment_open = WALDumpOpenSegment,
1403 : : .segment_close = WALDumpCloseSegment),
1404 : : &private);
1405 : : }
1406 : :
4820 alvherre@alvh.no-ip. 1407 [ - + ]:CBC 127 : if (!xlogreader_state)
1488 tgl@sss.pgh.pa.us 1408 :UBC 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 : : */
41 tgl@sss.pgh.pa.us 1415 :GNC 127 : atexit(cleanup_tmpwal_dir_atexit);
1416 : 127 : xlogreader_state_cleanup = xlogreader_state;
1417 : :
1418 : : /* first find a valid recptr to start from */
42 fujii@postgresql.org 1419 : 127 : first_record = XLogFindNextRecord(xlogreader_state, private.startptr, &errormsg);
1420 : :
180 alvherre@kurilemu.de 1421 [ + + ]: 127 : if (!XLogRecPtrIsValid(first_record))
1422 : : {
42 fujii@postgresql.org 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
42 fujii@postgresql.org 1427 :UNC 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 : : */
1821 tmunro@postgresql.or 1436 [ + + ]:CBC 126 : if (first_record != private.startptr &&
46 andrew@dunslane.net 1437 [ + + ]:GNC 10 : XLogSegmentOffset(private.startptr, private.segsize) != 0)
302 alvherre@kurilemu.de 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 : :
1615 michael@paquier.xyz 1445 [ + + + - ]:CBC 126 : if (config.stats == true && !config.quiet)
1446 : 6 : stats.startptr = first_record;
1447 : :
1448 : : /* Flag indicating that the decoding loop has been entered */
46 andrew@dunslane.net 1449 :GNC 126 : private.decoding_started = true;
1450 : :
1451 : : for (;;)
1452 : : {
1615 michael@paquier.xyz 1453 [ - + ]:CBC 1519833 : if (time_to_stop)
1454 : : {
1455 : : /* We've been Ctrl-C'ed, so leave */
1615 michael@paquier.xyz 1456 :UBC 0 : break;
1457 : : }
1458 : :
1459 : : /* try to read the next record */
1821 tmunro@postgresql.or 1460 :CBC 1519833 : record = XLogReadRecord(xlogreader_state, &errormsg);
4423 heikki.linnakangas@i 1461 [ + + ]: 1519833 : if (!record)
1462 : : {
1821 tmunro@postgresql.or 1463 [ - + - - ]: 123 : if (!config.follow || private.endptr_reached)
1464 : : break;
1465 : : else
1466 : : {
4382 bruce@momjian.us 1467 :UBC 0 : pg_usleep(1000000L); /* 1 second */
4423 heikki.linnakangas@i 1468 : 0 : continue;
1469 : : }
1470 : : }
1471 : :
1472 : : /* apply all specified filters */
1769 heikki.linnakangas@i 1473 [ + + ]:CBC 1519710 : if (config.filter_by_rmgr_enabled &&
1474 [ + + ]: 108411 : !config.filter_by_rmgr[record->xl_rmid])
4246 andres@anarazel.de 1475 : 103872 : continue;
1476 : :
1477 [ - + ]: 1415838 : if (config.filter_by_xid_enabled &&
4246 andres@anarazel.de 1478 [ # # ]:UBC 0 : config.filter_by_xid != record->xl_xid)
1479 : 0 : continue;
1480 : :
1481 : : /* check for extended filtering */
1503 tmunro@postgresql.or 1482 [ + + ]:CBC 1415838 : if (config.filter_by_extended &&
1483 [ + + ]: 705848 : !XLogRecordMatchesRelationBlock(xlogreader_state,
1484 [ + + ]: 352924 : config.filter_by_relation_enabled ?
1485 : : config.filter_by_relation :
1486 : : emptyRelFileLocator,
1487 [ + + ]: 352924 : config.filter_by_relation_block_enabled ?
1488 : : config.filter_by_relation_block :
1489 : : InvalidBlockNumber,
1490 : : config.filter_by_relation_forknum))
1491 : 352702 : continue;
1492 : :
1493 [ + + + + ]: 1063136 : if (config.filter_by_fpw && !XLogRecordHasFPW(xlogreader_state))
1494 : 105036 : continue;
1495 : :
1496 : : /* perform any per-record work */
2224 rhaas@postgresql.org 1497 [ + + ]: 958100 : if (!config.quiet)
1498 : : {
1499 [ + + ]: 785594 : if (config.stats == true)
1500 : : {
1488 jdavis@postgresql.or 1501 : 216822 : XLogRecStoreStats(&stats, xlogreader_state);
1615 michael@paquier.xyz 1502 : 216822 : stats.endptr = xlogreader_state->EndRecPtr;
1503 : : }
1504 : : else
2224 rhaas@postgresql.org 1505 : 568772 : XLogDumpDisplayRecord(&config, xlogreader_state);
1506 : : }
1507 : :
1508 : : /* save full pages if requested */
1225 michael@paquier.xyz 1509 [ + + ]: 958100 : if (config.save_fullpage_path != NULL)
1510 : 201 : XLogRecordSaveFPWs(xlogreader_state, config.save_fullpage_path);
1511 : :
1512 : : /* check whether we printed enough */
4246 andres@anarazel.de 1513 : 958100 : config.already_displayed_records++;
4820 alvherre@alvh.no-ip. 1514 [ + + ]: 958100 : if (config.stop_after_records > 0 &&
1515 [ + + ]: 18 : config.already_displayed_records >= config.stop_after_records)
1516 : 3 : break;
1517 : : }
1518 : :
2223 rhaas@postgresql.org 1519 [ + + + - ]: 126 : if (config.stats == true && !config.quiet)
4246 andres@anarazel.de 1520 : 6 : XLogDumpDisplayStats(&config, &stats);
1521 : :
1615 michael@paquier.xyz 1522 [ - + ]: 126 : if (time_to_stop)
1615 michael@paquier.xyz 1523 :UBC 0 : exit(0);
1524 : :
4820 alvherre@alvh.no-ip. 1525 [ + + ]:CBC 126 : if (errormsg)
302 alvherre@kurilemu.de 1526 :GNC 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 : : */
41 tgl@sss.pgh.pa.us 1534 : 120 : xlogreader_state_cleanup = NULL;
1535 : :
4820 alvherre@alvh.no-ip. 1536 :CBC 120 : XLogReaderFree(xlogreader_state);
1537 : :
46 andrew@dunslane.net 1538 [ + + ]:GNC 120 : if (private.archive_name)
1539 : 53 : free_archive_reader(&private);
1540 : :
4820 alvherre@alvh.no-ip. 1541 :CBC 120 : return EXIT_SUCCESS;
1542 : :
1543 : 14 : bad_argument:
1488 tgl@sss.pgh.pa.us 1544 : 14 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4820 alvherre@alvh.no-ip. 1545 : 14 : return EXIT_FAILURE;
1546 : : }
|