From d9f96c1b5ea468ac4dea427e51d9beb3cd087653 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 | 117 ++++++++++++++++++++++++++++ src/include/fe_utils/recovery_gen.h | 4 + 2 files changed, 121 insertions(+) diff --git a/src/fe_utils/recovery_gen.c b/src/fe_utils/recovery_gen.c index e9023584768..e22d7673ff8 100644 --- a/src/fe_utils/recovery_gen.c +++ b/src/fe_utils/recovery_gen.c @@ -10,6 +10,7 @@ #include "postgres_fe.h" #include "common/logging.h" +#include "common/file_utils.h" #include "fe_utils/recovery_gen.h" #include "fe_utils/string_utils.h" @@ -234,3 +235,119 @@ 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; + bool use_recovery_conf; + + char data[1024]; + size_t bytes_read; + + 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 in chunks and append to the buffer */ + while ((bytes_read = fread(data, 1, sizeof(data), cf)) > 0) + { + data[bytes_read] = '\0'; + appendPQExpBufferStr(contents, data); + } + + 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(PGconn *pgconn, const char *target_dir, PQExpBuffer contents) +{ + char tmp_filename[MAXPGPATH]; + char filename[MAXPGPATH]; + FILE *cf; + const char *config_filename; + + Assert(pgconn != NULL); + + config_filename = + (PQserverVersion(pgconn) < 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..f5f21ec088d 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(PGconn *pgconn, const char *target_dir, + PQExpBuffer contents); + #endif /* RECOVERY_GEN_H */ -- 2.51.0