From d9f96c1b5ea468ac4dea427e51d9beb3cd087653 Mon Sep 17 00:00:00 2001
From: Alena Vinter <dlaaren8@gmail.com>
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

