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