Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * recovery_gen.c
4 : * Generator for recovery configuration
5 : *
6 : * Portions Copyright (c) 2011-2025, PostgreSQL Global Development Group
7 : *
8 : *-------------------------------------------------------------------------
9 : */
10 : #include "postgres_fe.h"
11 :
12 : #include "common/logging.h"
13 : #include "fe_utils/recovery_gen.h"
14 : #include "fe_utils/string_utils.h"
15 :
16 : static char *escape_quotes(const char *src);
17 : static char *FindDbnameInConnOpts(PQconninfoOption *conn_opts);
18 :
19 : /*
20 : * Write recovery configuration contents into a fresh PQExpBuffer, and
21 : * return it.
22 : *
23 : * This accepts the dbname which will be appended to the primary_conninfo.
24 : * The dbname will be ignored by walreceiver process but slotsync worker uses
25 : * it to connect to the primary server.
26 : */
27 : PQExpBuffer
28 22 : GenerateRecoveryConfig(PGconn *pgconn, const char *replication_slot,
29 : char *dbname)
30 : {
31 : PQconninfoOption *connOptions;
32 : PQExpBufferData conninfo_buf;
33 : char *escaped;
34 : PQExpBuffer contents;
35 :
36 : Assert(pgconn != NULL);
37 :
38 22 : contents = createPQExpBuffer();
39 22 : if (!contents)
40 0 : pg_fatal("out of memory");
41 :
42 : /*
43 : * In PostgreSQL 12 and newer versions, standby_mode is gone, replaced by
44 : * standby.signal to trigger a standby state at recovery.
45 : */
46 22 : if (PQserverVersion(pgconn) < MINIMUM_VERSION_FOR_RECOVERY_GUC)
47 0 : appendPQExpBufferStr(contents, "standby_mode = 'on'\n");
48 :
49 22 : connOptions = PQconninfo(pgconn);
50 22 : if (connOptions == NULL)
51 0 : pg_fatal("out of memory");
52 :
53 22 : initPQExpBuffer(&conninfo_buf);
54 1056 : for (PQconninfoOption *opt = connOptions; opt && opt->keyword; opt++)
55 : {
56 : /* Omit empty settings and those libpqwalreceiver overrides. */
57 1034 : if (strcmp(opt->keyword, "replication") == 0 ||
58 1012 : strcmp(opt->keyword, "dbname") == 0 ||
59 990 : strcmp(opt->keyword, "fallback_application_name") == 0 ||
60 968 : (opt->val == NULL) ||
61 394 : (opt->val != NULL && opt->val[0] == '\0'))
62 662 : continue;
63 :
64 : /* Separate key-value pairs with spaces */
65 372 : if (conninfo_buf.len != 0)
66 350 : appendPQExpBufferChar(&conninfo_buf, ' ');
67 :
68 : /*
69 : * Write "keyword=value" pieces, the value string is escaped and/or
70 : * quoted if necessary.
71 : */
72 372 : appendPQExpBuffer(&conninfo_buf, "%s=", opt->keyword);
73 372 : appendConnStrVal(&conninfo_buf, opt->val);
74 : }
75 :
76 22 : if (dbname)
77 : {
78 : /*
79 : * If dbname is specified in the connection, append the dbname. This
80 : * will be used later for logical replication slot synchronization.
81 : */
82 16 : if (conninfo_buf.len != 0)
83 16 : appendPQExpBufferChar(&conninfo_buf, ' ');
84 :
85 16 : appendPQExpBuffer(&conninfo_buf, "%s=", "dbname");
86 16 : appendConnStrVal(&conninfo_buf, dbname);
87 : }
88 :
89 22 : if (PQExpBufferDataBroken(conninfo_buf))
90 0 : pg_fatal("out of memory");
91 :
92 : /*
93 : * Escape the connection string, so that it can be put in the config file.
94 : * Note that this is different from the escaping of individual connection
95 : * options above!
96 : */
97 22 : escaped = escape_quotes(conninfo_buf.data);
98 22 : termPQExpBuffer(&conninfo_buf);
99 22 : appendPQExpBuffer(contents, "primary_conninfo = '%s'\n", escaped);
100 22 : free(escaped);
101 :
102 22 : if (replication_slot)
103 : {
104 : /* unescaped: ReplicationSlotValidateName allows [a-z0-9_] only */
105 2 : appendPQExpBuffer(contents, "primary_slot_name = '%s'\n",
106 : replication_slot);
107 : }
108 :
109 22 : if (PQExpBufferBroken(contents))
110 0 : pg_fatal("out of memory");
111 :
112 22 : PQconninfoFree(connOptions);
113 :
114 22 : return contents;
115 : }
116 :
117 : /*
118 : * Write the configuration file in the directory specified in target_dir,
119 : * with the contents already collected in memory appended. Then write
120 : * the signal file into the target_dir. If the server does not support
121 : * recovery parameters as GUCs, the signal file is not necessary, and
122 : * configuration is written to recovery.conf.
123 : */
124 : void
125 12 : WriteRecoveryConfig(PGconn *pgconn, const char *target_dir, PQExpBuffer contents)
126 : {
127 : char filename[MAXPGPATH];
128 : FILE *cf;
129 : bool use_recovery_conf;
130 :
131 : Assert(pgconn != NULL);
132 :
133 12 : use_recovery_conf =
134 12 : PQserverVersion(pgconn) < MINIMUM_VERSION_FOR_RECOVERY_GUC;
135 :
136 12 : snprintf(filename, MAXPGPATH, "%s/%s", target_dir,
137 : use_recovery_conf ? "recovery.conf" : "postgresql.auto.conf");
138 :
139 12 : cf = fopen(filename, use_recovery_conf ? "w" : "a");
140 12 : if (cf == NULL)
141 0 : pg_fatal("could not open file \"%s\": %m", filename);
142 :
143 12 : if (fwrite(contents->data, contents->len, 1, cf) != 1)
144 0 : pg_fatal("could not write to file \"%s\": %m", filename);
145 :
146 12 : fclose(cf);
147 :
148 12 : if (!use_recovery_conf)
149 : {
150 12 : snprintf(filename, MAXPGPATH, "%s/%s", target_dir, "standby.signal");
151 12 : cf = fopen(filename, "w");
152 12 : if (cf == NULL)
153 0 : pg_fatal("could not create file \"%s\": %m", filename);
154 :
155 12 : fclose(cf);
156 : }
157 12 : }
158 :
159 : /*
160 : * Escape a string so that it can be used as a value in a key-value pair
161 : * a configuration file.
162 : */
163 : static char *
164 22 : escape_quotes(const char *src)
165 : {
166 22 : char *result = escape_single_quotes_ascii(src);
167 :
168 22 : if (!result)
169 0 : pg_fatal("out of memory");
170 22 : return result;
171 : }
172 :
173 : /*
174 : * FindDbnameInConnOpts
175 : *
176 : * This is a helper function for GetDbnameFromConnectionOptions(). Extract
177 : * the value of dbname from PQconninfoOption parameters, if it's present.
178 : * Returns a strdup'd result or NULL.
179 : */
180 : static char *
181 16 : FindDbnameInConnOpts(PQconninfoOption *conn_opts)
182 : {
183 16 : for (PQconninfoOption *conn_opt = conn_opts;
184 112 : conn_opt->keyword != NULL;
185 96 : conn_opt++)
186 : {
187 112 : if (strcmp(conn_opt->keyword, "dbname") == 0 &&
188 16 : conn_opt->val != NULL && conn_opt->val[0] != '\0')
189 16 : return pg_strdup(conn_opt->val);
190 : }
191 0 : return NULL;
192 : }
193 :
194 : /*
195 : * GetDbnameFromConnectionOptions
196 : *
197 : * This is a special purpose function to retrieve the dbname from either the
198 : * 'connstr' specified by the caller or from the environment variables.
199 : *
200 : * Returns NULL, if dbname is not specified by the user in the given
201 : * connection options.
202 : */
203 : char *
204 16 : GetDbnameFromConnectionOptions(const char *connstr)
205 : {
206 : PQconninfoOption *conn_opts;
207 16 : char *err_msg = NULL;
208 : char *dbname;
209 :
210 : /* First try to get the dbname from connection string. */
211 16 : if (connstr)
212 : {
213 12 : conn_opts = PQconninfoParse(connstr, &err_msg);
214 12 : if (conn_opts == NULL)
215 0 : pg_fatal("%s", err_msg);
216 :
217 12 : dbname = FindDbnameInConnOpts(conn_opts);
218 :
219 12 : PQconninfoFree(conn_opts);
220 12 : if (dbname)
221 12 : return dbname;
222 : }
223 :
224 : /*
225 : * Next try to get the dbname from default values that are available from
226 : * the environment.
227 : */
228 4 : conn_opts = PQconndefaults();
229 4 : if (conn_opts == NULL)
230 0 : pg_fatal("out of memory");
231 :
232 4 : dbname = FindDbnameInConnOpts(conn_opts);
233 :
234 4 : PQconninfoFree(conn_opts);
235 4 : return dbname;
236 : }
|