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-2024, 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 walreciever process but slotsync worker uses
24 : : * it to connect to the primary server.
25 : : */
26 : : PQExpBuffer
24 akapila@postgresql.o 27 :GNC 11 : 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 : :
1663 alvherre@alvh.no-ip. 35 [ - + ]:CBC 11 : Assert(pgconn != NULL);
36 : :
37 : 11 : contents = createPQExpBuffer();
38 [ - + ]: 11 : if (!contents)
737 tgl@sss.pgh.pa.us 39 :UBC 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 : : */
1663 alvherre@alvh.no-ip. 45 [ - + ]:CBC 11 : if (PQserverVersion(pgconn) < MINIMUM_VERSION_FOR_RECOVERY_GUC)
1663 alvherre@alvh.no-ip. 46 :UBC 0 : appendPQExpBufferStr(contents, "standby_mode = 'on'\n");
47 : :
1663 alvherre@alvh.no-ip. 48 :CBC 11 : connOptions = PQconninfo(pgconn);
49 [ - + ]: 11 : if (connOptions == NULL)
737 tgl@sss.pgh.pa.us 50 :UBC 0 : pg_fatal("out of memory");
51 : :
1663 alvherre@alvh.no-ip. 52 :CBC 11 : initPQExpBuffer(&conninfo_buf);
53 [ + - + + ]: 462 : for (PQconninfoOption *opt = connOptions; opt && opt->keyword; opt++)
54 : : {
55 : : /* Omit empty settings and those libpqwalreceiver overrides. */
56 [ + + ]: 451 : if (strcmp(opt->keyword, "replication") == 0 ||
57 [ + + ]: 440 : strcmp(opt->keyword, "dbname") == 0 ||
58 [ + + ]: 429 : strcmp(opt->keyword, "fallback_application_name") == 0 ||
59 [ + + ]: 418 : (opt->val == NULL) ||
60 [ + - + + ]: 197 : (opt->val != NULL && opt->val[0] == '\0'))
61 : 265 : continue;
62 : :
63 : : /* Separate key-value pairs with spaces */
64 [ + + ]: 186 : if (conninfo_buf.len != 0)
65 : 175 : appendPQExpBufferChar(&conninfo_buf, ' ');
66 : :
67 : : /*
68 : : * Write "keyword=value" pieces, the value string is escaped and/or
69 : : * quoted if necessary.
70 : : */
71 : 186 : appendPQExpBuffer(&conninfo_buf, "%s=", opt->keyword);
72 : 186 : appendConnStrVal(&conninfo_buf, opt->val);
73 : : }
74 : :
24 akapila@postgresql.o 75 [ + + ]:GNC 11 : 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 [ + - ]: 3 : if (conninfo_buf.len != 0)
82 : 3 : appendPQExpBufferChar(&conninfo_buf, ' ');
83 : :
84 : 3 : appendPQExpBuffer(&conninfo_buf, "%s=", "dbname");
85 : 3 : appendConnStrVal(&conninfo_buf, dbname);
86 : : }
87 : :
1663 alvherre@alvh.no-ip. 88 [ - + ]:CBC 11 : if (PQExpBufferDataBroken(conninfo_buf))
737 tgl@sss.pgh.pa.us 89 :UBC 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 : : */
1663 alvherre@alvh.no-ip. 96 :CBC 11 : escaped = escape_quotes(conninfo_buf.data);
97 : 11 : termPQExpBuffer(&conninfo_buf);
98 : 11 : appendPQExpBuffer(contents, "primary_conninfo = '%s'\n", escaped);
99 : 11 : free(escaped);
100 : :
101 [ + + ]: 11 : if (replication_slot)
102 : : {
103 : : /* unescaped: ReplicationSlotValidateName allows [a-z0-9_] only */
104 : 1 : appendPQExpBuffer(contents, "primary_slot_name = '%s'\n",
105 : : replication_slot);
106 : : }
107 : :
108 [ + - - + ]: 11 : if (PQExpBufferBroken(contents))
737 tgl@sss.pgh.pa.us 109 :UBC 0 : pg_fatal("out of memory");
110 : :
1663 alvherre@alvh.no-ip. 111 :CBC 11 : PQconninfoFree(connOptions);
112 : :
113 : 11 : 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
27 peter@eisentraut.org 124 :GNC 6 : WriteRecoveryConfig(PGconn *pgconn, const char *target_dir, PQExpBuffer contents)
125 : : {
126 : : char filename[MAXPGPATH];
127 : : FILE *cf;
128 : : bool use_recovery_conf;
129 : :
1663 alvherre@alvh.no-ip. 130 [ - + ]:CBC 6 : Assert(pgconn != NULL);
131 : :
132 : 6 : use_recovery_conf =
133 : 6 : PQserverVersion(pgconn) < MINIMUM_VERSION_FOR_RECOVERY_GUC;
134 : :
135 [ - + ]: 6 : snprintf(filename, MAXPGPATH, "%s/%s", target_dir,
136 : : use_recovery_conf ? "recovery.conf" : "postgresql.auto.conf");
137 : :
1523 fujii@postgresql.org 138 [ - + ]: 6 : cf = fopen(filename, use_recovery_conf ? "w" : "a");
1663 alvherre@alvh.no-ip. 139 [ - + ]: 6 : if (cf == NULL)
737 tgl@sss.pgh.pa.us 140 :UBC 0 : pg_fatal("could not open file \"%s\": %m", filename);
141 : :
1663 alvherre@alvh.no-ip. 142 [ - + ]:CBC 6 : if (fwrite(contents->data, contents->len, 1, cf) != 1)
737 tgl@sss.pgh.pa.us 143 :UBC 0 : pg_fatal("could not write to file \"%s\": %m", filename);
144 : :
1663 alvherre@alvh.no-ip. 145 :CBC 6 : fclose(cf);
146 : :
147 [ + - ]: 6 : if (!use_recovery_conf)
148 : : {
149 : 6 : snprintf(filename, MAXPGPATH, "%s/%s", target_dir, "standby.signal");
150 : 6 : cf = fopen(filename, "w");
151 [ - + ]: 6 : if (cf == NULL)
737 tgl@sss.pgh.pa.us 152 :UBC 0 : pg_fatal("could not create file \"%s\": %m", filename);
153 : :
1663 alvherre@alvh.no-ip. 154 :CBC 6 : fclose(cf);
155 : : }
156 : 6 : }
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 : 11 : escape_quotes(const char *src)
164 : : {
165 : 11 : char *result = escape_single_quotes_ascii(src);
166 : :
167 [ - + ]: 11 : if (!result)
737 tgl@sss.pgh.pa.us 168 :UBC 0 : pg_fatal("out of memory");
1663 alvherre@alvh.no-ip. 169 :CBC 11 : return result;
170 : : }
|