diff --git a/Makefile.in b/Makefile.in
index 8815eca1..8b443b50 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -27,7 +27,7 @@ include Makefile.global
$(info Building against PostgreSQL $(MAJORVERSION))
REPMGR_CLIENT_OBJS = repmgr-client.o repmgr-action-master.o repmgr-action-standby.o repmgr-action-cluster.o \
- config.o log.o strutil.o dbutils.o dirutil.o
+ config.o log.o strutil.o dbutils.o dirutil.o compat.o
REPMGRD_OBJS = repmgrd.o
$(REPMGR_CLIENT_OBJS): repmgr-client.h
@@ -56,6 +56,7 @@ additional-clean:
rm -f repmgr-action-master.o
rm -f repmgr-action-standby.o
rm -f repmgrd.o
+ rm -f compat.o
rm -f config.o
rm -f dbutils.o
rm -f dirutil.o
diff --git a/compat.c b/compat.c
new file mode 100644
index 00000000..e3d3ee31
--- /dev/null
+++ b/compat.c
@@ -0,0 +1,107 @@
+/*
+ *
+ * compat.c
+ * Provides a couple of useful string utility functions adapted
+ * from the backend code, which are not publicly exposed in all
+ * supported PostgreSQL versions. They're unlikely to change but
+ * it would be worth keeping an eye on them for any fixes/improvements.
+ *
+ * Copyright (c) 2ndQuadrant, 2010-2017
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "repmgr.h"
+#include "compat.h"
+
+/*
+ * Append the given string to the buffer, with suitable quoting for passing
+ * the string as a value, in a keyword/pair value in a libpq connection
+ * string
+ *
+ * This function is adapted from src/fe_utils/string_utils.c (before 9.6
+ * located in: src/bin/pg_dump/dumputils.c)
+ */
+void
+appendConnStrVal(PQExpBuffer buf, const char *str)
+{
+ const char *s;
+ bool needquotes;
+
+ /*
+ * If the string is one or more plain ASCII characters, no need to quote
+ * it. This is quite conservative, but better safe than sorry.
+ */
+ needquotes = true;
+ for (s = str; *s; s++)
+ {
+ if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') ||
+ (*s >= '0' && *s <= '9') || *s == '_' || *s == '.'))
+ {
+ needquotes = true;
+ break;
+ }
+ needquotes = false;
+ }
+
+ if (needquotes)
+ {
+ appendPQExpBufferChar(buf, '\'');
+ while (*str)
+ {
+ /* ' and \ must be escaped by to \' and \\ */
+ if (*str == '\'' || *str == '\\')
+ appendPQExpBufferChar(buf, '\\');
+
+ appendPQExpBufferChar(buf, *str);
+ str++;
+ }
+ appendPQExpBufferChar(buf, '\'');
+ }
+ else
+ appendPQExpBufferStr(buf, str);
+}
+
+/*
+ * Adapted from: src/fe_utils/string_utils.c
+ */
+void
+appendShellString(PQExpBuffer buf, const char *str)
+{
+ const char *p;
+
+ appendPQExpBufferChar(buf, '\'');
+ for (p = str; *p; p++)
+ {
+ if (*p == '\n' || *p == '\r')
+ {
+ fprintf(stderr,
+ _("shell command argument contains a newline or carriage return: \"%s\"\n"),
+ str);
+ exit(ERR_BAD_CONFIG);
+ }
+
+ if (*p == '\'')
+ appendPQExpBufferStr(buf, "'\"'\"'");
+ else
+ appendPQExpBufferChar(buf, *p);
+ }
+
+ appendPQExpBufferChar(buf, '\'');
+}
+
diff --git a/compat.h b/compat.h
new file mode 100644
index 00000000..5d355af8
--- /dev/null
+++ b/compat.h
@@ -0,0 +1,32 @@
+/*
+ * compat.h
+ * Copyright (c) 2ndQuadrant, 2010-2017
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#ifndef _COMPAT_H_
+#define _COMPAT_H_
+
+extern void
+appendConnStrVal(PQExpBuffer buf, const char *str);
+
+extern void
+appendShellString(PQExpBuffer buf, const char *str);
+
+#endif
diff --git a/repmgr-action-standby.c b/repmgr-action-standby.c
index 26eec735..0345e25e 100644
--- a/repmgr-action-standby.c
+++ b/repmgr-action-standby.c
@@ -8,6 +8,7 @@
#include "repmgr.h"
#include "dirutil.h"
+#include "compat.h"
#include "repmgr-client-global.h"
#include "repmgr-action-standby.h"
@@ -33,8 +34,11 @@ static char local_repmgr_tmp_directory[MAXPGPATH];
static void check_barman_config(void);
-static char *make_barman_ssh_command(char *buf);
static void check_source_server(void);
+static void check_source_server_via_barman(void);
+
+static void get_barman_property(char *dst, char *name, char *local_repmgr_directory);
+static char *make_barman_ssh_command(char *buf);
void
@@ -152,7 +156,7 @@ do_standby_clone(void)
*/
if (*runtime_options.upstream_conninfo)
- runtime_options.no_upstream_connection = false;
+ runtime_options.no_upstream_connection = true;
/* By default attempt to connect to the source server */
if (runtime_options.no_upstream_connection == false)
@@ -160,6 +164,19 @@ do_standby_clone(void)
check_source_server();
}
+ if (mode == barman && PQstatus(source_conn) != CONNECTION_OK)
+ {
+ /*
+ * Here we don't have a connection to the upstream node, and are executing
+ * in Barman mode - we can try and connect via the Barman server to extract
+ * the upstream node's conninfo string.
+ *
+ * To do this we need to extract Barman's conninfo string, replace the database
+ * name with the repmgr one (they could well be different) and remotely execute
+ * psql.
+ */
+ check_source_server_via_barman();
+ }
}
@@ -242,24 +259,6 @@ check_barman_config(void)
}
-static char *
-make_barman_ssh_command(char *buf)
-{
- static char config_opt[MAXLEN] = "";
-
- if (strlen(config_file_options.barman_config))
- maxlen_snprintf(config_opt,
- " --config=%s",
- config_file_options.barman_config);
-
- maxlen_snprintf(buf,
- "ssh %s barman%s",
- config_file_options.barman_server,
- config_opt);
-
- return buf;
-}
-
static void
check_source_server()
@@ -267,7 +266,7 @@ check_source_server()
int server_version_num = UNKNOWN_SERVER_VERSION_NUM;
char cluster_size[MAXLEN];
t_node_info node_record = T_NODE_INFO_INITIALIZER;
- int query_result;
+ int query_result;
t_extension_status extension_status;
/* Attempt to connect to the upstream server to verify its configuration */
@@ -283,15 +282,12 @@ check_source_server()
*/
if (PQstatus(source_conn) != CONNECTION_OK)
{
+ PQfinish(source_conn);
+
if (mode == barman)
- {
return;
- }
else
- {
- PQfinish(source_conn);
exit(ERR_DB_CON);
- }
}
/*
@@ -301,7 +297,7 @@ check_source_server()
/* Verify that upstream node is a supported server version */
- log_verbose(LOG_INFO, _("connected to upstream node, checking its state"));
+ log_verbose(LOG_INFO, _("connected to source node, checking its state"));
server_version_num = check_server_version(source_conn, "master", true, NULL);
@@ -357,19 +353,19 @@ check_source_server()
if (!runtime_options.force)
{
/* schema doesn't exist */
- log_error(_("repmgr extension not found on upstream server"));
+ log_error(_("repmgr extension not found on source node"));
log_hint(_("check that the upstream server is part of a repmgr cluster"));
PQfinish(source_conn);
exit(ERR_BAD_CONFIG);
}
- log_warning(_("repmgr extension not found on upstream server"));
+ log_warning(_("repmgr extension not found on source node"));
}
/* Fetch the source's data directory */
if (get_pg_setting(source_conn, "data_directory", upstream_data_directory) == false)
{
- log_error(_("unable to retrieve upstream node's data directory"));
+ log_error(_("unable to retrieve source node's data directory"));
log_hint(_("STANDBY CLONE must be run as a database superuser"));
PQfinish(source_conn);
exit(ERR_BAD_CONFIG);
@@ -430,3 +426,147 @@ check_source_server()
}
+static void
+check_source_server_via_barman()
+{
+ char buf[MAXLEN];
+ char barman_conninfo_str[MAXLEN];
+ t_conninfo_param_list barman_conninfo;
+ char *errmsg = NULL;
+ bool parse_success,
+ command_success;
+ char where_condition[MAXLEN];
+ PQExpBufferData command_output;
+ PQExpBufferData repmgr_conninfo_buf;
+
+ int c;
+
+ get_barman_property(barman_conninfo_str, "conninfo", local_repmgr_tmp_directory);
+
+ initialize_conninfo_params(&barman_conninfo, false);
+
+ /* parse_conninfo_string() here will remove the upstream's `application_name`, if set */
+ parse_success = parse_conninfo_string(barman_conninfo_str, &barman_conninfo, errmsg, true);
+
+ if (parse_success == false)
+ {
+ log_error(_("Unable to parse barman conninfo string \"%s\":\n%s"),
+ barman_conninfo_str, errmsg);
+ exit(ERR_BARMAN);
+ }
+
+ /* Overwrite database name in the parsed parameter list */
+ param_set(&barman_conninfo, "dbname", runtime_options.dbname);
+
+ /* Rebuild the Barman conninfo string */
+ initPQExpBuffer(&repmgr_conninfo_buf);
+
+ for (c = 0; c < barman_conninfo.size && barman_conninfo.keywords[c] != NULL; c++)
+ {
+ if (repmgr_conninfo_buf.len != 0)
+ appendPQExpBufferChar(&repmgr_conninfo_buf, ' ');
+
+ appendPQExpBuffer(&repmgr_conninfo_buf, "%s=",
+ barman_conninfo.keywords[c]);
+ appendConnStrVal(&repmgr_conninfo_buf,
+ barman_conninfo.values[c]);
+ }
+
+ log_verbose(LOG_DEBUG,
+ "repmgr database conninfo string on barman server: %s",
+ repmgr_conninfo_buf.data);
+
+ switch(config_file_options.upstream_node_id)
+ {
+ case NO_UPSTREAM_NODE:
+ // XXX did we get the upstream node ID earlier?
+ maxlen_snprintf(where_condition, "type='master'");
+ break;
+ default:
+ maxlen_snprintf(where_condition, "node_id=%d", config_file_options.upstream_node_id);
+ break;
+ }
+
+ initPQExpBuffer(&command_output);
+ maxlen_snprintf(buf,
+ "ssh %s \"psql -Aqt \\\"%s\\\" -c \\\""
+ " SELECT conninfo"
+ " FROM repmgr.nodes"
+ " WHERE %s"
+ " AND active IS TRUE"
+ "\\\"\"",
+ config_file_options.barman_host,
+ repmgr_conninfo_buf.data,
+ where_condition);
+
+ termPQExpBuffer(&repmgr_conninfo_buf);
+
+ command_success = local_command(buf, &command_output);
+
+ if (command_success == false)
+ {
+ log_error(_("unable to execute database query via Barman server"));
+ exit(ERR_BARMAN);
+ }
+
+ maxlen_snprintf(recovery_conninfo_str, "%s", command_output.data);
+ string_remove_trailing_newlines(recovery_conninfo_str);
+
+ upstream_record_found = true;
+ log_verbose(LOG_DEBUG,
+ "upstream node conninfo string extracted via barman server: %s",
+ recovery_conninfo_str);
+
+ termPQExpBuffer(&command_output);
+}
+
+
+static char *
+make_barman_ssh_command(char *buf)
+{
+ static char config_opt[MAXLEN] = "";
+
+ if (strlen(config_file_options.barman_config))
+ maxlen_snprintf(config_opt,
+ " --config=%s",
+ config_file_options.barman_config);
+
+ maxlen_snprintf(buf,
+ "ssh %s barman%s",
+ config_file_options.barman_server,
+ config_opt);
+
+ return buf;
+}
+
+
+void
+get_barman_property(char *dst, char *name, char *local_repmgr_directory)
+{
+ PQExpBufferData command_output;
+ char buf[MAXLEN];
+ char command[MAXLEN];
+ char *p;
+
+ initPQExpBuffer(&command_output);
+
+ maxlen_snprintf(command,
+ "grep \"^\t%s:\" %s/show-server.txt",
+ name, local_repmgr_tmp_directory);
+ (void)local_command(command, &command_output);
+
+ maxlen_snprintf(buf, "\t%s: ", name);
+ p = string_skip_prefix(buf, command_output.data);
+ if (p == NULL)
+ {
+ log_error("unexpected output from Barman: %s",
+ command_output.data);
+ exit(ERR_INTERNAL);
+ }
+
+ strncpy(dst, p, MAXLEN);
+ string_remove_trailing_newlines(dst);
+
+ termPQExpBuffer(&command_output);
+}
+
diff --git a/strutil.c b/strutil.c
index 0e07d0cf..583731cb 100644
--- a/strutil.c
+++ b/strutil.c
@@ -159,3 +159,31 @@ escape_string(PGconn *conn, const char *string)
return escaped_string;
}
+
+
+
+char *
+string_skip_prefix(const char *prefix, char *string)
+{
+ int n;
+
+ n = strlen(prefix);
+
+ if (strncmp(prefix, string, n))
+ return NULL;
+ else
+ return string + n;
+}
+
+char *
+string_remove_trailing_newlines(char *string)
+{
+ int n;
+
+ n = strlen(string) - 1;
+
+ while (n >= 0 && string[n] == '\n')
+ string[n] = 0;
+
+ return string;
+}
diff --git a/strutil.h b/strutil.h
index ed739518..cfa2ea2b 100644
--- a/strutil.h
+++ b/strutil.h
@@ -57,5 +57,11 @@ extern void
append_where_clause(PQExpBufferData *where_clause, const char *clause, ...)
__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
+extern char *
+string_skip_prefix(const char *prefix, char *string);
+
+extern char
+*string_remove_trailing_newlines(char *string);
+
#endif /* _STRUTIL_H_ */