|
23 | 23 | #include "access/xlogrecord.h"
|
24 | 24 | #include "access/xlogstats.h"
|
25 | 25 | #include "common/fe_memutils.h"
|
| 26 | +#include "common/file_perm.h" |
| 27 | +#include "common/file_utils.h" |
26 | 28 | #include "common/logging.h"
|
| 29 | +#include "common/relpath.h" |
27 | 30 | #include "getopt_long.h"
|
28 | 31 | #include "rmgrdesc.h"
|
| 32 | +#include "storage/bufpage.h" |
29 | 33 |
|
30 | 34 | /*
|
31 | 35 | * NOTE: For any code change or issue fix here, it is highly recommended to
|
@@ -70,6 +74,9 @@ typedef struct XLogDumpConfig
|
70 | 74 | bool filter_by_relation_block_enabled;
|
71 | 75 | ForkNumber filter_by_relation_forknum;
|
72 | 76 | bool filter_by_fpw;
|
| 77 | + |
| 78 | + /* save options */ |
| 79 | + char *save_fullpage_path; |
73 | 80 | } XLogDumpConfig;
|
74 | 81 |
|
75 | 82 |
|
@@ -112,6 +119,37 @@ verify_directory(const char *directory)
|
112 | 119 | return true;
|
113 | 120 | }
|
114 | 121 |
|
| 122 | +/* |
| 123 | + * Create if necessary the directory storing the full-page images extracted |
| 124 | + * from the WAL records read. |
| 125 | + */ |
| 126 | +static void |
| 127 | +create_fullpage_directory(char *path) |
| 128 | +{ |
| 129 | + int ret; |
| 130 | + |
| 131 | + switch ((ret = pg_check_dir(path))) |
| 132 | + { |
| 133 | + case 0: |
| 134 | + /* Does not exist, so create it */ |
| 135 | + if (pg_mkdir_p(path, pg_dir_create_mode) < 0) |
| 136 | + pg_fatal("could not create directory \"%s\": %m", path); |
| 137 | + break; |
| 138 | + case 1: |
| 139 | + /* Present and empty, so do nothing */ |
| 140 | + break; |
| 141 | + case 2: |
| 142 | + case 3: |
| 143 | + case 4: |
| 144 | + /* Exists and not empty */ |
| 145 | + pg_fatal("directory \"%s\" exists but is not empty", path); |
| 146 | + break; |
| 147 | + default: |
| 148 | + /* Trouble accessing directory */ |
| 149 | + pg_fatal("could not access directory \"%s\": %m", path); |
| 150 | + } |
| 151 | +} |
| 152 | + |
115 | 153 | /*
|
116 | 154 | * Split a pathname as dirname(1) and basename(1) would.
|
117 | 155 | *
|
@@ -439,6 +477,62 @@ XLogRecordHasFPW(XLogReaderState *record)
|
439 | 477 | return false;
|
440 | 478 | }
|
441 | 479 |
|
| 480 | +/* |
| 481 | + * Function to externally save all FPWs stored in the given WAL record. |
| 482 | + * Decompression is applied to all the blocks saved, if necessary. |
| 483 | + */ |
| 484 | +static void |
| 485 | +XLogRecordSaveFPWs(XLogReaderState *record, const char *savepath) |
| 486 | +{ |
| 487 | + int block_id; |
| 488 | + |
| 489 | + for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++) |
| 490 | + { |
| 491 | + PGAlignedBlock buf; |
| 492 | + Page page; |
| 493 | + char filename[MAXPGPATH]; |
| 494 | + char forkname[FORKNAMECHARS + 2]; /* _ + terminating zero */ |
| 495 | + FILE *file; |
| 496 | + BlockNumber blk; |
| 497 | + RelFileLocator rnode; |
| 498 | + ForkNumber fork; |
| 499 | + |
| 500 | + if (!XLogRecHasBlockRef(record, block_id)) |
| 501 | + continue; |
| 502 | + |
| 503 | + if (!XLogRecHasBlockImage(record, block_id)) |
| 504 | + continue; |
| 505 | + |
| 506 | + page = (Page) buf.data; |
| 507 | + |
| 508 | + /* Full page exists, so let's save it */ |
| 509 | + if (!RestoreBlockImage(record, block_id, page)) |
| 510 | + pg_fatal("%s", record->errormsg_buf); |
| 511 | + |
| 512 | + (void) XLogRecGetBlockTagExtended(record, block_id, |
| 513 | + &rnode, &fork, &blk, NULL); |
| 514 | + |
| 515 | + if (fork >= 0 && fork <= MAX_FORKNUM) |
| 516 | + sprintf(forkname, "_%s", forkNames[fork]); |
| 517 | + else |
| 518 | + pg_fatal("invalid fork number: %u", fork); |
| 519 | + |
| 520 | + snprintf(filename, MAXPGPATH, "%s/%08X-%08X.%u.%u.%u.%u%s", savepath, |
| 521 | + LSN_FORMAT_ARGS(record->ReadRecPtr), |
| 522 | + rnode.spcOid, rnode.dbOid, rnode.relNumber, blk, forkname); |
| 523 | + |
| 524 | + file = fopen(filename, PG_BINARY_W); |
| 525 | + if (!file) |
| 526 | + pg_fatal("could not open file \"%s\": %m", filename); |
| 527 | + |
| 528 | + if (fwrite(page, BLCKSZ, 1, file) != 1) |
| 529 | + pg_fatal("could not write file \"%s\": %m", filename); |
| 530 | + |
| 531 | + if (fclose(file) != 0) |
| 532 | + pg_fatal("could not write file \"%s\": %m", filename); |
| 533 | + } |
| 534 | +} |
| 535 | + |
442 | 536 | /*
|
443 | 537 | * Print a record to stdout
|
444 | 538 | */
|
@@ -679,6 +773,8 @@ usage(void)
|
679 | 773 | " (default: 1 or the value used in STARTSEG)\n"));
|
680 | 774 | printf(_(" -V, --version output version information, then exit\n"));
|
681 | 775 | printf(_(" -w, --fullpage only show records with a full page write\n"));
|
| 776 | + printf(_(" --save-fullpage=PATH\n" |
| 777 | + " save full page images\n")); |
682 | 778 | printf(_(" -x, --xid=XID only show records with transaction ID XID\n"));
|
683 | 779 | printf(_(" -z, --stats[=record] show statistics instead of records\n"
|
684 | 780 | " (optionally, show per-record statistics)\n"));
|
@@ -719,6 +815,7 @@ main(int argc, char **argv)
|
719 | 815 | {"xid", required_argument, NULL, 'x'},
|
720 | 816 | {"version", no_argument, NULL, 'V'},
|
721 | 817 | {"stats", optional_argument, NULL, 'z'},
|
| 818 | + {"save-fullpage", required_argument, NULL, 1}, |
722 | 819 | {NULL, 0, NULL, 0}
|
723 | 820 | };
|
724 | 821 |
|
@@ -770,6 +867,7 @@ main(int argc, char **argv)
|
770 | 867 | config.filter_by_relation_block_enabled = false;
|
771 | 868 | config.filter_by_relation_forknum = InvalidForkNumber;
|
772 | 869 | config.filter_by_fpw = false;
|
| 870 | + config.save_fullpage_path = NULL; |
773 | 871 | config.stats = false;
|
774 | 872 | config.stats_per_record = false;
|
775 | 873 |
|
@@ -942,6 +1040,9 @@ main(int argc, char **argv)
|
942 | 1040 | }
|
943 | 1041 | }
|
944 | 1042 | break;
|
| 1043 | + case 1: |
| 1044 | + config.save_fullpage_path = pg_strdup(optarg); |
| 1045 | + break; |
945 | 1046 | default:
|
946 | 1047 | goto bad_argument;
|
947 | 1048 | }
|
@@ -972,6 +1073,9 @@ main(int argc, char **argv)
|
972 | 1073 | }
|
973 | 1074 | }
|
974 | 1075 |
|
| 1076 | + if (config.save_fullpage_path != NULL) |
| 1077 | + create_fullpage_directory(config.save_fullpage_path); |
| 1078 | + |
975 | 1079 | /* parse files as start/end boundaries, extract path if not specified */
|
976 | 1080 | if (optind < argc)
|
977 | 1081 | {
|
@@ -1154,6 +1258,10 @@ main(int argc, char **argv)
|
1154 | 1258 | XLogDumpDisplayRecord(&config, xlogreader_state);
|
1155 | 1259 | }
|
1156 | 1260 |
|
| 1261 | + /* save full pages if requested */ |
| 1262 | + if (config.save_fullpage_path != NULL) |
| 1263 | + XLogRecordSaveFPWs(xlogreader_state, config.save_fullpage_path); |
| 1264 | + |
1157 | 1265 | /* check whether we printed enough */
|
1158 | 1266 | config.already_displayed_records++;
|
1159 | 1267 | if (config.stop_after_records > 0 &&
|
|
0 commit comments