From 29d3f5f7abc730112e8770d3da84232af4754c46 Mon Sep 17 00:00:00 2001 From: Alena Vinter Date: Tue, 2 Sep 2025 18:15:13 +0700 Subject: [PATCH 1/3] Implements helper function in recovery_gen These functions support pg_createsubscriber's need to temporarily configure recovery params and ensure proper cleanup after the conversion to logical replication is complete. --- src/fe_utils/recovery_gen.c | 113 ++++++++++++++++++++++++++++ src/include/fe_utils/recovery_gen.h | 4 + 2 files changed, 117 insertions(+) diff --git a/src/fe_utils/recovery_gen.c b/src/fe_utils/recovery_gen.c index e9023584768..e2e501ce693 100644 --- a/src/fe_utils/recovery_gen.c +++ b/src/fe_utils/recovery_gen.c @@ -9,7 +9,9 @@ */ #include "postgres_fe.h" +#include "common/file_utils.h" #include "common/logging.h" +#include "common/string.h" #include "fe_utils/recovery_gen.h" #include "fe_utils/string_utils.h" @@ -234,3 +236,114 @@ GetDbnameFromConnectionOptions(const char *connstr) PQconninfoFree(conn_opts); return dbname; } + +/* + * GetRecoveryConfig + * + * Reads the recovery configuration file from the target server's data directory + * and returns its contents as a PQExpBuffer. + */ +PQExpBuffer +GetRecoveryConfig(PGconn *pgconn, const char *target_dir) +{ + PQExpBuffer contents; + char filename[MAXPGPATH]; + FILE *cf; + char *line; + bool use_recovery_conf; + + Assert(pgconn != NULL); + + contents = createPQExpBuffer(); + if (!contents) + pg_fatal("out of memory"); + + use_recovery_conf = + PQserverVersion(pgconn) < MINIMUM_VERSION_FOR_RECOVERY_GUC; + + snprintf(filename, MAXPGPATH, "%s/%s", target_dir, + use_recovery_conf ? "recovery.conf" : "postgresql.auto.conf"); + + cf = fopen(filename, "r"); + if (cf == NULL) + pg_fatal("could not open file \"%s\": %m", filename); + + /* Read file contents line-by-line and append to the buffer */ + while ((line = pg_get_line(cf, NULL)) != NULL) + { + appendPQExpBufferStr(contents, line); + } + + if (ferror(cf)) + { + pg_fatal("could not read from file \"%s\": %m", filename); + } + + fclose(cf); + + return contents; +} + +/* + * ReplaceRecoveryConfig + * + * Replaces the recovery configuration file on the target server with new contents. + * + * The operation is performed atomically by writing to a temporary file first, + * then renaming it to the final filename. + * + * Returns false if an error occurs. In case of error, a temporary file may + * still be present on the server. + */ +bool +ReplaceRecoveryConfig(int version, const char *target_dir, PQExpBuffer contents) +{ + char tmp_filename[MAXPGPATH]; + char filename[MAXPGPATH]; + FILE *cf; + const char *config_filename; + + config_filename = + (version < MINIMUM_VERSION_FOR_RECOVERY_GUC) + ? "recovery.conf" + : "postgresql.auto.conf"; + + /* + * Construct full paths for the configuration file and its temporary + * version + */ + snprintf(filename, MAXPGPATH, "%s/%s", target_dir, config_filename); + snprintf(tmp_filename, MAXPGPATH, "%s.tmp", filename); + + /* + * Open temporary file for writing. Mode "w" ensures the file is recreated + * if it already exists. + */ + cf = fopen(tmp_filename, "w"); + if (cf == NULL) + { + pg_log_error("could not open file \"%s\": %m", tmp_filename); + return false; + } + + if (fwrite(contents->data, contents->len, 1, cf) != 1) + { + pg_log_error("could not write to file \"%s\": %m", tmp_filename); + return false; + } + + fclose(cf); + + /* + * Atomically replace the old configuration file with the new one by + * renaming the temporary file to the final filename. + */ + if (durable_rename(tmp_filename, filename) != 0) + { + pg_log_error("could not rename file \"%s\" to \"%s\": %m", + tmp_filename, filename); + return false; + } + + return true; +} diff --git a/src/include/fe_utils/recovery_gen.h b/src/include/fe_utils/recovery_gen.h index c13f2263bcd..b54397db740 100644 --- a/src/include/fe_utils/recovery_gen.h +++ b/src/include/fe_utils/recovery_gen.h @@ -27,4 +27,8 @@ extern void WriteRecoveryConfig(PGconn *pgconn, const char *target_dir, PQExpBuffer contents); extern char *GetDbnameFromConnectionOptions(const char *connstr); +extern PQExpBuffer GetRecoveryConfig(PGconn *pgconn, const char *target_dir); +extern bool ReplaceRecoveryConfig(int version, const char *target_dir, + PQExpBuffer contents); + #endif /* RECOVERY_GEN_H */ -- 2.51.0