Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * streamutil.c - utility functions for pg_basebackup, pg_receivewal and
4 : : * pg_recvlogical
5 : : *
6 : : * Author: Magnus Hagander <magnus@hagander.net>
7 : : *
8 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
9 : : *
10 : : * IDENTIFICATION
11 : : * src/bin/pg_basebackup/streamutil.c
12 : : *-------------------------------------------------------------------------
13 : : */
14 : :
15 : : #include "postgres_fe.h"
16 : :
17 : : #include <sys/time.h>
18 : : #include <unistd.h>
19 : :
20 : : #include "access/xlog_internal.h"
21 : : #include "common/connect.h"
22 : : #include "common/file_perm.h"
23 : : #include "common/logging.h"
24 : : #include "common/pg_parse_lsn.h"
25 : : #include "common/string.h"
26 : : #include "datatype/timestamp.h"
27 : : #include "port/pg_bswap.h"
28 : : #include "pqexpbuffer.h"
29 : : #include "streamutil.h"
30 : :
31 : : #define ERRCODE_DUPLICATE_OBJECT "42710"
32 : :
33 : : int WalSegSz;
34 : :
35 : : static bool RetrieveDataDirCreatePerm(PGconn *conn);
36 : :
37 : : /* SHOW command for replication connection was introduced in version 10 */
38 : : #define MINIMUM_VERSION_FOR_SHOW_CMD 100000
39 : :
40 : : /*
41 : : * Group access is supported from version 11.
42 : : */
43 : : #define MINIMUM_VERSION_FOR_GROUP_ACCESS 110000
44 : :
45 : : const char *progname;
46 : : char *connection_string = NULL;
47 : : char *dbhost = NULL;
48 : : char *dbuser = NULL;
49 : : char *dbport = NULL;
50 : : char *dbname = NULL;
51 : : int dbgetpassword = 0; /* 0=auto, -1=never, 1=always */
52 : : static char *password = NULL;
53 : : PGconn *conn = NULL;
54 : :
55 : : /*
56 : : * Connect to the server. Returns a valid PGconn pointer if connected,
57 : : * or NULL on non-permanent error. On permanent error, the function will
58 : : * call exit(1) directly.
59 : : */
60 : : PGconn *
5419 magnus@hagander.net 61 :CBC 446 : GetConnection(void)
62 : : {
63 : : PGconn *tmpconn;
4931 heikki.linnakangas@i 64 : 446 : int argcount = 7; /* dbname, replication, fallback_app_name,
65 : : * host, user, port, password */
66 : : int i;
67 : : const char **keywords;
68 : : const char **values;
69 : : const char *tmpparam;
70 : : bool need_password;
71 : 446 : PQconninfoOption *conn_opts = NULL;
72 : : PQconninfoOption *conn_opt;
73 : 446 : char *err_msg = NULL;
74 : :
75 : : /*
76 : : * pg_recvlogical uses dbname only; others use connection_string only.
77 : : * (Note: both variables will be NULL if there's no command line options.)
78 : : */
3671 noah@leadboat.com 79 [ + + - + ]: 446 : Assert(dbname == NULL || connection_string == NULL);
80 : :
81 : : /*
82 : : * Merge the connection info inputs given in form of connection string,
83 : : * options and default values (dbname=replication, replication=true, etc.)
84 : : */
4931 heikki.linnakangas@i 85 : 446 : i = 0;
86 [ + + ]: 446 : if (connection_string)
87 : : {
88 : 6 : conn_opts = PQconninfoParse(connection_string, &err_msg);
89 [ - + ]: 6 : if (conn_opts == NULL)
1602 tgl@sss.pgh.pa.us 90 :UBC 0 : pg_fatal("%s", err_msg);
91 : :
4931 heikki.linnakangas@i 92 [ + + ]:CBC 318 : for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++)
93 : : {
1071 dgustafsson@postgres 94 [ + + + - ]: 312 : if (conn_opt->val != NULL && conn_opt->val[0] != '\0')
4931 heikki.linnakangas@i 95 : 10 : argcount++;
96 : : }
97 : :
181 michael@paquier.xyz 98 : 6 : keywords = pg_malloc0_array(const char *, argcount + 1);
99 : 6 : values = pg_malloc0_array(const char *, argcount + 1);
100 : :
101 : : /*
102 : : * Set dbname here already, so it can be overridden by a dbname in the
103 : : * connection string.
104 : : */
1071 dgustafsson@postgres 105 : 6 : keywords[i] = "dbname";
106 : 6 : values[i] = "replication";
107 : 6 : i++;
108 : :
4931 heikki.linnakangas@i 109 [ + + ]: 318 : for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++)
110 : : {
1071 dgustafsson@postgres 111 [ + + + - ]: 312 : if (conn_opt->val != NULL && conn_opt->val[0] != '\0')
112 : : {
4931 heikki.linnakangas@i 113 : 10 : keywords[i] = conn_opt->keyword;
114 : 10 : values[i] = conn_opt->val;
115 : 10 : i++;
116 : : }
117 : : }
118 : : }
119 : : else
120 : : {
181 michael@paquier.xyz 121 : 440 : keywords = pg_malloc0_array(const char *, argcount + 1);
122 : 440 : values = pg_malloc0_array(const char *, argcount + 1);
1071 dgustafsson@postgres 123 : 440 : keywords[i] = "dbname";
661 tgl@sss.pgh.pa.us 124 [ + + ]: 440 : values[i] = (dbname == NULL) ? "replication" : dbname;
1071 dgustafsson@postgres 125 : 440 : i++;
126 : : }
127 : :
4931 heikki.linnakangas@i 128 : 446 : keywords[i] = "replication";
661 tgl@sss.pgh.pa.us 129 [ + + ]: 446 : values[i] = (dbname == NULL) ? "true" : "database";
4931 heikki.linnakangas@i 130 : 446 : i++;
131 : 446 : keywords[i] = "fallback_application_name";
132 : 446 : values[i] = progname;
133 : 446 : i++;
134 : :
5419 magnus@hagander.net 135 [ + + ]: 446 : if (dbhost)
136 : : {
137 : 179 : keywords[i] = "host";
138 : 179 : values[i] = dbhost;
139 : 179 : i++;
140 : : }
141 [ + + ]: 446 : if (dbuser)
142 : : {
143 : 7 : keywords[i] = "user";
144 : 7 : values[i] = dbuser;
145 : 7 : i++;
146 : : }
147 [ + + ]: 446 : if (dbport)
148 : : {
149 : 179 : keywords[i] = "port";
150 : 179 : values[i] = dbport;
151 : 179 : i++;
152 : : }
153 : :
154 : : /* If -W was given, force prompt for password, but only the first time */
2184 tgl@sss.pgh.pa.us 155 [ - + - - ]: 446 : need_password = (dbgetpassword == 1 && !password);
156 : :
157 : : do
158 : : {
159 : : /* Get a new password if appropriate */
4668 160 [ - + ]: 446 : if (need_password)
161 : : {
1533 peter@eisentraut.org 162 :UBC 0 : free(password);
2184 tgl@sss.pgh.pa.us 163 : 0 : password = simple_prompt("Password: ", false);
4668 164 : 0 : need_password = false;
165 : : }
166 : :
167 : : /* Use (or reuse, on a subsequent connection) password if we have it */
2184 tgl@sss.pgh.pa.us 168 [ - + ]:CBC 446 : if (password)
169 : : {
4931 heikki.linnakangas@i 170 :UBC 0 : keywords[i] = "password";
3649 tgl@sss.pgh.pa.us 171 : 0 : values[i] = password;
172 : : }
173 : : else
174 : : {
4668 tgl@sss.pgh.pa.us 175 :CBC 446 : keywords[i] = NULL;
176 : 446 : values[i] = NULL;
177 : : }
178 : :
179 : : /*
180 : : * Only expand dbname when we did not already parse the argument as a
181 : : * connection string ourselves.
182 : : */
1071 dgustafsson@postgres 183 : 446 : tmpconn = PQconnectdbParams(keywords, values, !connection_string);
184 : :
185 : : /*
186 : : * If there is too little memory even to allocate the PGconn object
187 : : * and PQconnectdbParams returns NULL, we call exit(1) directly.
188 : : */
5159 magnus@hagander.net 189 [ - + ]: 446 : if (!tmpconn)
1602 tgl@sss.pgh.pa.us 190 :UBC 0 : pg_fatal("could not connect to server");
191 : :
192 : : /* If we need a password and -w wasn't given, loop back and get one */
5419 magnus@hagander.net 193 [ + + - + ]:CBC 448 : if (PQstatus(tmpconn) == CONNECTION_BAD &&
194 : 2 : PQconnectionNeedsPassword(tmpconn) &&
5419 magnus@hagander.net 195 [ # # ]:UBC 0 : dbgetpassword != -1)
196 : : {
5159 197 : 0 : PQfinish(tmpconn);
4668 tgl@sss.pgh.pa.us 198 : 0 : need_password = true;
199 : : }
200 : : }
4662 peter_e@gmx.net 201 [ - + ]:CBC 446 : while (need_password);
202 : :
4668 tgl@sss.pgh.pa.us 203 [ + + ]: 446 : if (PQstatus(tmpconn) != CONNECTION_OK)
204 : : {
2119 peter@eisentraut.org 205 : 2 : pg_log_error("%s", PQerrorMessage(tmpconn));
4668 tgl@sss.pgh.pa.us 206 : 2 : PQfinish(tmpconn);
57 peter@eisentraut.org 207 :GNC 2 : pg_free(values);
208 : 2 : pg_free(keywords);
1516 peter@eisentraut.org 209 :CBC 2 : PQconninfoFree(conn_opts);
4668 tgl@sss.pgh.pa.us 210 : 2 : return NULL;
211 : : }
212 : :
213 : : /* Connection ok! */
57 peter@eisentraut.org 214 :GNC 444 : pg_free(values);
215 : 444 : pg_free(keywords);
1516 peter@eisentraut.org 216 :CBC 444 : PQconninfoFree(conn_opts);
217 : :
218 : : /*
219 : : * Set always-secure search path, so malicious users can't get control.
220 : : * The capacity to run normal SQL queries was added in PostgreSQL 10, so
221 : : * the search path cannot be changed (by us or attackers) on earlier
222 : : * versions.
223 : : */
3046 noah@leadboat.com 224 [ + + + - ]: 444 : if (dbname != NULL && PQserverVersion(tmpconn) >= 100000)
225 : : {
226 : : PGresult *res;
227 : :
3104 228 : 64 : res = PQexec(tmpconn, ALWAYS_SECURE_SEARCH_PATH_SQL);
229 [ - + ]: 64 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
230 : : {
832 peter@eisentraut.org 231 :UBC 0 : pg_log_error("could not clear \"search_path\": %s",
232 : : PQerrorMessage(tmpconn));
3104 noah@leadboat.com 233 : 0 : PQclear(res);
234 : 0 : PQfinish(tmpconn);
235 : 0 : exit(1);
236 : : }
3104 noah@leadboat.com 237 :CBC 64 : PQclear(res);
238 : : }
239 : :
240 : : /*
241 : : * Ensure we have the same value of integer_datetimes (now always "on") as
242 : : * the server we are connecting to.
243 : : */
4668 tgl@sss.pgh.pa.us 244 : 444 : tmpparam = PQparameterStatus(tmpconn, "integer_datetimes");
245 [ - + ]: 444 : if (!tmpparam)
246 : : {
832 peter@eisentraut.org 247 :UBC 0 : pg_log_error("could not determine server setting for \"integer_datetimes\"");
4668 tgl@sss.pgh.pa.us 248 : 0 : PQfinish(tmpconn);
249 : 0 : exit(1);
250 : : }
251 : :
4668 tgl@sss.pgh.pa.us 252 [ - + ]:CBC 444 : if (strcmp(tmpparam, "on") != 0)
253 : : {
832 peter@eisentraut.org 254 :UBC 0 : pg_log_error("\"integer_datetimes\" compile flag does not match server");
4668 tgl@sss.pgh.pa.us 255 : 0 : PQfinish(tmpconn);
256 : 0 : exit(1);
257 : : }
258 : :
259 : : /*
260 : : * Retrieve the source data directory mode and use it to construct a umask
261 : : * for creating directories and files.
262 : : */
3064 sfrost@snowman.net 263 [ - + ]:CBC 444 : if (!RetrieveDataDirCreatePerm(tmpconn))
264 : : {
3064 sfrost@snowman.net 265 :UBC 0 : PQfinish(tmpconn);
266 : 0 : exit(1);
267 : : }
268 : :
4668 tgl@sss.pgh.pa.us 269 :CBC 444 : return tmpconn;
270 : : }
271 : :
272 : : /*
273 : : * From version 10, explicitly set wal segment size using SHOW wal_segment_size
274 : : * since ControlFile is not accessible here.
275 : : */
276 : : bool
3264 andres@anarazel.de 277 : 214 : RetrieveWalSegSize(PGconn *conn)
278 : : {
279 : : PGresult *res;
280 : : char xlog_unit[3];
281 : : int xlog_val,
282 : 214 : multiplier = 1;
283 : :
284 : : /* check connection existence */
285 [ - + ]: 214 : Assert(conn != NULL);
286 : :
287 : : /* for previous versions set the default xlog seg size */
288 [ - + ]: 214 : if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_SHOW_CMD)
289 : : {
3264 andres@anarazel.de 290 :UBC 0 : WalSegSz = DEFAULT_XLOG_SEG_SIZE;
291 : 0 : return true;
292 : : }
293 : :
3264 andres@anarazel.de 294 :CBC 214 : res = PQexec(conn, "SHOW wal_segment_size");
295 [ - + ]: 214 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
296 : : {
2705 peter@eisentraut.org 297 :UBC 0 : pg_log_error("could not send replication command \"%s\": %s",
298 : : "SHOW wal_segment_size", PQerrorMessage(conn));
299 : :
3264 andres@anarazel.de 300 : 0 : PQclear(res);
301 : 0 : return false;
302 : : }
3264 andres@anarazel.de 303 [ + - - + ]:CBC 214 : if (PQntuples(res) != 1 || PQnfields(res) < 1)
304 : : {
2705 peter@eisentraut.org 305 :UBC 0 : pg_log_error("could not fetch WAL segment size: got %d rows and %d fields, expected %d rows and %d or more fields",
306 : : PQntuples(res), PQnfields(res), 1, 1);
307 : :
3264 andres@anarazel.de 308 : 0 : PQclear(res);
309 : 0 : return false;
310 : : }
311 : :
312 : : /* fetch xlog value and unit from the result */
1773 dgustafsson@postgres 313 [ - + ]:CBC 214 : if (sscanf(PQgetvalue(res, 0, 0), "%d%2s", &xlog_val, xlog_unit) != 2)
314 : : {
2705 peter@eisentraut.org 315 :UBC 0 : pg_log_error("WAL segment size could not be parsed");
2323 michael@paquier.xyz 316 : 0 : PQclear(res);
3264 andres@anarazel.de 317 : 0 : return false;
318 : : }
319 : :
2323 michael@paquier.xyz 320 :CBC 214 : PQclear(res);
321 : :
322 : : /* set the multiplier based on unit to convert xlog_val to bytes */
3264 andres@anarazel.de 323 [ + - ]: 214 : if (strcmp(xlog_unit, "MB") == 0)
324 : 214 : multiplier = 1024 * 1024;
3264 andres@anarazel.de 325 [ # # ]:UBC 0 : else if (strcmp(xlog_unit, "GB") == 0)
326 : 0 : multiplier = 1024 * 1024 * 1024;
327 : :
328 : : /* convert and set WalSegSz */
3264 andres@anarazel.de 329 :CBC 214 : WalSegSz = xlog_val * multiplier;
330 : :
331 [ + - + - : 214 : if (!IsValidWalSegSize(WalSegSz))
+ - - + ]
332 : : {
1095 peter@eisentraut.org 333 :UBC 0 : pg_log_error(ngettext("remote server reported invalid WAL segment size (%d byte)",
334 : : "remote server reported invalid WAL segment size (%d bytes)",
335 : : WalSegSz),
336 : : WalSegSz);
337 : 0 : pg_log_error_detail("The WAL segment size must be a power of two between 1 MB and 1 GB.");
3264 andres@anarazel.de 338 : 0 : return false;
339 : : }
340 : :
3264 andres@anarazel.de 341 :CBC 214 : return true;
342 : : }
343 : :
344 : : /*
345 : : * RetrieveDataDirCreatePerm
346 : : *
347 : : * This function is used to determine the privileges on the server's PG data
348 : : * directory and, based on that, set what the permissions will be for
349 : : * directories and files we create.
350 : : *
351 : : * PG11 added support for (optionally) group read/execute rights to be set on
352 : : * the data directory. Prior to PG11, only the owner was allowed to have rights
353 : : * on the data directory.
354 : : */
355 : : static bool
3064 sfrost@snowman.net 356 : 444 : RetrieveDataDirCreatePerm(PGconn *conn)
357 : : {
358 : : PGresult *res;
359 : : int data_directory_mode;
360 : :
361 : : /* check connection existence */
362 [ - + ]: 444 : Assert(conn != NULL);
363 : :
364 : : /* for previous versions leave the default group access */
365 [ - + ]: 444 : if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
3064 sfrost@snowman.net 366 :UBC 0 : return true;
367 : :
3064 sfrost@snowman.net 368 :CBC 444 : res = PQexec(conn, "SHOW data_directory_mode");
369 [ - + ]: 444 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
370 : : {
2705 peter@eisentraut.org 371 :UBC 0 : pg_log_error("could not send replication command \"%s\": %s",
372 : : "SHOW data_directory_mode", PQerrorMessage(conn));
373 : :
3064 sfrost@snowman.net 374 : 0 : PQclear(res);
375 : 0 : return false;
376 : : }
3064 sfrost@snowman.net 377 [ + - - + ]:CBC 444 : if (PQntuples(res) != 1 || PQnfields(res) < 1)
378 : : {
2705 peter@eisentraut.org 379 :UBC 0 : pg_log_error("could not fetch group access flag: got %d rows and %d fields, expected %d rows and %d or more fields",
380 : : PQntuples(res), PQnfields(res), 1, 1);
381 : :
3064 sfrost@snowman.net 382 : 0 : PQclear(res);
383 : 0 : return false;
384 : : }
385 : :
3064 sfrost@snowman.net 386 [ - + ]:CBC 444 : if (sscanf(PQgetvalue(res, 0, 0), "%o", &data_directory_mode) != 1)
387 : : {
2705 peter@eisentraut.org 388 :UBC 0 : pg_log_error("group access flag could not be parsed: %s",
389 : : PQgetvalue(res, 0, 0));
390 : :
3064 sfrost@snowman.net 391 : 0 : PQclear(res);
392 : 0 : return false;
393 : : }
394 : :
3064 sfrost@snowman.net 395 :CBC 444 : SetDataDirectoryCreatePerm(data_directory_mode);
396 : :
397 : 444 : PQclear(res);
398 : 444 : return true;
399 : : }
400 : :
401 : : /*
402 : : * Run IDENTIFY_SYSTEM through a given connection and give back to caller
403 : : * some result information if requested:
404 : : * - System identifier
405 : : * - Current timeline ID
406 : : * - Start LSN position
407 : : * - Database name (NULL in servers prior to 9.4)
408 : : */
409 : : bool
4348 andres@anarazel.de 410 : 451 : RunIdentifySystem(PGconn *conn, char **sysid, TimeLineID *starttli,
411 : : XLogRecPtr *startpos, char **db_name)
412 : : {
413 : : PGresult *res;
414 : :
415 : : /* Check connection existence */
416 [ - + ]: 451 : Assert(conn != NULL);
417 : :
418 : 451 : res = PQexec(conn, "IDENTIFY_SYSTEM");
419 [ - + ]: 451 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
420 : : {
2705 peter@eisentraut.org 421 :UBC 0 : pg_log_error("could not send replication command \"%s\": %s",
422 : : "IDENTIFY_SYSTEM", PQerrorMessage(conn));
423 : :
4343 sfrost@snowman.net 424 : 0 : PQclear(res);
4348 andres@anarazel.de 425 : 0 : return false;
426 : : }
4348 andres@anarazel.de 427 [ + - - + ]:CBC 451 : if (PQntuples(res) != 1 || PQnfields(res) < 3)
428 : : {
2705 peter@eisentraut.org 429 :UBC 0 : pg_log_error("could not identify system: got %d rows and %d fields, expected %d rows and %d or more fields",
430 : : PQntuples(res), PQnfields(res), 1, 3);
431 : :
4343 sfrost@snowman.net 432 : 0 : PQclear(res);
4348 andres@anarazel.de 433 : 0 : return false;
434 : : }
435 : :
436 : : /* Get system identifier */
4348 andres@anarazel.de 437 [ + + ]:CBC 451 : if (sysid != NULL)
438 : 378 : *sysid = pg_strdup(PQgetvalue(res, 0, 0));
439 : :
440 : : /* Get timeline ID to start streaming from */
441 [ + + ]: 451 : if (starttli != NULL)
442 : 378 : *starttli = atoi(PQgetvalue(res, 0, 1));
443 : :
444 : : /* Get LSN start position if necessary */
445 [ + + ]: 451 : if (startpos != NULL)
446 : : {
6 fujii@postgresql.org 447 [ - + ]:GNC 7 : if (!pg_parse_lsn(PQgetvalue(res, 0, 2), startpos))
448 : : {
2705 peter@eisentraut.org 449 :UBC 0 : pg_log_error("could not parse write-ahead log location \"%s\"",
450 : : PQgetvalue(res, 0, 2));
451 : :
4343 sfrost@snowman.net 452 : 0 : PQclear(res);
4348 andres@anarazel.de 453 : 0 : return false;
454 : : }
455 : : }
456 : :
457 : : /* Get database name, only available in 9.4 and newer versions */
4114 bruce@momjian.us 458 [ + + ]:CBC 451 : if (db_name != NULL)
459 : : {
3887 alvherre@alvh.no-ip. 460 : 73 : *db_name = NULL;
461 [ + - ]: 73 : if (PQserverVersion(conn) >= 90400)
462 : : {
463 [ - + ]: 73 : if (PQnfields(res) < 4)
464 : : {
2705 peter@eisentraut.org 465 :UBC 0 : pg_log_error("could not identify system: got %d rows and %d fields, expected %d rows and %d or more fields",
466 : : PQntuples(res), PQnfields(res), 1, 4);
467 : :
3887 alvherre@alvh.no-ip. 468 : 0 : PQclear(res);
469 : 0 : return false;
470 : : }
3887 alvherre@alvh.no-ip. 471 [ + + ]:CBC 73 : if (!PQgetisnull(res, 0, 3))
472 : 63 : *db_name = pg_strdup(PQgetvalue(res, 0, 3));
473 : : }
474 : : }
475 : :
4348 andres@anarazel.de 476 : 451 : PQclear(res);
477 : 451 : return true;
478 : : }
479 : :
480 : : /*
481 : : * Run READ_REPLICATION_SLOT through a given connection and give back to
482 : : * caller some result information if requested for this slot:
483 : : * - Start LSN position, InvalidXLogRecPtr if unknown.
484 : : * - Current timeline ID, 0 if unknown.
485 : : * Returns false on failure, and true otherwise.
486 : : */
487 : : bool
1766 michael@paquier.xyz 488 : 3 : GetSlotInformation(PGconn *conn, const char *slot_name,
489 : : XLogRecPtr *restart_lsn, TimeLineID *restart_tli)
490 : : {
491 : : PGresult *res;
492 : : PQExpBuffer query;
493 : 3 : XLogRecPtr lsn_loc = InvalidXLogRecPtr;
494 : 3 : TimeLineID tli_loc = 0;
495 : :
496 [ + - ]: 3 : if (restart_lsn)
497 : 3 : *restart_lsn = lsn_loc;
498 [ + - ]: 3 : if (restart_tli)
499 : 3 : *restart_tli = tli_loc;
500 : :
501 : 3 : query = createPQExpBuffer();
73 tgl@sss.pgh.pa.us 502 : 3 : appendPQExpBufferStr(query, "READ_REPLICATION_SLOT ");
503 : 3 : AppendQuotedIdentifier(query, slot_name);
1766 michael@paquier.xyz 504 : 3 : res = PQexec(conn, query->data);
505 : 3 : destroyPQExpBuffer(query);
506 : :
507 [ - + ]: 3 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
508 : : {
1766 michael@paquier.xyz 509 :UBC 0 : pg_log_error("could not send replication command \"%s\": %s",
510 : : "READ_REPLICATION_SLOT", PQerrorMessage(conn));
511 : 0 : PQclear(res);
512 : 0 : return false;
513 : : }
514 : :
515 : : /* The command should always return precisely one tuple and three fields */
1766 michael@paquier.xyz 516 [ + - - + ]:CBC 3 : if (PQntuples(res) != 1 || PQnfields(res) != 3)
517 : : {
1766 michael@paquier.xyz 518 :UBC 0 : pg_log_error("could not read replication slot \"%s\": got %d rows and %d fields, expected %d rows and %d fields",
519 : : slot_name, PQntuples(res), PQnfields(res), 1, 3);
520 : 0 : PQclear(res);
521 : 0 : return false;
522 : : }
523 : :
524 : : /*
525 : : * When the slot doesn't exist, the command returns a tuple with NULL
526 : : * values. This checks only the slot type field.
527 : : */
1766 michael@paquier.xyz 528 [ + + ]:CBC 3 : if (PQgetisnull(res, 0, 0))
529 : : {
1433 peter@eisentraut.org 530 : 1 : pg_log_error("replication slot \"%s\" does not exist", slot_name);
1766 michael@paquier.xyz 531 : 1 : PQclear(res);
532 : 1 : return false;
533 : : }
534 : :
535 : : /*
536 : : * Note that this cannot happen as READ_REPLICATION_SLOT supports only
537 : : * physical slots, but play it safe.
538 : : */
539 [ - + ]: 2 : if (strcmp(PQgetvalue(res, 0, 0), "physical") != 0)
540 : : {
1766 michael@paquier.xyz 541 :UBC 0 : pg_log_error("expected a physical replication slot, got type \"%s\" instead",
542 : : PQgetvalue(res, 0, 0));
543 : 0 : PQclear(res);
544 : 0 : return false;
545 : : }
546 : :
547 : : /* restart LSN */
1766 michael@paquier.xyz 548 [ + - ]:CBC 2 : if (!PQgetisnull(res, 0, 1))
549 : : {
6 fujii@postgresql.org 550 [ - + ]:GNC 2 : if (!pg_parse_lsn(PQgetvalue(res, 0, 1), &lsn_loc))
551 : : {
1766 michael@paquier.xyz 552 :UBC 0 : pg_log_error("could not parse restart_lsn \"%s\" for replication slot \"%s\"",
553 : : PQgetvalue(res, 0, 1), slot_name);
554 : 0 : PQclear(res);
555 : 0 : return false;
556 : : }
557 : : }
558 : :
559 : : /* current TLI */
1766 michael@paquier.xyz 560 [ + - ]:CBC 2 : if (!PQgetisnull(res, 0, 2))
747 peter@eisentraut.org 561 : 2 : tli_loc = (TimeLineID) atoll(PQgetvalue(res, 0, 2));
562 : :
1766 michael@paquier.xyz 563 : 2 : PQclear(res);
564 : :
565 : : /* Assign results if requested */
566 [ + - ]: 2 : if (restart_lsn)
567 : 2 : *restart_lsn = lsn_loc;
568 [ + - ]: 2 : if (restart_tli)
569 : 2 : *restart_tli = tli_loc;
570 : :
571 : 2 : return true;
572 : : }
573 : :
574 : : /*
575 : : * Create a replication slot for the given connection. This function
576 : : * returns true in case of success.
577 : : */
578 : : bool
4348 andres@anarazel.de 579 : 192 : CreateReplicationSlot(PGconn *conn, const char *slot_name, const char *plugin,
580 : : bool is_temporary, bool is_physical, bool reserve_wal,
581 : : bool slot_exists_ok, bool two_phase, bool failover)
582 : : {
583 : : PQExpBuffer query;
584 : : PGresult *res;
1787 rhaas@postgresql.org 585 : 192 : bool use_new_option_syntax = (PQserverVersion(conn) >= 150000);
586 : :
4348 andres@anarazel.de 587 : 192 : query = createPQExpBuffer();
588 : :
589 [ + + + - : 192 : Assert((is_physical && plugin == NULL) ||
+ - + - ]
590 : : (!is_physical && plugin != NULL));
1884 akapila@postgresql.o 591 [ + + - + ]: 192 : Assert(!(two_phase && is_physical));
510 msawada@postgresql.o 592 [ + + - + ]: 192 : Assert(!(failover && is_physical));
4348 andres@anarazel.de 593 [ - + ]: 192 : Assert(slot_name != NULL);
594 : :
595 : : /* Build base portion of query */
73 tgl@sss.pgh.pa.us 596 : 192 : appendPQExpBufferStr(query, "CREATE_REPLICATION_SLOT ");
597 : 192 : AppendQuotedIdentifier(query, slot_name);
3257 peter_e@gmx.net 598 [ + + ]: 192 : if (is_temporary)
2611 drowley@postgresql.o 599 : 156 : appendPQExpBufferStr(query, " TEMPORARY");
4348 andres@anarazel.de 600 [ + + ]: 192 : if (is_physical)
2611 drowley@postgresql.o 601 : 160 : appendPQExpBufferStr(query, " PHYSICAL");
602 : : else
603 : : {
73 tgl@sss.pgh.pa.us 604 : 32 : appendPQExpBufferStr(query, " LOGICAL ");
605 : 32 : AppendQuotedIdentifier(query, plugin);
606 : : }
607 : :
608 : : /* Add any requested options */
1787 rhaas@postgresql.org 609 [ + - ]: 192 : if (use_new_option_syntax)
610 : 192 : appendPQExpBufferStr(query, " (");
611 [ + + ]: 192 : if (is_physical)
612 : : {
3257 peter_e@gmx.net 613 [ + + ]: 160 : if (reserve_wal)
1787 rhaas@postgresql.org 614 : 159 : AppendPlainCommandOption(query, use_new_option_syntax,
615 : : "RESERVE_WAL");
616 : : }
617 : : else
618 : : {
510 msawada@postgresql.o 619 [ + + + - ]: 32 : if (failover && PQserverVersion(conn) >= 170000)
620 : 1 : AppendPlainCommandOption(query, use_new_option_syntax,
621 : : "FAILOVER");
622 : :
1884 akapila@postgresql.o 623 [ + + + - ]: 32 : if (two_phase && PQserverVersion(conn) >= 150000)
1787 rhaas@postgresql.org 624 : 1 : AppendPlainCommandOption(query, use_new_option_syntax,
625 : : "TWO_PHASE");
626 : :
3453 peter_e@gmx.net 627 [ + - ]: 32 : if (PQserverVersion(conn) >= 100000)
628 : : {
629 : : /* pg_recvlogical doesn't use an exported snapshot, so suppress */
1787 rhaas@postgresql.org 630 [ + - ]: 32 : if (use_new_option_syntax)
631 : 32 : AppendStringCommandOption(query, use_new_option_syntax,
632 : : "SNAPSHOT", "nothing");
633 : : else
1787 rhaas@postgresql.org 634 :UBC 0 : AppendPlainCommandOption(query, use_new_option_syntax,
635 : : "NOEXPORT_SNAPSHOT");
636 : : }
637 : : }
1787 rhaas@postgresql.org 638 [ + - ]:CBC 192 : if (use_new_option_syntax)
639 : : {
640 : : /* Suppress option list if it would be empty, otherwise terminate */
641 [ + + ]: 192 : if (query->data[query->len - 1] == '(')
642 : : {
643 : 1 : query->len -= 2;
644 : 1 : query->data[query->len] = '\0';
645 : : }
646 : : else
647 : 191 : appendPQExpBufferChar(query, ')');
648 : : }
649 : :
650 : : /* Now run the query */
4348 andres@anarazel.de 651 : 192 : res = PQexec(conn, query->data);
652 [ + + ]: 192 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
653 : : {
4064 654 : 1 : const char *sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
655 : :
4033 656 [ - + - - ]: 1 : if (slot_exists_ok &&
4033 andres@anarazel.de 657 :UBC 0 : sqlstate &&
658 [ # # ]: 0 : strcmp(sqlstate, ERRCODE_DUPLICATE_OBJECT) == 0)
659 : : {
4064 660 : 0 : destroyPQExpBuffer(query);
661 : 0 : PQclear(res);
662 : 0 : return true;
663 : : }
664 : : else
665 : : {
2705 peter@eisentraut.org 666 :CBC 1 : pg_log_error("could not send replication command \"%s\": %s",
667 : : query->data, PQerrorMessage(conn));
668 : :
4064 andres@anarazel.de 669 : 1 : destroyPQExpBuffer(query);
670 : 1 : PQclear(res);
671 : 1 : return false;
672 : : }
673 : : }
674 : :
4348 675 [ + - - + ]: 191 : if (PQntuples(res) != 1 || PQnfields(res) != 4)
676 : : {
2705 peter@eisentraut.org 677 :UBC 0 : pg_log_error("could not create replication slot \"%s\": got %d rows and %d fields, expected %d rows and %d fields",
678 : : slot_name,
679 : : PQntuples(res), PQnfields(res), 1, 4);
680 : :
4343 sfrost@snowman.net 681 : 0 : destroyPQExpBuffer(query);
682 : 0 : PQclear(res);
4348 andres@anarazel.de 683 : 0 : return false;
684 : : }
685 : :
4343 sfrost@snowman.net 686 :CBC 191 : destroyPQExpBuffer(query);
4348 andres@anarazel.de 687 : 191 : PQclear(res);
688 : 191 : return true;
689 : : }
690 : :
691 : : /*
692 : : * Drop a replication slot for the given connection. This function
693 : : * returns true in case of success.
694 : : */
695 : : bool
696 : 5 : DropReplicationSlot(PGconn *conn, const char *slot_name)
697 : : {
698 : : PQExpBuffer query;
699 : : PGresult *res;
700 : :
701 [ - + ]: 5 : Assert(slot_name != NULL);
702 : :
703 : 5 : query = createPQExpBuffer();
704 : :
705 : : /* Build query */
73 tgl@sss.pgh.pa.us 706 : 5 : appendPQExpBufferStr(query, "DROP_REPLICATION_SLOT ");
707 : 5 : AppendQuotedIdentifier(query, slot_name);
4348 andres@anarazel.de 708 : 5 : res = PQexec(conn, query->data);
709 [ - + ]: 5 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
710 : : {
2705 peter@eisentraut.org 711 :UBC 0 : pg_log_error("could not send replication command \"%s\": %s",
712 : : query->data, PQerrorMessage(conn));
713 : :
4343 sfrost@snowman.net 714 : 0 : destroyPQExpBuffer(query);
715 : 0 : PQclear(res);
4348 andres@anarazel.de 716 : 0 : return false;
717 : : }
718 : :
4348 andres@anarazel.de 719 [ + - - + ]:CBC 5 : if (PQntuples(res) != 0 || PQnfields(res) != 0)
720 : : {
2705 peter@eisentraut.org 721 :UBC 0 : pg_log_error("could not drop replication slot \"%s\": got %d rows and %d fields, expected %d rows and %d fields",
722 : : slot_name,
723 : : PQntuples(res), PQnfields(res), 0, 0);
724 : :
4343 sfrost@snowman.net 725 : 0 : destroyPQExpBuffer(query);
726 : 0 : PQclear(res);
4348 andres@anarazel.de 727 : 0 : return false;
728 : : }
729 : :
4064 tgl@sss.pgh.pa.us 730 :CBC 5 : destroyPQExpBuffer(query);
4348 andres@anarazel.de 731 : 5 : PQclear(res);
732 : 5 : return true;
733 : : }
734 : :
735 : : /*
736 : : * Append a suitably-quoted identifier or string literal to buf.
737 : : * "quote" should be either a double-quote or single-quote character.
738 : : *
739 : : * Caution: this quoting logic is sufficient for identifiers and literals
740 : : * in the replication grammar, but not always in regular SQL. Specifically,
741 : : * it'd fail for a string literal if standard_conforming_strings is off.
742 : : */
743 : : void
73 tgl@sss.pgh.pa.us 744 : 1410 : AppendQuotedString(PQExpBuffer buf, const char *str, char quote)
745 : : {
746 : 1410 : appendPQExpBufferChar(buf, quote);
747 [ + + ]: 19221 : while (*str)
748 : : {
749 : 17811 : char c = *str++;
750 : :
751 [ - + ]: 17811 : if (c == quote)
73 tgl@sss.pgh.pa.us 752 :UBC 0 : appendPQExpBufferChar(buf, c);
73 tgl@sss.pgh.pa.us 753 :CBC 17811 : appendPQExpBufferChar(buf, c);
754 : : }
755 : 1410 : appendPQExpBufferChar(buf, quote);
756 : 1410 : }
757 : :
758 : : /*
759 : : * Append a "plain" option - one with no value - to a server command that
760 : : * is being constructed.
761 : : *
762 : : * In the old syntax, all options were parser keywords, so you could just
763 : : * write things like SOME_COMMAND OPTION1 OPTION2 'opt2value' OPTION3 42. The
764 : : * new syntax uses a comma-separated list surrounded by parentheses, so the
765 : : * equivalent is SOME_COMMAND (OPTION1, OPTION2 'optvalue', OPTION3 42).
766 : : *
767 : : * Note: we assume option names do not require quotes. Do not use this
768 : : * with option names coming from outside sources.
769 : : */
770 : : void
1787 rhaas@postgresql.org 771 : 1537 : AppendPlainCommandOption(PQExpBuffer buf, bool use_new_option_syntax,
772 : : const char *option_name)
773 : : {
774 [ + + + + ]: 1537 : if (buf->len > 0 && buf->data[buf->len - 1] != '(')
775 : : {
776 [ + - ]: 1140 : if (use_new_option_syntax)
777 : 1140 : appendPQExpBufferStr(buf, ", ");
778 : : else
1787 rhaas@postgresql.org 779 :UBC 0 : appendPQExpBufferChar(buf, ' ');
780 : : }
781 : :
1787 rhaas@postgresql.org 782 :CBC 1537 : appendPQExpBuffer(buf, " %s", option_name);
783 : 1537 : }
784 : :
785 : : /*
786 : : * Append an option with an associated string value to a server command that
787 : : * is being constructed.
788 : : *
789 : : * See comments for AppendPlainCommandOption, above.
790 : : */
791 : : void
792 : 905 : AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax,
793 : : const char *option_name, const char *option_value)
794 : : {
795 : 905 : AppendPlainCommandOption(buf, use_new_option_syntax, option_name);
796 : :
797 [ + - ]: 905 : if (option_value != NULL)
798 : : {
73 tgl@sss.pgh.pa.us 799 : 905 : appendPQExpBufferChar(buf, ' ');
800 : 905 : AppendQuotedLiteral(buf, option_value);
801 : : }
1787 rhaas@postgresql.org 802 : 905 : }
803 : :
804 : : /*
805 : : * Append an option with an associated integer value to a server command that
806 : : * is being constructed.
807 : : *
808 : : * See comments for AppendPlainCommandOption, above.
809 : : */
810 : : void
811 : 198 : AppendIntegerCommandOption(PQExpBuffer buf, bool use_new_option_syntax,
812 : : const char *option_name, int32 option_value)
813 : : {
814 : 198 : AppendPlainCommandOption(buf, use_new_option_syntax, option_name);
815 : :
816 : 198 : appendPQExpBuffer(buf, " %d", option_value);
817 : 198 : }
818 : :
819 : : /*
820 : : * Frontend version of GetCurrentTimestamp(), since we are not linked with
821 : : * backend code.
822 : : */
823 : : TimestampTz
4545 824 : 2147 : feGetCurrentTimestamp(void)
825 : : {
826 : : TimestampTz result;
827 : : struct timeval tp;
828 : :
829 : 2147 : gettimeofday(&tp, NULL);
830 : :
3472 tgl@sss.pgh.pa.us 831 : 2147 : result = (TimestampTz) tp.tv_sec -
832 : : ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
4545 rhaas@postgresql.org 833 : 2147 : result = (result * USECS_PER_SEC) + tp.tv_usec;
834 : :
835 : 2147 : return result;
836 : : }
837 : :
838 : : /*
839 : : * Frontend version of TimestampDifference(), since we are not linked with
840 : : * backend code.
841 : : */
842 : : void
3472 tgl@sss.pgh.pa.us 843 : 1624 : feTimestampDifference(TimestampTz start_time, TimestampTz stop_time,
844 : : long *secs, int *microsecs)
845 : : {
846 : 1624 : TimestampTz diff = stop_time - start_time;
847 : :
4545 rhaas@postgresql.org 848 [ - + ]: 1624 : if (diff <= 0)
849 : : {
4545 rhaas@postgresql.org 850 :UBC 0 : *secs = 0;
851 : 0 : *microsecs = 0;
852 : : }
853 : : else
854 : : {
4545 rhaas@postgresql.org 855 :CBC 1624 : *secs = (long) (diff / USECS_PER_SEC);
856 : 1624 : *microsecs = (int) (diff % USECS_PER_SEC);
857 : : }
858 : 1624 : }
859 : :
860 : : /*
861 : : * Frontend version of TimestampDifferenceExceeds(), since we are not
862 : : * linked with backend code.
863 : : */
864 : : bool
3472 tgl@sss.pgh.pa.us 865 : 2490 : feTimestampDifferenceExceeds(TimestampTz start_time,
866 : : TimestampTz stop_time,
867 : : int msec)
868 : : {
869 : 2490 : TimestampTz diff = stop_time - start_time;
870 : :
4545 rhaas@postgresql.org 871 : 2490 : return (diff >= msec * INT64CONST(1000));
872 : : }
873 : :
874 : : /*
875 : : * Converts an int64 to network byte order.
876 : : */
877 : : void
878 : 800 : fe_sendint64(int64 i, char *buf)
879 : : {
3252 andres@anarazel.de 880 : 800 : uint64 n64 = pg_hton64(i);
881 : :
882 : 800 : memcpy(buf, &n64, sizeof(n64));
4545 rhaas@postgresql.org 883 : 800 : }
884 : :
885 : : /*
886 : : * Converts an int64 from network byte order to native format.
887 : : */
888 : : int64
889 : 2383 : fe_recvint64(char *buf)
890 : : {
891 : : uint64 n64;
892 : :
3252 andres@anarazel.de 893 : 2383 : memcpy(&n64, buf, sizeof(n64));
894 : :
895 : 2383 : return pg_ntoh64(n64);
896 : : }
|