diff --git a/src/bin/pg_rewind/fetch.h b/src/bin/pg_rewind/fetch.h index 185d5ea270e48f216c6b9a2bb20d7471669c2907..3f2c95073da0bde82195ea879a85cf76d4aa4bc8 100644 --- a/src/bin/pg_rewind/fetch.h +++ b/src/bin/pg_rewind/fetch.h @@ -43,4 +43,7 @@ extern void copy_executeFileMap(filemap_t *map); typedef void (*process_file_callback_t) (const char *path, file_type_t type, size_t size, const char *link_target); extern void traverse_datadir(const char *datadir, process_file_callback_t callback); +extern void GenerateRecoveryConf(void); +extern void WriteRecoveryConf(void); + #endif /* FETCH_H */ diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c index 6b4bf256accab2244495295b871c89eead6a1d73..51c61aebe97f454217a396a57befeaffd6b4538a 100644 --- a/src/bin/pg_rewind/libpq_fetch.c +++ b/src/bin/pg_rewind/libpq_fetch.c @@ -30,9 +30,13 @@ #include "catalog/catalog.h" #include "catalog/pg_type.h" +#include "pqexpbuffer.h" static PGconn *conn = NULL; +/* Contents of recovery.conf to be generated */ +static PQExpBuffer recoveryconfcontents = NULL; + /* * Files are fetched max CHUNKSIZE bytes at a time. * @@ -554,3 +558,213 @@ execute_pagemap(datapagemap_t *pagemap, const char *path) } pg_free(iter); } + +/* + * TODO: Most of the below code is copied directly from pg_basebackup.c. There + * are only a couple subtle differences if you diff the two (e.g. removal of + * recovery.done file, excluding "options" to avoid utility mode, etc.). We + * should create a common library between the two to create the recovery.conf + * file. + */ +static void +disconnect_and_exit(int code) +{ + if (conn != NULL) + PQfinish(conn); + + exit(code); +} + +/* + * Escape a parameter value so that it can be used as part of a libpq + * connection string, e.g. in: + * + * application_name= + * + * The returned string is malloc'd. Return NULL on out-of-memory. + */ +static char * +escapeConnectionParameter(const char *src) +{ + bool need_quotes = false; + bool need_escaping = false; + const char *p; + char *dstbuf; + char *dst; + + /* + * First check if quoting is needed. Any quote (') or backslash (\) + * characters need to be escaped. Parameters are separated by whitespace, + * so any string containing whitespace characters need to be quoted. An + * empty string is represented by ''. + */ + if (strchr(src, '\'') != NULL || strchr(src, '\\') != NULL) + need_escaping = true; + + for (p = src; *p; p++) + { + if (isspace((unsigned char) *p)) + { + need_quotes = true; + break; + } + } + + if (*src == '\0') + return pg_strdup("''"); + + if (!need_quotes && !need_escaping) + return pg_strdup(src); /* no quoting or escaping needed */ + + /* + * Allocate a buffer large enough for the worst case that all the source + * characters need to be escaped, plus quotes. + */ + dstbuf = pg_malloc(strlen(src) * 2 + 2 + 1); + + dst = dstbuf; + if (need_quotes) + *(dst++) = '\''; + for (; *src; src++) + { + if (*src == '\'' || *src == '\\') + *(dst++) = '\\'; + *(dst++) = *src; + } + if (need_quotes) + *(dst++) = '\''; + *dst = '\0'; + + return dstbuf; +} + +/* + * Escape a string so that it can be used as a value in a key-value pair + * a configuration file. + */ +static char * +escape_quotes(const char *src) +{ + char *result = escape_single_quotes_ascii(src); + + if (!result) + { + fprintf(stderr, _("%s: out of memory\n"), progname); + exit(1); + } + return result; +} + +/* + * Create a recovery.conf file in memory using a PQExpBuffer + */ +void +GenerateRecoveryConf(void) +{ + PQconninfoOption *connOptions; + PQconninfoOption *option; + PQExpBufferData conninfo_buf; + char *escaped; + + recoveryconfcontents = createPQExpBuffer(); + if (!recoveryconfcontents) + { + fprintf(stderr, _("%s: out of memory\n"), progname); + disconnect_and_exit(1); + } + + connOptions = PQconninfo(conn); + if (connOptions == NULL) + { + fprintf(stderr, _("%s: out of memory\n"), progname); + disconnect_and_exit(1); + } + + appendPQExpBufferStr(recoveryconfcontents, "standby_mode = 'on'\n"); + appendPQExpBufferStr(recoveryconfcontents, "recovery_target_timeline = 'latest'\n"); + + initPQExpBuffer(&conninfo_buf); + for (option = connOptions; option && option->keyword; option++) + { + /* + * Do not emit this setting if: - the setting is "replication", + * "dbname" or "fallback_application_name", since these would be + * overridden by the libpqwalreceiver module anyway. - not set or + * empty. + */ + if (strcmp(option->keyword, "replication") == 0 || + strcmp(option->keyword, "dbname") == 0 || + strcmp(option->keyword, "fallback_application_name") == 0 || + strcmp(option->keyword, "options") == 0 || + (option->val == NULL) || + (option->val[0] == '\0')) + continue; + + /* Separate key-value pairs with spaces */ + if (conninfo_buf.len != 0) + appendPQExpBufferStr(&conninfo_buf, " "); + + /* + * Write "keyword=value" pieces, the value string is escaped and/or + * quoted if necessary. + */ + escaped = escapeConnectionParameter(option->val); + appendPQExpBuffer(&conninfo_buf, "%s=%s", option->keyword, escaped); + free(escaped); + } + + /* + * Escape the connection string, so that it can be put in the config file. + * Note that this is different from the escaping of individual connection + * options above! + */ + escaped = escape_quotes(conninfo_buf.data); + appendPQExpBuffer(recoveryconfcontents, "primary_conninfo = '%s'\n", escaped); + free(escaped); + + if (PQExpBufferBroken(recoveryconfcontents) || + PQExpBufferDataBroken(conninfo_buf)) + { + fprintf(stderr, _("%s: out of memory\n"), progname); + disconnect_and_exit(1); + } + + termPQExpBuffer(&conninfo_buf); + + PQconninfoFree(connOptions); +} + + +/* + * Write a recovery.conf file into the directory specified in basedir, + * with the contents already collected in memory. + */ +void +WriteRecoveryConf(void) +{ + char filename[MAXPGPATH]; + FILE *cf; + + /* Remove recovery.done file that was most likely copied from source instance */ + snprintf(filename, sizeof(filename), "%s/recovery.done", datadir_target); + unlink(filename); + + snprintf(filename, sizeof(filename), "%s/recovery.conf", datadir_target); + + cf = fopen(filename, "w"); + if (cf == NULL) + { + fprintf(stderr, _("%s: could not create file \"%s\": %s\n"), progname, filename, strerror(errno)); + disconnect_and_exit(1); + } + + if (fwrite(recoveryconfcontents->data, recoveryconfcontents->len, 1, cf) != 1) + { + fprintf(stderr, + _("%s: could not write to file \"%s\": %s\n"), + progname, filename, strerror(errno)); + disconnect_and_exit(1); + } + + fclose(cf); +} diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c index 604f134002f849b30a4d3136ea6e0a83ebe7da4f..f77bc63ee5b07c64693221b1ed847e76278e05ae 100644 --- a/src/bin/pg_rewind/pg_rewind.c +++ b/src/bin/pg_rewind/pg_rewind.c @@ -43,6 +43,8 @@ static void ensureCleanShutdown(const char *argv0); static ControlFileData ControlFile_target; static ControlFileData ControlFile_source; +static bool writerecoveryconf = false; + const char *progname; /* Configuration options */ @@ -63,6 +65,7 @@ usage(const char *progname) printf(_(" -D, --target-pgdata=DIRECTORY existing data directory to modify\n")); printf(_(" --source-pgdata=DIRECTORY source data directory to synchronize with\n")); printf(_(" --source-server=CONNSTR source server to synchronize with\n")); + printf(_(" -R, --write-recovery-conf write recovery.conf after backup\n")); printf(_(" -n, --dry-run stop before modifying anything\n")); printf(_(" -P, --progress write progress messages\n")); printf(_(" --debug write a lot of debug messages\n")); @@ -78,6 +81,7 @@ main(int argc, char **argv) static struct option long_options[] = { {"help", no_argument, NULL, '?'}, {"target-pgdata", required_argument, NULL, 'D'}, + {"write-recovery-conf", no_argument, NULL, 'R'}, {"source-pgdata", required_argument, NULL, 1}, {"source-server", required_argument, NULL, 2}, {"version", no_argument, NULL, 'V'}, @@ -118,7 +122,7 @@ main(int argc, char **argv) } } - while ((c = getopt_long(argc, argv, "D:nP", long_options, &option_index)) != -1) + while ((c = getopt_long(argc, argv, "D:nPR", long_options, &option_index)) != -1) { switch (c) { @@ -134,6 +138,10 @@ main(int argc, char **argv) dry_run = true; break; + case 'R': + writerecoveryconf = true; + break; + case 3: debug = true; break; @@ -277,6 +285,13 @@ main(int argc, char **argv) if (!rewind_needed) { printf(_("no rewind required\n")); + + if (writerecoveryconf && connstr_source) + { + GenerateRecoveryConf(); + WriteRecoveryConf(); + } + exit(0); } @@ -367,6 +382,12 @@ main(int argc, char **argv) pg_log(PG_PROGRESS, "syncing target data directory\n"); syncTargetDirectory(argv[0]); + if (writerecoveryconf && connstr_source) + { + GenerateRecoveryConf(); + WriteRecoveryConf(); + } + printf(_("Done!\n")); return 0; diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h index e281369e39fe18c3725cbf5ce6f6333c281469d7..3296d672a946b562b77df5106abaf8ca74a77c90 100644 --- a/src/bin/pg_rewind/pg_rewind.h +++ b/src/bin/pg_rewind/pg_rewind.h @@ -27,6 +27,8 @@ extern bool debug; extern bool showprogress; extern bool dry_run; +extern const char *progname; + /* in parsexlog.c */ extern void extractPageMap(const char *datadir, XLogRecPtr startpoint, TimeLineID tli, XLogRecPtr endpoint);