Age Owner Branch data TLA 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
534 akapila@postgresql.o 28 :CBC 12 : 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 : :
2173 alvherre@alvh.no-ip. 36 [ - + ]: 12 : Assert(pgconn != NULL);
37 : :
38 : 12 : contents = createPQExpBuffer();
39 [ - + ]: 12 : if (!contents)
1247 tgl@sss.pgh.pa.us 40 :UBC 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 : : */
2173 alvherre@alvh.no-ip. 46 [ - + ]:CBC 12 : if (PQserverVersion(pgconn) < MINIMUM_VERSION_FOR_RECOVERY_GUC)
2173 alvherre@alvh.no-ip. 47 :UBC 0 : appendPQExpBufferStr(contents, "standby_mode = 'on'\n");
48 : :
2173 alvherre@alvh.no-ip. 49 :CBC 12 : connOptions = PQconninfo(pgconn);
50 [ - + ]: 12 : if (connOptions == NULL)
1247 tgl@sss.pgh.pa.us 51 :UBC 0 : pg_fatal("out of memory");
52 : :
2173 alvherre@alvh.no-ip. 53 :CBC 12 : initPQExpBuffer(&conninfo_buf);
54 [ + - + + ]: 624 : for (PQconninfoOption *opt = connOptions; opt && opt->keyword; opt++)
55 : : {
56 : : /* Omit empty settings and those libpqwalreceiver overrides. */
57 [ + + ]: 612 : if (strcmp(opt->keyword, "replication") == 0 ||
58 [ + + ]: 600 : strcmp(opt->keyword, "dbname") == 0 ||
59 [ + + ]: 588 : strcmp(opt->keyword, "fallback_application_name") == 0 ||
60 [ + + ]: 576 : (opt->val == NULL) ||
61 [ + - + + ]: 215 : (opt->val != NULL && opt->val[0] == '\0'))
62 : 409 : continue;
63 : :
64 : : /* Separate key-value pairs with spaces */
65 [ + + ]: 203 : if (conninfo_buf.len != 0)
66 : 191 : appendPQExpBufferChar(&conninfo_buf, ' ');
67 : :
68 : : /*
69 : : * Write "keyword=value" pieces, the value string is escaped and/or
70 : : * quoted if necessary.
71 : : */
72 : 203 : appendPQExpBuffer(&conninfo_buf, "%s=", opt->keyword);
73 : 203 : appendConnStrVal(&conninfo_buf, opt->val);
74 : : }
75 : :
534 akapila@postgresql.o 76 [ + + ]: 12 : 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 [ + - ]: 8 : if (conninfo_buf.len != 0)
83 : 8 : appendPQExpBufferChar(&conninfo_buf, ' ');
84 : :
85 : 8 : appendPQExpBuffer(&conninfo_buf, "%s=", "dbname");
86 : 8 : appendConnStrVal(&conninfo_buf, dbname);
87 : : }
88 : :
2173 alvherre@alvh.no-ip. 89 [ - + ]: 12 : if (PQExpBufferDataBroken(conninfo_buf))
1247 tgl@sss.pgh.pa.us 90 :UBC 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 : : */
2173 alvherre@alvh.no-ip. 97 :CBC 12 : escaped = escape_quotes(conninfo_buf.data);
98 : 12 : termPQExpBuffer(&conninfo_buf);
99 : 12 : appendPQExpBuffer(contents, "primary_conninfo = '%s'\n", escaped);
100 : 12 : free(escaped);
101 : :
102 [ + + ]: 12 : if (replication_slot)
103 : : {
104 : : /* unescaped: ReplicationSlotValidateName allows [a-z0-9_] only */
105 : 1 : appendPQExpBuffer(contents, "primary_slot_name = '%s'\n",
106 : : replication_slot);
107 : : }
108 : :
109 [ + - - + ]: 12 : if (PQExpBufferBroken(contents))
1247 tgl@sss.pgh.pa.us 110 :UBC 0 : pg_fatal("out of memory");
111 : :
2173 alvherre@alvh.no-ip. 112 :CBC 12 : PQconninfoFree(connOptions);
113 : :
114 : 12 : 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
537 peter@eisentraut.org 125 : 6 : WriteRecoveryConfig(PGconn *pgconn, const char *target_dir, PQExpBuffer contents)
126 : : {
127 : : char filename[MAXPGPATH];
128 : : FILE *cf;
129 : : bool use_recovery_conf;
130 : :
2173 alvherre@alvh.no-ip. 131 [ - + ]: 6 : Assert(pgconn != NULL);
132 : :
133 : 6 : use_recovery_conf =
134 : 6 : PQserverVersion(pgconn) < MINIMUM_VERSION_FOR_RECOVERY_GUC;
135 : :
136 [ - + ]: 6 : snprintf(filename, MAXPGPATH, "%s/%s", target_dir,
137 : : use_recovery_conf ? "recovery.conf" : "postgresql.auto.conf");
138 : :
2033 fujii@postgresql.org 139 [ - + ]: 6 : cf = fopen(filename, use_recovery_conf ? "w" : "a");
2173 alvherre@alvh.no-ip. 140 [ - + ]: 6 : if (cf == NULL)
1247 tgl@sss.pgh.pa.us 141 :UBC 0 : pg_fatal("could not open file \"%s\": %m", filename);
142 : :
2173 alvherre@alvh.no-ip. 143 [ - + ]:CBC 6 : if (fwrite(contents->data, contents->len, 1, cf) != 1)
1247 tgl@sss.pgh.pa.us 144 :UBC 0 : pg_fatal("could not write to file \"%s\": %m", filename);
145 : :
2173 alvherre@alvh.no-ip. 146 :CBC 6 : fclose(cf);
147 : :
148 [ + - ]: 6 : if (!use_recovery_conf)
149 : : {
150 : 6 : snprintf(filename, MAXPGPATH, "%s/%s", target_dir, "standby.signal");
151 : 6 : cf = fopen(filename, "w");
152 [ - + ]: 6 : if (cf == NULL)
1247 tgl@sss.pgh.pa.us 153 :UBC 0 : pg_fatal("could not create file \"%s\": %m", filename);
154 : :
2173 alvherre@alvh.no-ip. 155 :CBC 6 : fclose(cf);
156 : : }
157 : 6 : }
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 : 12 : escape_quotes(const char *src)
165 : : {
166 : 12 : char *result = escape_single_quotes_ascii(src);
167 : :
168 [ - + ]: 12 : if (!result)
1247 tgl@sss.pgh.pa.us 169 :UBC 0 : pg_fatal("out of memory");
2173 alvherre@alvh.no-ip. 170 :CBC 12 : 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 *
178 msawada@postgresql.o 181 : 8 : FindDbnameInConnOpts(PQconninfoOption *conn_opts)
182 : : {
183 : 8 : for (PQconninfoOption *conn_opt = conn_opts;
184 [ + - ]: 64 : conn_opt->keyword != NULL;
185 : 56 : conn_opt++)
186 : : {
187 [ + + ]: 64 : if (strcmp(conn_opt->keyword, "dbname") == 0 &&
188 [ + - + - ]: 8 : conn_opt->val != NULL && conn_opt->val[0] != '\0')
189 : 8 : return pg_strdup(conn_opt->val);
190 : : }
178 msawada@postgresql.o 191 :UBC 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 *
178 msawada@postgresql.o 204 :CBC 8 : GetDbnameFromConnectionOptions(const char *connstr)
205 : : {
206 : : PQconninfoOption *conn_opts;
207 : 8 : char *err_msg = NULL;
208 : : char *dbname;
209 : :
210 : : /* First try to get the dbname from connection string. */
211 [ + + ]: 8 : if (connstr)
212 : : {
213 : 6 : conn_opts = PQconninfoParse(connstr, &err_msg);
214 [ - + ]: 6 : if (conn_opts == NULL)
178 msawada@postgresql.o 215 :UBC 0 : pg_fatal("%s", err_msg);
216 : :
178 msawada@postgresql.o 217 :CBC 6 : dbname = FindDbnameInConnOpts(conn_opts);
218 : :
219 : 6 : PQconninfoFree(conn_opts);
220 [ + - ]: 6 : if (dbname)
221 : 6 : return dbname;
222 : : }
223 : :
224 : : /*
225 : : * Next try to get the dbname from default values that are available from
226 : : * the environment.
227 : : */
228 : 2 : conn_opts = PQconndefaults();
229 [ - + ]: 2 : if (conn_opts == NULL)
178 msawada@postgresql.o 230 :UBC 0 : pg_fatal("out of memory");
231 : :
178 msawada@postgresql.o 232 :CBC 2 : dbname = FindDbnameInConnOpts(conn_opts);
233 : :
234 : 2 : PQconninfoFree(conn_opts);
235 : 2 : return dbname;
236 : : }
|