Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pg_rewind.c
4 : : * Synchronizes a PostgreSQL data directory to a new timeline
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : *
8 : : *-------------------------------------------------------------------------
9 : : */
10 : : #include "postgres_fe.h"
11 : :
12 : : #include <sys/stat.h>
13 : : #include <fcntl.h>
14 : : #include <time.h>
15 : : #include <unistd.h>
16 : :
17 : : #include "access/timeline.h"
18 : : #include "access/xlog_internal.h"
19 : : #include "catalog/catversion.h"
20 : : #include "catalog/pg_control.h"
21 : : #include "common/controldata_utils.h"
22 : : #include "common/file_perm.h"
23 : : #include "common/restricted_token.h"
24 : : #include "common/string.h"
25 : : #include "fe_utils/option_utils.h"
26 : : #include "fe_utils/recovery_gen.h"
27 : : #include "fe_utils/string_utils.h"
28 : : #include "file_ops.h"
29 : : #include "filemap.h"
30 : : #include "getopt_long.h"
31 : : #include "pg_rewind.h"
32 : : #include "rewind_source.h"
33 : : #include "storage/bufpage.h"
34 : :
35 : : static void usage(const char *progname);
36 : :
37 : : static void perform_rewind(filemap_t *filemap, rewind_source *source,
38 : : XLogRecPtr chkptrec,
39 : : TimeLineID chkpttli,
40 : : XLogRecPtr chkptredo,
41 : : XLogRecPtr divergerec);
42 : :
43 : : static void createBackupLabel(XLogRecPtr startpoint, TimeLineID starttli,
44 : : XLogRecPtr checkpointloc);
45 : :
46 : : static void digestControlFile(ControlFileData *ControlFile,
47 : : const char *content, size_t size);
48 : : static void getRestoreCommand(const char *argv0);
49 : : static void sanityChecks(void);
50 : : static TimeLineHistoryEntry *getTimelineHistory(TimeLineID tli, bool is_source,
51 : : int *nentries);
52 : : static void findCommonAncestorTimeline(TimeLineHistoryEntry *a_history,
53 : : int a_nentries,
54 : : TimeLineHistoryEntry *b_history,
55 : : int b_nentries,
56 : : XLogRecPtr *recptr, int *tliIndex);
57 : : static void ensureCleanShutdown(const char *argv0);
58 : : static void disconnect_atexit(void);
59 : :
60 : : static ControlFileData ControlFile_target;
61 : : static ControlFileData ControlFile_source;
62 : : static ControlFileData ControlFile_source_after;
63 : :
64 : : static const char *progname;
65 : : int WalSegSz;
66 : :
67 : : /* Configuration options */
68 : : char *datadir_target = NULL;
69 : : static char *datadir_source = NULL;
70 : : static char *connstr_source = NULL;
71 : : static char *restore_command = NULL;
72 : : static char *config_file = NULL;
73 : :
74 : : static bool debug = false;
75 : : bool showprogress = false;
76 : : bool dry_run = false;
77 : : bool do_sync = true;
78 : : static bool restore_wal = false;
79 : : DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
80 : :
81 : : /* Target history */
82 : : TimeLineHistoryEntry *targetHistory;
83 : : int targetNentries;
84 : :
85 : : /* Progress counters */
86 : : uint64 fetch_size;
87 : : uint64 fetch_done;
88 : :
89 : : static PGconn *conn;
90 : : static rewind_source *source;
91 : :
92 : : static void
4199 heikki.linnakangas@i 93 :CBC 1 : usage(const char *progname)
94 : : {
4108 peter_e@gmx.net 95 : 1 : printf(_("%s resynchronizes a PostgreSQL cluster with another copy of the cluster.\n\n"), progname);
4199 heikki.linnakangas@i 96 : 1 : printf(_("Usage:\n %s [OPTION]...\n\n"), progname);
97 : 1 : printf(_("Options:\n"));
856 peter@eisentraut.org 98 : 1 : printf(_(" -c, --restore-target-wal use \"restore_command\" in target configuration to\n"
99 : : " retrieve WAL files from archives\n"));
4108 peter_e@gmx.net 100 : 1 : printf(_(" -D, --target-pgdata=DIRECTORY existing data directory to modify\n"));
4022 101 : 1 : printf(_(" --source-pgdata=DIRECTORY source data directory to synchronize with\n"));
102 : 1 : printf(_(" --source-server=CONNSTR source server to synchronize with\n"));
4108 103 : 1 : printf(_(" -n, --dry-run stop before modifying anything\n"));
2701 alvherre@alvh.no-ip. 104 : 1 : printf(_(" -N, --no-sync do not wait for changes to be written\n"
105 : : " safely to disk\n"));
4108 peter_e@gmx.net 106 : 1 : printf(_(" -P, --progress write progress messages\n"));
2333 peter@eisentraut.org 107 : 1 : printf(_(" -R, --write-recovery-conf write configuration for replication\n"
108 : : " (requires --source-server)\n"));
1623 109 : 1 : printf(_(" --config-file=FILENAME use specified main server configuration\n"
110 : : " file when running target cluster\n"));
4108 peter_e@gmx.net 111 : 1 : printf(_(" --debug write a lot of debug messages\n"));
2333 peter@eisentraut.org 112 : 1 : printf(_(" --no-ensure-shutdown do not automatically fix unclean shutdown\n"));
1110 nathan@postgresql.or 113 : 1 : printf(_(" --sync-method=METHOD set method for syncing files to disk\n"));
4108 peter_e@gmx.net 114 : 1 : printf(_(" -V, --version output version information, then exit\n"));
115 : 1 : printf(_(" -?, --help show this help, then exit\n"));
2396 peter@eisentraut.org 116 : 1 : printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
117 : 1 : printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
4199 heikki.linnakangas@i 118 : 1 : }
119 : :
120 : :
121 : : int
122 : 28 : main(int argc, char **argv)
123 : : {
124 : : static struct option long_options[] = {
125 : : {"help", no_argument, NULL, '?'},
126 : : {"target-pgdata", required_argument, NULL, 'D'},
127 : : {"write-recovery-conf", no_argument, NULL, 'R'},
128 : : {"source-pgdata", required_argument, NULL, 1},
129 : : {"source-server", required_argument, NULL, 2},
130 : : {"no-ensure-shutdown", no_argument, NULL, 4},
131 : : {"config-file", required_argument, NULL, 5},
132 : : {"version", no_argument, NULL, 'V'},
133 : : {"restore-target-wal", no_argument, NULL, 'c'},
134 : : {"dry-run", no_argument, NULL, 'n'},
135 : : {"no-sync", no_argument, NULL, 'N'},
136 : : {"progress", no_argument, NULL, 'P'},
137 : : {"debug", no_argument, NULL, 3},
138 : : {"sync-method", required_argument, NULL, 6},
139 : : {NULL, 0, NULL, 0}
140 : : };
141 : : int option_index;
142 : : int c;
143 : : XLogRecPtr divergerec;
144 : : int lastcommontliIndex;
145 : : XLogRecPtr chkptrec;
146 : : TimeLineID chkpttli;
147 : : XLogRecPtr chkptredo;
148 : : uint32 chkptdatachecksums;
149 : : TimeLineID source_tli;
150 : : TimeLineID target_tli;
151 : : XLogRecPtr target_wal_endrec;
152 : : XLogSegNo last_common_segno;
153 : : size_t size;
154 : : char *buffer;
2550 alvherre@alvh.no-ip. 155 : 28 : bool no_ensure_shutdown = false;
156 : : bool rewind_needed;
2547 157 : 28 : bool writerecoveryconf = false;
158 : : filemap_t *filemap;
159 : :
2729 peter@eisentraut.org 160 : 28 : pg_logging_init(argv[0]);
4184 heikki.linnakangas@i 161 : 28 : set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_rewind"));
4199 162 : 28 : progname = get_progname(argv[0]);
163 : :
164 : : /* Process command-line arguments */
165 [ + - ]: 28 : if (argc > 1)
166 : : {
167 [ + + - + ]: 28 : if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
168 : : {
169 : 1 : usage(progname);
170 : 1 : exit(0);
171 : : }
172 [ + + - + ]: 27 : if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
173 : : {
174 : 1 : puts("pg_rewind (PostgreSQL) " PG_VERSION);
175 : 1 : exit(0);
176 : : }
177 : : }
178 : :
2363 michael@paquier.xyz 179 [ + + ]: 137 : while ((c = getopt_long(argc, argv, "cD:nNPR", long_options, &option_index)) != -1)
180 : : {
4199 heikki.linnakangas@i 181 [ + - + + : 112 : switch (c)
+ + + + +
+ + - + ]
182 : : {
2363 michael@paquier.xyz 183 : 1 : case 'c':
184 : 1 : restore_wal = true;
185 : 1 : break;
186 : :
4199 heikki.linnakangas@i 187 :UBC 0 : case 'P':
188 : 0 : showprogress = true;
189 : 0 : break;
190 : :
4199 heikki.linnakangas@i 191 :CBC 1 : case 'n':
192 : 1 : dry_run = true;
193 : 1 : break;
194 : :
2994 michael@paquier.xyz 195 : 18 : case 'N':
196 : 18 : do_sync = false;
197 : 18 : break;
198 : :
2547 alvherre@alvh.no-ip. 199 : 6 : case 'R':
200 : 6 : writerecoveryconf = true;
201 : 6 : break;
202 : :
4199 heikki.linnakangas@i 203 : 22 : case 3:
204 : 22 : debug = true;
2194 tgl@sss.pgh.pa.us 205 : 22 : pg_logging_increase_verbosity();
4199 heikki.linnakangas@i 206 : 22 : break;
207 : :
208 : 25 : case 'D': /* -D or --target-pgdata */
209 : 25 : datadir_target = pg_strdup(optarg);
210 : 25 : break;
211 : :
212 : 16 : case 1: /* --source-pgdata */
213 : 16 : datadir_source = pg_strdup(optarg);
214 : 16 : break;
215 : :
216 : 9 : case 2: /* --source-server */
217 : 9 : connstr_source = pg_strdup(optarg);
218 : 9 : break;
219 : :
2550 alvherre@alvh.no-ip. 220 : 3 : case 4:
221 : 3 : no_ensure_shutdown = true;
222 : 3 : break;
223 : :
1627 michael@paquier.xyz 224 : 10 : case 5:
225 : 10 : config_file = pg_strdup(optarg);
226 : 10 : break;
227 : :
1110 nathan@postgresql.or 228 :UBC 0 : case 6:
229 [ # # ]: 0 : if (!parse_sync_method(optarg, &sync_method))
230 : 0 : exit(1);
231 : 0 : break;
232 : :
1626 tgl@sss.pgh.pa.us 233 :CBC 1 : default:
234 : : /* getopt_long already emitted a complaint */
235 : 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
236 : 1 : exit(1);
237 : : }
238 : : }
239 : :
4199 heikki.linnakangas@i 240 [ + + + + ]: 25 : if (datadir_source == NULL && connstr_source == NULL)
241 : : {
2729 peter@eisentraut.org 242 : 1 : pg_log_error("no source specified (--source-pgdata or --source-server)");
1626 tgl@sss.pgh.pa.us 243 : 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4199 heikki.linnakangas@i 244 : 1 : exit(1);
245 : : }
246 : :
3635 247 [ + + + + ]: 24 : if (datadir_source != NULL && connstr_source != NULL)
248 : : {
2729 peter@eisentraut.org 249 : 1 : pg_log_error("only one of --source-pgdata or --source-server can be specified");
1626 tgl@sss.pgh.pa.us 250 : 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
3635 heikki.linnakangas@i 251 : 1 : exit(1);
252 : : }
253 : :
4199 254 [ - + ]: 23 : if (datadir_target == NULL)
255 : : {
2729 peter@eisentraut.org 256 :UBC 0 : pg_log_error("no target data directory specified (--target-pgdata)");
1626 tgl@sss.pgh.pa.us 257 : 0 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4199 heikki.linnakangas@i 258 : 0 : exit(1);
259 : : }
260 : :
2547 alvherre@alvh.no-ip. 261 [ + + + + ]:CBC 23 : if (writerecoveryconf && connstr_source == NULL)
262 : : {
2335 peter@eisentraut.org 263 : 1 : pg_log_error("no source server information (--source-server) specified for --write-recovery-conf");
1626 tgl@sss.pgh.pa.us 264 : 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
2547 alvherre@alvh.no-ip. 265 : 1 : exit(1);
266 : : }
267 : :
4108 peter_e@gmx.net 268 [ + + ]: 22 : if (optind < argc)
269 : : {
2729 peter@eisentraut.org 270 : 1 : pg_log_error("too many command-line arguments (first is \"%s\")",
271 : : argv[optind]);
1626 tgl@sss.pgh.pa.us 272 : 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4199 heikki.linnakangas@i 273 : 1 : exit(1);
274 : : }
275 : :
276 : : /*
277 : : * Don't allow pg_rewind to be run as root, to avoid overwriting the
278 : : * ownership of files in the data directory. We need only check for root
279 : : * -- any other user won't have sufficient permissions to modify files in
280 : : * the data directory.
281 : : */
282 : : #ifndef WIN32
4184 283 [ - + ]: 21 : if (geteuid() == 0)
284 : : {
2729 peter@eisentraut.org 285 :UBC 0 : pg_log_error("cannot be executed by \"root\"");
1626 tgl@sss.pgh.pa.us 286 : 0 : pg_log_error_hint("You must run %s as the PostgreSQL superuser.",
287 : : progname);
3086 magnus@hagander.net 288 : 0 : exit(1);
289 : : }
290 : : #endif
291 : :
2729 peter@eisentraut.org 292 :CBC 21 : get_restricted_token();
293 : :
294 : : /* Set mask based on PGDATA permissions */
3042 tgl@sss.pgh.pa.us 295 [ - + ]: 21 : if (!GetDataDirectoryCreatePerm(datadir_target))
1626 tgl@sss.pgh.pa.us 296 :UBC 0 : pg_fatal("could not read permissions of directory \"%s\": %m",
297 : : datadir_target);
298 : :
3042 tgl@sss.pgh.pa.us 299 :CBC 21 : umask(pg_mode_mask);
300 : :
2363 michael@paquier.xyz 301 : 21 : getRestoreCommand(argv[0]);
302 : :
2547 alvherre@alvh.no-ip. 303 : 21 : atexit(disconnect_atexit);
304 : :
305 : : /* Ok, we have all the options and we're ready to start. */
306 alvherre@kurilemu.de 306 [ + + ]: 21 : if (dry_run)
307 : : {
81 peter@eisentraut.org 308 : 1 : pg_log_info("executing in dry-run mode");
309 : 1 : pg_log_info_detail("The target directory will not be modified.");
310 : : }
311 : :
312 : : /* First, connect to remote server. */
2146 heikki.linnakangas@i 313 [ + + ]: 21 : if (connstr_source)
314 : : {
315 : 8 : conn = PQconnectdb(connstr_source);
316 : :
317 [ - + ]: 8 : if (PQstatus(conn) == CONNECTION_BAD)
2143 peter@eisentraut.org 318 :UBC 0 : pg_fatal("%s", PQerrorMessage(conn));
319 : :
2146 heikki.linnakangas@i 320 [ - + ]:CBC 8 : if (showprogress)
2146 heikki.linnakangas@i 321 :UBC 0 : pg_log_info("connected to server");
322 : :
2146 heikki.linnakangas@i 323 :CBC 8 : source = init_libpq_source(conn);
324 : : }
325 : : else
326 : 13 : source = init_local_source(datadir_source);
327 : :
328 : : /*
329 : : * Check the status of the target instance.
330 : : *
331 : : * If the target instance was not cleanly shut down, start and stop the
332 : : * target cluster once in single-user mode to enforce recovery to finish,
333 : : * ensuring that the cluster can be used by pg_rewind. Note that if
334 : : * no_ensure_shutdown is specified, pg_rewind ignores this step, and users
335 : : * need to make sure by themselves that the target cluster is in a clean
336 : : * state.
337 : : */
531 fujii@postgresql.org 338 : 21 : buffer = slurpFile(datadir_target, XLOG_CONTROL_FILE, &size);
2146 heikki.linnakangas@i 339 : 21 : digestControlFile(&ControlFile_target, buffer, size);
340 : 21 : pg_free(buffer);
341 : :
2550 alvherre@alvh.no-ip. 342 [ + + ]: 21 : if (!no_ensure_shutdown &&
343 [ + + ]: 18 : ControlFile_target.state != DB_SHUTDOWNED &&
344 [ + + ]: 11 : ControlFile_target.state != DB_SHUTDOWNED_IN_RECOVERY)
345 : : {
346 : 10 : ensureCleanShutdown(argv[0]);
347 : :
531 fujii@postgresql.org 348 : 9 : buffer = slurpFile(datadir_target, XLOG_CONTROL_FILE, &size);
2550 alvherre@alvh.no-ip. 349 : 9 : digestControlFile(&ControlFile_target, buffer, size);
350 : 9 : pg_free(buffer);
351 : : }
352 : :
531 fujii@postgresql.org 353 : 20 : buffer = source->fetch_file(source, XLOG_CONTROL_FILE, &size);
4199 heikki.linnakangas@i 354 : 20 : digestControlFile(&ControlFile_source, buffer, size);
355 : 20 : pg_free(buffer);
356 : :
357 : 20 : sanityChecks();
358 : :
359 : : /*
360 : : * Usually, the TLI can be found in the latest checkpoint record. But if
361 : : * the source server is just being promoted (or it's a standby that's
362 : : * following a primary that's just being promoted), and the checkpoint
363 : : * requested by the promotion hasn't completed yet, the latest timeline is
364 : : * in minRecoveryPoint. So we check which is later, the TLI of the
365 : : * minRecoveryPoint or the latest checkpoint.
366 : : */
1305 367 : 18 : source_tli = Max(ControlFile_source.minRecoveryPointTLI,
368 : : ControlFile_source.checkPointCopy.ThisTimeLineID);
369 : :
370 : : /* Similarly for the target. */
371 : 18 : target_tli = Max(ControlFile_target.minRecoveryPointTLI,
372 : : ControlFile_target.checkPointCopy.ThisTimeLineID);
373 : :
374 : : /*
375 : : * Find the common ancestor timeline between the clusters.
376 : : *
377 : : * If both clusters are already on the same timeline, there's nothing to
378 : : * do.
379 : : */
380 [ + + ]: 18 : if (target_tli == source_tli)
381 : : {
2729 peter@eisentraut.org 382 : 1 : pg_log_info("source and target cluster are on the same timeline");
3944 peter_e@gmx.net 383 : 1 : rewind_needed = false;
234 alvherre@kurilemu.de 384 : 1 : target_wal_endrec = InvalidXLogRecPtr;
385 : : }
386 : : else
387 : : {
388 : : XLogRecPtr chkptendrec;
389 : : TimeLineHistoryEntry *sourceHistory;
390 : : int sourceNentries;
391 : :
392 : : /*
393 : : * Retrieve timelines for both source and target, and find the point
394 : : * where they diverged.
395 : : */
1305 heikki.linnakangas@i 396 : 17 : sourceHistory = getTimelineHistory(source_tli, true, &sourceNentries);
397 : 17 : targetHistory = getTimelineHistory(target_tli, false, &targetNentries);
398 : :
399 : 17 : findCommonAncestorTimeline(sourceHistory, sourceNentries,
400 : : targetHistory, targetNentries,
401 : : &divergerec, &lastcommontliIndex);
402 : :
440 alvherre@kurilemu.de 403 : 17 : pg_log_info("servers diverged at WAL location %X/%08X on timeline %u",
404 : : LSN_FORMAT_ARGS(divergerec),
405 : : targetHistory[lastcommontliIndex].tli);
406 : :
407 : : /*
408 : : * Convert the divergence LSN to a segment number, that will be used
409 : : * to decide how WAL segments should be processed.
410 : : */
330 michael@paquier.xyz 411 : 17 : XLByteToSeg(divergerec, last_common_segno, ControlFile_target.xlog_seg_size);
412 : :
413 : : /*
414 : : * Don't need the source history anymore. The target history is still
415 : : * needed by the routines in parsexlog.c, when we read the target WAL.
416 : : */
1305 heikki.linnakangas@i 417 : 17 : pfree(sourceHistory);
418 : :
419 : :
420 : : /*
421 : : * Determine the end-of-WAL on the target.
422 : : *
423 : : * The WAL ends at the last shutdown checkpoint, or at
424 : : * minRecoveryPoint if it was a standby. (If we supported rewinding a
425 : : * server that was not shut down cleanly, we would need to replay
426 : : * until we reach the first invalid record, like crash recovery does.)
427 : : */
428 : :
429 : : /* read the checkpoint record on the target to see where it ends. */
2117 430 : 17 : chkptendrec = readOneRecord(datadir_target,
431 : : ControlFile_target.checkPoint,
432 : : targetNentries - 1,
433 : : restore_command);
434 : :
435 [ + + ]: 17 : if (ControlFile_target.minRecoveryPoint > chkptendrec)
436 : : {
437 : 1 : target_wal_endrec = ControlFile_target.minRecoveryPoint;
438 : : }
439 : : else
440 : : {
441 : 16 : target_wal_endrec = chkptendrec;
442 : : }
443 : :
444 : : /*
445 : : * Check for the possibility that the target is in fact a direct
446 : : * ancestor of the source. In that case, there is no divergent history
447 : : * in the target that needs rewinding.
448 : : */
449 [ + - ]: 17 : if (target_wal_endrec > divergerec)
450 : : {
4199 451 : 17 : rewind_needed = true;
452 : : }
453 : : else
454 : : {
455 : : /* the last common checkpoint record must be part of target WAL */
2117 heikki.linnakangas@i 456 [ # # ]:UBC 0 : Assert(target_wal_endrec == divergerec);
457 : :
458 : 0 : rewind_needed = false;
459 : : }
460 : : }
461 : :
4199 heikki.linnakangas@i 462 [ + + ]:CBC 18 : if (!rewind_needed)
463 : : {
2729 peter@eisentraut.org 464 : 1 : pg_log_info("no rewind required");
2543 michael@paquier.xyz 465 [ - + - - ]: 1 : if (writerecoveryconf && !dry_run)
2547 alvherre@alvh.no-ip. 466 :UBC 0 : WriteRecoveryConfig(conn, datadir_target,
467 : : GenerateRecoveryConfig(conn, NULL,
468 : : GetDbnameFromConnectionOptions(connstr_source)));
4199 heikki.linnakangas@i 469 :CBC 1 : exit(0);
470 : : }
471 : :
472 : : /* Initialize hashtable that tracks WAL files protected from removal */
674 alvherre@alvh.no-ip. 473 : 17 : keepwal_init();
474 : :
2363 michael@paquier.xyz 475 : 17 : findLastCheckpoint(datadir_target, divergerec, lastcommontliIndex,
476 : : &chkptrec, &chkpttli, &chkptredo, &chkptdatachecksums,
477 : : restore_command);
440 alvherre@kurilemu.de 478 : 17 : pg_log_info("rewinding from last common checkpoint at %X/%08X on timeline %u",
479 : : LSN_FORMAT_ARGS(chkptrec), chkpttli);
480 : :
481 : : /*
482 : : * Replay on the rewound server resumes from the last common checkpoint
483 : : * and adopts the data checksum state recorded there, not the state in the
484 : : * control file installed from the source. If checksums were enabled at
485 : : * the divergence point but the source runs without them, the rewound
486 : : * server would verify checksums while the blocks copied from the source
487 : : * have none. The control files cannot reveal this: an offline disable on
488 : : * the source after the divergence leaves both of them saying "off".
489 : : *
490 : : * Only the fully enabled state needs checking. An in-progress state at
491 : : * the divergence point means a transition was still running there, and
492 : : * all of its page rewrites are logged after that point, so replay brings
493 : : * the rewound server to whatever state the copied WAL ends in.
494 : : */
6 dgustafsson@postgres 495 [ + + ]:GNC 17 : if (chkptdatachecksums == PG_DATA_CHECKSUM_VERSION &&
496 [ - + ]: 16 : ControlFile_source.data_checksum_version != PG_DATA_CHECKSUM_VERSION)
497 : : {
6 dgustafsson@postgres 498 :UNC 0 : pg_log_error("data checksums were enabled at the point of divergence but are disabled on the source server");
499 : 0 : pg_log_error_detail("Blocks copied from the source would have no checksums, but the rewound server would resume with checksum verification enabled.");
500 : 0 : pg_log_error_hint("Either enable data checksums on the source server or recreate the target server from a base backup.");
501 : 0 : exit(1);
502 : : }
503 : :
504 : : /*
505 : : * The same comparison is needed against the target: every block the
506 : : * rewind does not copy keeps the target's content. The control files
507 : : * cannot reveal this case either, in the other direction: a standby's
508 : : * checkpoints are written by its upstream primary, so a standby whose
509 : : * checksums were disabled offline still has "on" checkpoints in its WAL,
510 : : * while both control files may agree.
511 : : */
6 dgustafsson@postgres 512 [ + + ]:GNC 17 : if (chkptdatachecksums == PG_DATA_CHECKSUM_VERSION &&
513 [ - + ]: 16 : ControlFile_target.data_checksum_version != PG_DATA_CHECKSUM_VERSION)
514 : : {
6 dgustafsson@postgres 515 :UNC 0 : pg_log_error("data checksums were enabled at the point of divergence but are disabled on the target server");
516 : 0 : pg_log_error_detail("Blocks kept from the target would have no checksums, but the rewound server would resume with checksum verification enabled.");
517 : 0 : pg_log_error_hint("Either enable data checksums on the target server with pg_checksums or recreate the target server from a base backup.");
518 : 0 : exit(1);
519 : : }
520 : :
521 : : /* Initialize the hash table to track the status of each file */
2146 heikki.linnakangas@i 522 :CBC 17 : filehash_init();
523 : :
524 : : /*
525 : : * Collect information about all files in the both data directories.
526 : : */
2729 peter@eisentraut.org 527 [ - + ]: 17 : if (showprogress)
2729 peter@eisentraut.org 528 :UBC 0 : pg_log_info("reading source file list");
2146 heikki.linnakangas@i 529 :CBC 17 : source->traverse_files(source, &process_source_file);
530 : :
2729 peter@eisentraut.org 531 [ - + ]: 17 : if (showprogress)
2729 peter@eisentraut.org 532 :UBC 0 : pg_log_info("reading target file list");
4176 heikki.linnakangas@i 533 :CBC 17 : traverse_datadir(datadir_target, &process_target_file);
534 : :
535 : : /*
536 : : * Read the target WAL from last checkpoint before the point of fork, to
537 : : * extract all the pages that were modified on the target cluster after
538 : : * the fork.
539 : : */
2729 peter@eisentraut.org 540 [ - + ]: 17 : if (showprogress)
2729 peter@eisentraut.org 541 :UBC 0 : pg_log_info("reading WAL in target");
3946 teodor@sigaev.ru 542 :CBC 17 : extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
543 : : target_wal_endrec, restore_command);
544 : :
545 : : /*
546 : : * We have collected all information we need from both systems. Decide
547 : : * what to do with each file.
548 : : */
330 michael@paquier.xyz 549 : 17 : filemap = decide_file_actions(last_common_segno);
4199 heikki.linnakangas@i 550 [ - + ]: 17 : if (showprogress)
2146 heikki.linnakangas@i 551 :UBC 0 : calculate_totals(filemap);
552 : :
553 : : /* this is too verbose even for verbose mode */
4199 heikki.linnakangas@i 554 [ + + ]:CBC 17 : if (debug)
2146 555 : 15 : print_filemap(filemap);
556 : :
557 : : /*
558 : : * Ok, we're ready to start copying things over.
559 : : */
4199 560 [ - + ]: 17 : if (showprogress)
561 : : {
285 peter@eisentraut.org 562 :UBC 0 : pg_log_info("need to copy %" PRIu64 " MB (total source directory size is %" PRIu64 " MB)",
563 : : filemap->fetch_size / (1024 * 1024),
564 : : filemap->total_size / (1024 * 1024));
565 : :
4199 heikki.linnakangas@i 566 : 0 : fetch_size = filemap->fetch_size;
567 : 0 : fetch_done = 0;
568 : : }
569 : :
570 : : /*
571 : : * We have now collected all the information we need from both systems,
572 : : * and we are ready to start modifying the target directory.
573 : : *
574 : : * This is the point of no return. Once we start copying things, there is
575 : : * no turning back!
576 : : */
6 dgustafsson@postgres 577 :GNC 17 : perform_rewind(filemap, source, chkptrec, chkpttli, chkptredo,
578 : : divergerec);
579 : :
2146 heikki.linnakangas@i 580 [ - + ]:CBC 16 : if (showprogress)
2146 heikki.linnakangas@i 581 :UBC 0 : pg_log_info("syncing target data directory");
2146 heikki.linnakangas@i 582 :CBC 16 : sync_target_dir();
583 : :
584 : : /* Also update the standby configuration, if requested. */
585 [ + + + - ]: 16 : if (writerecoveryconf && !dry_run)
586 : 5 : WriteRecoveryConfig(conn, datadir_target,
587 : : GenerateRecoveryConfig(conn, NULL,
588 : : GetDbnameFromConnectionOptions(connstr_source)));
589 : :
590 : : /* don't need the source connection anymore */
591 : 16 : source->destroy(source);
592 [ + + ]: 16 : if (conn)
593 : : {
594 : 8 : PQfinish(conn);
595 : 8 : conn = NULL;
596 : : }
597 : :
598 : 16 : pg_log_info("Done!");
599 : :
600 : 16 : return 0;
601 : : }
602 : :
603 : : /*
604 : : * Perform the rewind.
605 : : *
606 : : * We have already collected all the information we need from the
607 : : * target and the source.
608 : : */
609 : : static void
610 : 17 : perform_rewind(filemap_t *filemap, rewind_source *source,
611 : : XLogRecPtr chkptrec,
612 : : TimeLineID chkpttli,
613 : : XLogRecPtr chkptredo,
614 : : XLogRecPtr divergerec)
615 : : {
616 : : XLogRecPtr endrec;
617 : : TimeLineID endtli;
618 : : ControlFileData ControlFile_new;
619 : : size_t size;
620 : : char *buffer;
621 : :
622 : : /*
623 : : * Execute the actions in the file map, fetching data from the source
624 : : * system as needed.
625 : : */
626 [ + + ]: 18985 : for (int i = 0; i < filemap->nentries; i++)
627 : : {
628 : 18969 : file_entry_t *entry = filemap->entries[i];
629 : :
630 : : /*
631 : : * If this is a relation file, copy the modified blocks.
632 : : *
633 : : * This is in addition to any other changes.
634 : : */
635 [ + + ]: 18969 : if (entry->target_pages_to_overwrite.bitmapsize > 0)
636 : : {
637 : : datapagemap_iterator_t *iter;
638 : : BlockNumber blkno;
639 : : off_t offset;
640 : :
641 : 424 : iter = datapagemap_iterate(&entry->target_pages_to_overwrite);
642 [ + + ]: 2104 : while (datapagemap_next(iter, &blkno))
643 : : {
644 : 1680 : offset = blkno * BLCKSZ;
645 : 1680 : source->queue_fetch_range(source, entry->path, offset, BLCKSZ);
646 : : }
647 : 424 : pg_free(iter);
648 : : }
649 : :
650 [ + + + + : 18969 : switch (entry->action)
+ + - - ]
651 : : {
652 : 13020 : case FILE_ACTION_NONE:
653 : : /* nothing else to do */
654 : 13020 : break;
655 : :
656 : 5193 : case FILE_ACTION_COPY:
1629 dgustafsson@postgres 657 : 5193 : source->queue_fetch_file(source, entry->path, entry->source_size);
2146 heikki.linnakangas@i 658 : 5192 : break;
659 : :
660 : 5 : case FILE_ACTION_TRUNCATE:
661 : 5 : truncate_target_file(entry->path, entry->source_size);
662 : 5 : break;
663 : :
664 : 6 : case FILE_ACTION_COPY_TAIL:
665 : 6 : source->queue_fetch_range(source, entry->path,
666 : 6 : entry->target_size,
667 : 6 : entry->source_size - entry->target_size);
668 : 6 : break;
669 : :
670 : 736 : case FILE_ACTION_REMOVE:
671 : 736 : remove_target(entry);
672 : 736 : break;
673 : :
674 : 9 : case FILE_ACTION_CREATE:
675 : 9 : create_target(entry);
676 : 9 : break;
677 : :
2146 heikki.linnakangas@i 678 :UBC 0 : case FILE_ACTION_UNDECIDED:
1955 peter@eisentraut.org 679 : 0 : pg_fatal("no action decided for file \"%s\"", entry->path);
680 : : break;
681 : : }
682 : : }
683 : :
684 : : /* Complete any remaining range-fetches that we queued up above. */
2146 heikki.linnakangas@i 685 :CBC 16 : source->finish_fetch(source);
686 : :
687 : 16 : close_target_file();
688 : :
4199 689 : 16 : progress_report(true);
690 : :
691 : : /*
692 : : * Fetch the control file from the source last. This ensures that the
693 : : * minRecoveryPoint is up-to-date.
694 : : */
531 fujii@postgresql.org 695 : 16 : buffer = source->fetch_file(source, XLOG_CONTROL_FILE, &size);
2138 heikki.linnakangas@i 696 : 16 : digestControlFile(&ControlFile_source_after, buffer, size);
697 : 16 : pg_free(buffer);
698 : :
699 : : /*
700 : : * Sanity check: If the source is a local system, the control file should
701 : : * not have changed since we started.
702 : : *
703 : : * XXX: We assume it hasn't been modified, but actually, what could go
704 : : * wrong? The logic handles a libpq source that's modified concurrently,
705 : : * why not a local datadir?
706 : : */
707 [ + + ]: 16 : if (datadir_source &&
708 [ - + ]: 8 : memcmp(&ControlFile_source, &ControlFile_source_after,
709 : : sizeof(ControlFileData)) != 0)
710 : : {
2138 heikki.linnakangas@i 711 :UBC 0 : pg_fatal("source system was modified while pg_rewind was running");
712 : : }
713 : :
2729 peter@eisentraut.org 714 [ - + ]:CBC 16 : if (showprogress)
2729 peter@eisentraut.org 715 :UBC 0 : pg_log_info("creating backup label and updating control file");
716 : :
717 : : /*
718 : : * Create a backup label file, to tell the target where to begin the WAL
719 : : * replay. Normally, from the last common checkpoint between the source
720 : : * and the target. But if the source is a standby server, it's possible
721 : : * that the last common checkpoint is *after* the standby's restartpoint.
722 : : * That implies that the source server has applied the checkpoint record,
723 : : * but hasn't performed a corresponding restartpoint yet. Make sure we
724 : : * start at the restartpoint's redo point in that case.
725 : : *
726 : : * Use the old version of the source's control file for this. The server
727 : : * might have finished the restartpoint after we started copying files,
728 : : * but we must begin from the redo point at the time that started copying.
729 : : */
2138 heikki.linnakangas@i 730 [ + + ]:CBC 16 : if (ControlFile_source.checkPointCopy.redo < chkptredo)
731 : : {
732 : 1 : chkptredo = ControlFile_source.checkPointCopy.redo;
733 : 1 : chkpttli = ControlFile_source.checkPointCopy.ThisTimeLineID;
734 : 1 : chkptrec = ControlFile_source.checkPoint;
735 : : }
736 : 16 : createBackupLabel(chkptredo, chkpttli, chkptrec);
737 : :
738 : : /*
739 : : * Update control file of target, to tell the target how far it must
740 : : * replay the WAL (minRecoveryPoint).
741 : : */
4199 742 [ + + ]: 16 : if (connstr_source)
743 : : {
744 : : /*
745 : : * The source is a live server. Like in an online backup, it's
746 : : * important that we recover all the WAL that was generated while we
747 : : * were copying files.
748 : : */
2138 749 [ + + ]: 8 : if (ControlFile_source_after.state == DB_IN_ARCHIVE_RECOVERY)
750 : : {
751 : : /*
752 : : * Source is a standby server. We must replay to its
753 : : * minRecoveryPoint.
754 : : */
755 : 1 : endrec = ControlFile_source_after.minRecoveryPoint;
756 : 1 : endtli = ControlFile_source_after.minRecoveryPointTLI;
757 : : }
758 : : else
759 : : {
760 : : /*
761 : : * Source is a production, non-standby, server. We must replay to
762 : : * the last WAL insert location.
763 : : */
764 [ - + ]: 7 : if (ControlFile_source_after.state != DB_IN_PRODUCTION)
2138 heikki.linnakangas@i 765 :UBC 0 : pg_fatal("source system was in unexpected state at end of rewind");
766 : :
2138 heikki.linnakangas@i 767 :CBC 7 : endrec = source->get_current_wal_insert_lsn(source);
1305 768 : 7 : endtli = Max(ControlFile_source_after.checkPointCopy.ThisTimeLineID,
769 : : ControlFile_source_after.minRecoveryPointTLI);
770 : : }
771 : : }
772 : : else
773 : : {
774 : : /*
775 : : * Source is a local data directory. It should've shut down cleanly,
776 : : * and we must replay to the latest shutdown checkpoint.
777 : : */
2138 778 : 8 : endrec = ControlFile_source_after.checkPoint;
779 : 8 : endtli = ControlFile_source_after.checkPointCopy.ThisTimeLineID;
780 : : }
781 : :
782 : 16 : memcpy(&ControlFile_new, &ControlFile_source_after, sizeof(ControlFileData));
4199 783 : 16 : ControlFile_new.minRecoveryPoint = endrec;
784 : 16 : ControlFile_new.minRecoveryPointTLI = endtli;
785 : 16 : ControlFile_new.state = DB_IN_ARCHIVE_RECOVERY;
786 : :
787 : : /*
788 : : * Keep the target's own data checksum state. Most of the data directory
789 : : * is still the target's: only blocks it changed since the divergence were
790 : : * copied from the source, so the source's state says nothing about the
791 : : * pages that stay. Replay from the last common checkpoint applies any
792 : : * WAL-logged transition the target has not seen (the watermark tells them
793 : : * apart), which converges the rewound server to the source's state
794 : : * whenever the WAL carries it.
795 : : */
6 dgustafsson@postgres 796 :GNC 16 : ControlFile_new.data_checksum_version = ControlFile_target.data_checksum_version;
797 : 16 : ControlFile_new.data_checksum_lsn = ControlFile_target.data_checksum_lsn;
798 : 16 : ControlFile_new.data_checksum_is_local = ControlFile_target.data_checksum_is_local;
799 : :
800 : : /*
801 : : * The watermark is only meaningful within the history the node replays.
802 : : * Records at or below the divergence point are common to both histories
803 : : * and stay covered, but a watermark above it was set by a transition
804 : : * record on the target's own abandoned fork: numerically it can cover
805 : : * transition records the source wrote after the divergence, and replay
806 : : * would skip them as already applied. Clamp it to the divergence point,
807 : : * so that every transition record on the source's history takes effect.
808 : : */
809 [ - + ]: 16 : if (ControlFile_new.data_checksum_lsn > divergerec)
6 dgustafsson@postgres 810 :UNC 0 : ControlFile_new.data_checksum_lsn = divergerec;
811 : :
2543 michael@paquier.xyz 812 [ + + ]:CBC 16 : if (!dry_run)
813 : 15 : update_controlfile(datadir_target, &ControlFile_new, do_sync);
4199 heikki.linnakangas@i 814 : 16 : }
815 : :
816 : : static void
817 : 20 : sanityChecks(void)
818 : : {
819 : : /* TODO Check that there's no backup_label in either cluster */
820 : :
821 : : /* Check system_identifier match */
822 [ - + ]: 20 : if (ControlFile_target.system_identifier != ControlFile_source.system_identifier)
2729 peter@eisentraut.org 823 :UBC 0 : pg_fatal("source and target clusters are from different systems");
824 : :
825 : : /* check version */
4199 heikki.linnakangas@i 826 [ + - ]:CBC 20 : if (ControlFile_target.pg_control_version != PG_CONTROL_VERSION ||
827 [ + - ]: 20 : ControlFile_source.pg_control_version != PG_CONTROL_VERSION ||
828 [ + - ]: 20 : ControlFile_target.catalog_version_no != CATALOG_VERSION_NO ||
829 [ - + ]: 20 : ControlFile_source.catalog_version_no != CATALOG_VERSION_NO)
830 : : {
2729 peter@eisentraut.org 831 :UBC 0 : pg_fatal("clusters are not compatible with this version of pg_rewind");
832 : : }
833 : :
834 : : /*
835 : : * Target cluster need to use checksums or hint bit wal-logging, this to
836 : : * prevent from data corruption that could occur because of hint bits.
837 : : */
4199 heikki.linnakangas@i 838 [ + + ]:CBC 20 : if (ControlFile_target.data_checksum_version != PG_DATA_CHECKSUM_VERSION &&
4199 heikki.linnakangas@i 839 [ - + ]:GBC 1 : !ControlFile_target.wal_log_hints)
840 : : {
2729 peter@eisentraut.org 841 :UBC 0 : pg_fatal("target server needs to use either data checksums or \"wal_log_hints = on\"");
842 : : }
843 : :
844 : : /*
845 : : * The rewound target keeps its control file fields from the source, but
846 : : * every block the rewind does not copy keeps the target's content. If
847 : : * checksums are enabled on the target and disabled on the source, the
848 : : * result would claim enabled checksums while the blocks copied from the
849 : : * source have none, and the target would fail checksum verification as
850 : : * soon as it reads them. Refuse that combination, and refuse an
851 : : * interrupted online transition on either side, same as pg_checksums.
852 : : *
853 : : * The opposite mismatch is allowed: recovery under the backup label
854 : : * written by pg_rewind adopts the checksum state as of the divergence
855 : : * point, so a target whose own checkpoints carry no checksums stays
856 : : * without them, or converges through the replayed WAL if the source
857 : : * enabled them online. Only warn about it, so that an offline change on
858 : : * the source is not overlooked.
859 : : *
860 : : * That reasoning does not hold when the target is a standby, whose
861 : : * checkpoints were written by its upstream primary; see the checks after
862 : : * findLastCheckpoint().
863 : : */
6 dgustafsson@postgres 864 [ + - ]:GNC 20 : if (ControlFile_target.data_checksum_version == PG_DATA_CHECKSUM_INPROGRESS_ON ||
865 [ - + ]: 20 : ControlFile_target.data_checksum_version == PG_DATA_CHECKSUM_INPROGRESS_OFF)
866 : : {
6 dgustafsson@postgres 867 :UNC 0 : pg_log_error("an online data checksum state transition was interrupted on the target server");
868 : 0 : pg_log_error_hint("Start the server and shut it down cleanly to reset the state, then retry; on a standby, let replication complete the transition first.");
869 : 0 : exit(1);
870 : : }
6 dgustafsson@postgres 871 [ + - ]:GNC 20 : if (ControlFile_source.data_checksum_version == PG_DATA_CHECKSUM_INPROGRESS_ON ||
872 [ - + ]: 20 : ControlFile_source.data_checksum_version == PG_DATA_CHECKSUM_INPROGRESS_OFF)
873 : : {
6 dgustafsson@postgres 874 :UNC 0 : pg_log_error("an online data checksum state transition is incomplete on the source server");
875 : 0 : pg_log_error_hint("Let the transition complete, or reset the state with a clean restart, then retry.");
876 : 0 : exit(1);
877 : : }
6 dgustafsson@postgres 878 [ + + ]:GNC 20 : if (ControlFile_target.data_checksum_version == PG_DATA_CHECKSUM_VERSION &&
879 [ - + ]: 19 : ControlFile_source.data_checksum_version == PG_DATA_CHECKSUM_OFF)
880 : : {
6 dgustafsson@postgres 881 :UNC 0 : pg_log_error("data checksums are enabled on the target server but disabled on the source server");
882 : 0 : pg_log_error_detail("Blocks copied from the source would have no checksums, and the target would fail checksum verification after the rewind.");
883 : 0 : pg_log_error_hint("Either disable data checksums on the target server or enable them on the source server, then retry.");
884 : 0 : exit(1);
885 : : }
6 dgustafsson@postgres 886 [ + + ]:GNC 20 : if (ControlFile_target.data_checksum_version == PG_DATA_CHECKSUM_OFF &&
887 [ + - ]: 1 : ControlFile_source.data_checksum_version == PG_DATA_CHECKSUM_VERSION)
888 : : {
889 : 1 : pg_log_warning("data checksums are disabled on the target server but enabled on the source server");
890 : 1 : pg_log_warning_detail("The rewound server will keep data checksums disabled unless the source server enabled them online after the point of divergence.");
891 : : }
892 : :
893 : : /*
894 : : * Target cluster better not be running. This doesn't guard against
895 : : * someone starting the cluster concurrently. Also, this is probably more
896 : : * strict than necessary; it's OK if the target node was not shut down
897 : : * cleanly, as long as it isn't running at the moment.
898 : : */
3946 teodor@sigaev.ru 899 [ + + ]:CBC 20 : if (ControlFile_target.state != DB_SHUTDOWNED &&
900 [ + + ]: 2 : ControlFile_target.state != DB_SHUTDOWNED_IN_RECOVERY)
2729 peter@eisentraut.org 901 : 1 : pg_fatal("target server must be shut down cleanly");
902 : :
903 : : /*
904 : : * When the source is a data directory, also require that the source
905 : : * server is shut down. There isn't any very strong reason for this
906 : : * limitation, but better safe than sorry.
907 : : */
3946 teodor@sigaev.ru 908 [ + + ]: 19 : if (datadir_source &&
909 [ + + ]: 11 : ControlFile_source.state != DB_SHUTDOWNED &&
910 [ + + ]: 2 : ControlFile_source.state != DB_SHUTDOWNED_IN_RECOVERY)
2729 peter@eisentraut.org 911 : 1 : pg_fatal("source data directory must be shut down cleanly");
4199 heikki.linnakangas@i 912 : 18 : }
913 : :
914 : : /*
915 : : * Print a progress report based on the fetch_size and fetch_done variables.
916 : : *
917 : : * Progress report is written at maximum once per second, except that the
918 : : * last progress report is always printed.
919 : : *
920 : : * If finished is set to true, this is the last progress report. The cursor
921 : : * is moved to the next line.
922 : : */
923 : : void
2225 924 : 52773 : progress_report(bool finished)
925 : : {
926 : : static pg_time_t last_progress_report = 0;
927 : : int percent;
928 : : char fetch_done_str[32];
929 : : char fetch_size_str[32];
930 : : pg_time_t now;
931 : :
2686 tgl@sss.pgh.pa.us 932 [ + - ]: 52773 : if (!showprogress)
933 : 52773 : return;
934 : :
2686 tgl@sss.pgh.pa.us 935 :UBC 0 : now = time(NULL);
2225 heikki.linnakangas@i 936 [ # # # # ]: 0 : if (now == last_progress_report && !finished)
2686 tgl@sss.pgh.pa.us 937 : 0 : return; /* Max once per second */
938 : :
939 : 0 : last_progress_report = now;
940 [ # # ]: 0 : percent = fetch_size ? (int) ((fetch_done) * 100 / fetch_size) : 0;
941 : :
942 : : /*
943 : : * Avoid overflowing past 100% or the full size. This may make the total
944 : : * size number change as we approach the end of the backup (the estimate
945 : : * will always be wrong if WAL is included), but that's better than having
946 : : * the done column be bigger than the total.
947 : : */
948 [ # # ]: 0 : if (percent > 100)
949 : 0 : percent = 100;
950 [ # # ]: 0 : if (fetch_done > fetch_size)
951 : 0 : fetch_size = fetch_done;
952 : :
1831 peter@eisentraut.org 953 : 0 : snprintf(fetch_done_str, sizeof(fetch_done_str), UINT64_FORMAT,
954 : : fetch_done / 1024);
955 : 0 : snprintf(fetch_size_str, sizeof(fetch_size_str), UINT64_FORMAT,
956 : : fetch_size / 1024);
957 : :
2686 tgl@sss.pgh.pa.us 958 : 0 : fprintf(stderr, _("%*s/%s kB (%d%%) copied"),
2678 959 : 0 : (int) strlen(fetch_size_str), fetch_done_str, fetch_size_str,
960 : : percent);
961 : :
962 : : /*
963 : : * Stay on the same line if reporting to a terminal and we're not done
964 : : * yet.
965 : : */
2224 heikki.linnakangas@i 966 [ # # # # ]: 0 : fputc((!finished && isatty(fileno(stderr))) ? '\r' : '\n', stderr);
967 : : }
968 : :
969 : : /*
970 : : * Find minimum from two WAL locations assuming InvalidXLogRecPtr means
971 : : * infinity as src/include/access/timeline.h states. This routine should
972 : : * be used only when comparing WAL locations related to history files.
973 : : */
974 : : static XLogRecPtr
3946 teodor@sigaev.ru 975 :CBC 17 : MinXLogRecPtr(XLogRecPtr a, XLogRecPtr b)
976 : : {
318 alvherre@kurilemu.de 977 [ + + ]: 17 : if (!XLogRecPtrIsValid(a))
3946 teodor@sigaev.ru 978 : 1 : return b;
318 alvherre@kurilemu.de 979 [ + - ]: 16 : else if (!XLogRecPtrIsValid(b))
3946 teodor@sigaev.ru 980 : 16 : return a;
981 : : else
3946 teodor@sigaev.ru 982 :UBC 0 : return Min(a, b);
983 : : }
984 : :
985 : : /*
986 : : * Retrieve timeline history for the source or target system.
987 : : */
988 : : static TimeLineHistoryEntry *
1305 heikki.linnakangas@i 989 :CBC 34 : getTimelineHistory(TimeLineID tli, bool is_source, int *nentries)
990 : : {
991 : : TimeLineHistoryEntry *history;
992 : :
993 : : /*
994 : : * Timeline 1 does not have a history file, so there is no need to check
995 : : * and fake an entry with infinite start and end positions.
996 : : */
3946 teodor@sigaev.ru 997 [ + + ]: 34 : if (tli == 1)
998 : : {
205 michael@paquier.xyz 999 : 16 : history = pg_malloc_object(TimeLineHistoryEntry);
3946 teodor@sigaev.ru 1000 : 16 : history->tli = tli;
1001 : 16 : history->begin = history->end = InvalidXLogRecPtr;
1002 : 16 : *nentries = 1;
1003 : : }
1004 : : else
1005 : : {
1006 : : char path[MAXPGPATH];
1007 : : char *histfile;
1008 : :
1009 : 18 : TLHistoryFilePath(path, tli);
1010 : :
1011 : : /* Get history file from appropriate source */
1305 heikki.linnakangas@i 1012 [ + + ]: 18 : if (is_source)
2146 1013 : 16 : histfile = source->fetch_file(source, path, NULL);
1014 : : else
1305 1015 : 2 : histfile = slurpFile(datadir_target, path, NULL);
1016 : :
3946 teodor@sigaev.ru 1017 : 18 : history = rewind_parseTimeLineHistory(histfile, tli, nentries);
4199 heikki.linnakangas@i 1018 : 18 : pg_free(histfile);
1019 : : }
1020 : :
1021 : : /* In debugging mode, print what we read */
3946 teodor@sigaev.ru 1022 [ + + ]: 34 : if (debug)
1023 : : {
1024 : : int i;
1025 : :
1305 heikki.linnakangas@i 1026 [ + + ]: 30 : if (is_source)
2729 peter@eisentraut.org 1027 [ + - ]: 15 : pg_log_debug("Source timeline history:");
1028 : : else
1305 heikki.linnakangas@i 1029 [ + - ]: 15 : pg_log_debug("Target timeline history:");
1030 : :
773 1031 [ + + ]: 77 : for (i = 0; i < *nentries; i++)
1032 : : {
1033 : : TimeLineHistoryEntry *entry;
1034 : :
3946 teodor@sigaev.ru 1035 : 47 : entry = &history[i];
440 alvherre@kurilemu.de 1036 [ + - ]: 47 : pg_log_debug("%u: %X/%08X - %X/%08X", entry->tli,
1037 : : LSN_FORMAT_ARGS(entry->begin),
1038 : : LSN_FORMAT_ARGS(entry->end));
1039 : : }
1040 : : }
1041 : :
3946 teodor@sigaev.ru 1042 : 34 : return history;
1043 : : }
1044 : :
1045 : : /*
1046 : : * Determine the TLI of the last common timeline in the timeline history of
1047 : : * two clusters. *tliIndex is set to the index of last common timeline in
1048 : : * the arrays, and *recptr is set to the position where the timeline history
1049 : : * diverged (ie. the first WAL record that's not the same in both clusters).
1050 : : */
1051 : : static void
1305 heikki.linnakangas@i 1052 : 17 : findCommonAncestorTimeline(TimeLineHistoryEntry *a_history, int a_nentries,
1053 : : TimeLineHistoryEntry *b_history, int b_nentries,
1054 : : XLogRecPtr *recptr, int *tliIndex)
1055 : : {
1056 : : int i,
1057 : : n;
1058 : :
1059 : : /*
1060 : : * Trace the history forward, until we hit the timeline diverge. It may
1061 : : * still be possible that the source and target nodes used the same
1062 : : * timeline number in their history but with different start position
1063 : : * depending on the history files that each node has fetched in previous
1064 : : * recovery processes. Hence check the start position of the new timeline
1065 : : * as well and move down by one extra timeline entry if they do not match.
1066 : : */
1067 : 17 : n = Min(a_nentries, b_nentries);
3946 teodor@sigaev.ru 1068 [ + + ]: 35 : for (i = 0; i < n; i++)
1069 : : {
1305 heikki.linnakangas@i 1070 [ + - ]: 18 : if (a_history[i].tli != b_history[i].tli ||
1071 [ + - ]: 18 : a_history[i].begin != b_history[i].begin)
1072 : : break;
1073 : : }
1074 : :
3946 teodor@sigaev.ru 1075 [ + - ]: 17 : if (i > 0)
1076 : : {
1077 : 17 : i--;
1305 heikki.linnakangas@i 1078 : 17 : *recptr = MinXLogRecPtr(a_history[i].end, b_history[i].end);
3946 teodor@sigaev.ru 1079 : 17 : *tliIndex = i;
1080 : 17 : return;
1081 : : }
1082 : : else
1083 : : {
2729 peter@eisentraut.org 1084 :UBC 0 : pg_fatal("could not find common ancestor of the source and target cluster's timelines");
1085 : : }
1086 : : }
1087 : :
1088 : :
1089 : : /*
1090 : : * Create a backup_label file that forces recovery to begin at the last common
1091 : : * checkpoint.
1092 : : */
1093 : : static void
4199 heikki.linnakangas@i 1094 :CBC 16 : createBackupLabel(XLogRecPtr startpoint, TimeLineID starttli, XLogRecPtr checkpointloc)
1095 : : {
1096 : : XLogSegNo startsegno;
1097 : : time_t stamp_time;
1098 : : char strfbuf[128];
1099 : : char xlogfilename[MAXFNAMELEN];
1100 : : struct tm *tmp;
1101 : : char buf[1000];
1102 : : int len;
1103 : :
3288 andres@anarazel.de 1104 : 16 : XLByteToSeg(startpoint, startsegno, WalSegSz);
1105 : 16 : XLogFileName(xlogfilename, starttli, startsegno, WalSegSz);
1106 : :
1107 : : /*
1108 : : * Construct backup label file
1109 : : */
4199 heikki.linnakangas@i 1110 : 16 : stamp_time = time(NULL);
1111 : 16 : tmp = localtime(&stamp_time);
1112 : 16 : strftime(strfbuf, sizeof(strfbuf), "%Y-%m-%d %H:%M:%S %Z", tmp);
1113 : :
1114 : 16 : len = snprintf(buf, sizeof(buf),
1115 : : "START WAL LOCATION: %X/%08X (file %s)\n"
1116 : : "CHECKPOINT LOCATION: %X/%08X\n"
1117 : : "BACKUP METHOD: pg_rewind\n"
1118 : : "BACKUP FROM: standby\n"
1119 : : "START TIME: %s\n",
1120 : : /* omit LABEL: line */
2035 peter@eisentraut.org 1121 : 16 : LSN_FORMAT_ARGS(startpoint), xlogfilename,
1122 : 16 : LSN_FORMAT_ARGS(checkpointloc),
1123 : : strfbuf);
4199 heikki.linnakangas@i 1124 [ - + ]: 16 : if (len >= sizeof(buf))
2729 peter@eisentraut.org 1125 :UBC 0 : pg_fatal("backup label buffer too small"); /* shouldn't happen */
1126 : :
1127 : : /* TODO: move old file out of the way, if any. */
3378 tgl@sss.pgh.pa.us 1128 :CBC 16 : open_target_file("backup_label", true); /* BACKUP_LABEL_FILE */
4199 heikki.linnakangas@i 1129 : 16 : write_target_range(buf, 0, len);
3829 andres@anarazel.de 1130 : 16 : close_target_file();
4199 heikki.linnakangas@i 1131 : 16 : }
1132 : :
1133 : : /*
1134 : : * Check CRC of control file
1135 : : */
1136 : : static void
1137 : 66 : checkControlFile(ControlFileData *ControlFile)
1138 : : {
1139 : : pg_crc32c crc;
1140 : :
1141 : : /* Calculate CRC */
1142 : 66 : INIT_CRC32C(crc);
577 peter@eisentraut.org 1143 : 66 : COMP_CRC32C(crc, ControlFile, offsetof(ControlFileData, crc));
4199 heikki.linnakangas@i 1144 : 66 : FIN_CRC32C(crc);
1145 : :
1146 : : /* And simply compare it */
1147 [ - + ]: 66 : if (!EQ_CRC32C(crc, ControlFile->crc))
2729 peter@eisentraut.org 1148 :UBC 0 : pg_fatal("unexpected control file CRC");
4199 heikki.linnakangas@i 1149 :CBC 66 : }
1150 : :
1151 : : /*
1152 : : * Verify control file contents in the buffer 'content', and copy it to
1153 : : * *ControlFile.
1154 : : */
1155 : : static void
2146 1156 : 66 : digestControlFile(ControlFileData *ControlFile, const char *content,
1157 : : size_t size)
1158 : : {
3350 tgl@sss.pgh.pa.us 1159 [ - + ]: 66 : if (size != PG_CONTROL_FILE_SIZE)
285 peter@eisentraut.org 1160 :UBC 0 : pg_fatal("unexpected control file size %zu, expected %d",
1161 : : size, PG_CONTROL_FILE_SIZE);
1162 : :
2146 heikki.linnakangas@i 1163 :CBC 66 : memcpy(ControlFile, content, sizeof(ControlFileData));
1164 : :
1165 : : /* set and validate WalSegSz */
3288 andres@anarazel.de 1166 : 66 : WalSegSz = ControlFile->xlog_seg_size;
1167 : :
1168 [ + - + - : 66 : if (!IsValidWalSegSize(WalSegSz))
+ - - + ]
1169 : : {
1119 peter@eisentraut.org 1170 :UBC 0 : pg_log_error(ngettext("invalid WAL segment size in control file (%d byte)",
1171 : : "invalid WAL segment size in control file (%d bytes)",
1172 : : WalSegSz),
1173 : : WalSegSz);
1174 : 0 : pg_log_error_detail("The WAL segment size must be a power of two between 1 MB and 1 GB.");
1175 : 0 : exit(1);
1176 : : }
1177 : :
1178 : : /* Additional checks on control file */
4199 heikki.linnakangas@i 1179 :CBC 66 : checkControlFile(ControlFile);
1180 : 66 : }
1181 : :
1182 : : /*
1183 : : * Get value of GUC parameter restore_command from the target cluster.
1184 : : *
1185 : : * This uses a logic based on "postgres -C" to get the value from the
1186 : : * cluster.
1187 : : */
1188 : : static void
2363 michael@paquier.xyz 1189 : 21 : getRestoreCommand(const char *argv0)
1190 : : {
1191 : : int rc;
1192 : : char postgres_exec_path[MAXPGPATH];
1193 : : PQExpBuffer postgres_cmd;
1194 : :
1195 [ + + ]: 21 : if (!restore_wal)
1196 : 20 : return;
1197 : :
1198 : : /* find postgres executable */
1199 : 1 : rc = find_other_exec(argv0, "postgres",
1200 : : PG_BACKEND_VERSIONSTR,
1201 : : postgres_exec_path);
1202 : :
1203 [ - + ]: 1 : if (rc < 0)
1204 : : {
1205 : : char full_path[MAXPGPATH];
1206 : :
2363 michael@paquier.xyz 1207 [ # # ]:UBC 0 : if (find_my_exec(argv0, full_path) < 0)
1208 : 0 : strlcpy(full_path, progname, sizeof(full_path));
1209 : :
1210 [ # # ]: 0 : if (rc == -1)
1626 tgl@sss.pgh.pa.us 1211 : 0 : pg_fatal("program \"%s\" is needed by %s but was not found in the same directory as \"%s\"",
1212 : : "postgres", progname, full_path);
1213 : : else
1214 : 0 : pg_fatal("program \"%s\" was found by \"%s\" but was not the same version as %s",
1215 : : "postgres", full_path, progname);
1216 : : }
1217 : :
1218 : : /*
1219 : : * Build a command able to retrieve the value of GUC parameter
1220 : : * restore_command, if set.
1221 : : */
1664 michael@paquier.xyz 1222 :CBC 1 : postgres_cmd = createPQExpBuffer();
1223 : :
1224 : : /* path to postgres, properly quoted */
1225 : 1 : appendShellString(postgres_cmd, postgres_exec_path);
1226 : :
1227 : : /* add -D switch, with properly quoted data directory */
1228 : 1 : appendPQExpBufferStr(postgres_cmd, " -D ");
1229 : 1 : appendShellString(postgres_cmd, datadir_target);
1230 : :
1231 : : /* add custom configuration file only if requested */
1627 1232 [ + - ]: 1 : if (config_file != NULL)
1233 : : {
1234 : 1 : appendPQExpBufferStr(postgres_cmd, " -c config_file=");
1235 : 1 : appendShellString(postgres_cmd, config_file);
1236 : : }
1237 : :
1238 : : /* add -C switch, for restore_command */
1664 1239 : 1 : appendPQExpBufferStr(postgres_cmd, " -C restore_command");
1240 : :
954 dgustafsson@postgres 1241 : 1 : restore_command = pipe_read_line(postgres_cmd->data);
1242 [ - + ]: 1 : if (restore_command == NULL)
746 michael@paquier.xyz 1243 :UBC 0 : pg_fatal("could not read \"restore_command\" from target cluster");
1244 : :
954 dgustafsson@postgres 1245 :CBC 1 : (void) pg_strip_crlf(restore_command);
1246 : :
1247 [ - + ]: 1 : if (strcmp(restore_command, "") == 0)
856 peter@eisentraut.org 1248 :UBC 0 : pg_fatal("\"restore_command\" is not set in the target cluster");
1249 : :
856 peter@eisentraut.org 1250 [ + - ]:CBC 1 : pg_log_debug("using for rewind \"restore_command = \'%s\'\"",
1251 : : restore_command);
1252 : :
1664 michael@paquier.xyz 1253 : 1 : destroyPQExpBuffer(postgres_cmd);
1254 : : }
1255 : :
1256 : :
1257 : : /*
1258 : : * Ensure clean shutdown of target instance by launching single-user mode
1259 : : * postgres to do crash recovery.
1260 : : */
1261 : : static void
2550 alvherre@alvh.no-ip. 1262 : 10 : ensureCleanShutdown(const char *argv0)
1263 : : {
1264 : : int ret;
1265 : : char exec_path[MAXPGPATH];
1266 : : PQExpBuffer postgres_cmd;
1267 : :
1268 : : /* locate postgres binary */
1269 [ - + ]: 10 : if ((ret = find_other_exec(argv0, "postgres",
1270 : : PG_BACKEND_VERSIONSTR,
1271 : : exec_path)) < 0)
1272 : : {
1273 : : char full_path[MAXPGPATH];
1274 : :
2550 alvherre@alvh.no-ip. 1275 [ # # ]:UBC 0 : if (find_my_exec(argv0, full_path) < 0)
1276 : 0 : strlcpy(full_path, progname, sizeof(full_path));
1277 : :
1278 [ # # ]: 0 : if (ret == -1)
1626 tgl@sss.pgh.pa.us 1279 : 0 : pg_fatal("program \"%s\" is needed by %s but was not found in the same directory as \"%s\"",
1280 : : "postgres", progname, full_path);
1281 : : else
1282 : 0 : pg_fatal("program \"%s\" was found by \"%s\" but was not the same version as %s",
1283 : : "postgres", full_path, progname);
1284 : : }
1285 : :
2550 alvherre@alvh.no-ip. 1286 :CBC 10 : pg_log_info("executing \"%s\" for target server to complete crash recovery",
1287 : : exec_path);
1288 : :
1289 : : /*
1290 : : * Skip processing if requested, but only after ensuring presence of
1291 : : * postgres.
1292 : : */
1293 [ - + ]: 10 : if (dry_run)
2550 alvherre@alvh.no-ip. 1294 :UBC 0 : return;
1295 : :
1296 : : /*
1297 : : * Finally run postgres in single-user mode. There is no need to use
1298 : : * fsync here. This makes the recovery faster, and the target data folder
1299 : : * is synced at the end anyway.
1300 : : */
1664 michael@paquier.xyz 1301 :CBC 10 : postgres_cmd = createPQExpBuffer();
1302 : :
1303 : : /* path to postgres, properly quoted */
1304 : 10 : appendShellString(postgres_cmd, exec_path);
1305 : :
1306 : : /* add set of options with properly quoted data directory */
1307 : 10 : appendPQExpBufferStr(postgres_cmd, " --single -F -D ");
1308 : 10 : appendShellString(postgres_cmd, datadir_target);
1309 : :
1310 : : /* add custom configuration file only if requested */
1627 1311 [ + + ]: 10 : if (config_file != NULL)
1312 : : {
1313 : 9 : appendPQExpBufferStr(postgres_cmd, " -c config_file=");
1314 : 9 : appendShellString(postgres_cmd, config_file);
1315 : : }
1316 : :
1317 : : /* finish with the database name, and a properly quoted redirection */
1664 1318 : 10 : appendPQExpBufferStr(postgres_cmd, " template1 < ");
1319 : 10 : appendShellString(postgres_cmd, DEVNULL);
1320 : :
1483 tgl@sss.pgh.pa.us 1321 : 10 : fflush(NULL);
1664 michael@paquier.xyz 1322 [ + + ]: 10 : if (system(postgres_cmd->data) != 0)
1323 : : {
2326 peter@eisentraut.org 1324 : 1 : pg_log_error("postgres single-user mode in target cluster failed");
1626 tgl@sss.pgh.pa.us 1325 : 1 : pg_log_error_detail("Command was: %s", postgres_cmd->data);
1326 : 1 : exit(1);
1327 : : }
1328 : :
1664 michael@paquier.xyz 1329 : 9 : destroyPQExpBuffer(postgres_cmd);
1330 : : }
1331 : :
1332 : : static void
2547 alvherre@alvh.no-ip. 1333 : 21 : disconnect_atexit(void)
1334 : : {
1335 [ - + ]: 21 : if (conn != NULL)
2547 alvherre@alvh.no-ip. 1336 :UBC 0 : PQfinish(conn);
2547 alvherre@alvh.no-ip. 1337 :CBC 21 : }
|