Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pg_recvlogical.c - receive data from a logical decoding slot in a streaming
4 : : * fashion and write it to a local file.
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : *
8 : : * IDENTIFICATION
9 : : * src/bin/pg_basebackup/pg_recvlogical.c
10 : : *-------------------------------------------------------------------------
11 : : */
12 : :
13 : : #include "postgres_fe.h"
14 : :
15 : : #include <dirent.h>
16 : : #include <limits.h>
17 : : #include <sys/select.h>
18 : : #include <sys/stat.h>
19 : : #include <unistd.h>
20 : :
21 : : #include "common/file_perm.h"
22 : : #include "common/logging.h"
23 : : #include "fe_utils/option_utils.h"
24 : : #include "getopt_long.h"
25 : : #include "libpq-fe.h"
26 : : #include "libpq/pqsignal.h"
27 : : #include "libpq/protocol.h"
28 : : #include "pqexpbuffer.h"
29 : : #include "streamutil.h"
30 : :
31 : : /* Time to sleep between reconnection attempts */
32 : : #define RECONNECT_SLEEP_TIME 5
33 : :
34 : : typedef enum
35 : : {
36 : : STREAM_STOP_NONE,
37 : : STREAM_STOP_END_OF_WAL,
38 : : STREAM_STOP_KEEPALIVE,
39 : : STREAM_STOP_SIGNAL
40 : : } StreamStopReason;
41 : :
42 : : /* Global Options */
43 : : static char *outfile = NULL;
44 : : static int verbose = 0;
45 : : static bool two_phase = false; /* enable-two-phase option */
46 : : static bool failover = false; /* enable-failover option */
47 : : static int noloop = 0;
48 : : static int standby_message_timeout = 10 * 1000; /* 10 sec = default */
49 : : static int fsync_interval = 10 * 1000; /* 10 sec = default */
50 : : static XLogRecPtr startpos = InvalidXLogRecPtr;
51 : : static XLogRecPtr endpos = InvalidXLogRecPtr;
52 : : static bool do_create_slot = false;
53 : : static bool slot_exists_ok = false;
54 : : static bool do_start_slot = false;
55 : : static bool do_drop_slot = false;
56 : : static char *replication_slot = NULL;
57 : :
58 : : /* filled pairwise with option, value. value may be NULL */
59 : : static char **options;
60 : : static size_t noptions = 0;
61 : : static const char *plugin = "test_decoding";
62 : :
63 : : /* Global State */
64 : : static int outfd = -1;
65 : : static volatile sig_atomic_t time_to_abort = false;
66 : : static volatile sig_atomic_t stop_reason = STREAM_STOP_NONE;
67 : : static volatile sig_atomic_t output_reopen = false;
68 : : static bool output_isfile;
69 : : static TimestampTz output_last_fsync = -1;
70 : : static bool output_needs_fsync = false;
71 : : static XLogRecPtr output_written_lsn = InvalidXLogRecPtr;
72 : : static XLogRecPtr output_fsync_lsn = InvalidXLogRecPtr;
73 : :
74 : : static void usage(void);
75 : : static void StreamLogicalLog(void);
76 : : static bool flushAndSendFeedback(PGconn *conn, TimestampTz *now);
77 : : static void prepareToTerminate(PGconn *conn, XLogRecPtr endpos,
78 : : StreamStopReason reason,
79 : : XLogRecPtr lsn);
80 : :
81 : : static void
4512 rhaas@postgresql.org 82 :CBC 1 : usage(void)
83 : : {
4145 bruce@momjian.us 84 : 1 : printf(_("%s controls PostgreSQL logical decoding streams.\n\n"),
85 : : progname);
4512 rhaas@postgresql.org 86 : 1 : printf(_("Usage:\n"));
87 : 1 : printf(_(" %s [OPTION]...\n"), progname);
4304 peter_e@gmx.net 88 : 1 : printf(_("\nAction to be performed:\n"));
89 : 1 : printf(_(" --create-slot create a new replication slot (for the slot's name see --slot)\n"));
90 : 1 : printf(_(" --drop-slot drop the replication slot (for the slot's name see --slot)\n"));
91 : 1 : printf(_(" --start start streaming in a replication slot (for the slot's name see --slot)\n"));
4512 rhaas@postgresql.org 92 : 1 : printf(_("\nOptions:\n"));
391 peter@eisentraut.org 93 : 1 : printf(_(" --enable-failover enable replication slot synchronization to standby servers when\n"
94 : : " creating a replication slot\n"));
95 : 1 : printf(_(" -E, --endpos=LSN exit after receiving the specified LSN\n"));
4304 peter_e@gmx.net 96 : 1 : printf(_(" -f, --file=FILE receive log into this file, - for stdout\n"));
4444 andres@anarazel.de 97 : 1 : printf(_(" -F --fsync-interval=SECS\n"
98 : : " time between fsyncs to the output file (default: %d)\n"), (fsync_interval / 1000));
3965 peter_e@gmx.net 99 : 1 : printf(_(" --if-not-exists do not error if slot already exists when creating a slot\n"));
4304 100 : 1 : printf(_(" -I, --startpos=LSN where in an existing slot should the streaming start\n"));
4512 rhaas@postgresql.org 101 : 1 : printf(_(" -n, --no-loop do not loop on connection lost\n"));
4304 peter_e@gmx.net 102 : 1 : printf(_(" -o, --option=NAME[=VALUE]\n"
103 : : " pass option NAME with optional value VALUE to the\n"
104 : : " output plugin\n"));
105 : 1 : printf(_(" -P, --plugin=PLUGIN use output plugin PLUGIN (default: %s)\n"), plugin);
106 : 1 : printf(_(" -s, --status-interval=SECS\n"
107 : : " time between status packets sent to server (default: %d)\n"), (standby_message_timeout / 1000));
108 : 1 : printf(_(" -S, --slot=SLOTNAME name of the logical replication slot\n"));
391 peter@eisentraut.org 109 : 1 : printf(_(" -t, --enable-two-phase enable decoding of prepared transactions when creating a slot\n"));
110 : 1 : printf(_(" --two-phase (same as --enable-two-phase, deprecated)\n"));
4512 rhaas@postgresql.org 111 : 1 : printf(_(" -v, --verbose output verbose messages\n"));
112 : 1 : printf(_(" -V, --version output version information, then exit\n"));
113 : 1 : printf(_(" -?, --help show this help, then exit\n"));
114 : 1 : printf(_("\nConnection options:\n"));
115 : 1 : printf(_(" -d, --dbname=DBNAME database to connect to\n"));
116 : 1 : printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
117 : 1 : printf(_(" -p, --port=PORT database server port number\n"));
118 : 1 : printf(_(" -U, --username=NAME connect as specified database user\n"));
119 : 1 : printf(_(" -w, --no-password never prompt for password\n"));
120 : 1 : printf(_(" -W, --password force password prompt (should happen automatically)\n"));
2339 peter@eisentraut.org 121 : 1 : printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
122 : 1 : printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
4512 rhaas@postgresql.org 123 : 1 : }
124 : :
125 : : /*
126 : : * Send a Standby Status Update message to server.
127 : : */
128 : : static bool
3439 tgl@sss.pgh.pa.us 129 : 36 : sendFeedback(PGconn *conn, TimestampTz now, bool force, bool replyRequested)
130 : : {
131 : : static XLogRecPtr last_written_lsn = InvalidXLogRecPtr;
132 : : static XLogRecPtr last_fsync_lsn = InvalidXLogRecPtr;
133 : :
134 : : char replybuf[1 + 8 + 8 + 8 + 8 + 1];
4512 rhaas@postgresql.org 135 : 36 : int len = 0;
136 : :
137 : : /*
138 : : * we normally don't want to send superfluous feedback, but if it's
139 : : * because of a timeout we need to, otherwise wal_sender_timeout will kill
140 : : * us.
141 : : */
142 [ - + ]: 36 : if (!force &&
4512 rhaas@postgresql.org 143 [ # # ]:UBC 0 : last_written_lsn == output_written_lsn &&
2264 noah@leadboat.com 144 [ # # ]: 0 : last_fsync_lsn == output_fsync_lsn)
4512 rhaas@postgresql.org 145 : 0 : return true;
146 : :
4512 rhaas@postgresql.org 147 [ + + ]:CBC 36 : if (verbose)
383 alvherre@kurilemu.de 148 : 3 : pg_log_info("confirming write up to %X/%08X, flush to %X/%08X (slot %s)",
149 : : LSN_FORMAT_ARGS(output_written_lsn),
150 : : LSN_FORMAT_ARGS(output_fsync_lsn),
151 : : replication_slot);
152 : :
353 nathan@postgresql.or 153 : 36 : replybuf[len] = PqReplMsg_StandbyStatusUpdate;
4512 rhaas@postgresql.org 154 : 36 : len += 1;
4463 bruce@momjian.us 155 : 36 : fe_sendint64(output_written_lsn, &replybuf[len]); /* write */
4512 rhaas@postgresql.org 156 : 36 : len += 8;
3321 tgl@sss.pgh.pa.us 157 : 36 : fe_sendint64(output_fsync_lsn, &replybuf[len]); /* flush */
4512 rhaas@postgresql.org 158 : 36 : len += 8;
4463 bruce@momjian.us 159 : 36 : fe_sendint64(InvalidXLogRecPtr, &replybuf[len]); /* apply */
4512 rhaas@postgresql.org 160 : 36 : len += 8;
4463 bruce@momjian.us 161 : 36 : fe_sendint64(now, &replybuf[len]); /* sendTime */
4512 rhaas@postgresql.org 162 : 36 : len += 8;
3321 tgl@sss.pgh.pa.us 163 : 36 : replybuf[len] = replyRequested ? 1 : 0; /* replyRequested */
4512 rhaas@postgresql.org 164 : 36 : len += 1;
165 : :
166 : 36 : startpos = output_written_lsn;
167 : 36 : last_written_lsn = output_written_lsn;
168 : 36 : last_fsync_lsn = output_fsync_lsn;
169 : :
170 [ + - - + ]: 36 : if (PQputCopyData(conn, replybuf, len) <= 0 || PQflush(conn))
171 : : {
2672 peter@eisentraut.org 172 :UBC 0 : pg_log_error("could not send feedback packet: %s",
173 : : PQerrorMessage(conn));
4512 rhaas@postgresql.org 174 : 0 : return false;
175 : : }
176 : :
4512 rhaas@postgresql.org 177 :CBC 36 : return true;
178 : : }
179 : :
180 : : static void
2765 peter@eisentraut.org 181 : 64 : disconnect_atexit(void)
182 : : {
4512 rhaas@postgresql.org 183 [ + + ]: 64 : if (conn != NULL)
184 : 36 : PQfinish(conn);
185 : 64 : }
186 : :
187 : : static void
3439 tgl@sss.pgh.pa.us 188 : 49 : OutputFsync(TimestampTz now)
189 : : {
4512 rhaas@postgresql.org 190 : 49 : output_last_fsync = now;
191 : :
192 : 49 : output_fsync_lsn = output_written_lsn;
193 : :
194 : : /*
195 : : * Save the last flushed position as the replication start point. On
196 : : * reconnect, replication resumes from there to avoid re-sending flushed
197 : : * data.
198 : : */
190 fujii@postgresql.org 199 : 49 : startpos = output_fsync_lsn;
200 : :
4512 rhaas@postgresql.org 201 [ - + ]: 49 : if (fsync_interval <= 0)
190 fujii@postgresql.org 202 :UBC 0 : return;
203 : :
4454 heikki.linnakangas@i 204 [ + + ]:CBC 49 : if (!output_needs_fsync)
190 fujii@postgresql.org 205 : 34 : return;
206 : :
4454 heikki.linnakangas@i 207 : 15 : output_needs_fsync = false;
208 : :
209 : : /* can only fsync if it's a regular file */
4036 andres@anarazel.de 210 [ + + ]: 15 : if (!output_isfile)
190 fujii@postgresql.org 211 : 11 : return;
212 : :
4036 andres@anarazel.de 213 [ - + ]: 4 : if (fsync(outfd) != 0)
1569 tgl@sss.pgh.pa.us 214 :UBC 0 : pg_fatal("could not fsync file \"%s\": %m", outfile);
215 : : }
216 : :
217 : : /*
218 : : * Start the log streaming
219 : : */
220 : : static void
4317 andres@anarazel.de 221 :CBC 29 : StreamLogicalLog(void)
222 : : {
223 : : PGresult *res;
4512 rhaas@postgresql.org 224 : 29 : char *copybuf = NULL;
3439 tgl@sss.pgh.pa.us 225 : 29 : TimestampTz last_status = -1;
226 : : PQExpBuffer query;
227 : : XLogRecPtr cur_record_lsn;
228 : :
1101 michael@paquier.xyz 229 : 29 : cur_record_lsn = InvalidXLogRecPtr;
230 : :
231 : : /*
232 : : * Connect in replication mode to the server
233 : : */
4512 rhaas@postgresql.org 234 [ + + ]: 29 : if (!conn)
235 : 1 : conn = GetConnection();
236 [ - + ]: 29 : if (!conn)
237 : : /* Error message already written in GetConnection() */
4512 rhaas@postgresql.org 238 :UBC 0 : return;
239 : :
240 : : /*
241 : : * Start the replication
242 : : */
4512 rhaas@postgresql.org 243 [ + + ]:CBC 29 : if (verbose)
383 alvherre@kurilemu.de 244 : 2 : pg_log_info("starting log streaming at %X/%08X (slot %s)",
245 : : LSN_FORMAT_ARGS(startpos),
246 : : replication_slot);
247 : :
248 : : /* Initiate the replication stream at specified location */
1707 tgl@sss.pgh.pa.us 249 : 29 : query = createPQExpBuffer();
40 250 : 29 : appendPQExpBufferStr(query, "START_REPLICATION SLOT ");
251 : 29 : AppendQuotedIdentifier(query, replication_slot);
252 : 29 : appendPQExpBuffer(query, " LOGICAL %X/%08X", LSN_FORMAT_ARGS(startpos));
253 : :
254 : : /* print options if there are any */
4512 rhaas@postgresql.org 255 [ + + ]: 29 : if (noptions)
256 : 21 : appendPQExpBufferStr(query, " (");
257 : :
14 peter@eisentraut.org 258 [ + + ]:GNC 71 : for (size_t i = 0; i < noptions; i++)
259 : : {
260 : : /* separator */
4512 rhaas@postgresql.org 261 [ + + ]:CBC 42 : if (i > 0)
262 : 21 : appendPQExpBufferStr(query, ", ");
263 : :
264 : : /* write option name */
40 tgl@sss.pgh.pa.us 265 : 42 : AppendQuotedIdentifier(query, options[i * 2]);
266 : :
267 : : /* write option value if specified */
268 [ + - ]: 42 : if (options[i * 2 + 1] != NULL)
269 : : {
270 : 42 : appendPQExpBufferChar(query, ' ');
271 : 42 : AppendQuotedLiteral(query, options[i * 2 + 1]);
272 : : }
273 : : }
274 : :
4512 rhaas@postgresql.org 275 [ + + ]: 29 : if (noptions)
276 : 21 : appendPQExpBufferChar(query, ')');
277 : :
278 : 29 : res = PQexec(conn, query->data);
279 [ + + ]: 29 : if (PQresultStatus(res) != PGRES_COPY_BOTH)
280 : : {
2672 peter@eisentraut.org 281 : 6 : pg_log_error("could not send replication command \"%s\": %s",
282 : : query->data, PQresultErrorMessage(res));
4512 rhaas@postgresql.org 283 : 6 : PQclear(res);
284 : 6 : goto error;
285 : : }
286 : 23 : PQclear(res);
287 : 23 : resetPQExpBuffer(query);
288 : :
289 [ + + ]: 23 : if (verbose)
2672 peter@eisentraut.org 290 : 2 : pg_log_info("streaming initiated");
291 : :
4512 rhaas@postgresql.org 292 [ + + ]: 411 : while (!time_to_abort)
293 : : {
294 : : int r;
295 : : size_t bytes_left;
296 : : size_t bytes_written;
297 : : TimestampTz now;
298 : : size_t hdr_len;
299 : :
1101 michael@paquier.xyz 300 : 407 : cur_record_lsn = InvalidXLogRecPtr;
301 : :
4512 rhaas@postgresql.org 302 [ + + ]: 407 : if (copybuf != NULL)
303 : : {
304 : 256 : PQfreemem(copybuf);
305 : 256 : copybuf = NULL;
306 : : }
307 : :
308 : : /*
309 : : * Potentially send a status message to the primary.
310 : : */
311 : 407 : now = feGetCurrentTimestamp();
312 : :
313 [ + + + + ]: 792 : if (outfd != -1 &&
314 : 385 : feTimestampDifferenceExceeds(output_last_fsync, now,
315 : : fsync_interval))
190 fujii@postgresql.org 316 : 22 : OutputFsync(now);
317 : :
4512 rhaas@postgresql.org 318 [ + - + + ]: 814 : if (standby_message_timeout > 0 &&
319 : 407 : feTimestampDifferenceExceeds(last_status, now,
320 : : standby_message_timeout))
321 : : {
322 : : /* Time to send feedback! */
323 [ - + ]: 23 : if (!sendFeedback(conn, now, true, false))
324 : 3 : goto error;
325 : :
326 : 23 : last_status = now;
327 : : }
328 : :
329 : : /* got SIGHUP, close output file */
4454 heikki.linnakangas@i 330 [ + + - + : 407 : if (outfd != -1 && output_reopen && strcmp(outfile, "-") != 0)
- - ]
331 : : {
4454 heikki.linnakangas@i 332 :UBC 0 : now = feGetCurrentTimestamp();
190 fujii@postgresql.org 333 : 0 : OutputFsync(now);
4454 heikki.linnakangas@i 334 : 0 : close(outfd);
335 : 0 : outfd = -1;
336 : : }
4454 heikki.linnakangas@i 337 :CBC 407 : output_reopen = false;
338 : :
339 : : /* open the output file, if not open yet */
4453 340 [ + + ]: 407 : if (outfd == -1)
341 : : {
342 : : struct stat statbuf;
343 : :
344 [ + + ]: 22 : if (strcmp(outfile, "-") == 0)
345 : 18 : outfd = fileno(stdout);
346 : : else
347 : 4 : outfd = open(outfile, O_CREAT | O_APPEND | O_WRONLY | PG_BINARY,
348 : : pg_file_create_mode);
349 [ - + ]: 22 : if (outfd == -1)
350 : : {
2672 peter@eisentraut.org 351 :UBC 0 : pg_log_error("could not open log file \"%s\": %m", outfile);
4453 heikki.linnakangas@i 352 : 0 : goto error;
353 : : }
354 : :
4036 andres@anarazel.de 355 [ - + ]:CBC 22 : if (fstat(outfd, &statbuf) != 0)
356 : : {
2672 peter@eisentraut.org 357 :UBC 0 : pg_log_error("could not stat file \"%s\": %m", outfile);
1822 michael@paquier.xyz 358 : 0 : goto error;
359 : : }
360 : :
4036 andres@anarazel.de 361 [ + + + - ]:CBC 22 : output_isfile = S_ISREG(statbuf.st_mode) && !isatty(outfd);
362 : : }
363 : :
4512 rhaas@postgresql.org 364 : 407 : r = PQgetCopyData(conn, ©buf, 1);
365 [ + + ]: 407 : if (r == 0)
366 : 128 : {
367 : : /*
368 : : * In async mode, and no data available. We block on reading but
369 : : * not more than the specified timeout, so that we can send a
370 : : * response back to the client.
371 : : */
372 : : fd_set input_mask;
3439 tgl@sss.pgh.pa.us 373 : 135 : TimestampTz message_target = 0;
374 : 135 : TimestampTz fsync_target = 0;
375 : : struct timeval timeout;
4512 rhaas@postgresql.org 376 : 135 : struct timeval *timeoutptr = NULL;
377 : :
3791 peter_e@gmx.net 378 [ - + ]: 135 : if (PQsocket(conn) < 0)
379 : : {
2672 peter@eisentraut.org 380 :UBC 0 : pg_log_error("invalid socket: %s", PQerrorMessage(conn));
3791 peter_e@gmx.net 381 :CBC 3 : goto error;
382 : : }
383 : :
4512 rhaas@postgresql.org 384 [ + + ]: 2295 : FD_ZERO(&input_mask);
385 : 135 : FD_SET(PQsocket(conn), &input_mask);
386 : :
387 : : /* Compute when we need to wakeup to send a keepalive message. */
388 [ + - ]: 135 : if (standby_message_timeout)
389 : 135 : message_target = last_status + (standby_message_timeout - 1) *
390 : : ((int64) 1000);
391 : :
392 : : /* Compute when we need to wakeup to fsync the output file. */
4454 heikki.linnakangas@i 393 [ + - + + ]: 135 : if (fsync_interval > 0 && output_needs_fsync)
4512 rhaas@postgresql.org 394 : 77 : fsync_target = output_last_fsync + (fsync_interval - 1) *
395 : : ((int64) 1000);
396 : :
397 : : /* Now compute when to wakeup. */
398 [ - + - - ]: 135 : if (message_target > 0 || fsync_target > 0)
399 : : {
400 : : TimestampTz targettime;
401 : : long secs;
402 : : int usecs;
403 : :
404 : 135 : targettime = message_target;
405 : :
406 [ + + + + ]: 135 : if (fsync_target > 0 && fsync_target < targettime)
407 : 7 : targettime = fsync_target;
408 : :
409 : 135 : feTimestampDifference(now,
410 : : targettime,
411 : : &secs,
412 : : &usecs);
413 [ + + ]: 135 : if (secs <= 0)
414 : 7 : timeout.tv_sec = 1; /* Always sleep at least 1 sec */
415 : : else
416 : 128 : timeout.tv_sec = secs;
417 : 135 : timeout.tv_usec = usecs;
418 : 135 : timeoutptr = &timeout;
419 : : }
420 : :
421 : 135 : r = select(PQsocket(conn) + 1, &input_mask, NULL, NULL, timeoutptr);
422 [ + - + + : 135 : if (r == 0 || (r < 0 && errno == EINTR))
+ - ]
423 : : {
424 : : /*
425 : : * Got a timeout or signal. Continue the loop and either
426 : : * deliver a status packet to the server or just go back into
427 : : * blocking.
428 : : */
429 : 132 : continue;
430 : : }
431 [ - + ]: 131 : else if (r < 0)
432 : : {
1919 peter@eisentraut.org 433 :UBC 0 : pg_log_error("%s() failed: %m", "select");
4512 rhaas@postgresql.org 434 : 0 : goto error;
435 : : }
436 : :
437 : : /* Else there is actually data on the socket */
4512 rhaas@postgresql.org 438 [ + + ]:CBC 131 : if (PQconsumeInput(conn) == 0)
439 : : {
2672 peter@eisentraut.org 440 : 3 : pg_log_error("could not receive data from WAL stream: %s",
441 : : PQerrorMessage(conn));
4512 rhaas@postgresql.org 442 : 3 : goto error;
443 : : }
444 : 128 : continue;
445 : : }
446 : :
447 : : /* End of copy stream */
448 [ + + ]: 272 : if (r == -1)
449 : 16 : break;
450 : :
451 : : /* Failure while reading the copy stream */
452 [ - + ]: 264 : if (r == -2)
453 : : {
2672 peter@eisentraut.org 454 :UBC 0 : pg_log_error("could not read COPY data: %s",
455 : : PQerrorMessage(conn));
4512 rhaas@postgresql.org 456 : 0 : goto error;
457 : : }
458 : :
459 : : /* Check the message type. */
353 nathan@postgresql.or 460 [ + + ]:CBC 264 : if (copybuf[0] == PqReplMsg_Keepalive)
4512 rhaas@postgresql.org 461 : 140 : {
462 : : int pos;
463 : : bool replyRequested;
464 : : XLogRecPtr walEnd;
3489 simon@2ndQuadrant.co 465 : 143 : bool endposReached = false;
466 : :
467 : : /*
468 : : * Parse the keepalive message, enclosed in the CopyData message.
469 : : * We just check if the server requested a reply, and ignore the
470 : : * rest.
471 : : */
353 nathan@postgresql.or 472 : 143 : pos = 1; /* skip msgtype PqReplMsg_Keepalive */
4512 rhaas@postgresql.org 473 : 143 : walEnd = fe_recvint64(©buf[pos]);
474 : 143 : output_written_lsn = Max(walEnd, output_written_lsn);
475 : :
476 : 143 : pos += 8; /* read walEnd */
477 : :
478 : 143 : pos += 8; /* skip sendTime */
479 : :
480 [ - + ]: 143 : if (r < pos + 1)
481 : : {
2672 peter@eisentraut.org 482 :UBC 0 : pg_log_error("streaming header too small: %d", r);
4512 rhaas@postgresql.org 483 : 0 : goto error;
484 : : }
4512 rhaas@postgresql.org 485 :CBC 143 : replyRequested = copybuf[pos];
486 : :
261 alvherre@kurilemu.de 487 [ + + + + ]: 143 : if (XLogRecPtrIsValid(endpos) && walEnd >= endpos)
488 : : {
489 : : /*
490 : : * If there's nothing to read on the socket until a keepalive
491 : : * we know that the server has nothing to send us; and if
492 : : * walEnd has passed endpos, we know nothing else can have
493 : : * committed before endpos. So we can bail out now.
494 : : */
3489 simon@2ndQuadrant.co 495 : 3 : endposReached = true;
496 : : }
497 : :
498 : : /* Send a reply, if necessary */
499 [ + + + + ]: 143 : if (replyRequested || endposReached)
500 : : {
501 [ - + ]: 4 : if (!flushAndSendFeedback(conn, &now))
4512 rhaas@postgresql.org 502 :UBC 0 : goto error;
4512 rhaas@postgresql.org 503 :CBC 4 : last_status = now;
504 : : }
505 : :
3489 simon@2ndQuadrant.co 506 [ + + ]: 143 : if (endposReached)
507 : : {
1101 michael@paquier.xyz 508 : 3 : stop_reason = STREAM_STOP_KEEPALIVE;
3489 simon@2ndQuadrant.co 509 : 3 : time_to_abort = true;
510 : 3 : break;
511 : : }
512 : :
4512 rhaas@postgresql.org 513 : 140 : continue;
514 : : }
353 nathan@postgresql.or 515 [ - + ]: 121 : else if (copybuf[0] != PqReplMsg_WALData)
516 : : {
2672 peter@eisentraut.org 517 :UBC 0 : pg_log_error("unrecognized streaming header: \"%c\"",
518 : : copybuf[0]);
4512 rhaas@postgresql.org 519 : 0 : goto error;
520 : : }
521 : :
522 : : /*
523 : : * Read the header of the WALData message, enclosed in the CopyData
524 : : * message. We only need the WAL location field (dataStart), the rest
525 : : * of the header is ignored.
526 : : */
353 nathan@postgresql.or 527 :CBC 121 : hdr_len = 1; /* msgtype PqReplMsg_WALData */
4512 rhaas@postgresql.org 528 : 121 : hdr_len += 8; /* dataStart */
529 : 121 : hdr_len += 8; /* walEnd */
530 : 121 : hdr_len += 8; /* sendTime */
531 [ - + ]: 121 : if (r < hdr_len + 1)
532 : : {
2672 peter@eisentraut.org 533 :UBC 0 : pg_log_error("streaming header too small: %d", r);
4512 rhaas@postgresql.org 534 : 0 : goto error;
535 : : }
536 : :
537 : : /* Extract WAL location for this block */
3489 simon@2ndQuadrant.co 538 :CBC 121 : cur_record_lsn = fe_recvint64(©buf[1]);
539 : :
261 alvherre@kurilemu.de 540 [ + + - + ]: 121 : if (XLogRecPtrIsValid(endpos) && cur_record_lsn > endpos)
541 : : {
542 : : /*
543 : : * We've read past our endpoint, so prepare to go away being
544 : : * cautious about what happens to our output data.
545 : : */
3489 simon@2ndQuadrant.co 546 [ # # ]:UBC 0 : if (!flushAndSendFeedback(conn, &now))
547 : 0 : goto error;
1101 michael@paquier.xyz 548 : 0 : stop_reason = STREAM_STOP_END_OF_WAL;
3489 simon@2ndQuadrant.co 549 : 0 : time_to_abort = true;
550 : 0 : break;
551 : : }
552 : :
3489 simon@2ndQuadrant.co 553 :CBC 121 : output_written_lsn = Max(cur_record_lsn, output_written_lsn);
554 : :
4512 rhaas@postgresql.org 555 : 121 : bytes_left = r - hdr_len;
556 : 121 : bytes_written = 0;
557 : :
558 : : /* signal that a fsync is needed */
4454 heikki.linnakangas@i 559 : 121 : output_needs_fsync = true;
560 : :
4512 rhaas@postgresql.org 561 [ + + ]: 242 : while (bytes_left)
562 : : {
563 : : ssize_t ret;
564 : :
565 : 121 : ret = write(outfd,
566 : 121 : copybuf + hdr_len + bytes_written,
567 : : bytes_left);
568 : :
569 [ - + ]: 121 : if (ret < 0)
570 : : {
10 peter@eisentraut.org 571 :UNC 0 : pg_log_error("could not write %zu bytes to log file \"%s\": %m",
572 : : bytes_left, outfile);
4512 rhaas@postgresql.org 573 :UBC 0 : goto error;
574 : : }
575 : :
576 : : /* Write was successful, advance our position */
4512 rhaas@postgresql.org 577 :CBC 121 : bytes_written += ret;
578 : 121 : bytes_left -= ret;
579 : : }
580 : :
581 [ - + ]: 121 : if (write(outfd, "\n", 1) != 1)
582 : : {
10 peter@eisentraut.org 583 :UNC 0 : pg_log_error("could not write %zu bytes to log file \"%s\": %m",
584 : : (size_t) 1, outfile);
4512 rhaas@postgresql.org 585 :UBC 0 : goto error;
586 : : }
587 : :
261 alvherre@kurilemu.de 588 [ + + + + ]:CBC 121 : if (XLogRecPtrIsValid(endpos) && cur_record_lsn == endpos)
589 : : {
590 : : /* endpos was exactly the record we just processed, we're done */
3489 simon@2ndQuadrant.co 591 [ - + ]: 5 : if (!flushAndSendFeedback(conn, &now))
3489 simon@2ndQuadrant.co 592 :UBC 0 : goto error;
1101 michael@paquier.xyz 593 :CBC 5 : stop_reason = STREAM_STOP_END_OF_WAL;
3489 simon@2ndQuadrant.co 594 : 5 : time_to_abort = true;
595 : 5 : break;
596 : : }
597 : : }
598 : :
599 : : /* Clean up connection state if stream has been aborted */
1101 michael@paquier.xyz 600 [ + + ]: 20 : if (time_to_abort)
601 : 12 : prepareToTerminate(conn, endpos, stop_reason, cur_record_lsn);
602 : :
4512 rhaas@postgresql.org 603 : 20 : res = PQgetResult(conn);
3489 simon@2ndQuadrant.co 604 [ + + ]: 20 : if (PQresultStatus(res) == PGRES_COPY_OUT)
605 : : {
2264 noah@leadboat.com 606 : 12 : PQclear(res);
607 : :
608 : : /*
609 : : * We're doing a client-initiated clean exit and have sent CopyDone to
610 : : * the server. Drain any messages, so we don't miss a last-minute
611 : : * ErrorResponse. The walsender stops generating WALData records once
612 : : * it sees CopyDone, so expect this to finish quickly. After CopyDone,
613 : : * it's too late for sendFeedback(), even if this were to take a long
614 : : * time. Hence, use synchronous-mode PQgetCopyData().
615 : : */
616 : : while (1)
617 : 149 : {
618 : : int r;
619 : :
620 [ + + ]: 161 : if (copybuf != NULL)
621 : : {
622 : 157 : PQfreemem(copybuf);
623 : 157 : copybuf = NULL;
624 : : }
625 : 161 : r = PQgetCopyData(conn, ©buf, 0);
626 [ + + ]: 161 : if (r == -1)
627 : 12 : break;
628 [ - + ]: 149 : if (r == -2)
629 : : {
2264 noah@leadboat.com 630 :UBC 0 : pg_log_error("could not read COPY data: %s",
631 : : PQerrorMessage(conn));
632 : 0 : time_to_abort = false; /* unclean exit */
633 : 0 : goto error;
634 : : }
635 : : }
636 : :
2264 noah@leadboat.com 637 :CBC 12 : res = PQgetResult(conn);
638 : : }
639 [ + + ]: 20 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
640 : : {
2672 peter@eisentraut.org 641 : 7 : pg_log_error("unexpected termination of replication stream: %s",
642 : : PQresultErrorMessage(res));
487 dgustafsson@postgres 643 : 7 : PQclear(res);
4512 rhaas@postgresql.org 644 : 7 : goto error;
645 : : }
646 : 13 : PQclear(res);
647 : :
648 [ + - + + ]: 13 : if (outfd != -1 && strcmp(outfile, "-") != 0)
649 : : {
3439 tgl@sss.pgh.pa.us 650 : 4 : TimestampTz t = feGetCurrentTimestamp();
651 : :
4512 rhaas@postgresql.org 652 : 4 : OutputFsync(t);
653 [ - + ]: 4 : if (close(outfd) != 0)
2672 peter@eisentraut.org 654 :UBC 0 : pg_log_error("could not close file \"%s\": %m", outfile);
655 : : }
4512 rhaas@postgresql.org 656 :CBC 13 : outfd = -1;
657 : 29 : error:
4464 heikki.linnakangas@i 658 [ - + ]: 29 : if (copybuf != NULL)
659 : : {
4464 heikki.linnakangas@i 660 :UBC 0 : PQfreemem(copybuf);
661 : 0 : copybuf = NULL;
662 : : }
4512 rhaas@postgresql.org 663 :CBC 29 : destroyPQExpBuffer(query);
664 : 29 : PQfinish(conn);
665 : 29 : conn = NULL;
666 : : }
667 : :
668 : : /*
669 : : * Unfortunately we can't do sensible signal handling on windows...
670 : : */
671 : : #ifndef WIN32
672 : :
673 : : /*
674 : : * When SIGINT/SIGTERM are caught, just tell the system to exit at the next
675 : : * possible moment.
676 : : */
677 : : static void
1410 tgl@sss.pgh.pa.us 678 : 4 : sigexit_handler(SIGNAL_ARGS)
679 : : {
1101 michael@paquier.xyz 680 : 4 : stop_reason = STREAM_STOP_SIGNAL;
4512 rhaas@postgresql.org 681 : 4 : time_to_abort = true;
682 : 4 : }
683 : :
684 : : /*
685 : : * Trigger the output file to be reopened.
686 : : */
687 : : static void
1410 tgl@sss.pgh.pa.us 688 :UBC 0 : sighup_handler(SIGNAL_ARGS)
689 : : {
4512 rhaas@postgresql.org 690 : 0 : output_reopen = true;
691 : 0 : }
692 : : #endif
693 : :
694 : :
695 : : int
4512 rhaas@postgresql.org 696 :CBC 72 : main(int argc, char **argv)
697 : : {
698 : : static struct option long_options[] = {
699 : : /* general options */
700 : : {"file", required_argument, NULL, 'f'},
701 : : {"fsync-interval", required_argument, NULL, 'F'},
702 : : {"no-loop", no_argument, NULL, 'n'},
703 : : {"enable-failover", no_argument, NULL, 5},
704 : : {"enable-two-phase", no_argument, NULL, 't'},
705 : : {"two-phase", no_argument, NULL, 't'}, /* deprecated */
706 : : {"verbose", no_argument, NULL, 'v'},
707 : : {"version", no_argument, NULL, 'V'},
708 : : {"help", no_argument, NULL, '?'},
709 : : /* connection options */
710 : : {"dbname", required_argument, NULL, 'd'},
711 : : {"host", required_argument, NULL, 'h'},
712 : : {"port", required_argument, NULL, 'p'},
713 : : {"username", required_argument, NULL, 'U'},
714 : : {"no-password", no_argument, NULL, 'w'},
715 : : {"password", no_argument, NULL, 'W'},
716 : : /* replication options */
717 : : {"startpos", required_argument, NULL, 'I'},
718 : : {"endpos", required_argument, NULL, 'E'},
719 : : {"option", required_argument, NULL, 'o'},
720 : : {"plugin", required_argument, NULL, 'P'},
721 : : {"status-interval", required_argument, NULL, 's'},
722 : : {"slot", required_argument, NULL, 'S'},
723 : : /* action */
724 : : {"create-slot", no_argument, NULL, 1},
725 : : {"start", no_argument, NULL, 2},
726 : : {"drop-slot", no_argument, NULL, 3},
727 : : {"if-not-exists", no_argument, NULL, 4},
728 : : {NULL, 0, NULL, 0}
729 : : };
730 : : int c;
731 : : int option_index;
732 : : uint32 hi,
733 : : lo;
734 : : char *db_name;
735 : :
2672 peter@eisentraut.org 736 : 72 : pg_logging_init(argv[0]);
4512 rhaas@postgresql.org 737 : 72 : progname = get_progname(argv[0]);
3862 alvherre@alvh.no-ip. 738 : 72 : set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_basebackup"));
739 : :
4512 rhaas@postgresql.org 740 [ + + ]: 72 : if (argc > 1)
741 : : {
742 [ + + - + ]: 71 : if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
743 : : {
744 : 1 : usage();
745 : 1 : exit(0);
746 : : }
747 [ + - ]: 70 : else if (strcmp(argv[1], "-V") == 0 ||
748 [ + + ]: 70 : strcmp(argv[1], "--version") == 0)
749 : : {
750 : 1 : puts("pg_recvlogical (PostgreSQL) " PG_VERSION);
751 : 1 : exit(0);
752 : : }
753 : : }
754 : :
1321 peter@eisentraut.org 755 : 411 : while ((c = getopt_long(argc, argv, "E:f:F:ntvd:h:p:U:wWI:o:P:s:S:",
4512 rhaas@postgresql.org 756 [ + + ]: 411 : long_options, &option_index)) != -1)
757 : : {
758 [ + + + + : 342 : switch (c)
+ + + - -
- - - - +
+ + + + +
+ + - + ]
759 : : {
760 : : /* general options */
761 : 29 : case 'f':
762 : 29 : outfile = pg_strdup(optarg);
763 : 29 : break;
4444 andres@anarazel.de 764 : 3 : case 'F':
1827 michael@paquier.xyz 765 [ - + ]: 3 : if (!option_parse_int(optarg, "-F/--fsync-interval", 0,
766 : : INT_MAX / 1000,
767 : : &fsync_interval))
4444 andres@anarazel.de 768 :UBC 0 : exit(1);
1827 michael@paquier.xyz 769 :CBC 3 : fsync_interval *= 1000;
4444 andres@anarazel.de 770 : 3 : break;
4512 rhaas@postgresql.org 771 : 25 : case 'n':
772 : 25 : noloop = 1;
773 : 25 : break;
1851 akapila@postgresql.o 774 : 2 : case 't':
775 : 2 : two_phase = true;
776 : 2 : break;
1321 peter@eisentraut.org 777 : 1 : case 'v':
778 : 1 : verbose++;
779 : 1 : break;
477 msawada@postgresql.o 780 : 1 : case 5:
781 : 1 : failover = true;
782 : 1 : break;
783 : : /* connection options */
4512 rhaas@postgresql.org 784 : 66 : case 'd':
785 : 66 : dbname = pg_strdup(optarg);
786 : 66 : break;
4512 rhaas@postgresql.org 787 :UBC 0 : case 'h':
788 : 0 : dbhost = pg_strdup(optarg);
789 : 0 : break;
790 : 0 : case 'p':
791 : 0 : dbport = pg_strdup(optarg);
792 : 0 : break;
793 : 0 : case 'U':
794 : 0 : dbuser = pg_strdup(optarg);
795 : 0 : break;
796 : 0 : case 'w':
797 : 0 : dbgetpassword = -1;
798 : 0 : break;
799 : 0 : case 'W':
800 : 0 : dbgetpassword = 1;
801 : 0 : break;
802 : : /* replication options */
4444 andres@anarazel.de 803 : 0 : case 'I':
383 alvherre@kurilemu.de 804 [ # # ]: 0 : if (sscanf(optarg, "%X/%08X", &hi, &lo) != 2)
1569 tgl@sss.pgh.pa.us 805 : 0 : pg_fatal("could not parse start position \"%s\"", optarg);
4444 andres@anarazel.de 806 : 0 : startpos = ((uint64) hi) << 32 | lo;
807 : 0 : break;
3489 simon@2ndQuadrant.co 808 :CBC 9 : case 'E':
383 alvherre@kurilemu.de 809 [ - + ]: 9 : if (sscanf(optarg, "%X/%08X", &hi, &lo) != 2)
1569 tgl@sss.pgh.pa.us 810 :UBC 0 : pg_fatal("could not parse end position \"%s\"", optarg);
3489 simon@2ndQuadrant.co 811 :CBC 9 : endpos = ((uint64) hi) << 32 | lo;
812 : 9 : break;
4512 rhaas@postgresql.org 813 : 42 : case 'o':
814 : : {
4463 bruce@momjian.us 815 : 42 : char *data = pg_strdup(optarg);
816 : 42 : char *val = strchr(data, '=');
817 : :
4512 rhaas@postgresql.org 818 [ + - ]: 42 : if (val != NULL)
819 : : {
820 : : /* remove =; separate data from val */
821 : 42 : *val = '\0';
822 : 42 : val++;
823 : : }
824 : :
825 : 42 : noptions += 1;
148 michael@paquier.xyz 826 : 42 : options = pg_realloc_array(options, char *, noptions * 2);
827 : :
4512 rhaas@postgresql.org 828 : 42 : options[(noptions - 1) * 2] = data;
829 : 42 : options[(noptions - 1) * 2 + 1] = val;
830 : : }
831 : :
832 : 42 : break;
833 : 27 : case 'P':
834 : 27 : plugin = pg_strdup(optarg);
835 : 27 : break;
836 : 2 : case 's':
1827 michael@paquier.xyz 837 [ - + ]: 2 : if (!option_parse_int(optarg, "-s/--status-interval", 0,
838 : : INT_MAX / 1000,
839 : : &standby_message_timeout))
4512 rhaas@postgresql.org 840 :UBC 0 : exit(1);
1827 michael@paquier.xyz 841 :CBC 2 : standby_message_timeout *= 1000;
4512 rhaas@postgresql.org 842 : 2 : break;
843 : 68 : case 'S':
844 : 68 : replication_slot = pg_strdup(optarg);
845 : 68 : break;
846 : : /* action */
847 : 32 : case 1:
848 : 32 : do_create_slot = true;
849 : 32 : break;
850 : 30 : case 2:
851 : 30 : do_start_slot = true;
852 : 30 : break;
853 : 4 : case 3:
854 : 4 : do_drop_slot = true;
855 : 4 : break;
4031 andres@anarazel.de 856 :UBC 0 : case 4:
857 : 0 : slot_exists_ok = true;
858 : 0 : break;
859 : :
4512 rhaas@postgresql.org 860 :CBC 1 : default:
861 : : /* getopt_long already emitted a complaint */
1569 tgl@sss.pgh.pa.us 862 : 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4512 rhaas@postgresql.org 863 : 1 : exit(1);
864 : : }
865 : : }
866 : :
867 : : /*
868 : : * Any non-option arguments?
869 : : */
870 [ - + ]: 69 : if (optind < argc)
871 : : {
2672 peter@eisentraut.org 872 :UBC 0 : pg_log_error("too many command-line arguments (first is \"%s\")",
873 : : argv[optind]);
1569 tgl@sss.pgh.pa.us 874 : 0 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4512 rhaas@postgresql.org 875 : 0 : exit(1);
876 : : }
877 : :
878 : : /*
879 : : * Required arguments
880 : : */
4512 rhaas@postgresql.org 881 [ + + ]:CBC 69 : if (replication_slot == NULL)
882 : : {
2672 peter@eisentraut.org 883 : 1 : pg_log_error("no slot specified");
1569 tgl@sss.pgh.pa.us 884 : 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4512 rhaas@postgresql.org 885 : 1 : exit(1);
886 : : }
887 : :
888 [ + + + + ]: 68 : if (do_start_slot && outfile == NULL)
889 : : {
2672 peter@eisentraut.org 890 : 1 : pg_log_error("no target file specified");
1569 tgl@sss.pgh.pa.us 891 : 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4512 rhaas@postgresql.org 892 : 1 : exit(1);
893 : : }
894 : :
895 [ + + + + ]: 67 : if (!do_drop_slot && dbname == NULL)
896 : : {
2672 peter@eisentraut.org 897 : 1 : pg_log_error("no database specified");
1569 tgl@sss.pgh.pa.us 898 : 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4512 rhaas@postgresql.org 899 : 1 : exit(1);
900 : : }
901 : :
902 [ + + + + : 66 : if (!do_drop_slot && !do_create_slot && !do_start_slot)
+ + ]
903 : : {
2672 peter@eisentraut.org 904 : 1 : pg_log_error("at least one action needs to be specified");
1569 tgl@sss.pgh.pa.us 905 : 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4512 rhaas@postgresql.org 906 : 1 : exit(1);
907 : : }
908 : :
909 [ + + + - : 65 : if (do_drop_slot && (do_create_slot || do_start_slot))
- + ]
910 : : {
2672 peter@eisentraut.org 911 :UBC 0 : pg_log_error("cannot use --create-slot or --start together with --drop-slot");
1569 tgl@sss.pgh.pa.us 912 : 0 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4512 rhaas@postgresql.org 913 : 0 : exit(1);
914 : : }
915 : :
261 alvherre@kurilemu.de 916 [ - + - - :CBC 65 : if (XLogRecPtrIsValid(startpos) && (do_create_slot || do_drop_slot))
- - ]
917 : : {
2672 peter@eisentraut.org 918 :UBC 0 : pg_log_error("cannot use --create-slot or --drop-slot together with --startpos");
1569 tgl@sss.pgh.pa.us 919 : 0 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4512 rhaas@postgresql.org 920 : 0 : exit(1);
921 : : }
922 : :
261 alvherre@kurilemu.de 923 [ + + - + ]:CBC 65 : if (XLogRecPtrIsValid(endpos) && !do_start_slot)
924 : : {
2672 peter@eisentraut.org 925 :UBC 0 : pg_log_error("--endpos may only be specified with --start");
1569 tgl@sss.pgh.pa.us 926 : 0 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
3489 simon@2ndQuadrant.co 927 : 0 : exit(1);
928 : : }
929 : :
477 msawada@postgresql.o 930 [ + + ]:CBC 65 : if (!do_create_slot)
931 : : {
932 [ + + ]: 33 : if (two_phase)
933 : : {
391 peter@eisentraut.org 934 : 1 : pg_log_error("%s may only be specified with --create-slot", "--enable-two-phase");
477 msawada@postgresql.o 935 : 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
936 : 1 : exit(1);
937 : : }
938 : :
939 [ - + ]: 32 : if (failover)
940 : : {
391 peter@eisentraut.org 941 :UBC 0 : pg_log_error("%s may only be specified with --create-slot", "--enable-failover");
477 msawada@postgresql.o 942 : 0 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
943 : 0 : exit(1);
944 : : }
945 : : }
946 : :
947 : : /*
948 : : * Obtain a connection to server. Notably, if we need a password, we want
949 : : * to collect it from the user immediately.
950 : : */
4315 andres@anarazel.de 951 :CBC 64 : conn = GetConnection();
952 [ - + ]: 64 : if (!conn)
953 : : /* Error message already written in GetConnection() */
4315 andres@anarazel.de 954 :UBC 0 : exit(1);
2765 peter@eisentraut.org 955 :CBC 64 : atexit(disconnect_atexit);
956 : :
957 : : /*
958 : : * Trap signals. (Don't do this until after the initial password prompt,
959 : : * if one is needed, in GetConnection.)
960 : : */
961 : : #ifndef WIN32
1410 dgustafsson@postgres 962 : 64 : pqsignal(SIGINT, sigexit_handler);
963 : 64 : pqsignal(SIGTERM, sigexit_handler);
1707 tgl@sss.pgh.pa.us 964 : 64 : pqsignal(SIGHUP, sighup_handler);
965 : : #endif
966 : :
967 : : /*
968 : : * Run IDENTIFY_SYSTEM to check the connection type for each action.
969 : : * --create-slot and --start actions require a database-specific
970 : : * replication connection because they handle logical replication slots.
971 : : * --drop-slot can remove replication slots from any replication
972 : : * connection without this restriction.
973 : : */
4315 andres@anarazel.de 974 [ - + ]: 64 : if (!RunIdentifySystem(conn, NULL, NULL, NULL, &db_name))
2765 peter@eisentraut.org 975 :UBC 0 : exit(1);
976 : :
487 fujii@postgresql.org 977 [ + + - + ]:CBC 64 : if (!do_drop_slot && db_name == NULL)
1569 tgl@sss.pgh.pa.us 978 :UBC 0 : pg_fatal("could not establish database-specific replication connection");
979 : :
980 : : /*
981 : : * Set umask so that directories/files are created with the same
982 : : * permissions as directories/files in the source data directory.
983 : : *
984 : : * pg_mode_mask is set to owner-only by default and then updated in
985 : : * GetConnection() where we get the mode from the server-side with
986 : : * RetrieveDataDirCreatePerm() and then call SetDataDirectoryCreatePerm().
987 : : */
3031 sfrost@snowman.net 988 :CBC 64 : umask(pg_mode_mask);
989 : :
990 : : /* Drop a replication slot. */
4512 rhaas@postgresql.org 991 [ + + ]: 64 : if (do_drop_slot)
992 : : {
993 [ - + ]: 4 : if (verbose)
2672 peter@eisentraut.org 994 :UBC 0 : pg_log_info("dropping replication slot \"%s\"", replication_slot);
995 : :
4315 andres@anarazel.de 996 [ - + ]:CBC 4 : if (!DropReplicationSlot(conn, replication_slot))
2765 peter@eisentraut.org 997 :UBC 0 : exit(1);
998 : : }
999 : :
1000 : : /* Create a replication slot. */
4512 rhaas@postgresql.org 1001 [ + + ]:CBC 64 : if (do_create_slot)
1002 : : {
1003 [ - + ]: 32 : if (verbose)
2672 peter@eisentraut.org 1004 :UBC 0 : pg_log_info("creating replication slot \"%s\"", replication_slot);
1005 : :
3224 peter_e@gmx.net 1006 [ - + ]:CBC 32 : if (!CreateReplicationSlot(conn, replication_slot, plugin, false,
1007 : : false, false, slot_exists_ok, two_phase,
1008 : : failover))
2765 peter@eisentraut.org 1009 :UBC 0 : exit(1);
4031 andres@anarazel.de 1010 :CBC 32 : startpos = InvalidXLogRecPtr;
1011 : : }
1012 : :
4512 rhaas@postgresql.org 1013 [ + + ]: 64 : if (!do_start_slot)
2765 peter@eisentraut.org 1014 : 36 : exit(0);
1015 : :
1016 : : /* Stream loop */
1017 : : while (true)
1018 : : {
4315 andres@anarazel.de 1019 : 29 : StreamLogicalLog();
4512 rhaas@postgresql.org 1020 [ + + ]: 29 : if (time_to_abort)
1021 : : {
1022 : : /*
1023 : : * We've been Ctrl-C'ed or reached an exit limit condition. That's
1024 : : * not an error, so exit without an errorcode.
1025 : : */
2765 peter@eisentraut.org 1026 : 12 : exit(0);
1027 : : }
1028 : :
1029 : : /*
1030 : : * Ensure all written data is flushed to disk before exiting or
1031 : : * starting a new replication.
1032 : : */
190 fujii@postgresql.org 1033 [ + + ]: 17 : if (outfd != -1)
1034 : 10 : OutputFsync(feGetCurrentTimestamp());
1035 : :
1036 [ + + ]: 17 : if (noloop)
1037 : : {
1569 tgl@sss.pgh.pa.us 1038 : 16 : pg_fatal("disconnected");
1039 : : }
1040 : : else
1041 : : {
1042 : : /* translator: check source for value for %d */
2672 peter@eisentraut.org 1043 : 1 : pg_log_info("disconnected; waiting %d seconds to try again",
1044 : : RECONNECT_SLEEP_TIME);
4512 rhaas@postgresql.org 1045 : 1 : pg_usleep(RECONNECT_SLEEP_TIME * 1000000);
1046 : : }
1047 : : }
1048 : : }
1049 : :
1050 : : /*
1051 : : * Fsync our output data, and send a feedback message to the server. Returns
1052 : : * true if successful, false otherwise.
1053 : : *
1054 : : * If successful, *now is updated to the current timestamp just before sending
1055 : : * feedback.
1056 : : */
1057 : : static bool
3489 simon@2ndQuadrant.co 1058 : 13 : flushAndSendFeedback(PGconn *conn, TimestampTz *now)
1059 : : {
1060 : : /* flush data to disk, so that we send a recent flush pointer */
190 fujii@postgresql.org 1061 : 13 : OutputFsync(*now);
3489 simon@2ndQuadrant.co 1062 : 13 : *now = feGetCurrentTimestamp();
1063 [ - + ]: 13 : if (!sendFeedback(conn, *now, true, false))
3489 simon@2ndQuadrant.co 1064 :UBC 0 : return false;
1065 : :
3489 simon@2ndQuadrant.co 1066 :CBC 13 : return true;
1067 : : }
1068 : :
1069 : : /*
1070 : : * Try to inform the server about our upcoming demise, but don't wait around or
1071 : : * retry on failure.
1072 : : */
1073 : : static void
1101 michael@paquier.xyz 1074 : 12 : prepareToTerminate(PGconn *conn, XLogRecPtr endpos, StreamStopReason reason,
1075 : : XLogRecPtr lsn)
1076 : : {
1077 : : /*
1078 : : * If pg_recvlogical is terminated by a signal, we can reach here without
1079 : : * sending final feedback. In that case, send feedback once more before
1080 : : * sending CopyDone so the replication slot can advance far enough to
1081 : : * reduce the chance of resending duplicate data when pg_recvlogical is
1082 : : * restarted.
1083 : : *
1084 : : * This is still only a best-effort attempt. Depending on when the signal
1085 : : * arrives, the receiver may have written decoded output that the server
1086 : : * cannot yet safely treat as confirmed, so a later restart can still see
1087 : : * duplicate data.
1088 : : *
1089 : : * For other termination cases, such as STREAM_STOP_KEEPALIVE and
1090 : : * STREAM_STOP_END_OF_WAL, feedback has already been sent before reaching
1091 : : * here, so there is no need to call flushAndSendFeedback() again.
1092 : : */
17 fujii@postgresql.org 1093 [ + + ]:GNC 12 : if (reason == STREAM_STOP_SIGNAL)
1094 : : {
1095 : 4 : TimestampTz now = feGetCurrentTimestamp();
1096 : :
1097 : 4 : (void) flushAndSendFeedback(conn, &now);
1098 : : }
1099 : :
3489 simon@2ndQuadrant.co 1100 :CBC 12 : (void) PQputCopyEnd(conn, NULL);
1101 : 12 : (void) PQflush(conn);
1102 : :
1103 [ + + ]: 12 : if (verbose)
1104 : : {
1101 michael@paquier.xyz 1105 [ + - - - : 1 : switch (reason)
- ]
1106 : : {
1107 : 1 : case STREAM_STOP_SIGNAL:
1108 : 1 : pg_log_info("received interrupt signal, exiting");
1109 : 1 : break;
1101 michael@paquier.xyz 1110 :UBC 0 : case STREAM_STOP_KEEPALIVE:
383 alvherre@kurilemu.de 1111 : 0 : pg_log_info("end position %X/%08X reached by keepalive",
1112 : : LSN_FORMAT_ARGS(endpos));
1101 michael@paquier.xyz 1113 : 0 : break;
1114 : 0 : case STREAM_STOP_END_OF_WAL:
261 alvherre@kurilemu.de 1115 [ # # ]: 0 : Assert(XLogRecPtrIsValid(lsn));
383 1116 : 0 : pg_log_info("end position %X/%08X reached by WAL record at %X/%08X",
1117 : : LSN_FORMAT_ARGS(endpos), LSN_FORMAT_ARGS(lsn));
1101 michael@paquier.xyz 1118 : 0 : break;
1119 : 0 : case STREAM_STOP_NONE:
1120 : 0 : Assert(false);
1121 : : break;
1122 : : }
1123 : : }
3489 simon@2ndQuadrant.co 1124 :CBC 12 : }
|