2011-10-26 20:13:33 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* pg_receivexlog.c - receive streaming transaction log data and write it
|
|
|
|
* to a local file.
|
|
|
|
*
|
|
|
|
* Author: Magnus Hagander <magnus@hagander.net>
|
|
|
|
*
|
2013-01-01 23:15:01 +01:00
|
|
|
* Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
|
2011-10-26 20:13:33 +02:00
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
|
|
|
* src/bin/pg_basebackup/pg_receivexlog.c
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
2012-12-13 13:59:13 +01:00
|
|
|
#include "postgres_fe.h"
|
2011-10-26 20:13:33 +02:00
|
|
|
|
|
|
|
#include <dirent.h>
|
2013-03-17 19:11:48 +01:00
|
|
|
#include <signal.h>
|
2011-10-26 20:13:33 +02:00
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
2013-03-17 19:11:48 +01:00
|
|
|
#include "libpq-fe.h"
|
|
|
|
#include "access/xlog_internal.h"
|
2011-10-26 20:13:33 +02:00
|
|
|
#include "getopt_long.h"
|
|
|
|
|
2013-03-17 19:11:48 +01:00
|
|
|
#include "receivelog.h"
|
|
|
|
#include "streamutil.h"
|
|
|
|
|
|
|
|
|
2012-05-27 11:05:24 +02:00
|
|
|
/* Time to sleep between reconnection attempts */
|
|
|
|
#define RECONNECT_SLEEP_TIME 5
|
|
|
|
|
2011-10-26 20:13:33 +02:00
|
|
|
/* Global options */
|
|
|
|
char *basedir = NULL;
|
|
|
|
int verbose = 0;
|
2012-05-27 11:05:24 +02:00
|
|
|
int noloop = 0;
|
2012-06-10 12:12:36 +02:00
|
|
|
int standby_message_timeout = 10 * 1000; /* 10 sec = default */
|
2011-10-26 20:13:33 +02:00
|
|
|
volatile bool time_to_abort = false;
|
|
|
|
|
|
|
|
|
|
|
|
static void usage(void);
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
static XLogRecPtr FindStreamingStart(uint32 *tli);
|
2011-10-26 20:13:33 +02:00
|
|
|
static void StreamLog();
|
2012-07-31 16:11:11 +02:00
|
|
|
static bool stop_streaming(XLogRecPtr segendpos, uint32 timeline,
|
|
|
|
bool segment_finished);
|
2011-10-26 20:13:33 +02:00
|
|
|
|
|
|
|
static void
|
|
|
|
usage(void)
|
|
|
|
{
|
2012-06-11 23:55:27 +02:00
|
|
|
printf(_("%s receives PostgreSQL streaming transaction logs.\n\n"),
|
2011-10-26 20:13:33 +02:00
|
|
|
progname);
|
|
|
|
printf(_("Usage:\n"));
|
|
|
|
printf(_(" %s [OPTION]...\n"), progname);
|
2012-06-11 23:55:27 +02:00
|
|
|
printf(_("\nOptions:\n"));
|
2012-07-31 16:11:11 +02:00
|
|
|
printf(_(" -D, --directory=DIR receive transaction log files into this directory\n"));
|
|
|
|
printf(_(" -n, --no-loop do not loop on connection lost\n"));
|
|
|
|
printf(_(" -v, --verbose output verbose messages\n"));
|
|
|
|
printf(_(" -V, --version output version information, then exit\n"));
|
|
|
|
printf(_(" -?, --help show this help, then exit\n"));
|
2011-10-26 20:13:33 +02:00
|
|
|
printf(_("\nConnection options:\n"));
|
2013-02-25 13:48:27 +01:00
|
|
|
printf(_(" -d, --dbname=CONNSTR connection string\n"));
|
2012-07-31 16:11:11 +02:00
|
|
|
printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
|
|
|
|
printf(_(" -p, --port=PORT database server port number\n"));
|
2012-08-24 06:06:59 +02:00
|
|
|
printf(_(" -s, --status-interval=INTERVAL\n"
|
|
|
|
" time between status packets sent to server (in seconds)\n"));
|
2012-07-31 16:11:11 +02:00
|
|
|
printf(_(" -U, --username=NAME connect as specified database user\n"));
|
|
|
|
printf(_(" -w, --no-password never prompt for password\n"));
|
|
|
|
printf(_(" -W, --password force password prompt (should happen automatically)\n"));
|
2011-10-26 20:13:33 +02:00
|
|
|
printf(_("\nReport bugs to <pgsql-bugs@postgresql.org>.\n"));
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
stop_streaming(XLogRecPtr xlogpos, uint32 timeline, bool segment_finished)
|
2011-10-26 20:13:33 +02:00
|
|
|
{
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
static uint32 prevtimeline = 0;
|
|
|
|
static XLogRecPtr prevpos = InvalidXLogRecPtr;
|
|
|
|
|
|
|
|
/* we assume that we get called once at the end of each segment */
|
2012-05-25 11:36:22 +02:00
|
|
|
if (verbose && segment_finished)
|
2011-10-26 20:13:33 +02:00
|
|
|
fprintf(stderr, _("%s: finished segment at %X/%X (timeline %u)\n"),
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
progname, (uint32) (xlogpos >> 32), (uint32) xlogpos,
|
2012-06-24 17:51:37 +02:00
|
|
|
timeline);
|
2011-10-26 20:13:33 +02:00
|
|
|
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
/*
|
Fix walsender failure at promotion.
If a standby server has a cascading standby server connected to it, it's
possible that WAL has already been sent up to the next WAL page boundary,
splitting a WAL record in the middle, when the first standby server is
promoted. Don't throw an assertion failure or error in walsender if that
happens.
Also, fix a variant of the same bug in pg_receivexlog: if it had already
received WAL on previous timeline up to a segment boundary, when the
upstream standby server is promoted so that the timeline switch record falls
on the previous segment, pg_receivexlog would miss the segment containing
the timeline switch. To fix that, have walsender send the position of the
timeline switch at end-of-streaming, in addition to the next timeline's ID.
It was previously assumed that the switch happened exactly where the
streaming stopped.
Note: this is an incompatible change in the streaming protocol. You might
get an error if you try to stream over timeline switches, if the client is
running 9.3beta1 and the server is more recent. It should be fine after a
reconnect, however.
Reported by Fujii Masao.
2013-05-08 19:10:17 +02:00
|
|
|
* Note that we report the previous, not current, position here. After a
|
|
|
|
* timeline switch, xlogpos points to the beginning of the segment because
|
|
|
|
* that's where we always begin streaming. Reporting the end of previous
|
|
|
|
* timeline isn't totally accurate, because the next timeline can begin
|
|
|
|
* slightly before the end of the WAL that we received on the previous
|
|
|
|
* timeline, but it's close enough for reporting purposes.
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
*/
|
|
|
|
if (prevtimeline != 0 && prevtimeline != timeline)
|
|
|
|
fprintf(stderr, _("%s: switched to timeline %u at %X/%X\n"),
|
|
|
|
progname, timeline,
|
|
|
|
(uint32) (prevpos >> 32), (uint32) prevpos);
|
|
|
|
|
|
|
|
prevtimeline = timeline;
|
|
|
|
prevpos = xlogpos;
|
|
|
|
|
2011-10-26 20:13:33 +02:00
|
|
|
if (time_to_abort)
|
|
|
|
{
|
2012-12-19 13:01:11 +01:00
|
|
|
fprintf(stderr, _("%s: received interrupt signal, exiting\n"),
|
2011-10-26 20:13:33 +02:00
|
|
|
progname);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
* Determine starting location for streaming, based on any existing xlog
|
|
|
|
* segments in the directory. We start at the end of the last one that is
|
|
|
|
* complete (size matches XLogSegSize), on the timeline with highest ID.
|
|
|
|
*
|
|
|
|
* If there are no WAL files in the directory, returns InvalidXLogRecPtr.
|
2011-10-26 20:13:33 +02:00
|
|
|
*/
|
|
|
|
static XLogRecPtr
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
FindStreamingStart(uint32 *tli)
|
2011-10-26 20:13:33 +02:00
|
|
|
{
|
|
|
|
DIR *dir;
|
|
|
|
struct dirent *dirent;
|
2012-06-24 17:06:38 +02:00
|
|
|
XLogSegNo high_segno = 0;
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
uint32 high_tli = 0;
|
2011-10-26 20:13:33 +02:00
|
|
|
|
|
|
|
dir = opendir(basedir);
|
|
|
|
if (dir == NULL)
|
|
|
|
{
|
|
|
|
fprintf(stderr, _("%s: could not open directory \"%s\": %s\n"),
|
|
|
|
progname, basedir, strerror(errno));
|
|
|
|
disconnect_and_exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((dirent = readdir(dir)) != NULL)
|
|
|
|
{
|
|
|
|
char fullpath[MAXPGPATH];
|
|
|
|
struct stat statbuf;
|
2012-06-24 17:06:38 +02:00
|
|
|
uint32 tli;
|
|
|
|
unsigned int log,
|
2011-10-26 20:13:33 +02:00
|
|
|
seg;
|
2012-06-24 17:06:38 +02:00
|
|
|
XLogSegNo segno;
|
2011-10-26 20:13:33 +02:00
|
|
|
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
/*
|
|
|
|
* Check if the filename looks like an xlog file, or a .partial file.
|
|
|
|
* Xlog files are always 24 characters, and .partial files are 32
|
|
|
|
* characters.
|
|
|
|
*/
|
|
|
|
if (strlen(dirent->d_name) != 24 ||
|
|
|
|
!strspn(dirent->d_name, "0123456789ABCDEF") == 24)
|
2011-10-26 20:13:33 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Looks like an xlog file. Parse its position.
|
|
|
|
*/
|
|
|
|
if (sscanf(dirent->d_name, "%08X%08X%08X", &tli, &log, &seg) != 3)
|
|
|
|
{
|
2012-07-31 16:11:11 +02:00
|
|
|
fprintf(stderr,
|
|
|
|
_("%s: could not parse transaction log file name \"%s\"\n"),
|
2011-10-26 20:13:33 +02:00
|
|
|
progname, dirent->d_name);
|
|
|
|
disconnect_and_exit(1);
|
|
|
|
}
|
2012-06-24 17:06:38 +02:00
|
|
|
segno = ((uint64) log) << 32 | seg;
|
2011-10-26 20:13:33 +02:00
|
|
|
|
|
|
|
/* Check if this is a completed segment or not */
|
|
|
|
snprintf(fullpath, sizeof(fullpath), "%s/%s", basedir, dirent->d_name);
|
|
|
|
if (stat(fullpath, &statbuf) != 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, _("%s: could not stat file \"%s\": %s\n"),
|
|
|
|
progname, fullpath, strerror(errno));
|
|
|
|
disconnect_and_exit(1);
|
|
|
|
}
|
|
|
|
|
2011-11-03 15:37:08 +01:00
|
|
|
if (statbuf.st_size == XLOG_SEG_SIZE)
|
2011-10-26 20:13:33 +02:00
|
|
|
{
|
|
|
|
/* Completed segment */
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
if (segno > high_segno || (segno == high_segno && tli > high_tli))
|
2011-10-26 20:13:33 +02:00
|
|
|
{
|
2012-06-24 17:06:38 +02:00
|
|
|
high_segno = segno;
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
high_tli = tli;
|
2011-10-26 20:13:33 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-07-31 16:11:11 +02:00
|
|
|
fprintf(stderr,
|
|
|
|
_("%s: segment file \"%s\" has incorrect size %d, skipping\n"),
|
2011-11-03 15:37:08 +01:00
|
|
|
progname, dirent->d_name, (int) statbuf.st_size);
|
|
|
|
continue;
|
2011-10-26 20:13:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
closedir(dir);
|
|
|
|
|
2012-06-24 17:06:38 +02:00
|
|
|
if (high_segno > 0)
|
2011-10-26 20:13:33 +02:00
|
|
|
{
|
|
|
|
XLogRecPtr high_ptr;
|
2012-06-10 21:20:04 +02:00
|
|
|
|
2011-11-03 15:37:08 +01:00
|
|
|
/*
|
2012-06-10 21:20:04 +02:00
|
|
|
* Move the starting pointer to the start of the next segment, since
|
|
|
|
* the highest one we've seen was completed.
|
2011-11-03 15:37:08 +01:00
|
|
|
*/
|
2012-06-24 17:06:38 +02:00
|
|
|
high_segno++;
|
2011-10-26 20:13:33 +02:00
|
|
|
|
2012-06-24 17:06:38 +02:00
|
|
|
XLogSegNoOffsetToRecPtr(high_segno, 0, high_ptr);
|
2011-10-26 20:13:33 +02:00
|
|
|
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
*tli = high_tli;
|
2011-10-26 20:13:33 +02:00
|
|
|
return high_ptr;
|
|
|
|
}
|
|
|
|
else
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
return InvalidXLogRecPtr;
|
2011-10-26 20:13:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Start the log streaming
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
StreamLog(void)
|
|
|
|
{
|
|
|
|
PGresult *res;
|
|
|
|
XLogRecPtr startpos;
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
uint32 starttli;
|
|
|
|
XLogRecPtr serverpos;
|
|
|
|
uint32 servertli;
|
2012-06-24 17:51:37 +02:00
|
|
|
uint32 hi,
|
|
|
|
lo;
|
2011-10-26 20:13:33 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Connect in replication mode to the server
|
|
|
|
*/
|
|
|
|
conn = GetConnection();
|
2012-05-27 11:05:24 +02:00
|
|
|
if (!conn)
|
|
|
|
/* Error message already written in GetConnection() */
|
|
|
|
return;
|
2011-10-26 20:13:33 +02:00
|
|
|
|
Make pg_basebackup work with pre-9.3 servers, and add server version check.
A new 'starttli' field was added to the response of BASE_BACKUP command.
Make pg_basebackup tolerate the case that it's missing, so that it still
works with older servers.
Add an explicit check for the server version, so that you get a nicer error
message if you try to use it with a pre-9.1 server.
The streaming protocol message format changed in 9.3, so -X stream still won't
work with pre-9.3 servers. I added a version check to ReceiveXLogStream()
earlier, but write that slightly differently, so that in 9.4, it will still
work with a 9.3 server. (In 9.4, the error message needs to be adjusted to
"9.3 or above", though). Also, if the version check fails, don't retry.
2013-03-22 12:02:59 +01:00
|
|
|
if (!CheckServerVersionForStreaming(conn))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Error message already written in CheckServerVersionForStreaming().
|
|
|
|
* There's no hope of recovering from a version mismatch, so don't
|
|
|
|
* retry.
|
|
|
|
*/
|
|
|
|
disconnect_and_exit(1);
|
|
|
|
}
|
|
|
|
|
2011-10-26 20:13:33 +02:00
|
|
|
/*
|
|
|
|
* Run IDENTIFY_SYSTEM so we can get the timeline and current xlog
|
|
|
|
* position.
|
|
|
|
*/
|
|
|
|
res = PQexec(conn, "IDENTIFY_SYSTEM");
|
|
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
|
|
{
|
2012-07-31 16:11:11 +02:00
|
|
|
fprintf(stderr, _("%s: could not send replication command \"%s\": %s"),
|
|
|
|
progname, "IDENTIFY_SYSTEM", PQerrorMessage(conn));
|
2011-10-26 20:13:33 +02:00
|
|
|
disconnect_and_exit(1);
|
|
|
|
}
|
2012-01-20 13:57:02 +01:00
|
|
|
if (PQntuples(res) != 1 || PQnfields(res) != 3)
|
2011-10-26 20:13:33 +02:00
|
|
|
{
|
2012-07-31 16:11:11 +02:00
|
|
|
fprintf(stderr,
|
|
|
|
_("%s: could not identify system: got %d rows and %d fields, expected %d rows and %d fields\n"),
|
|
|
|
progname, PQntuples(res), PQnfields(res), 1, 3);
|
2011-10-26 20:13:33 +02:00
|
|
|
disconnect_and_exit(1);
|
|
|
|
}
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
servertli = atoi(PQgetvalue(res, 0, 1));
|
2012-06-24 17:51:37 +02:00
|
|
|
if (sscanf(PQgetvalue(res, 0, 2), "%X/%X", &hi, &lo) != 2)
|
2011-10-26 20:13:33 +02:00
|
|
|
{
|
2012-07-31 16:11:11 +02:00
|
|
|
fprintf(stderr,
|
|
|
|
_("%s: could not parse transaction log location \"%s\"\n"),
|
2011-10-26 20:13:33 +02:00
|
|
|
progname, PQgetvalue(res, 0, 2));
|
|
|
|
disconnect_and_exit(1);
|
|
|
|
}
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
serverpos = ((uint64) hi) << 32 | lo;
|
2011-10-26 20:13:33 +02:00
|
|
|
PQclear(res);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Figure out where to start streaming.
|
|
|
|
*/
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
startpos = FindStreamingStart(&starttli);
|
|
|
|
if (startpos == InvalidXLogRecPtr)
|
|
|
|
{
|
|
|
|
startpos = serverpos;
|
|
|
|
starttli = servertli;
|
|
|
|
}
|
2011-10-26 20:13:33 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Always start streaming at the beginning of a segment
|
|
|
|
*/
|
2012-06-24 17:51:37 +02:00
|
|
|
startpos -= startpos % XLOG_SEG_SIZE;
|
2011-10-26 20:13:33 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Start the replication
|
|
|
|
*/
|
|
|
|
if (verbose)
|
2012-07-31 16:11:11 +02:00
|
|
|
fprintf(stderr,
|
|
|
|
_("%s: starting log streaming at %X/%X (timeline %u)\n"),
|
|
|
|
progname, (uint32) (startpos >> 32), (uint32) startpos,
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
starttli);
|
2011-10-26 20:13:33 +02:00
|
|
|
|
Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.
This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.
When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.
This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
2013-01-17 19:23:00 +01:00
|
|
|
ReceiveXlogStream(conn, startpos, starttli, NULL, basedir,
|
|
|
|
stop_streaming, standby_message_timeout, ".partial");
|
2011-11-03 15:43:25 +01:00
|
|
|
|
|
|
|
PQfinish(conn);
|
2011-10-26 20:13:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* When sigint is called, just tell the system to exit at the next possible
|
|
|
|
* moment.
|
|
|
|
*/
|
2011-12-11 00:15:15 +01:00
|
|
|
#ifndef WIN32
|
|
|
|
|
2011-10-26 20:13:33 +02:00
|
|
|
static void
|
|
|
|
sigint_handler(int signum)
|
|
|
|
{
|
|
|
|
time_to_abort = true;
|
|
|
|
}
|
2011-12-11 00:15:15 +01:00
|
|
|
#endif
|
|
|
|
|
2011-10-26 20:13:33 +02:00
|
|
|
int
|
|
|
|
main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
static struct option long_options[] = {
|
|
|
|
{"help", no_argument, NULL, '?'},
|
|
|
|
{"version", no_argument, NULL, 'V'},
|
2012-06-11 23:55:27 +02:00
|
|
|
{"directory", required_argument, NULL, 'D'},
|
2013-02-25 13:48:27 +01:00
|
|
|
{"dbname", required_argument, NULL, 'd'},
|
2011-10-26 20:13:33 +02:00
|
|
|
{"host", required_argument, NULL, 'h'},
|
|
|
|
{"port", required_argument, NULL, 'p'},
|
|
|
|
{"username", required_argument, NULL, 'U'},
|
2012-07-31 16:11:11 +02:00
|
|
|
{"no-loop", no_argument, NULL, 'n'},
|
2011-10-26 20:13:33 +02:00
|
|
|
{"no-password", no_argument, NULL, 'w'},
|
|
|
|
{"password", no_argument, NULL, 'W'},
|
2012-07-31 16:11:11 +02:00
|
|
|
{"status-interval", required_argument, NULL, 's'},
|
2011-10-26 20:13:33 +02:00
|
|
|
{"verbose", no_argument, NULL, 'v'},
|
|
|
|
{NULL, 0, NULL, 0}
|
|
|
|
};
|
2012-11-30 20:49:55 +01:00
|
|
|
|
2011-10-26 20:13:33 +02:00
|
|
|
int c;
|
|
|
|
int option_index;
|
|
|
|
|
|
|
|
progname = get_progname(argv[0]);
|
|
|
|
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_receivexlog"));
|
|
|
|
|
|
|
|
if (argc > 1)
|
|
|
|
{
|
|
|
|
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
|
|
|
|
{
|
|
|
|
usage();
|
|
|
|
exit(0);
|
|
|
|
}
|
2012-07-31 16:11:11 +02:00
|
|
|
else if (strcmp(argv[1], "-V") == 0 ||
|
|
|
|
strcmp(argv[1], "--version") == 0)
|
2011-10-26 20:13:33 +02:00
|
|
|
{
|
|
|
|
puts("pg_receivexlog (PostgreSQL) " PG_VERSION);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-25 13:48:27 +01:00
|
|
|
while ((c = getopt_long(argc, argv, "D:d:h:p:U:s:nwWv",
|
2011-10-26 20:13:33 +02:00
|
|
|
long_options, &option_index)) != -1)
|
|
|
|
{
|
|
|
|
switch (c)
|
|
|
|
{
|
|
|
|
case 'D':
|
2012-10-02 21:35:10 +02:00
|
|
|
basedir = pg_strdup(optarg);
|
2011-10-26 20:13:33 +02:00
|
|
|
break;
|
2013-02-25 13:48:27 +01:00
|
|
|
case 'd':
|
|
|
|
connection_string = pg_strdup(optarg);
|
|
|
|
break;
|
2011-10-26 20:13:33 +02:00
|
|
|
case 'h':
|
2012-10-02 21:35:10 +02:00
|
|
|
dbhost = pg_strdup(optarg);
|
2011-10-26 20:13:33 +02:00
|
|
|
break;
|
|
|
|
case 'p':
|
|
|
|
if (atoi(optarg) <= 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, _("%s: invalid port number \"%s\"\n"),
|
|
|
|
progname, optarg);
|
|
|
|
exit(1);
|
|
|
|
}
|
2012-10-02 21:35:10 +02:00
|
|
|
dbport = pg_strdup(optarg);
|
2011-10-26 20:13:33 +02:00
|
|
|
break;
|
|
|
|
case 'U':
|
2012-10-02 21:35:10 +02:00
|
|
|
dbuser = pg_strdup(optarg);
|
2011-10-26 20:13:33 +02:00
|
|
|
break;
|
|
|
|
case 'w':
|
|
|
|
dbgetpassword = -1;
|
|
|
|
break;
|
|
|
|
case 'W':
|
|
|
|
dbgetpassword = 1;
|
|
|
|
break;
|
|
|
|
case 's':
|
2012-06-10 12:12:36 +02:00
|
|
|
standby_message_timeout = atoi(optarg) * 1000;
|
2011-10-26 20:13:33 +02:00
|
|
|
if (standby_message_timeout < 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, _("%s: invalid status interval \"%s\"\n"),
|
|
|
|
progname, optarg);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
break;
|
2012-05-27 11:05:24 +02:00
|
|
|
case 'n':
|
|
|
|
noloop = 1;
|
|
|
|
break;
|
2011-10-26 20:13:33 +02:00
|
|
|
case 'v':
|
|
|
|
verbose++;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
|
|
|
|
/*
|
|
|
|
* getopt_long already emitted a complaint
|
|
|
|
*/
|
|
|
|
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
|
|
|
|
progname);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Any non-option arguments?
|
|
|
|
*/
|
|
|
|
if (optind < argc)
|
|
|
|
{
|
|
|
|
fprintf(stderr,
|
|
|
|
_("%s: too many command-line arguments (first is \"%s\")\n"),
|
|
|
|
progname, argv[optind]);
|
|
|
|
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
|
|
|
|
progname);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Required arguments
|
|
|
|
*/
|
|
|
|
if (basedir == NULL)
|
|
|
|
{
|
|
|
|
fprintf(stderr, _("%s: no target directory specified\n"), progname);
|
|
|
|
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
|
|
|
|
progname);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef WIN32
|
|
|
|
pqsignal(SIGINT, sigint_handler);
|
|
|
|
#endif
|
|
|
|
|
2012-05-27 11:05:24 +02:00
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
StreamLog();
|
|
|
|
if (time_to_abort)
|
2012-07-31 16:11:11 +02:00
|
|
|
{
|
2012-05-27 11:05:24 +02:00
|
|
|
/*
|
2012-06-10 21:20:04 +02:00
|
|
|
* We've been Ctrl-C'ed. That's not an error, so exit without an
|
|
|
|
* errorcode.
|
2012-05-27 11:05:24 +02:00
|
|
|
*/
|
|
|
|
exit(0);
|
2012-07-31 16:11:11 +02:00
|
|
|
}
|
2012-05-27 11:05:24 +02:00
|
|
|
else if (noloop)
|
|
|
|
{
|
2012-12-19 13:01:11 +01:00
|
|
|
fprintf(stderr, _("%s: disconnected\n"), progname);
|
2012-05-27 11:05:24 +02:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-07-31 16:11:11 +02:00
|
|
|
fprintf(stderr,
|
2012-08-28 09:10:04 +02:00
|
|
|
/* translator: check source for value for %d */
|
2012-12-19 13:01:11 +01:00
|
|
|
_("%s: disconnected; waiting %d seconds to try again\n"),
|
2012-05-27 11:05:24 +02:00
|
|
|
progname, RECONNECT_SLEEP_TIME);
|
|
|
|
pg_usleep(RECONNECT_SLEEP_TIME * 1000000);
|
|
|
|
}
|
|
|
|
}
|
2011-10-26 20:13:33 +02:00
|
|
|
}
|