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/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
1443 tgl@sss.pgh.pa.us 86 :UBC 0 : sigint_handler(SIGNAL_ARGS)
87 : : {
1729 michael@paquier.xyz 88 : 0 : time_to_stop = true;
89 : 0 : }
90 : : #endif
91 : :
92 : : static void
4934 alvherre@alvh.no-ip. 93 :CBC 1 : print_rmgr_list(void)
94 : : {
95 : : int i;
96 : :
1604 jdavis@postgresql.or 97 [ + + ]: 24 : for (i = 0; i <= RM_MAX_BUILTIN_ID; i++)
98 : : {
99 : 23 : printf("%s\n", GetRmgrDesc(i)->rm_name);
100 : : }
4934 alvherre@alvh.no-ip. 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 : : {
4838 bruce@momjian.us 110 : 72 : DIR *dir = opendir(directory);
111 : :
4934 alvherre@alvh.no-ip. 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
1339 michael@paquier.xyz 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)
1339 michael@paquier.xyz 132 :UBC 0 : pg_fatal("could not create directory \"%s\": %m", path);
1339 michael@paquier.xyz 133 :CBC 1 : break;
1339 michael@paquier.xyz 134 :UBC 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 : : }
1339 michael@paquier.xyz 147 :CBC 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
4934 alvherre@alvh.no-ip. 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 : : {
2458 166 : 60 : *dir = pnstrdup(path, sep - path);
4934 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
3264 andres@anarazel.de 183 : 238 : open_file_in_directory(const char *directory, const char *fname)
184 : : {
4934 alvherre@alvh.no-ip. 185 : 238 : int fd = -1;
186 : : char fpath[MAXPGPATH];
187 : :
3264 andres@anarazel.de 188 [ - + ]: 238 : Assert(directory != NULL);
189 : :
190 : 238 : snprintf(fpath, MAXPGPATH, "%s/%s", directory, fname);
191 : 238 : fd = open(fpath, O_RDONLY | PG_BINARY, 0);
192 : :
193 [ + + - + ]: 238 : if (fd < 0 && errno != ENOENT)
1602 tgl@sss.pgh.pa.us 194 :UBC 0 : pg_fatal("could not open file \"%s\": %m", fname);
3264 andres@anarazel.de 195 :CBC 238 : 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
212 rhaas@postgresql.org 205 : 89 : search_directory(const char *directory, const char *fname, int *WalSegSz)
206 : : {
3264 andres@anarazel.de 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 [ + + ]: 652 : while ((xlde = readdir(xldir)) != NULL)
224 : : {
225 [ + + ]: 636 : if (IsXLogFileName(xlde->d_name))
226 : : {
227 : 65 : fd = open_file_in_directory(directory, xlde->d_name);
1618 228 : 65 : fname = pg_strdup(xlde->d_name);
3264 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 : :
2917 tgl@sss.pgh.pa.us 242 : 71 : r = read(fd, buf.data, XLOG_BLCKSZ);
2962 michael@paquier.xyz 243 [ + - ]: 71 : if (r == XLOG_BLCKSZ)
244 : : {
2917 tgl@sss.pgh.pa.us 245 : 71 : XLogLongPageHeader longhdr = (XLogLongPageHeader) buf.data;
246 : :
212 rhaas@postgresql.org 247 [ + - + + : 71 : if (!IsValidWalSegSize(longhdr->xlp_seg_size))
+ - - + ]
248 : : {
129 peter@eisentraut.org 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);
1095 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 : :
212 rhaas@postgresql.org 257 : 70 : *WalSegSz = longhdr->xlp_seg_size;
258 : : }
1644 andres@anarazel.de 259 [ # # ]:UBC 0 : else if (r < 0)
1602 tgl@sss.pgh.pa.us 260 : 0 : pg_fatal("could not read file \"%s\": %m",
261 : : fname);
262 : : else
43 peter@eisentraut.org 263 :UNC 0 : pg_fatal("could not read file \"%s\": read %zd of %zu",
264 : : fname, r, (size_t) XLOG_BLCKSZ);
3264 andres@anarazel.de 265 :CBC 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 *
212 rhaas@postgresql.org 288 : 72 : identify_target_directory(char *directory, char *fname, int *WalSegSz)
289 : : {
290 : : char fpath[MAXPGPATH];
291 : :
3264 andres@anarazel.de 292 [ + + ]: 72 : if (directory != NULL)
293 : : {
212 rhaas@postgresql.org 294 [ + + ]: 71 : if (search_directory(directory, fname, WalSegSz))
2529 alvherre@alvh.no-ip. 295 : 54 : return pg_strdup(directory);
296 : :
297 : : /* directory / XLOGDIR */
3264 andres@anarazel.de 298 : 16 : snprintf(fpath, MAXPGPATH, "%s/%s", directory, XLOGDIR);
212 rhaas@postgresql.org 299 [ + - ]: 16 : if (search_directory(fpath, fname, WalSegSz))
2529 alvherre@alvh.no-ip. 300 : 16 : return pg_strdup(fpath);
301 : : }
302 : : else
303 : : {
304 : : const char *datadir;
305 : :
306 : : /* current directory */
212 rhaas@postgresql.org 307 [ - + ]: 1 : if (search_directory(".", fname, WalSegSz))
2529 alvherre@alvh.no-ip. 308 :UBC 0 : return pg_strdup(".");
309 : : /* XLOGDIR */
212 rhaas@postgresql.org 310 [ - + ]:CBC 1 : if (search_directory(XLOGDIR, fname, WalSegSz))
2529 alvherre@alvh.no-ip. 311 :UBC 0 : return pg_strdup(XLOGDIR);
312 : :
4934 alvherre@alvh.no-ip. 313 :CBC 1 : datadir = getenv("PGDATA");
314 : : /* $PGDATA / XLOGDIR */
315 [ - + ]: 1 : if (datadir != NULL)
316 : : {
3264 andres@anarazel.de 317 :UBC 0 : snprintf(fpath, MAXPGPATH, "%s/%s", datadir, XLOGDIR);
212 rhaas@postgresql.org 318 [ # # ]: 0 : if (search_directory(fpath, fname, WalSegSz))
2529 alvherre@alvh.no-ip. 319 : 0 : return pg_strdup(fpath);
320 : : }
321 : : }
322 : :
323 : : /* could not locate WAL file */
3264 andres@anarazel.de 324 [ + - ]:CBC 1 : if (fname)
1602 tgl@sss.pgh.pa.us 325 : 1 : pg_fatal("could not locate WAL file \"%s\"", fname);
326 : : else
1602 tgl@sss.pgh.pa.us 327 :UBC 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
160 andrew@dunslane.net 337 :CBC 51029 : required_read_len(XLogDumpPrivate *private, XLogRecPtr targetPagePtr,
338 : : int reqLen)
339 : : {
340 : 51029 : int count = XLOG_BLCKSZ;
341 : :
342 [ + + ]: 51029 : if (XLogRecPtrIsValid(private->endptr))
343 : : {
344 [ + + ]: 43903 : if (targetPagePtr + XLOG_BLCKSZ <= private->endptr)
345 : 43671 : count = XLOG_BLCKSZ;
346 [ + + ]: 232 : else if (targetPagePtr + reqLen <= private->endptr)
347 : 122 : count = private->endptr - targetPagePtr;
348 : : else
349 : : {
350 : 110 : private->endptr_reached = true;
351 : 110 : return -1;
352 : : }
353 : : }
354 : :
355 : 50919 : return count;
356 : : }
357 : :
358 : : /* pg_waldump's XLogReaderRoutine->segment_open callback */
359 : : static void
2297 alvherre@alvh.no-ip. 360 : 87 : WALDumpOpenSegment(XLogReaderState *state, XLogSegNo nextSegNo,
361 : : TimeLineID *tli_p)
362 : : {
2467 363 : 87 : TimeLineID tli = *tli_p;
364 : : char fname[MAXPGPATH];
365 : : int tries;
366 : :
2297 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 : : */
2467 375 [ + - ]: 87 : for (tries = 0; tries < 10; tries++)
376 : : {
2297 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;
2467 alvherre@alvh.no-ip. 380 [ # # ]:UBC 0 : if (errno == ENOENT)
4934 381 : 0 : {
2962 michael@paquier.xyz 382 : 0 : int save_errno = errno;
383 : :
384 : : /* File not there yet, try again */
2467 alvherre@alvh.no-ip. 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 : :
1602 tgl@sss.pgh.pa.us 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
2302 alvherre@alvh.no-ip. 402 :CBC 87 : WALDumpCloseSegment(XLogReaderState *state)
403 : : {
404 : 87 : close(state->seg.ws_file);
405 : : /* need to check errno? */
406 : 87 : state->seg.ws_file = -1;
407 : 87 : }
408 : :
409 : : /* pg_waldump's XLogReaderRoutine->page_read callback */
410 : : static int
1935 tmunro@postgresql.or 411 : 22601 : WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
412 : : XLogRecPtr targetPtr, char *readBuff)
413 : : {
414 : 22601 : XLogDumpPrivate *private = state->private_data;
160 andrew@dunslane.net 415 : 22601 : 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 [ + + ]: 22601 : if (count < 0)
420 : 64 : return -1;
421 : :
1935 tmunro@postgresql.or 422 [ - + ]: 22537 : if (!WALRead(state, readBuff, targetPagePtr, count, private->timeline,
423 : : &errinfo))
424 : : {
2467 alvherre@alvh.no-ip. 425 :UBC 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;
43 peter@eisentraut.org 434 :UNC 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 : :
1935 tmunro@postgresql.or 443 :CBC 22537 : 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
160 andrew@dunslane.net 452 :UBC 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
160 andrew@dunslane.net 463 :GBC 12 : TarWALDumpCloseSegment(XLogReaderState *state)
464 : : {
155 tgl@sss.pgh.pa.us 465 : 12 : close(state->seg.ws_file);
466 : : /* need to check errno? */
467 : 12 : state->seg.ws_file = -1;
160 andrew@dunslane.net 468 : 12 : }
469 : :
470 : : /*
471 : : * pg_waldump's XLogReaderRoutine->page_read callback to support dumping WAL
472 : : * files from tar archives.
473 : : */
474 : : static int
160 andrew@dunslane.net 475 :CBC 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 : : {
160 andrew@dunslane.net 514 :GBC 3 : close(state->seg.ws_file);
515 : 3 : 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 : : */
160 andrew@dunslane.net 524 [ + + + + ]:CBC 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 : : {
160 andrew@dunslane.net 536 :GBC 18 : XLogFileName(fname, state->seg.ws_tli, nextSegNo, segsize);
537 : 18 : state->seg.ws_file = open_file_in_directory(TmpWalSegDir, fname);
538 : : }
539 : : }
540 : :
541 : : /* Continue reading from the open WAL segment, if any */
160 andrew@dunslane.net 542 [ + + ]:CBC 28382 : if (state->seg.ws_file >= 0)
160 andrew@dunslane.net 543 :GBC 1110 : return WALDumpReadPage(state, targetPagePtr, count, targetPtr,
544 : : readBuff);
545 : :
546 : : /* Otherwise, read the WAL page from the archive streamer */
160 andrew@dunslane.net 547 :CBC 27272 : 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
1617 tmunro@postgresql.or 555 : 364253 : XLogRecordMatchesRelationBlock(XLogReaderState *record,
556 : : RelFileLocator matchRlocator,
557 : : BlockNumber matchBlock,
558 : : ForkNumber matchFork)
559 : : {
560 : : int block_id;
561 : :
562 [ + + ]: 779231 : for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
563 : : {
564 : : RelFileLocator rlocator;
565 : : ForkNumber forknum;
566 : : BlockNumber blk;
567 : :
1599 tgl@sss.pgh.pa.us 568 [ + + ]: 415200 : if (!XLogRecGetBlockTagExtended(record, block_id,
569 : : &rlocator, &forknum, &blk, NULL))
1617 tmunro@postgresql.or 570 : 94 : continue;
571 : :
572 [ + + + + ]: 415106 : if ((matchFork == InvalidForkNumber || matchFork == forknum) &&
1513 rhaas@postgresql.org 573 [ + + + - : 285296 : (RelFileLocatorEquals(matchRlocator, emptyRelFileLocator) ||
- + ]
574 [ + + + - : 285296 : RelFileLocatorEquals(matchRlocator, rlocator)) &&
+ - + + ]
1617 tmunro@postgresql.or 575 [ + + ]: 12 : (matchBlock == InvalidBlockNumber || matchBlock == blk))
576 : 222 : return true;
577 : : }
578 : :
579 : 364031 : return false;
580 : : }
581 : :
582 : : /*
583 : : * Boolean to return whether the given WAL record contains a full page write.
584 : : */
585 : : static bool
586 : 112803 : XLogRecordHasFPW(XLogReaderState *record)
587 : : {
588 : : int block_id;
589 : :
590 [ + + ]: 239292 : for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
591 : : {
592 [ + - + + ]: 129780 : if (!XLogRecHasBlockRef(record, block_id))
593 : 27 : continue;
594 : :
595 [ + + ]: 129753 : if (XLogRecHasBlockImage(record, block_id))
596 : 3291 : return true;
597 : : }
598 : :
599 : 109512 : 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
1339 michael@paquier.xyz 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))
1339 michael@paquier.xyz 632 :UBC 0 : pg_fatal("%s", record->errormsg_buf);
633 : :
1339 michael@paquier.xyz 634 :CBC 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
1339 michael@paquier.xyz 640 :UBC 0 : pg_fatal("invalid fork number: %u", fork);
641 : :
1156 michael@paquier.xyz 642 :CBC 1 : snprintf(filename, MAXPGPATH, "%s/%08X-%08X-%08X.%u.%u.%u.%u%s", savepath,
643 : : record->seg.ws_tli,
1339 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)
1339 michael@paquier.xyz 649 :UBC 0 : pg_fatal("could not open file \"%s\": %m", filename);
650 : :
1339 michael@paquier.xyz 651 [ - + ]:CBC 1 : if (fwrite(page, BLCKSZ, 1, file) != 1)
1339 michael@paquier.xyz 652 :UBC 0 : pg_fatal("could not write file \"%s\": %m", filename);
653 : :
1339 michael@paquier.xyz 654 [ - + ]:CBC 1 : if (fclose(file) != 0)
1339 michael@paquier.xyz 655 :UBC 0 : pg_fatal("could not close file \"%s\": %m", filename);
656 : : }
1339 michael@paquier.xyz 657 :CBC 201 : }
658 : :
659 : : /*
660 : : * Print a record to stdout
661 : : */
662 : : static void
4298 heikki.linnakangas@i 663 : 586963 : XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
664 : : {
665 : : const char *id;
1604 jdavis@postgresql.or 666 : 586963 : const RmgrDescData *desc = GetRmgrDesc(XLogRecGetRmid(record));
667 : : uint32 rec_len;
668 : : uint32 fpi_len;
4298 heikki.linnakangas@i 669 : 586963 : uint8 info = XLogRecGetInfo(record);
670 : 586963 : XLogRecPtr xl_prev = XLogRecGetPrev(record);
671 : : StringInfoData s;
672 : :
1602 jdavis@postgresql.or 673 : 586963 : XLogRecGetLen(record, &rec_len, &fpi_len);
674 : :
4298 heikki.linnakangas@i 675 : 586963 : 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 : :
2494 andres@anarazel.de 682 : 586963 : id = desc->rm_identify(info);
683 [ - + ]: 586963 : if (id == NULL)
2494 andres@anarazel.de 684 :UBC 0 : printf("desc: UNKNOWN (%x) ", info & ~XLR_INFO_MASK);
685 : : else
2494 andres@anarazel.de 686 :CBC 586963 : printf("desc: %s ", id);
687 : :
2487 688 : 586963 : initStringInfo(&s);
689 : 586963 : desc->rm_desc(&s, record);
690 : 586963 : printf("%s", s.data);
691 : :
1602 jdavis@postgresql.or 692 : 586963 : resetStringInfo(&s);
693 : 586963 : XLogRecGetBlockRefInfo(record, true, config->bkp_details, &s, NULL);
694 : 586963 : printf("%s", s.data);
695 : 586963 : pfree(s.data);
4934 alvherre@alvh.no-ip. 696 : 586963 : }
697 : :
698 : : /*
699 : : * Display a single row of record counts and sizes for an rmgr or record.
700 : : */
701 : : static void
4360 andres@anarazel.de 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 : :
4141 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 : :
4360 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
1602 jdavis@postgresql.or 743 : 6 : XLogDumpDisplayStats(XLogDumpConfig *config, XLogStats *stats)
744 : : {
745 : : int ri,
746 : : rj;
4360 andres@anarazel.de 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 : : */
294 alvherre@kurilemu.de 757 [ - + ]: 6 : if (!XLogRecPtrIsValid(stats->endptr))
1729 michael@paquier.xyz 758 :UBC 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 : :
1603 jdavis@postgresql.or 765 [ + + ]:CBC 1542 : for (ri = 0; ri <= RM_MAX_ID; ri++)
766 : : {
1602 767 [ + + + + ]: 1536 : if (!RmgrIdIsValid(ri))
768 : 630 : continue;
769 : :
4360 andres@anarazel.de 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 : : }
4114 bruce@momjian.us 774 : 6 : total_len = total_rec_len + total_fpi_len;
775 : :
416 alvherre@kurilemu.de 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 : :
4360 andres@anarazel.de 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 : :
1604 jdavis@postgresql.or 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 : :
1603 797 [ + + + + ]: 1536 : if (!RmgrIdIsValid(ri))
1604 798 : 630 : continue;
799 : :
800 : 906 : desc = GetRmgrDesc(ri);
801 : :
4360 andres@anarazel.de 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 : :
1603 jdavis@postgresql.or 809 [ + + + - ]: 453 : if (RmgrIdIsCustom(ri) && count == 0)
1604 810 : 384 : continue;
811 : :
4360 andres@anarazel.de 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)
4360 andres@anarazel.de 834 :UBC 0 : id = psprintf("UNKNOWN (%x)", rj << 4);
835 : :
4360 andres@anarazel.de 836 :CBC 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 : :
4141 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 : :
4360 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
155 tgl@sss.pgh.pa.us 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 : 3 : WALDumpCloseSegment(xlogreader_state_cleanup);
885 : :
886 [ + + ]: 120 : if (TmpWalSegDir != NULL)
887 : : {
155 tgl@sss.pgh.pa.us 888 :GBC 15 : rmtree(TmpWalSegDir, true);
889 : 15 : TmpWalSegDir = NULL;
890 : : }
155 tgl@sss.pgh.pa.us 891 :CBC 120 : }
892 : :
893 : : static void
4934 alvherre@alvh.no-ip. 894 : 1 : usage(void)
895 : : {
3394 peter_e@gmx.net 896 : 1 : printf(_("%s decodes and displays PostgreSQL write-ahead logs for debugging.\n\n"),
897 : : progname);
3603 898 : 1 : printf(_("Usage:\n"));
3423 899 : 1 : printf(_(" %s [OPTION]... [STARTSEG [ENDSEG]]\n"), progname);
3603 900 : 1 : printf(_("\nOptions:\n"));
901 : 1 : printf(_(" -b, --bkp-details output detailed information about backup blocks\n"));
1616 tmunro@postgresql.or 902 : 1 : printf(_(" -B, --block=N with --relation, only show records that modify block N\n"));
3394 peter_e@gmx.net 903 : 1 : printf(_(" -e, --end=RECPTR stop reading at WAL location RECPTR\n"));
3603 904 : 1 : printf(_(" -f, --follow keep retrying after reaching end of WAL\n"));
1616 tmunro@postgresql.or 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"));
3603 peter_e@gmx.net 907 : 1 : printf(_(" -n, --limit=N number of records to display\n"));
160 andrew@dunslane.net 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"));
2338 rhaas@postgresql.org 911 : 1 : printf(_(" -q, --quiet do not print any output, except for errors\n"));
3289 peter_e@gmx.net 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"));
1616 tmunro@postgresql.or 914 : 1 : printf(_(" -R, --relation=T/D/R only show records that modify blocks in relation T/D/R\n"));
3394 peter_e@gmx.net 915 : 1 : printf(_(" -s, --start=RECPTR start reading at WAL location RECPTR\n"));
1443 tgl@sss.pgh.pa.us 916 : 1 : printf(_(" -t, --timeline=TLI timeline from which to read WAL records\n"
917 : : " (default: 1 or the value used in STARTSEG)\n"));
3603 peter_e@gmx.net 918 : 1 : printf(_(" -V, --version output version information, then exit\n"));
1617 tmunro@postgresql.or 919 : 1 : printf(_(" -w, --fullpage only show records with a full page write\n"));
1616 920 : 1 : printf(_(" -x, --xid=XID only show records with transaction ID XID\n"));
3389 tgl@sss.pgh.pa.us 921 : 1 : printf(_(" -z, --stats[=record] show statistics instead of records\n"
922 : : " (optionally, show per-record statistics)\n"));
1228 peter@eisentraut.org 923 : 1 : printf(_(" --save-fullpage=DIR save full page images to DIR\n"));
3603 peter_e@gmx.net 924 : 1 : printf(_(" -?, --help show this help, then exit\n"));
2372 peter@eisentraut.org 925 : 1 : printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
926 : 1 : printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
4934 alvherre@alvh.no-ip. 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;
2529 938 : 258 : char *waldir = NULL;
939 : : char *errormsg;
160 andrew@dunslane.net 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;
4934 alvherre@alvh.no-ip. 965 : 258 : int optindex = 0;
966 : :
967 : : #ifndef WIN32
1729 michael@paquier.xyz 968 : 258 : pqsignal(SIGINT, sigint_handler);
969 : : #endif
970 : :
2705 peter@eisentraut.org 971 : 258 : pg_logging_init(argv[0]);
3486 rhaas@postgresql.org 972 : 258 : set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_waldump"));
4934 alvherre@alvh.no-ip. 973 : 258 : progname = get_progname(argv[0]);
974 : :
2640 peter@eisentraut.org 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 : :
1935 tmunro@postgresql.or 989 : 142 : memset(&private, 0, sizeof(XLogDumpPrivate));
4934 alvherre@alvh.no-ip. 990 : 142 : memset(&config, 0, sizeof(XLogDumpConfig));
1602 jdavis@postgresql.or 991 : 142 : memset(&stats, 0, sizeof(XLogStats));
992 : :
1935 tmunro@postgresql.or 993 : 142 : private.timeline = 1;
160 andrew@dunslane.net 994 : 142 : private.segsize = 0;
1935 tmunro@postgresql.or 995 : 142 : private.startptr = InvalidXLogRecPtr;
996 : 142 : private.endptr = InvalidXLogRecPtr;
997 : 142 : private.endptr_reached = false;
160 andrew@dunslane.net 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 : :
2338 rhaas@postgresql.org 1003 : 142 : config.quiet = false;
4934 alvherre@alvh.no-ip. 1004 : 142 : config.bkp_details = false;
1005 : 142 : config.stop_after_records = -1;
1006 : 142 : config.already_displayed_records = 0;
4537 heikki.linnakangas@i 1007 : 142 : config.follow = false;
1008 : : /* filter_by_rmgr array was zeroed by memset above */
1883 1009 : 142 : config.filter_by_rmgr_enabled = false;
4934 alvherre@alvh.no-ip. 1010 : 142 : config.filter_by_xid = InvalidTransactionId;
1011 : 142 : config.filter_by_xid_enabled = false;
1617 tmunro@postgresql.or 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;
1339 michael@paquier.xyz 1017 : 142 : config.save_fullpage_path = NULL;
4360 andres@anarazel.de 1018 : 142 : config.stats = false;
1019 : 142 : config.stats_per_record = false;
1020 : :
1729 michael@paquier.xyz 1021 : 142 : stats.startptr = InvalidXLogRecPtr;
1022 : 142 : stats.endptr = InvalidXLogRecPtr;
1023 : :
4934 alvherre@alvh.no-ip. 1024 [ + + ]: 142 : if (argc <= 1)
1025 : : {
2705 peter@eisentraut.org 1026 : 1 : pg_log_error("no arguments specified");
4934 alvherre@alvh.no-ip. 1027 : 1 : goto bad_argument;
1028 : : }
1029 : :
1616 tmunro@postgresql.or 1030 : 668 : while ((option = getopt_long(argc, argv, "bB:e:fF:n:p:qr:R:s:t:wx:z",
4934 alvherre@alvh.no-ip. 1031 [ + + ]: 668 : long_options, &optindex)) != -1)
1032 : : {
1033 [ - + + - : 540 : switch (option)
+ + + + +
+ + + + -
+ + + ]
1034 : : {
4934 alvherre@alvh.no-ip. 1035 :UBC 0 : case 'b':
1036 : 0 : config.bkp_details = true;
1037 : 0 : break;
1616 tmunro@postgresql.or 1038 :CBC 4 : case 'B':
1039 [ + + ]: 4 : if (sscanf(optarg, "%u", &config.filter_by_relation_block) != 1 ||
1040 [ - + ]: 3 : !BlockNumberIsValid(config.filter_by_relation_block))
1041 : : {
1560 peter@eisentraut.org 1042 : 1 : pg_log_error("invalid block number: \"%s\"", optarg);
1616 tmunro@postgresql.or 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;
4934 alvherre@alvh.no-ip. 1048 : 114 : case 'e':
15 fujii@postgresql.org 1049 [ + + ]:GNC 114 : if (!pg_parse_lsn(optarg, &private.endptr))
1050 : : {
1560 peter@eisentraut.org 1051 :CBC 3 : pg_log_error("invalid WAL location: \"%s\"",
1052 : : optarg);
4934 alvherre@alvh.no-ip. 1053 : 3 : goto bad_argument;
1054 : : }
1055 : 111 : break;
4537 heikki.linnakangas@i 1056 :UBC 0 : case 'f':
1057 : 0 : config.follow = true;
1058 : 0 : break;
1617 tmunro@postgresql.or 1059 :CBC 4 : case 'F':
1616 1060 : 4 : config.filter_by_relation_forknum = forkname_to_number(optarg);
1061 [ + + ]: 4 : if (config.filter_by_relation_forknum == InvalidForkNumber)
1062 : : {
1560 peter@eisentraut.org 1063 : 1 : pg_log_error("invalid fork name: \"%s\"", optarg);
1617 tmunro@postgresql.or 1064 : 1 : goto bad_argument;
1065 : : }
1066 : 3 : config.filter_by_extended = true;
1067 : 3 : break;
4934 alvherre@alvh.no-ip. 1068 : 4 : case 'n':
1069 [ + + ]: 4 : if (sscanf(optarg, "%d", &config.stop_after_records) != 1)
1070 : : {
1560 peter@eisentraut.org 1071 : 1 : pg_log_error("invalid value \"%s\" for option %s", optarg, "-n/--limit");
4934 alvherre@alvh.no-ip. 1072 : 1 : goto bad_argument;
1073 : : }
1074 : 3 : break;
1075 : 121 : case 'p':
2529 1076 : 121 : waldir = pg_strdup(optarg);
4934 1077 : 121 : break;
2338 rhaas@postgresql.org 1078 : 77 : case 'q':
1079 : 77 : config.quiet = true;
1080 : 77 : break;
4934 alvherre@alvh.no-ip. 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 : : */
1604 jdavis@postgresql.or 1098 [ - + ]: 4 : if (sscanf(optarg, "custom%03d", &rmid) == 1)
1099 : : {
1603 jdavis@postgresql.or 1100 [ # # ]:UBC 0 : if (!RmgrIdIsCustom(rmid))
1101 : : {
1604 1102 : 0 : pg_log_error("custom resource manager \"%s\" does not exist",
1103 : : optarg);
1604 jdavis@postgresql.or 1104 :CBC 1 : goto bad_argument;
1105 : : }
1604 jdavis@postgresql.or 1106 :UBC 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 */
1604 jdavis@postgresql.or 1112 [ + + ]:CBC 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 : : }
4934 alvherre@alvh.no-ip. 1129 : 3 : break;
1616 tmunro@postgresql.or 1130 : 8 : case 'R':
1429 rhaas@postgresql.org 1131 [ + + ]: 8 : if (sscanf(optarg, "%u/%u/%u",
1132 : : &config.filter_by_relation.spcOid,
1133 : : &config.filter_by_relation.dbOid,
1513 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 : : {
1560 peter@eisentraut.org 1138 : 1 : pg_log_error("invalid relation specification: \"%s\"", optarg);
1139 : 1 : pg_log_error_detail("Expecting \"tablespace OID/database OID/relation filenode\".");
1616 tmunro@postgresql.or 1140 : 1 : goto bad_argument;
1141 : : }
1142 : 7 : config.filter_by_relation_enabled = true;
1143 : 7 : config.filter_by_extended = true;
1144 : 7 : break;
4934 alvherre@alvh.no-ip. 1145 : 120 : case 's':
15 fujii@postgresql.org 1146 [ + + ]:GNC 120 : if (!pg_parse_lsn(optarg, &private.startptr))
1147 : : {
1560 peter@eisentraut.org 1148 :CBC 3 : pg_log_error("invalid WAL location: \"%s\"",
1149 : : optarg);
4934 alvherre@alvh.no-ip. 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 : :
1255 peter@eisentraut.org 1164 : 72 : errno = 0;
1165 : 72 : val = strtoul(optarg, &endptr, 0);
1166 : :
1167 [ - + - - ]: 72 : while (*endptr != '\0' && isspace((unsigned char) *endptr))
1255 peter@eisentraut.org 1168 :UBC 0 : endptr++;
1169 : :
1255 peter@eisentraut.org 1170 [ - + ]:CBC 72 : if (*endptr != '\0')
1171 : : {
1255 peter@eisentraut.org 1172 :UBC 0 : pg_log_error("invalid value \"%s\" for option %s",
1173 : : optarg, "-t/--timeline");
1174 : 0 : goto bad_argument;
1175 : : }
1176 : :
1255 peter@eisentraut.org 1177 [ + - + - :CBC 72 : if (errno == ERANGE || val < 1 || val > UINT_MAX)
- + ]
1178 : : {
1255 peter@eisentraut.org 1179 :UBC 0 : pg_log_error("%s must be in range %u..%u",
1180 : : "-t/--timeline", 1, UINT_MAX);
1181 : 0 : goto bad_argument;
1182 : : }
1183 : :
1255 peter@eisentraut.org 1184 :CBC 72 : private.timeline = val;
1185 : :
1186 : 72 : break;
1187 : : }
1617 tmunro@postgresql.or 1188 : 3 : case 'w':
1189 : 3 : config.filter_by_fpw = true;
1190 : 3 : break;
4934 alvherre@alvh.no-ip. 1191 :UBC 0 : case 'x':
1192 [ # # ]: 0 : if (sscanf(optarg, "%u", &config.filter_by_xid) != 1)
1193 : : {
1560 peter@eisentraut.org 1194 : 0 : pg_log_error("invalid transaction ID specification: \"%s\"",
1195 : : optarg);
4934 alvherre@alvh.no-ip. 1196 : 0 : goto bad_argument;
1197 : : }
1198 : 0 : config.filter_by_xid_enabled = true;
1199 : 0 : break;
4360 andres@anarazel.de 1200 :CBC 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;
4360 andres@anarazel.de 1207 [ # # ]:UBC 0 : else if (strcmp(optarg, "rmgr") != 0)
1208 : : {
1560 peter@eisentraut.org 1209 : 0 : pg_log_error("unrecognized value for option %s: %s",
1210 : : "--stats", optarg);
4360 andres@anarazel.de 1211 : 0 : goto bad_argument;
1212 : : }
1213 : : }
4360 andres@anarazel.de 1214 :CBC 6 : break;
1339 michael@paquier.xyz 1215 : 1 : case 1:
1216 : 1 : config.save_fullpage_path = pg_strdup(optarg);
1217 : 1 : break;
4934 alvherre@alvh.no-ip. 1218 : 1 : default:
1219 : 1 : goto bad_argument;
1220 : : }
1221 : : }
1222 : :
1617 tmunro@postgresql.or 1223 [ + + ]: 128 : if (config.filter_by_relation_block_enabled &&
1224 [ - + ]: 3 : !config.filter_by_relation_enabled)
1225 : : {
1560 peter@eisentraut.org 1226 :UBC 0 : pg_log_error("option %s requires option %s to be specified",
1227 : : "-B/--block", "-R/--relation");
1617 tmunro@postgresql.or 1228 : 0 : goto bad_argument;
1229 : : }
1230 : :
4934 alvherre@alvh.no-ip. 1231 [ + + ]:CBC 128 : if ((optind + 2) < argc)
1232 : : {
2705 peter@eisentraut.org 1233 : 1 : pg_log_error("too many command-line arguments (first is \"%s\")",
1234 : : argv[optind + 2]);
4934 alvherre@alvh.no-ip. 1235 : 1 : goto bad_argument;
1236 : : }
1237 : :
2529 1238 [ + + ]: 127 : if (waldir != NULL)
1239 : : {
1240 : : /* Check whether the path looks like a tar archive by its extension */
24 rhaas@postgresql.org 1241 [ + + ]: 121 : if (parse_tar_compress_algorithm(waldir, &compression) >= 0)
1242 : : {
160 andrew@dunslane.net 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 : : {
2438 michael@paquier.xyz 1248 : 1 : pg_log_error("could not open directory \"%s\": %m", waldir);
4934 alvherre@alvh.no-ip. 1249 : 1 : goto bad_argument;
1250 : : }
1251 : : }
1252 : :
1339 michael@paquier.xyz 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 */
4934 alvherre@alvh.no-ip. 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 : : */
160 andrew@dunslane.net 1268 [ - + ]: 7 : if (private.archive_name)
1269 : : {
160 andrew@dunslane.net 1270 :UBC 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 : :
4934 alvherre@alvh.no-ip. 1275 :CBC 7 : split_path(argv[optind], &directory, &fname);
1276 : :
2529 1277 [ + + + + ]: 7 : if (waldir == NULL && directory != NULL)
1278 : : {
1279 : 5 : waldir = directory;
1280 : :
1281 [ - + ]: 5 : if (!verify_directory(waldir))
1602 tgl@sss.pgh.pa.us 1282 :UBC 0 : pg_fatal("could not open directory \"%s\": %m", waldir);
1283 : : }
1284 : :
24 rhaas@postgresql.org 1285 [ + - - + ]:CBC 14 : if (fname != NULL &&
1286 : 7 : parse_tar_compress_algorithm(fname, &compression) >= 0)
1287 : : {
160 andrew@dunslane.net 1288 :UBC 0 : private.archive_dir = waldir;
1289 : 0 : private.archive_name = fname;
1290 : : }
1291 : : else
1292 : : {
160 andrew@dunslane.net 1293 :CBC 7 : waldir = identify_target_directory(waldir, fname, &private.segsize);
2529 alvherre@alvh.no-ip. 1294 : 6 : fd = open_file_in_directory(waldir, fname);
4934 1295 [ - + ]: 6 : if (fd < 0)
1602 tgl@sss.pgh.pa.us 1296 :UBC 0 : pg_fatal("could not open file \"%s\"", fname);
4934 alvherre@alvh.no-ip. 1297 :CBC 6 : close(fd);
1298 : :
1299 : : /* parse position from file */
160 andrew@dunslane.net 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);
160 andrew@dunslane.net 1304 [ # # ]:UBC 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 */
160 andrew@dunslane.net 1313 [ + + + - ]:CBC 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)
160 andrew@dunslane.net 1333 :UBC 0 : pg_fatal("ENDSEG %s is before STARTSEG %s",
1334 : : argv[optind + 1], argv[optind]);
1335 : :
160 andrew@dunslane.net 1336 [ + - ]:CBC 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 : : {
160 andrew@dunslane.net 1347 :UBC 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 : : }
160 andrew@dunslane.net 1354 [ + + ]:CBC 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 */
294 alvherre@kurilemu.de 1358 [ + + ]: 123 : if (!XLogRecPtrIsValid(private.startptr))
1359 : : {
2705 peter@eisentraut.org 1360 : 3 : pg_log_error("no start WAL location given");
4934 alvherre@alvh.no-ip. 1361 : 3 : goto bad_argument;
1362 : : }
1363 : :
1364 : : /* --follow is not supported with tar archives */
160 andrew@dunslane.net 1365 [ - + - - ]: 120 : if (config.follow && private.archive_name)
1366 : : {
160 andrew@dunslane.net 1367 :UBC 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 */
160 andrew@dunslane.net 1374 [ + + ]:CBC 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)
160 andrew@dunslane.net 1381 :UBC 0 : private.archive_dir = pg_strdup(".");
1382 : :
1383 : : /* Set up for reading tar file */
160 andrew@dunslane.net 1384 :CBC 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 : :
4934 alvherre@alvh.no-ip. 1404 [ - + ]: 120 : if (!xlogreader_state)
1602 tgl@sss.pgh.pa.us 1405 :UBC 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 : : */
155 tgl@sss.pgh.pa.us 1412 :CBC 120 : atexit(cleanup_tmpwal_dir_atexit);
1413 : 120 : xlogreader_state_cleanup = xlogreader_state;
1414 : :
1415 : : /* first find a valid recptr to start from */
156 fujii@postgresql.org 1416 : 120 : first_record = XLogFindNextRecord(xlogreader_state, private.startptr, &errormsg);
1417 : :
294 alvherre@kurilemu.de 1418 [ + + ]: 120 : if (!XLogRecPtrIsValid(first_record))
1419 : : {
156 fujii@postgresql.org 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
156 fujii@postgresql.org 1424 :UBC 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 : : */
1935 tmunro@postgresql.or 1433 [ + + ]:CBC 119 : if (first_record != private.startptr &&
160 andrew@dunslane.net 1434 [ + + ]: 10 : XLogSegmentOffset(private.startptr, private.segsize) != 0)
416 alvherre@kurilemu.de 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 : :
1729 michael@paquier.xyz 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 */
160 andrew@dunslane.net 1446 : 119 : private.decoding_started = true;
1447 : :
1448 : : for (;;)
1449 : : {
1729 michael@paquier.xyz 1450 [ - + ]: 1569506 : if (time_to_stop)
1451 : : {
1452 : : /* We've been Ctrl-C'ed, so leave */
1729 michael@paquier.xyz 1453 :UBC 0 : break;
1454 : : }
1455 : :
1456 : : /* try to read the next record */
1935 tmunro@postgresql.or 1457 :CBC 1569506 : record = XLogReadRecord(xlogreader_state, &errormsg);
4537 heikki.linnakangas@i 1458 [ + + ]: 1569506 : if (!record)
1459 : : {
1935 tmunro@postgresql.or 1460 [ - + - - ]: 116 : if (!config.follow || private.endptr_reached)
1461 : : break;
1462 : : else
1463 : : {
4496 bruce@momjian.us 1464 :UBC 0 : pg_usleep(1000000L); /* 1 second */
4537 heikki.linnakangas@i 1465 : 0 : continue;
1466 : : }
1467 : : }
1468 : :
1469 : : /* apply all specified filters */
1883 heikki.linnakangas@i 1470 [ + + ]:CBC 1569390 : if (config.filter_by_rmgr_enabled &&
1471 [ + + ]: 112803 : !config.filter_by_rmgr[record->xl_rmid])
4360 andres@anarazel.de 1472 : 106791 : continue;
1473 : :
1474 [ - + ]: 1462599 : if (config.filter_by_xid_enabled &&
4360 andres@anarazel.de 1475 [ # # ]:UBC 0 : config.filter_by_xid != record->xl_xid)
1476 : 0 : continue;
1477 : :
1478 : : /* check for extended filtering */
1617 tmunro@postgresql.or 1479 [ + + ]:CBC 1462599 : if (config.filter_by_extended &&
1480 [ + + ]: 728506 : !XLogRecordMatchesRelationBlock(xlogreader_state,
1481 [ + + ]: 364253 : config.filter_by_relation_enabled ?
1482 : : config.filter_by_relation :
1483 : : emptyRelFileLocator,
1484 [ + + ]: 364253 : config.filter_by_relation_block_enabled ?
1485 : : config.filter_by_relation_block :
1486 : : InvalidBlockNumber,
1487 : : config.filter_by_relation_forknum))
1488 : 364031 : continue;
1489 : :
1490 [ + + + + ]: 1098568 : if (config.filter_by_fpw && !XLogRecordHasFPW(xlogreader_state))
1491 : 109512 : continue;
1492 : :
1493 : : /* perform any per-record work */
2338 rhaas@postgresql.org 1494 [ + + ]: 989056 : if (!config.quiet)
1495 : : {
1496 [ + + ]: 812569 : if (config.stats == true)
1497 : : {
1602 jdavis@postgresql.or 1498 : 225606 : XLogRecStoreStats(&stats, xlogreader_state);
1729 michael@paquier.xyz 1499 : 225606 : stats.endptr = xlogreader_state->EndRecPtr;
1500 : : }
1501 : : else
2338 rhaas@postgresql.org 1502 : 586963 : XLogDumpDisplayRecord(&config, xlogreader_state);
1503 : : }
1504 : :
1505 : : /* save full pages if requested */
1339 michael@paquier.xyz 1506 [ + + ]: 989056 : if (config.save_fullpage_path != NULL)
1507 : 201 : XLogRecordSaveFPWs(xlogreader_state, config.save_fullpage_path);
1508 : :
1509 : : /* check whether we printed enough */
4360 andres@anarazel.de 1510 : 989056 : config.already_displayed_records++;
4934 alvherre@alvh.no-ip. 1511 [ + + ]: 989056 : if (config.stop_after_records > 0 &&
1512 [ + + ]: 18 : config.already_displayed_records >= config.stop_after_records)
1513 : 3 : break;
1514 : : }
1515 : :
2337 rhaas@postgresql.org 1516 [ + + + - ]: 119 : if (config.stats == true && !config.quiet)
4360 andres@anarazel.de 1517 : 6 : XLogDumpDisplayStats(&config, &stats);
1518 : :
1729 michael@paquier.xyz 1519 [ - + ]: 119 : if (time_to_stop)
1729 michael@paquier.xyz 1520 :UBC 0 : exit(0);
1521 : :
4934 alvherre@alvh.no-ip. 1522 [ + + ]:CBC 119 : if (errormsg)
416 alvherre@kurilemu.de 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 : : */
155 tgl@sss.pgh.pa.us 1531 : 113 : xlogreader_state_cleanup = NULL;
1532 : :
4934 alvherre@alvh.no-ip. 1533 : 113 : XLogReaderFree(xlogreader_state);
1534 : :
160 andrew@dunslane.net 1535 [ + + ]: 113 : if (private.archive_name)
1536 : 48 : free_archive_reader(&private);
1537 : :
4934 alvherre@alvh.no-ip. 1538 : 113 : return EXIT_SUCCESS;
1539 : :
1540 : 18 : bad_argument:
1602 tgl@sss.pgh.pa.us 1541 : 18 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4934 alvherre@alvh.no-ip. 1542 : 18 : return EXIT_FAILURE;
1543 : : }
|