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