1 /*-------------------------------------------------------------------------
2  *
3  * recovery_gen.c
4  *		Generator for recovery configuration
5  *
6  * Portions Copyright (c) 2011-2020, 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