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 :
18 : /*
19 : * Write recovery configuration contents into a fresh PQExpBuffer, and
20 : * return it.
21 : *
22 : * This accepts the dbname which will be appended to the primary_conninfo.
23 : * The dbname will be ignored by walreceiver process but slotsync worker uses
24 : * it to connect to the primary server.
25 : */
26 : PQExpBuffer
27 22 : GenerateRecoveryConfig(PGconn *pgconn, const char *replication_slot,
28 : char *dbname)
29 : {
30 : PQconninfoOption *connOptions;
31 : PQExpBufferData conninfo_buf;
32 : char *escaped;
33 : PQExpBuffer contents;
34 :
35 : Assert(pgconn != NULL);
36 :
37 22 : contents = createPQExpBuffer();
38 22 : if (!contents)
39 0 : pg_fatal("out of memory");
40 :
41 : /*
42 : * In PostgreSQL 12 and newer versions, standby_mode is gone, replaced by
43 : * standby.signal to trigger a standby state at recovery.
44 : */
45 22 : if (PQserverVersion(pgconn) < MINIMUM_VERSION_FOR_RECOVERY_GUC)
46 0 : appendPQExpBufferStr(contents, "standby_mode = 'on'\n");
47 :
48 22 : connOptions = PQconninfo(pgconn);
49 22 : if (connOptions == NULL)
50 0 : pg_fatal("out of memory");
51 :
52 22 : initPQExpBuffer(&conninfo_buf);
53 968 : for (PQconninfoOption *opt = connOptions; opt && opt->keyword; opt++)
54 : {
55 : /* Omit empty settings and those libpqwalreceiver overrides. */
56 946 : if (strcmp(opt->keyword, "replication") == 0 ||
57 924 : strcmp(opt->keyword, "dbname") == 0 ||
58 902 : strcmp(opt->keyword, "fallback_application_name") == 0 ||
59 880 : (opt->val == NULL) ||
60 394 : (opt->val != NULL && opt->val[0] == '\0'))
61 574 : continue;
62 :
63 : /* Separate key-value pairs with spaces */
64 372 : if (conninfo_buf.len != 0)
65 350 : appendPQExpBufferChar(&conninfo_buf, ' ');
66 :
67 : /*
68 : * Write "keyword=value" pieces, the value string is escaped and/or
69 : * quoted if necessary.
70 : */
71 372 : appendPQExpBuffer(&conninfo_buf, "%s=", opt->keyword);
72 372 : appendConnStrVal(&conninfo_buf, opt->val);
73 : }
74 :
75 22 : if (dbname)
76 : {
77 : /*
78 : * If dbname is specified in the connection, append the dbname. This
79 : * will be used later for logical replication slot synchronization.
80 : */
81 6 : if (conninfo_buf.len != 0)
82 6 : appendPQExpBufferChar(&conninfo_buf, ' ');
83 :
84 6 : appendPQExpBuffer(&conninfo_buf, "%s=", "dbname");
85 6 : appendConnStrVal(&conninfo_buf, dbname);
86 : }
87 :
88 22 : if (PQExpBufferDataBroken(conninfo_buf))
89 0 : pg_fatal("out of memory");
90 :
91 : /*
92 : * Escape the connection string, so that it can be put in the config file.
93 : * Note that this is different from the escaping of individual connection
94 : * options above!
95 : */
96 22 : escaped = escape_quotes(conninfo_buf.data);
97 22 : termPQExpBuffer(&conninfo_buf);
98 22 : appendPQExpBuffer(contents, "primary_conninfo = '%s'\n", escaped);
99 22 : free(escaped);
100 :
101 22 : if (replication_slot)
102 : {
103 : /* unescaped: ReplicationSlotValidateName allows [a-z0-9_] only */
104 2 : appendPQExpBuffer(contents, "primary_slot_name = '%s'\n",
105 : replication_slot);
106 : }
107 :
108 22 : if (PQExpBufferBroken(contents))
109 0 : pg_fatal("out of memory");
110 :
111 22 : PQconninfoFree(connOptions);
112 :
113 22 : return contents;
114 : }
115 :
116 : /*
117 : * Write the configuration file in the directory specified in target_dir,
118 : * with the contents already collected in memory appended. Then write
119 : * the signal file into the target_dir. If the server does not support
120 : * recovery parameters as GUCs, the signal file is not necessary, and
121 : * configuration is written to recovery.conf.
122 : */
123 : void
124 12 : WriteRecoveryConfig(PGconn *pgconn, const char *target_dir, PQExpBuffer contents)
125 : {
126 : char filename[MAXPGPATH];
127 : FILE *cf;
128 : bool use_recovery_conf;
129 :
130 : Assert(pgconn != NULL);
131 :
132 12 : use_recovery_conf =
133 12 : PQserverVersion(pgconn) < MINIMUM_VERSION_FOR_RECOVERY_GUC;
134 :
135 12 : snprintf(filename, MAXPGPATH, "%s/%s", target_dir,
136 : use_recovery_conf ? "recovery.conf" : "postgresql.auto.conf");
137 :
138 12 : cf = fopen(filename, use_recovery_conf ? "w" : "a");
139 12 : if (cf == NULL)
140 0 : pg_fatal("could not open file \"%s\": %m", filename);
141 :
142 12 : if (fwrite(contents->data, contents->len, 1, cf) != 1)
143 0 : pg_fatal("could not write to file \"%s\": %m", filename);
144 :
145 12 : fclose(cf);
146 :
147 12 : if (!use_recovery_conf)
148 : {
149 12 : snprintf(filename, MAXPGPATH, "%s/%s", target_dir, "standby.signal");
150 12 : cf = fopen(filename, "w");
151 12 : if (cf == NULL)
152 0 : pg_fatal("could not create file \"%s\": %m", filename);
153 :
154 12 : fclose(cf);
155 : }
156 12 : }
157 :
158 : /*
159 : * Escape a string so that it can be used as a value in a key-value pair
160 : * a configuration file.
161 : */
162 : static char *
163 22 : escape_quotes(const char *src)
164 : {
165 22 : char *result = escape_single_quotes_ascii(src);
166 :
167 22 : if (!result)
168 0 : pg_fatal("out of memory");
169 22 : return result;
170 : }
|