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