Branch data 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 *
61 : 448 : GetConnection(void)
62 : : {
63 : : PGconn *tmpconn;
64 : 448 : 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 : 448 : PQconninfoOption *conn_opts = NULL;
72 : : PQconninfoOption *conn_opt;
73 : 448 : 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 : : */
79 : : 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 : : */
85 : 448 : i = 0;
86 [ + + ]: 448 : if (connection_string)
87 : : {
88 : 6 : conn_opts = PQconninfoParse(connection_string, &err_msg);
89 [ - + ]: 6 : if (conn_opts == NULL)
90 : 0 : pg_fatal("%s", err_msg);
91 : :
92 [ + + ]: 318 : for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++)
93 : : {
94 [ + + + - ]: 312 : if (conn_opt->val != NULL && conn_opt->val[0] != '\0')
95 : 10 : argcount++;
96 : : }
97 : :
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 : : */
105 : 6 : keywords[i] = "dbname";
106 : 6 : values[i] = "replication";
107 : 6 : i++;
108 : :
109 [ + + ]: 318 : for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++)
110 : : {
111 [ + + + - ]: 312 : if (conn_opt->val != NULL && conn_opt->val[0] != '\0')
112 : : {
113 : 10 : keywords[i] = conn_opt->keyword;
114 : 10 : values[i] = conn_opt->val;
115 : 10 : i++;
116 : : }
117 : : }
118 : : }
119 : : else
120 : : {
121 : 442 : keywords = pg_malloc0_array(const char *, argcount + 1);
122 : 442 : values = pg_malloc0_array(const char *, argcount + 1);
123 : 442 : keywords[i] = "dbname";
124 [ + + ]: 442 : values[i] = (dbname == NULL) ? "replication" : dbname;
125 : 442 : i++;
126 : : }
127 : :
128 : 448 : keywords[i] = "replication";
129 [ + + ]: 448 : values[i] = (dbname == NULL) ? "true" : "database";
130 : 448 : i++;
131 : 448 : keywords[i] = "fallback_application_name";
132 : 448 : values[i] = progname;
133 : 448 : i++;
134 : :
135 [ + + ]: 448 : if (dbhost)
136 : : {
137 : 181 : keywords[i] = "host";
138 : 181 : values[i] = dbhost;
139 : 181 : i++;
140 : : }
141 [ + + ]: 448 : if (dbuser)
142 : : {
143 : 7 : keywords[i] = "user";
144 : 7 : values[i] = dbuser;
145 : 7 : i++;
146 : : }
147 [ + + ]: 448 : if (dbport)
148 : : {
149 : 181 : keywords[i] = "port";
150 : 181 : values[i] = dbport;
151 : 181 : i++;
152 : : }
153 : :
154 : : /* If -W was given, force prompt for password, but only the first time */
155 [ - + - - ]: 448 : need_password = (dbgetpassword == 1 && !password);
156 : :
157 : : do
158 : : {
159 : : /* Get a new password if appropriate */
160 [ - + ]: 448 : if (need_password)
161 : : {
162 : 0 : free(password);
163 : 0 : password = simple_prompt("Password: ", false);
164 : 0 : need_password = false;
165 : : }
166 : :
167 : : /* Use (or reuse, on a subsequent connection) password if we have it */
168 [ - + ]: 448 : if (password)
169 : : {
170 : 0 : keywords[i] = "password";
171 : 0 : values[i] = password;
172 : : }
173 : : else
174 : : {
175 : 448 : keywords[i] = NULL;
176 : 448 : 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 : : */
183 : 448 : 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 : : */
189 [ - + ]: 448 : if (!tmpconn)
190 : 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 */
193 [ + + - + ]: 450 : if (PQstatus(tmpconn) == CONNECTION_BAD &&
194 : 2 : PQconnectionNeedsPassword(tmpconn) &&
195 [ # # ]: 0 : dbgetpassword != -1)
196 : : {
197 : 0 : PQfinish(tmpconn);
198 : 0 : need_password = true;
199 : : }
200 : : }
201 [ - + ]: 448 : while (need_password);
202 : :
203 [ + + ]: 448 : if (PQstatus(tmpconn) != CONNECTION_OK)
204 : : {
205 : 2 : pg_log_error("%s", PQerrorMessage(tmpconn));
206 : 2 : PQfinish(tmpconn);
207 : 2 : pg_free(values);
208 : 2 : pg_free(keywords);
209 : 2 : PQconninfoFree(conn_opts);
210 : 2 : return NULL;
211 : : }
212 : :
213 : : /* Connection ok! */
214 : 446 : pg_free(values);
215 : 446 : pg_free(keywords);
216 : 446 : 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 : : */
224 [ + + + - ]: 446 : if (dbname != NULL && PQserverVersion(tmpconn) >= 100000)
225 : : {
226 : : PGresult *res;
227 : :
228 : 64 : res = PQexec(tmpconn, ALWAYS_SECURE_SEARCH_PATH_SQL);
229 [ - + ]: 64 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
230 : : {
231 : 0 : pg_log_error("could not clear \"search_path\": %s",
232 : : PQerrorMessage(tmpconn));
233 : 0 : PQclear(res);
234 : 0 : PQfinish(tmpconn);
235 : 0 : exit(1);
236 : : }
237 : 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 : : */
244 : 446 : tmpparam = PQparameterStatus(tmpconn, "integer_datetimes");
245 [ - + ]: 446 : if (!tmpparam)
246 : : {
247 : 0 : pg_log_error("could not determine server setting for \"integer_datetimes\"");
248 : 0 : PQfinish(tmpconn);
249 : 0 : exit(1);
250 : : }
251 : :
252 [ - + ]: 446 : if (strcmp(tmpparam, "on") != 0)
253 : : {
254 : 0 : pg_log_error("\"integer_datetimes\" compile flag does not match server");
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 : : */
263 [ - + ]: 446 : if (!RetrieveDataDirCreatePerm(tmpconn))
264 : : {
265 : 0 : PQfinish(tmpconn);
266 : 0 : exit(1);
267 : : }
268 : :
269 : 446 : 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
277 : 215 : RetrieveWalSegSize(PGconn *conn)
278 : : {
279 : : PGresult *res;
280 : : char xlog_unit[3];
281 : : int xlog_val,
282 : 215 : multiplier = 1;
283 : :
284 : : /* check connection existence */
285 : : Assert(conn != NULL);
286 : :
287 : : /* for previous versions set the default xlog seg size */
288 [ - + ]: 215 : if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_SHOW_CMD)
289 : : {
290 : 0 : WalSegSz = DEFAULT_XLOG_SEG_SIZE;
291 : 0 : return true;
292 : : }
293 : :
294 : 215 : res = PQexec(conn, "SHOW wal_segment_size");
295 [ - + ]: 215 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
296 : : {
297 : 0 : pg_log_error("could not send replication command \"%s\": %s",
298 : : "SHOW wal_segment_size", PQerrorMessage(conn));
299 : :
300 : 0 : PQclear(res);
301 : 0 : return false;
302 : : }
303 [ + - - + ]: 215 : if (PQntuples(res) != 1 || PQnfields(res) < 1)
304 : : {
305 : 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 : :
308 : 0 : PQclear(res);
309 : 0 : return false;
310 : : }
311 : :
312 : : /* fetch xlog value and unit from the result */
313 [ - + ]: 215 : if (sscanf(PQgetvalue(res, 0, 0), "%d%2s", &xlog_val, xlog_unit) != 2)
314 : : {
315 : 0 : pg_log_error("WAL segment size could not be parsed");
316 : 0 : PQclear(res);
317 : 0 : return false;
318 : : }
319 : :
320 : 215 : PQclear(res);
321 : :
322 : : /* set the multiplier based on unit to convert xlog_val to bytes */
323 [ + - ]: 215 : if (strcmp(xlog_unit, "MB") == 0)
324 : 215 : multiplier = 1024 * 1024;
325 [ # # ]: 0 : else if (strcmp(xlog_unit, "GB") == 0)
326 : 0 : multiplier = 1024 * 1024 * 1024;
327 : :
328 : : /* convert and set WalSegSz */
329 : 215 : WalSegSz = xlog_val * multiplier;
330 : :
331 [ + - + - : 215 : if (!IsValidWalSegSize(WalSegSz))
+ - - + ]
332 : : {
333 : 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.");
338 : 0 : return false;
339 : : }
340 : :
341 : 215 : 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
356 : 446 : RetrieveDataDirCreatePerm(PGconn *conn)
357 : : {
358 : : PGresult *res;
359 : : int data_directory_mode;
360 : :
361 : : /* check connection existence */
362 : : Assert(conn != NULL);
363 : :
364 : : /* for previous versions leave the default group access */
365 [ - + ]: 446 : if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
366 : 0 : return true;
367 : :
368 : 446 : res = PQexec(conn, "SHOW data_directory_mode");
369 [ - + ]: 446 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
370 : : {
371 : 0 : pg_log_error("could not send replication command \"%s\": %s",
372 : : "SHOW data_directory_mode", PQerrorMessage(conn));
373 : :
374 : 0 : PQclear(res);
375 : 0 : return false;
376 : : }
377 [ + - - + ]: 446 : if (PQntuples(res) != 1 || PQnfields(res) < 1)
378 : : {
379 : 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 : :
382 : 0 : PQclear(res);
383 : 0 : return false;
384 : : }
385 : :
386 [ - + ]: 446 : if (sscanf(PQgetvalue(res, 0, 0), "%o", &data_directory_mode) != 1)
387 : : {
388 : 0 : pg_log_error("group access flag could not be parsed: %s",
389 : : PQgetvalue(res, 0, 0));
390 : :
391 : 0 : PQclear(res);
392 : 0 : return false;
393 : : }
394 : :
395 : 446 : SetDataDirectoryCreatePerm(data_directory_mode);
396 : :
397 : 446 : PQclear(res);
398 : 446 : 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
410 : 453 : RunIdentifySystem(PGconn *conn, char **sysid, TimeLineID *starttli,
411 : : XLogRecPtr *startpos, char **db_name)
412 : : {
413 : : PGresult *res;
414 : :
415 : : /* Check connection existence */
416 : : Assert(conn != NULL);
417 : :
418 : 453 : res = PQexec(conn, "IDENTIFY_SYSTEM");
419 [ - + ]: 453 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
420 : : {
421 : 0 : pg_log_error("could not send replication command \"%s\": %s",
422 : : "IDENTIFY_SYSTEM", PQerrorMessage(conn));
423 : :
424 : 0 : PQclear(res);
425 : 0 : return false;
426 : : }
427 [ + - - + ]: 453 : if (PQntuples(res) != 1 || PQnfields(res) < 3)
428 : : {
429 : 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 : :
432 : 0 : PQclear(res);
433 : 0 : return false;
434 : : }
435 : :
436 : : /* Get system identifier */
437 [ + + ]: 453 : if (sysid != NULL)
438 : 380 : *sysid = pg_strdup(PQgetvalue(res, 0, 0));
439 : :
440 : : /* Get timeline ID to start streaming from */
441 [ + + ]: 453 : if (starttli != NULL)
442 : 380 : *starttli = atoi(PQgetvalue(res, 0, 1));
443 : :
444 : : /* Get LSN start position if necessary */
445 [ + + ]: 453 : if (startpos != NULL)
446 : : {
447 [ - + ]: 7 : if (!pg_parse_lsn(PQgetvalue(res, 0, 2), startpos))
448 : : {
449 : 0 : pg_log_error("could not parse write-ahead log location \"%s\"",
450 : : PQgetvalue(res, 0, 2));
451 : :
452 : 0 : PQclear(res);
453 : 0 : return false;
454 : : }
455 : : }
456 : :
457 : : /* Get database name, only available in 9.4 and newer versions */
458 [ + + ]: 453 : if (db_name != NULL)
459 : : {
460 : 73 : *db_name = NULL;
461 [ + - ]: 73 : if (PQserverVersion(conn) >= 90400)
462 : : {
463 [ - + ]: 73 : if (PQnfields(res) < 4)
464 : : {
465 : 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 : :
468 : 0 : PQclear(res);
469 : 0 : return false;
470 : : }
471 [ + + ]: 73 : if (!PQgetisnull(res, 0, 3))
472 : 63 : *db_name = pg_strdup(PQgetvalue(res, 0, 3));
473 : : }
474 : : }
475 : :
476 : 453 : PQclear(res);
477 : 453 : 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
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();
502 : 3 : appendPQExpBufferStr(query, "READ_REPLICATION_SLOT ");
503 : 3 : AppendQuotedIdentifier(query, slot_name);
504 : 3 : res = PQexec(conn, query->data);
505 : 3 : destroyPQExpBuffer(query);
506 : :
507 [ - + ]: 3 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
508 : : {
509 : 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 */
516 [ + - - + ]: 3 : if (PQntuples(res) != 1 || PQnfields(res) != 3)
517 : : {
518 : 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 : : */
528 [ + + ]: 3 : if (PQgetisnull(res, 0, 0))
529 : : {
530 : 1 : pg_log_error("replication slot \"%s\" does not exist", slot_name);
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 : : {
541 : 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 */
548 [ + - ]: 2 : if (!PQgetisnull(res, 0, 1))
549 : : {
550 [ - + ]: 2 : if (!pg_parse_lsn(PQgetvalue(res, 0, 1), &lsn_loc))
551 : : {
552 : 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 */
560 [ + - ]: 2 : if (!PQgetisnull(res, 0, 2))
561 : 2 : tli_loc = (TimeLineID) atoll(PQgetvalue(res, 0, 2));
562 : :
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
579 : 193 : 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;
585 : 193 : bool use_new_option_syntax = (PQserverVersion(conn) >= 150000);
586 : :
587 : 193 : query = createPQExpBuffer();
588 : :
589 : : Assert((is_physical && plugin == NULL) ||
590 : : (!is_physical && plugin != NULL));
591 : : Assert(!(two_phase && is_physical));
592 : : Assert(!(failover && is_physical));
593 : : Assert(slot_name != NULL);
594 : :
595 : : /* Build base portion of query */
596 : 193 : appendPQExpBufferStr(query, "CREATE_REPLICATION_SLOT ");
597 : 193 : AppendQuotedIdentifier(query, slot_name);
598 [ + + ]: 193 : if (is_temporary)
599 : 157 : appendPQExpBufferStr(query, " TEMPORARY");
600 [ + + ]: 193 : if (is_physical)
601 : 161 : appendPQExpBufferStr(query, " PHYSICAL");
602 : : else
603 : : {
604 : 32 : appendPQExpBufferStr(query, " LOGICAL ");
605 : 32 : AppendQuotedIdentifier(query, plugin);
606 : : }
607 : :
608 : : /* Add any requested options */
609 [ + - ]: 193 : if (use_new_option_syntax)
610 : 193 : appendPQExpBufferStr(query, " (");
611 [ + + ]: 193 : if (is_physical)
612 : : {
613 [ + + ]: 161 : if (reserve_wal)
614 : 160 : AppendPlainCommandOption(query, use_new_option_syntax,
615 : : "RESERVE_WAL");
616 : : }
617 : : else
618 : : {
619 [ + + + - ]: 32 : if (failover && PQserverVersion(conn) >= 170000)
620 : 1 : AppendPlainCommandOption(query, use_new_option_syntax,
621 : : "FAILOVER");
622 : :
623 [ + + + - ]: 32 : if (two_phase && PQserverVersion(conn) >= 150000)
624 : 1 : AppendPlainCommandOption(query, use_new_option_syntax,
625 : : "TWO_PHASE");
626 : :
627 [ + - ]: 32 : if (PQserverVersion(conn) >= 100000)
628 : : {
629 : : /* pg_recvlogical doesn't use an exported snapshot, so suppress */
630 [ + - ]: 32 : if (use_new_option_syntax)
631 : 32 : AppendStringCommandOption(query, use_new_option_syntax,
632 : : "SNAPSHOT", "nothing");
633 : : else
634 : 0 : AppendPlainCommandOption(query, use_new_option_syntax,
635 : : "NOEXPORT_SNAPSHOT");
636 : : }
637 : : }
638 [ + - ]: 193 : if (use_new_option_syntax)
639 : : {
640 : : /* Suppress option list if it would be empty, otherwise terminate */
641 [ + + ]: 193 : if (query->data[query->len - 1] == '(')
642 : : {
643 : 1 : query->len -= 2;
644 : 1 : query->data[query->len] = '\0';
645 : : }
646 : : else
647 : 192 : appendPQExpBufferChar(query, ')');
648 : : }
649 : :
650 : : /* Now run the query */
651 : 193 : res = PQexec(conn, query->data);
652 [ + + ]: 193 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
653 : : {
654 : 1 : const char *sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
655 : :
656 [ - + - - ]: 1 : if (slot_exists_ok &&
657 : 0 : sqlstate &&
658 [ # # ]: 0 : strcmp(sqlstate, ERRCODE_DUPLICATE_OBJECT) == 0)
659 : : {
660 : 0 : destroyPQExpBuffer(query);
661 : 0 : PQclear(res);
662 : 0 : return true;
663 : : }
664 : : else
665 : : {
666 : 1 : pg_log_error("could not send replication command \"%s\": %s",
667 : : query->data, PQerrorMessage(conn));
668 : :
669 : 1 : destroyPQExpBuffer(query);
670 : 1 : PQclear(res);
671 : 1 : return false;
672 : : }
673 : : }
674 : :
675 [ + - - + ]: 192 : if (PQntuples(res) != 1 || PQnfields(res) != 4)
676 : : {
677 : 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 : :
681 : 0 : destroyPQExpBuffer(query);
682 : 0 : PQclear(res);
683 : 0 : return false;
684 : : }
685 : :
686 : 192 : destroyPQExpBuffer(query);
687 : 192 : PQclear(res);
688 : 192 : 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 : : Assert(slot_name != NULL);
702 : :
703 : 5 : query = createPQExpBuffer();
704 : :
705 : : /* Build query */
706 : 5 : appendPQExpBufferStr(query, "DROP_REPLICATION_SLOT ");
707 : 5 : AppendQuotedIdentifier(query, slot_name);
708 : 5 : res = PQexec(conn, query->data);
709 [ - + ]: 5 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
710 : : {
711 : 0 : pg_log_error("could not send replication command \"%s\": %s",
712 : : query->data, PQerrorMessage(conn));
713 : :
714 : 0 : destroyPQExpBuffer(query);
715 : 0 : PQclear(res);
716 : 0 : return false;
717 : : }
718 : :
719 [ + - - + ]: 5 : if (PQntuples(res) != 0 || PQnfields(res) != 0)
720 : : {
721 : 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 : :
725 : 0 : destroyPQExpBuffer(query);
726 : 0 : PQclear(res);
727 : 0 : return false;
728 : : }
729 : :
730 : 5 : destroyPQExpBuffer(query);
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
744 : 1416 : AppendQuotedString(PQExpBuffer buf, const char *str, char quote)
745 : : {
746 : 1416 : appendPQExpBufferChar(buf, quote);
747 [ + + ]: 19129 : while (*str)
748 : : {
749 : 17713 : char c = *str++;
750 : :
751 [ - + ]: 17713 : if (c == quote)
752 : 0 : appendPQExpBufferChar(buf, c);
753 : 17713 : appendPQExpBufferChar(buf, c);
754 : : }
755 : 1416 : appendPQExpBufferChar(buf, quote);
756 : 1416 : }
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
771 : 1544 : AppendPlainCommandOption(PQExpBuffer buf, bool use_new_option_syntax,
772 : : const char *option_name)
773 : : {
774 [ + + + + ]: 1544 : if (buf->len > 0 && buf->data[buf->len - 1] != '(')
775 : : {
776 [ + - ]: 1145 : if (use_new_option_syntax)
777 : 1145 : appendPQExpBufferStr(buf, ", ");
778 : : else
779 : 0 : appendPQExpBufferChar(buf, ' ');
780 : : }
781 : :
782 : 1544 : appendPQExpBuffer(buf, " %s", option_name);
783 : 1544 : }
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 : 909 : AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax,
793 : : const char *option_name, const char *option_value)
794 : : {
795 : 909 : AppendPlainCommandOption(buf, use_new_option_syntax, option_name);
796 : :
797 [ + - ]: 909 : if (option_value != NULL)
798 : : {
799 : 909 : appendPQExpBufferChar(buf, ' ');
800 : 909 : AppendQuotedLiteral(buf, option_value);
801 : : }
802 : 909 : }
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 : 199 : AppendIntegerCommandOption(PQExpBuffer buf, bool use_new_option_syntax,
812 : : const char *option_name, int32 option_value)
813 : : {
814 : 199 : AppendPlainCommandOption(buf, use_new_option_syntax, option_name);
815 : :
816 : 199 : appendPQExpBuffer(buf, " %d", option_value);
817 : 199 : }
818 : :
819 : : /*
820 : : * Frontend version of GetCurrentTimestamp(), since we are not linked with
821 : : * backend code.
822 : : */
823 : : TimestampTz
824 : 1172 : feGetCurrentTimestamp(void)
825 : : {
826 : : TimestampTz result;
827 : : struct timeval tp;
828 : :
829 : 1172 : gettimeofday(&tp, NULL);
830 : :
831 : 1172 : result = (TimestampTz) tp.tv_sec -
832 : : ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
833 : 1172 : result = (result * USECS_PER_SEC) + tp.tv_usec;
834 : :
835 : 1172 : return result;
836 : : }
837 : :
838 : : /*
839 : : * Frontend version of TimestampDifference(), since we are not linked with
840 : : * backend code.
841 : : */
842 : : void
843 : 724 : feTimestampDifference(TimestampTz start_time, TimestampTz stop_time,
844 : : long *secs, int *microsecs)
845 : : {
846 : 724 : TimestampTz diff = stop_time - start_time;
847 : :
848 [ - + ]: 724 : if (diff <= 0)
849 : : {
850 : 0 : *secs = 0;
851 : 0 : *microsecs = 0;
852 : : }
853 : : else
854 : : {
855 : 724 : *secs = (long) (diff / USECS_PER_SEC);
856 : 724 : *microsecs = (int) (diff % USECS_PER_SEC);
857 : : }
858 : 724 : }
859 : :
860 : : /*
861 : : * Frontend version of TimestampDifferenceExceeds(), since we are not
862 : : * linked with backend code.
863 : : */
864 : : bool
865 : 1523 : feTimestampDifferenceExceeds(TimestampTz start_time,
866 : : TimestampTz stop_time,
867 : : int msec)
868 : : {
869 : 1523 : TimestampTz diff = stop_time - start_time;
870 : :
871 : 1523 : return (diff >= msec * INT64CONST(1000));
872 : : }
873 : :
874 : : /*
875 : : * Converts an int64 to network byte order.
876 : : */
877 : : void
878 : 804 : fe_sendint64(int64 i, char *buf)
879 : : {
880 : 804 : uint64 n64 = pg_hton64(i);
881 : :
882 : 804 : memcpy(buf, &n64, sizeof(n64));
883 : 804 : }
884 : :
885 : : /*
886 : : * Converts an int64 from network byte order to native format.
887 : : */
888 : : int64
889 : 1940 : fe_recvint64(char *buf)
890 : : {
891 : : uint64 n64;
892 : :
893 : 1940 : memcpy(&n64, buf, sizeof(n64));
894 : :
895 : 1940 : return pg_ntoh64(n64);
896 : : }
|