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