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