Files
repmgr/repmgr-client.c
2017-08-16 16:33:07 +09:00

3070 lines
74 KiB
C

/*
* repmgr-client.c - Command interpreter for the repmgr package
*
* Copyright (c) 2ndQuadrant, 2010-2017
*
* This module is a command-line utility to easily setup a cluster of
* hot standby servers for an HA environment
*
* Commands implemented are:
*
* [ PRIMARY | MASTER ] REGISTER
* [ PRIMARY | MASTER ] UNREGISTER
*
* STANDBY CLONE
* STANDBY REGISTER
* STANDBY UNREGISTER
* STANDBY PROMOTE
* STANDBY FOLLOW
* STANDBY SWITCHOVER
*
* BDR REGISTER
* BDR UNREGISTER
*
* CLUSTER SHOW
* CLUSTER EVENT
* CLUSTER CROSSCHECK
* CLUSTER MATRIX
*
* NODE STATUS
* NODE CHECK
*
* For internal use:
* NODE REJOIN
* NODE SERVICE
*/
#include <unistd.h>
#include <sys/stat.h>
#include "repmgr.h"
#include "compat.h"
#include "repmgr-client.h"
#include "repmgr-client-global.h"
#include "repmgr-action-primary.h"
#include "repmgr-action-standby.h"
#include "repmgr-action-bdr.h"
#include "repmgr-action-node.h"
#include "repmgr-action-cluster.h"
#include <storage/fd.h> /* for PG_TEMP_FILE_PREFIX */
/* globally available variables *
* ============================ */
t_runtime_options runtime_options = T_RUNTIME_OPTIONS_INITIALIZER;
t_configuration_options config_file_options = T_CONFIGURATION_OPTIONS_INITIALIZER;
/* conninfo params for the node we're operating on */
t_conninfo_param_list source_conninfo;
bool config_file_required = true;
char pg_bindir[MAXLEN] = "";
char path_buf[MAXLEN] = "";
/*
* if --node-id/--node-name provided, place that node's record here
* for later use
*/
t_node_info target_node_info = T_NODE_INFO_INITIALIZER;
/* Collate command line errors and warnings here for friendlier reporting */
static ItemList cli_errors = { NULL, NULL };
static ItemList cli_warnings = { NULL, NULL };
int
main(int argc, char **argv)
{
int optindex;
int c;
char *repmgr_command = NULL;
char *repmgr_action = NULL;
bool valid_repmgr_command_found = true;
int action = NO_ACTION;
char *dummy_action = "";
bool help_option = false;
set_progname(argv[0]);
/*
* Tell the logger we're a command-line program - this will
* ensure any output logged before the logger is initialized
* will be formatted correctly. Can be overriden with "--log-to-file".
*/
logger_output_mode = OM_COMMAND_LINE;
/*
* Initialize and pre-populate conninfo parameters; these will be
* overwritten if matching command line parameters are provided.
*
* Only some actions will need these, but we need to do this before
* the command line is parsed.
*
* Note: PQconndefaults() does not provide a default value for
* "dbname", but if none is provided will default to "username"
* when the connection is made.
*/
initialize_conninfo_params(&source_conninfo, false);
for (c = 0; c < source_conninfo.size && source_conninfo.keywords[c]; c++)
{
if (strcmp(source_conninfo.keywords[c], "host") == 0 &&
(source_conninfo.values[c] != NULL))
{
strncpy(runtime_options.host, source_conninfo.values[c], MAXLEN);
}
else if (strcmp(source_conninfo.keywords[c], "hostaddr") == 0 &&
(source_conninfo.values[c] != NULL))
{
strncpy(runtime_options.host, source_conninfo.values[c], MAXLEN);
}
else if (strcmp(source_conninfo.keywords[c], "port") == 0 &&
(source_conninfo.values[c] != NULL))
{
strncpy(runtime_options.port, source_conninfo.values[c], MAXLEN);
}
else if (strcmp(source_conninfo.keywords[c], "dbname") == 0 &&
(source_conninfo.values[c] != NULL))
{
strncpy(runtime_options.dbname, source_conninfo.values[c], MAXLEN);
}
else if (strcmp(source_conninfo.keywords[c], "user") == 0 &&
(source_conninfo.values[c] != NULL))
{
strncpy(runtime_options.username, source_conninfo.values[c], MAXLEN);
}
}
/* set default user for -R/--remote-user */
{
struct passwd *pw = NULL;
pw = getpwuid(geteuid());
if (pw == NULL)
{
fprintf(stderr, _("could not get current user name: %s\n"), strerror(errno));
exit(ERR_BAD_CONFIG);
}
strncpy(runtime_options.username, pw->pw_name, MAXLEN);
}
while ((c = getopt_long(argc, argv, "?Vb:f:Fd:h:p:U:R:S:L:vtD:crC:", long_options,
&optindex)) != -1)
{
/*
* NOTE: some integer parameters (e.g. -p/--port) are stored internally
* as strings. We use repmgr_atoi() to check these but discard the
* returned integer; repmgr_atoi() will append the error message to the
* provided list.
*/
switch (c)
{
/*
* Options which cause repmgr to exit in this block;
* these are the only ones which can be executed as root user
*/
case OPT_HELP: /* --help */
help_option = true;
break;
//do_help();
//exit(SUCCESS);
case '?':
/* Actual help option given */
if (strcmp(argv[optind - 1], "-?") == 0)
{
do_help();
exit(SUCCESS);
}
break;
case 'V':
/*
* in contrast to repmgr3 and earlier, we only display the repmgr version
* as it's not specific to a particular PostgreSQL version
*/
printf("%s %s\n", progname(), REPMGR_VERSION);
exit(SUCCESS);
/* general configuration options
* ----------------------------- */
/* -b/--pg_bindir */
case 'b':
strncpy(runtime_options.pg_bindir, optarg, MAXLEN);
break;
/* -f/--config-file */
case 'f':
strncpy(runtime_options.config_file, optarg, MAXLEN);
break;
/* --dry-run */
case OPT_DRY_RUN:
runtime_options.dry_run = true;
break;
/* -F/--force */
case 'F':
runtime_options.force = true;
break;
/* --replication-user (primary/standby register only) */
case OPT_REPLICATION_USER:
strncpy(runtime_options.replication_user, optarg, MAXLEN);
break;
/* -W/--wait */
case 'W':
runtime_options.wait = true;
break;
/* database connection options */
/* --------------------------- */
/*
* These are the standard database connection options; with the
* exception of -d/--dbname (which could be a conninfo string)
* we'll also set these values in "source_conninfo" (overwriting
* preset values from environment variables).
* XXX check this is same as psql
*/
/* -d/--dbname */
case 'd':
strncpy(runtime_options.dbname, optarg, MAXLEN);
/* dbname will be set in source_conninfo later after checking if it's a conninfo string */
runtime_options.connection_param_provided = true;
break;
/* -h/--host */
case 'h':
strncpy(runtime_options.host, optarg, MAXLEN);
param_set(&source_conninfo, "host", optarg);
runtime_options.connection_param_provided = true;
runtime_options.host_param_provided = true;
break;
case 'p':
(void) repmgr_atoi(optarg, "-p/--port", &cli_errors, false);
param_set(&source_conninfo, "port", optarg);
strncpy(runtime_options.port,
optarg,
MAXLEN);
runtime_options.connection_param_provided = true;
break;
/* -U/--user */
case 'U':
strncpy(runtime_options.username, optarg, MAXLEN);
param_set(&source_conninfo, "user", optarg);
runtime_options.connection_param_provided = true;
break;
/* other connection options */
/* ------------------------ */
/* -R/--remote_user */
case 'R':
strncpy(runtime_options.remote_user, optarg, MAXLEN);
break;
/* -S/--superuser */
case 'S':
strncpy(runtime_options.superuser, optarg, MAXLEN);
break;
/* node options *
* ------------ */
/* -D/--pgdata/--data-dir */
case 'D':
strncpy(runtime_options.data_dir, optarg, MAXPGPATH);
break;
/* --node-id */
case OPT_NODE_ID:
runtime_options.node_id = repmgr_atoi(optarg, "--node-id", &cli_errors, false);
break;
/* --node-name */
case OPT_NODE_NAME:
strncpy(runtime_options.node_name, optarg, MAXLEN);
break;
/* standby options *
* --------------- */
/* --upstream-node-id */
case OPT_UPSTREAM_NODE_ID:
runtime_options.upstream_node_id = repmgr_atoi(optarg, "--upstream-node-id", &cli_errors, false);
break;
/* "standby clone" options *
* ----------------------- */
/* -c/--fast-checkpoint */
case 'c':
runtime_options.fast_checkpoint = true;
break;
/* --copy-external-config-files(=[samepath|pgdata]) */
case OPT_COPY_EXTERNAL_CONFIG_FILES:
runtime_options.copy_external_config_files = true;
if (optarg != NULL)
{
if (strcmp(optarg, "samepath") == 0)
{
runtime_options.copy_external_config_files_destination = CONFIG_FILE_SAMEPATH;
}
/* allow "data_directory" as synonym for "pgdata" */
else if (strcmp(optarg, "pgdata") == 0 || strcmp(optarg, "data_directory") == 0)
{
runtime_options.copy_external_config_files_destination = CONFIG_FILE_PGDATA;
}
else
{
item_list_append(&cli_errors,
_("value provided for \"--copy-external-config-files\" must be \"samepath\" or \"pgdata\""));
}
}
break;
/* -w/--wal-keep-segments */
case 'w':
(void) repmgr_atoi(optarg, "-w/--wal-keep-segments", &cli_errors, false);
strncpy(runtime_options.wal_keep_segments,
optarg,
MAXLEN);
runtime_options.wal_keep_segments_used = true;
break;
/* --no-upstream-connection */
case OPT_NO_UPSTREAM_CONNECTION:
runtime_options.no_upstream_connection = true;
break;
/* --recovery-min-apply-delay */
case OPT_RECOVERY_MIN_APPLY_DELAY:
{
char *ptr = NULL;
int targ = strtol(optarg, &ptr, 10);
if (targ < 1)
{
item_list_append(&cli_errors, _("invalid value provided for '--recovery-min-apply-delay'"));
break;
}
if (ptr && *ptr)
{
if (strcmp(ptr, "ms") != 0 && strcmp(ptr, "s") != 0 &&
strcmp(ptr, "min") != 0 && strcmp(ptr, "h") != 0 &&
strcmp(ptr, "d") != 0)
{
item_list_append(&cli_errors,
_("value provided for '--recovery-min-apply-delay' must be one of ms/s/min/h/d"));
break;
}
}
strncpy(runtime_options.recovery_min_apply_delay, optarg, MAXLEN);
break;
}
case OPT_UPSTREAM_CONNINFO:
strncpy(runtime_options.upstream_conninfo, optarg, MAXLEN);
break;
case OPT_USE_RECOVERY_CONNINFO_PASSWORD:
runtime_options.use_recovery_conninfo_password = true;
break;
case OPT_WITHOUT_BARMAN:
runtime_options.without_barman = true;
break;
/* "standby register" options *
* -------------------------- */
case OPT_REGISTER_WAIT:
runtime_options.wait_register_sync = true;
if (optarg != NULL)
{
runtime_options.wait_register_sync_seconds = repmgr_atoi(optarg, "--wait-sync", &cli_errors, false);
}
break;
/* "standby switchover" options *
* ---------------------------- */
case OPT_ALWAYS_PROMOTE:
runtime_options.always_promote = true;
break;
case OPT_FORCE_REWIND:
runtime_options.force_rewind = true;
break;
case OPT_SIBLINGS_FOLLOW:
runtime_options.siblings_follow = true;
break;
/* "node status" options *
* --------------------- */
case OPT_IS_SHUTDOWN:
runtime_options.is_shutdown = true;
break;
/* "node check" options *
* --------------------- */
case OPT_ARCHIVER:
runtime_options.archiver = true;
break;
case OPT_DOWNSTREAM:
runtime_options.downstream = true;
break;
case OPT_REPLICATION_LAG:
runtime_options.replication_lag = true;
break;
case OPT_ROLE:
runtime_options.role = true;
break;
case OPT_SLOTS:
runtime_options.slots = true;
break;
/* "node join" options *
* ------------------- */
case OPT_CONFIG_FILES:
strncpy(runtime_options.config_files, optarg, MAXLEN);
break;
/* "node service" options *
* ---------------------- */
/* --action (repmgr node service --action) */
case OPT_ACTION:
strncpy(runtime_options.action, optarg, MAXLEN);
break;
case OPT_LIST_ACTIONS:
runtime_options.list_actions = true;
break;
case OPT_CHECK:
runtime_options.check = true;
break;
case OPT_CHECKPOINT:
runtime_options.checkpoint = true;
break;
/* "cluster event" options *
* ----------------------- */
case OPT_EVENT:
strncpy(runtime_options.event, optarg, MAXLEN);
break;
case OPT_LIMIT:
runtime_options.limit = repmgr_atoi(optarg, "--limit", &cli_errors, false);
runtime_options.limit_provided = true;
break;
case OPT_ALL:
runtime_options.all = true;
break;
/* logging options *
* --------------- */
/* -L/--log-level */
case 'L':
{
int detected_log_level = detect_log_level(optarg);
if (detected_log_level != -1)
{
strncpy(runtime_options.log_level, optarg, MAXLEN);
}
else
{
PQExpBufferData invalid_log_level;
initPQExpBuffer(&invalid_log_level);
appendPQExpBuffer(&invalid_log_level, _("invalid log level \"%s\" provided"), optarg);
item_list_append(&cli_errors, invalid_log_level.data);
termPQExpBuffer(&invalid_log_level);
}
break;
}
/* --log-to-file */
case OPT_LOG_TO_FILE:
runtime_options.log_to_file = true;
logger_output_mode = OM_DAEMON;
break;
/* --terse */
case 't':
runtime_options.terse = true;
break;
/* --verbose */
case 'v':
runtime_options.verbose = true;
break;
/* output options */
/* -------------- */
case OPT_CSV:
runtime_options.csv = true;
break;
case OPT_NAGIOS:
runtime_options.nagios = true;
break;
case OPT_OPTFORMAT:
runtime_options.optformat = true;
break;
/* internal options */
case OPT_CONFIG_ARCHIVE_DIR:
/* TODO: check this is an absolute path */
strncpy(runtime_options.config_archive_dir, optarg, MAXPGPATH);
break;
/* options deprecated since 3.3 *
* ---------------------------- */
case OPT_DATA_DIR:
item_list_append(&cli_warnings,
_("--data-dir is deprecated; use -D/--pgdata instead"));
break;
case OPT_NO_CONNINFO_PASSWORD:
item_list_append(&cli_warnings,
_("--no-conninfo-password is deprecated; pasuse --use-recovery-conninfo-password to explicitly set a password"));
break;
/* -C/--remote-config-file */
case 'C':
item_list_append(&cli_warnings,
_("--remote-config-file is no longer required"));
break;
}
}
/*
* If -d/--dbname appears to be a conninfo string, validate by attempting
* to parse it (and if successful, store the parsed parameters)
*/
if (runtime_options.dbname)
{
if (strncmp(runtime_options.dbname, "postgresql://", 13) == 0 ||
strncmp(runtime_options.dbname, "postgres://", 11) == 0 ||
strchr(runtime_options.dbname, '=') != NULL)
{
char *errmsg = NULL;
PQconninfoOption *opts;
runtime_options.conninfo_provided = true;
opts = PQconninfoParse(runtime_options.dbname, &errmsg);
if (opts == NULL)
{
PQExpBufferData conninfo_error;
initPQExpBuffer(&conninfo_error);
appendPQExpBuffer(&conninfo_error, _("error parsing conninfo:\n%s"), errmsg);
item_list_append(&cli_errors, conninfo_error.data);
termPQExpBuffer(&conninfo_error);
pfree(errmsg);
}
else
{
/*
* Store any parameters provided in the conninfo string in our
* internal array; also overwrite any options set in
* runtime_options.(host|port|username), as the conninfo
* settings take priority
*/
PQconninfoOption *opt;
for (opt = opts; opt->keyword != NULL; opt++)
{
if (opt->val != NULL && opt->val[0] != '\0')
{
param_set(&source_conninfo, opt->keyword, opt->val);
}
if (strcmp(opt->keyword, "host") == 0 &&
(opt->val != NULL && opt->val[0] != '\0'))
{
strncpy(runtime_options.host, opt->val, MAXLEN);
runtime_options.host_param_provided = true;
}
if (strcmp(opt->keyword, "hostaddr") == 0 &&
(opt->val != NULL && opt->val[0] != '\0'))
{
strncpy(runtime_options.host, opt->val, MAXLEN);
runtime_options.host_param_provided = true;
}
else if (strcmp(opt->keyword, "port") == 0 &&
(opt->val != NULL && opt->val[0] != '\0'))
{
strncpy(runtime_options.port, opt->val, MAXLEN);
}
else if (strcmp(opt->keyword, "user") == 0 &&
(opt->val != NULL && opt->val[0] != '\0'))
{
strncpy(runtime_options.username, opt->val, MAXLEN);
}
}
PQconninfoFree(opts);
}
}
else
{
param_set(&source_conninfo, "dbname", runtime_options.dbname);
}
}
/*
* Disallow further running as root to prevent directory ownership problems.
* We check this here to give the root user a chance to execute --help/--version
* options.
*/
if (geteuid() == 0 && help_option == false)
{
fprintf(stderr,
_("%s: cannot be run as root\n"
"Please log in (using, e.g., \"su\") as the "
"(unprivileged) user that owns "
"the data directory.\n"
),
progname());
free_conninfo_params(&source_conninfo);
exit(ERR_BAD_CONFIG);
}
/* Exit here already if errors in command line options found */
if (cli_errors.head != NULL)
{
free_conninfo_params(&source_conninfo);
exit_with_cli_errors(&cli_errors);
}
/*
* Determine the node type and action; following are valid:
*
* { PRIMARY | MASTER } REGISTER |
* STANDBY {REGISTER | UNREGISTER | CLONE [node] | PROMOTE | FOLLOW [node] | SWITCHOVER | REWIND} |
* BDR { REGISTER | UNREGISTER } |
* NODE { STATUS | CHECK | REJOIN | ARCHIVE-CONFIG | RESTORE-CONFIG | SERVICE } |
* CLUSTER { CROSSCHECK | MATRIX | SHOW | CLEANUP | EVENT }
*
* [node] is an optional hostname, provided instead of the -h/--host option
*/
if (optind < argc)
{
repmgr_command = argv[optind++];
}
if (optind < argc)
{
repmgr_action = argv[optind++];
}
else
{
repmgr_action = dummy_action;
}
if (repmgr_command != NULL)
{
#ifndef BDR_ONLY
if (strcasecmp(repmgr_command, "PRIMARY") == 0 || strcasecmp(repmgr_command, "MASTER") == 0 )
{
if (help_option == true)
{
do_primary_help();
exit(SUCCESS);
}
if (strcasecmp(repmgr_action, "REGISTER") == 0)
action = PRIMARY_REGISTER;
else if (strcasecmp(repmgr_action, "UNREGISTER") == 0)
action = PRIMARY_UNREGISTER;
else if (strcasecmp(repmgr_action, "CHECK") == 0)
action = NODE_CHECK;
else if (strcasecmp(repmgr_action, "STATUS") == 0)
action = NODE_STATUS;
}
else if (strcasecmp(repmgr_command, "STANDBY") == 0)
{
if (help_option == true)
{
do_standby_help();
exit(SUCCESS);
}
if (strcasecmp(repmgr_action, "CLONE") == 0)
action = STANDBY_CLONE;
else if (strcasecmp(repmgr_action, "REGISTER") == 0)
action = STANDBY_REGISTER;
else if (strcasecmp(repmgr_action, "UNREGISTER") == 0)
action = STANDBY_UNREGISTER;
else if (strcasecmp(repmgr_action, "PROMOTE") == 0)
action = STANDBY_PROMOTE;
else if (strcasecmp(repmgr_action, "FOLLOW") == 0)
action = STANDBY_FOLLOW;
else if (strcasecmp(repmgr_action, "SWITCHOVER") == 0)
action = STANDBY_SWITCHOVER;
else if (strcasecmp(repmgr_action, "CHECK") == 0)
action = NODE_CHECK;
else if (strcasecmp(repmgr_action, "STATUS") == 0)
action = NODE_STATUS;
}
else if (strcasecmp(repmgr_command, "BDR") == 0)
#else
if (strcasecmp(repmgr_command, "BDR") == 0)
#endif
{
if (help_option == true)
{
do_bdr_help();
exit(SUCCESS);
}
if (strcasecmp(repmgr_action, "REGISTER") == 0)
action = BDR_REGISTER;
else if (strcasecmp(repmgr_action, "UNREGISTER") == 0)
action = BDR_UNREGISTER;
else if (strcasecmp(repmgr_action, "CHECK") == 0)
action = NODE_CHECK;
else if (strcasecmp(repmgr_action, "STATUS") == 0)
action = NODE_STATUS;
}
else if (strcasecmp(repmgr_command, "NODE") == 0)
{
if (help_option == true)
{
do_node_help();
exit(SUCCESS);
}
if (strcasecmp(repmgr_action, "CHECK") == 0)
action = NODE_CHECK;
else if (strcasecmp(repmgr_action, "STATUS") == 0)
action = NODE_STATUS;
else if (strcasecmp(repmgr_action, "REJOIN") == 0)
action = NODE_REJOIN;
else if (strcasecmp(repmgr_action, "SERVICE") == 0)
action = NODE_SERVICE;
}
else if (strcasecmp(repmgr_command, "CLUSTER") == 0)
{
if (help_option == true)
{
do_cluster_help();
exit(SUCCESS);
}
if (strcasecmp(repmgr_action, "SHOW") == 0)
action = CLUSTER_SHOW;
else if (strcasecmp(repmgr_action, "EVENT") == 0)
action = CLUSTER_EVENT;
/* allow "CLUSTER EVENTS" as synonym for "CLUSTER EVENT" */
else if (strcasecmp(repmgr_action, "EVENTS") == 0)
action = CLUSTER_EVENT;
else if (strcasecmp(repmgr_action, "CROSSCHECK") == 0)
action = CLUSTER_CROSSCHECK;
else if (strcasecmp(repmgr_action, "MATRIX") == 0)
action = CLUSTER_MATRIX;
}
else
{
valid_repmgr_command_found = false;
}
}
if (help_option == true)
{
do_help();
exit(SUCCESS);
}
if (action == NO_ACTION)
{
PQExpBufferData command_error;
initPQExpBuffer(&command_error);
if (repmgr_command == NULL)
{
appendPQExpBuffer(&command_error,
_("no repmgr command provided"));
}
else if (valid_repmgr_command_found == false && repmgr_action[0] == '\0')
{
appendPQExpBuffer(&command_error,
_("unknown repmgr command '%s'"),
repmgr_command);
}
else if (repmgr_action[0] == '\0')
{
appendPQExpBuffer(&command_error,
_("no action provided for command '%s'"),
repmgr_command);
}
else
{
appendPQExpBuffer(&command_error,
_("unknown repmgr action '%s %s'"),
repmgr_command,
repmgr_action);
}
item_list_append(&cli_errors, command_error.data);
}
/* STANDBY CLONE historically accepts the upstream hostname as an additional argument */
if (action == STANDBY_CLONE)
{
if (optind < argc)
{
if (runtime_options.host_param_provided == true)
{
PQExpBufferData additional_host_arg;
initPQExpBuffer(&additional_host_arg);
appendPQExpBuffer(&additional_host_arg,
_("host name provided both with %s and as an extra parameter"),
runtime_options.conninfo_provided == true ? "host=" : "-h/--host");
item_list_append(&cli_errors, additional_host_arg.data);
}
else
{
strncpy(runtime_options.host, argv[optind++], MAXLEN);
param_set(&source_conninfo, "host", runtime_options.host);
runtime_options.host_param_provided = true;
}
}
}
if (optind < argc)
{
PQExpBufferData too_many_args;
initPQExpBuffer(&too_many_args);
appendPQExpBuffer(&too_many_args, _("too many command-line arguments (first extra is \"%s\")"), argv[optind]);
item_list_append(&cli_errors, too_many_args.data);
}
check_cli_parameters(action);
/*
* Sanity checks for command line parameters completed by now;
* any further errors will be runtime ones
*/
if (cli_errors.head != NULL)
{
free_conninfo_params(&source_conninfo);
exit_with_cli_errors(&cli_errors);
}
/*
* Print any warnings about inappropriate command line options,
* unless -t/--terse set
*/
if (cli_warnings.head != NULL && runtime_options.terse == false)
{
log_warning(_("following problems with command line parameters detected:"));
print_item_list(&cli_warnings);
}
/* post-processing following command line parameter checks
* ======================================================= */
if (runtime_options.csv == true)
{
runtime_options.output_mode = OM_CSV;
}
else if (runtime_options.nagios == true)
{
runtime_options.output_mode = OM_NAGIOS;
}
else if (runtime_options.optformat == true)
{
runtime_options.output_mode = OM_OPTFORMAT;
}
/*
* The configuration file is not required for some actions (e.g. 'standby clone'),
* however if available we'll parse it anyway for options like 'log_level',
* 'use_replication_slots' etc.
*/
load_config(runtime_options.config_file,
runtime_options.verbose,
runtime_options.terse,
&config_file_options,
argv[0]);
/* Some configuration file items can be overriden by command line options */
/* Command-line parameter -L/--log-level overrides any setting in config file*/
if (*runtime_options.log_level != '\0')
{
strncpy(config_file_options.log_level, runtime_options.log_level, MAXLEN);
}
/*
* Initialise pg_bindir - command line parameter will override
* any setting in the configuration file
*/
if (!strlen(runtime_options.pg_bindir))
{
strncpy(runtime_options.pg_bindir, config_file_options.pg_bindir, MAXLEN);
}
/* Add trailing slash */
if (strlen(runtime_options.pg_bindir))
{
int len = strlen(runtime_options.pg_bindir);
if (runtime_options.pg_bindir[len - 1] != '/')
{
maxlen_snprintf(pg_bindir, "%s/", runtime_options.pg_bindir);
}
else
{
strncpy(pg_bindir, runtime_options.pg_bindir, MAXLEN);
}
}
/*
* Initialize the logger. We've previously requested STDERR logging only
* to ensure the repmgr command doesn't have its output diverted to a logging
* facility (which usually doesn't make sense for a command line program).
*
* If required (e.g. when calling repmgr from repmgrd), this behaviour can be
* overridden with "--log-to-file".
*/
logger_init(&config_file_options, progname());
if (runtime_options.verbose)
logger_set_verbose();
if (runtime_options.terse)
logger_set_terse();
/*
* Node configuration information is not needed for all actions, with
* STANDBY CLONE being the main exception.
*/
if (config_file_required)
{
/*
* if a configuration file was provided, the configuration file parser
* will already have errored out if no valid node_id found
*/
if (config_file_options.node_id == NODE_NOT_FOUND)
{
free_conninfo_params(&source_conninfo);
log_error(_("no node information was found - please supply a configuration file"));
exit(ERR_BAD_CONFIG);
}
}
/*
* If a node was specified (by --node-id or --node-name), check it exists
* (and pre-populate a record for later use).
*
* At this point check_cli_parameters() will already have determined
* if provision of these is valid for the action, otherwise it unsets them.
*
* We need to check this much later than other command line parameters
* as we need to wait until the configuration file is parsed and we can
* obtain the conninfo string.
*/
if (runtime_options.node_id != UNKNOWN_NODE_ID || runtime_options.node_name[0] != '\0')
{
PGconn *conn = NULL;
RecordStatus record_status = RECORD_NOT_FOUND;
log_verbose(LOG_DEBUG, "connecting to local node to retrieve record for node specified with --node-id or --node-name");
if (strlen(config_file_options.conninfo))
conn = establish_db_connection(config_file_options.conninfo, true);
else
conn = establish_db_connection_by_params(&source_conninfo, true);
if (runtime_options.node_id != UNKNOWN_NODE_ID)
{
record_status = get_node_record(conn, runtime_options.node_id, &target_node_info);
if (record_status != RECORD_FOUND)
{
log_error(_("node %i (specified with --node-id) not found"),
runtime_options.node_id);
PQfinish(conn);
free_conninfo_params(&source_conninfo);
exit(ERR_BAD_CONFIG);
}
}
else if (runtime_options.node_name[0] != '\0')
{
char *escaped = escape_string(conn, runtime_options.node_name);
if (escaped == NULL)
{
log_error(_("unable to escape value provided for --node-name"));
PQfinish(conn);
free_conninfo_params(&source_conninfo);
exit(ERR_BAD_CONFIG);
}
record_status = get_node_record_by_name(conn, escaped, &target_node_info);
pfree(escaped);
if (record_status != RECORD_FOUND)
{
log_error(_("node %s (specified with --node-name) not found"),
runtime_options.node_name);
PQfinish(conn);
free_conninfo_params(&source_conninfo);
exit(ERR_BAD_CONFIG);
}
}
PQfinish(conn);
}
switch (action)
{
#ifndef BDR_ONLY
/* PRIMARY */
case PRIMARY_REGISTER:
do_primary_register();
break;
case PRIMARY_UNREGISTER:
do_primary_unregister();
break;
/* STANDBY */
case STANDBY_CLONE:
do_standby_clone();
break;
case STANDBY_REGISTER:
do_standby_register();
break;
case STANDBY_UNREGISTER:
do_standby_unregister();
break;
case STANDBY_PROMOTE:
do_standby_promote();
break;
case STANDBY_FOLLOW:
do_standby_follow();
break;
case STANDBY_SWITCHOVER:
do_standby_switchover();
break;
break;
#else
/* we won't ever reach here, but stop the compiler complaining */
case PRIMARY_REGISTER:
case PRIMARY_UNREGISTER:
case STANDBY_CLONE:
case STANDBY_REGISTER:
case STANDBY_UNREGISTER:
case STANDBY_PROMOTE:
case STANDBY_FOLLOW:
case STANDBY_SWITCHOVER:
break;
#endif
/* BDR */
case BDR_REGISTER:
do_bdr_register();
break;
case BDR_UNREGISTER:
do_bdr_unregister();
break;
/* NODE */
case NODE_STATUS:
do_node_status();
break;
case NODE_CHECK:
do_node_check();
break;
case NODE_REJOIN:
do_node_rejoin();
break;
case NODE_SERVICE:
do_node_service();
break;
/* CLUSTER */
case CLUSTER_SHOW:
do_cluster_show();
break;
case CLUSTER_EVENT:
do_cluster_event();
break;
case CLUSTER_CROSSCHECK:
do_cluster_crosscheck();
break;
case CLUSTER_MATRIX:
do_cluster_matrix();
break;
default:
/* An action will have been determined by this point */
break;
}
free_conninfo_params(&source_conninfo);
return SUCCESS;
}
/*
* Check for useless or conflicting parameters, and also whether a
* configuration file is required.
*
* Messages will be added to the command line warning and error lists
* as appropriate.
*
* XXX for each individual actions, check only required actions
* for non-required actions check warn if provided
*/
static void
check_cli_parameters(const int action)
{
/* ========================================================================
* check all parameters required for an action are provided, and warn
* about ineffective actions
* ========================================================================
*/
switch (action)
{
case PRIMARY_REGISTER:
/* no required parameters */
break;
case STANDBY_CLONE:
{
standy_clone_mode mode = get_standby_clone_mode();
config_file_required = false;
if (mode == barman)
{
if (runtime_options.copy_external_config_files)
{
item_list_append(&cli_warnings,
_("--copy-external-config-files ineffective in Barman mode"));
}
if (runtime_options.fast_checkpoint)
{
item_list_append(&cli_warnings,
_("-c/--fast-checkpoint has no effect in Barman mode"));
}
}
else
{
if (!runtime_options.host_param_provided)
{
item_list_append_format(&cli_errors,
_("host name for the source node must be provided when executing %s"),
action_name(action));
}
if (!runtime_options.connection_param_provided)
{
item_list_append_format(&cli_errors,
_("database connection parameters for the source node must be provided when executing %s"),
action_name(action));
}
// XXX if -D/--pgdata provided, and also config_file_options.pgdaga, warn -D/--pgdata will be ignored
if (*runtime_options.upstream_conninfo)
{
if (runtime_options.use_recovery_conninfo_password == true)
{
item_list_append(&cli_warnings,
_("--use-recovery-conninfo-password ineffective when specifying --upstream-conninfo"));
}
if (*runtime_options.replication_user)
{
item_list_append(&cli_warnings,
_("--replication-user ineffective when specifying --upstream-conninfo"));
}
}
if (runtime_options.no_upstream_connection == true)
{
item_list_append(&cli_warnings,
_("--no-upstream-connection only effective in Barman mode"));
}
}
}
break;
case STANDBY_FOLLOW:
{
/*
* if `repmgr standby follow` executed with host params, ensure data
* directory was provided
*/
}
break;
case NODE_REJOIN:
if (runtime_options.connection_param_provided == false)
{
item_list_append(
&cli_errors,
"database connection parameters for an available node must be provided when executing NODE REJOIN");
}
break;
case CLUSTER_SHOW:
case CLUSTER_MATRIX:
case CLUSTER_CROSSCHECK:
if (runtime_options.connection_param_provided)
config_file_required = false;
break;
case CLUSTER_EVENT:
/* no required parameters */
break;
}
/* ========================================================================
* warn if parameters provided for an action where they're not relevant
* ========================================================================
*/
/* --host etc.*/
if (runtime_options.connection_param_provided)
{
switch (action)
{
case STANDBY_CLONE:
case STANDBY_FOLLOW:
case CLUSTER_SHOW:
case CLUSTER_MATRIX:
case CLUSTER_CROSSCHECK:
break;
default:
item_list_append_format(&cli_warnings,
_("database connection parameters not required when executing %s"),
action_name(action));
}
}
/* -D/--pgdata */
if (runtime_options.data_dir[0])
{
switch (action)
{
case STANDBY_CLONE:
case STANDBY_FOLLOW:
case NODE_SERVICE:
break;
default:
item_list_append_format(&cli_warnings,
_("-D/--pgdata not required when executing %s"),
action_name(action));
}
}
/*
* --node-id
*
* NOTE: overrides --node-name, if present
*/
if (runtime_options.node_id != UNKNOWN_NODE_ID)
{
switch (action)
{
case PRIMARY_UNREGISTER:
case STANDBY_UNREGISTER:
case CLUSTER_EVENT:
case CLUSTER_MATRIX:
case CLUSTER_CROSSCHECK:
break;
default:
item_list_append_format(&cli_warnings,
_("--node-id not required when executing %s"),
action_name(action));
runtime_options.node_id = UNKNOWN_NODE_ID;
}
}
if (runtime_options.node_name[0])
{
switch (action)
{
case STANDBY_UNREGISTER:
case CLUSTER_EVENT:
if (runtime_options.node_id != UNKNOWN_NODE_ID)
{
item_list_append(&cli_warnings,
_("--node-id provided, ignoring --node-name"));
memset(runtime_options.node_name, 0, sizeof(runtime_options.node_name));
}
break;
default:
item_list_append_format(&cli_warnings,
_("--node-name not required when executing %s"),
action_name(action));
memset(runtime_options.node_name, 0, sizeof(runtime_options.node_name));
}
}
if (runtime_options.upstream_node_id != UNKNOWN_NODE_ID)
{
switch (action)
{
case STANDBY_CLONE:
case STANDBY_REGISTER:
break;
default:
item_list_append_format(&cli_warnings,
_("--upstream-node-id will be ignored when executing %s"),
action_name(action));
}
}
if (runtime_options.event[0])
{
switch (action)
{
case CLUSTER_EVENT:
break;
default:
item_list_append_format(&cli_warnings,
_("--event not required when executing %s"),
action_name(action));
}
}
if (runtime_options.replication_user[0])
{
switch (action)
{
case PRIMARY_REGISTER:
case STANDBY_REGISTER:
break;
case STANDBY_CLONE:
case STANDBY_FOLLOW:
item_list_append_format(&cli_warnings,
_("--replication-user ignored when executing %s)"),
action_name(action));
default:
item_list_append_format(&cli_warnings,
_("--replication-user not required when executing %s"),
action_name(action));
}
}
if (runtime_options.limit_provided)
{
switch (action)
{
case CLUSTER_EVENT:
if (runtime_options.limit < 1)
{
item_list_append_format(&cli_errors,
_("value for --limit must be 1 or greater (provided: %i)"),
runtime_options.limit);
}
break;
default:
item_list_append_format(&cli_warnings,
_("--limit not required when executing %s"),
action_name(action));
}
}
if (runtime_options.all)
{
switch (action)
{
case CLUSTER_EVENT:
if (runtime_options.limit_provided == true)
{
runtime_options.all = false;
item_list_append(&cli_warnings,
_("--limit provided, ignoring --all"));
}
break;
default:
item_list_append_format(&cli_warnings,
_("--all not required when executing %s"),
action_name(action));
}
}
/* repmgr node service --action */
if (runtime_options.action[0] != '\0')
{
switch (action)
{
case NODE_SERVICE:
break;
default:
item_list_append_format(&cli_warnings,
_("--action will be ignored when executing %s"),
action_name(action));
}
}
/* repmgr node status --is-shutdown */
if (runtime_options.is_shutdown == true)
{
switch (action)
{
case NODE_STATUS:
break;
default:
item_list_append_format(
&cli_warnings,
_("--is-shutdown will be ignored when executing %s"),
action_name(action));
}
}
if (runtime_options.always_promote == true)
{
switch (action)
{
case STANDBY_SWITCHOVER:
break;
default:
item_list_append_format(
&cli_warnings,
_("--always-promote will be ignored when executing %s"),
action_name(action));
}
}
if (runtime_options.force_rewind == true)
{
switch (action)
{
case STANDBY_SWITCHOVER:
case NODE_REJOIN:
break;
default:
item_list_append_format(
&cli_warnings,
_("--force-rewind will be ignored when executing %s"),
action_name(action));
}
}
if (runtime_options.config_files[0] != '\0')
{
switch (action)
{
case NODE_REJOIN:
break;
default:
item_list_append_format(
&cli_warnings,
_("--config-files will be ignored when executing %s"),
action_name(action));
}
}
/* check only one of --csv, --nagios and --optformat used */
{
int used_options = 0;
if (runtime_options.csv == true)
used_options ++;
if (runtime_options.nagios == true)
used_options ++;
if (runtime_options.optformat == true)
used_options ++;
if (used_options > 1)
{
/* TODO: list which options were used */
item_list_append(
&cli_errors,
"only one of --csv, --nagios and --optformat can be used");
}
}
}
static const char*
action_name(const int action)
{
switch(action)
{
case PRIMARY_REGISTER:
return "PRIMARY REGISTER";
case PRIMARY_UNREGISTER:
return "PRIMARY UNREGISTER";
case STANDBY_CLONE:
return "STANDBY CLONE";
case STANDBY_REGISTER:
return "STANDBY REGISTER";
case STANDBY_UNREGISTER:
return "STANDBY UNREGISTER";
case STANDBY_PROMOTE:
return "STANDBY PROMOTE";
case STANDBY_FOLLOW:
return "STANDBY FOLLOW";
case BDR_REGISTER:
return "BDR REGISTER";
case BDR_UNREGISTER:
return "BDR UNREGISTER";
case NODE_STATUS:
return "NODE STATUS";
case NODE_CHECK:
return "NODE CHECK";
case NODE_REJOIN:
return "NODE REJOIN";
case NODE_SERVICE:
return "NODE SERVICE";
case CLUSTER_SHOW:
return "CLUSTER SHOW";
case CLUSTER_EVENT:
return "CLUSTER EVENT";
case CLUSTER_MATRIX:
return "CLUSTER MATRIX";
case CLUSTER_CROSSCHECK:
return "CLUSTER CROSSCHECK";
}
return "UNKNOWN ACTION";
}
void
print_error_list(ItemList *error_list, int log_level)
{
ItemListCell *cell = NULL;
for (cell = error_list->head; cell; cell = cell->next)
{
switch(log_level)
{
/* Currently we only need errors and warnings */
case LOG_ERROR:
log_error("%s", cell->string);
break;
case LOG_WARNING:
log_warning("%s", cell->string);
break;
}
}
}
void
print_help_header(void)
{
printf(_("%s: replication management tool for PostgreSQL\n"), progname());
puts("");
/* add a big friendly warning if root is executing "repmgr --help" */
if (geteuid() == 0)
{
printf(_(" **************************************************\n"));
printf(_(" *** repmgr must be executed by a non-superuser ***\n"));
printf(_(" **************************************************\n"));
puts("");
}
}
static void
do_help(void)
{
print_help_header();
printf(_("Usage:\n"));
#ifndef BDR_ONLY
printf(_(" %s [OPTIONS] primary {register|unregister}\n"), progname());
printf(_(" %s [OPTIONS] standby {register|unregister|clone|promote|follow}\n"), progname());
#endif
printf(_(" %s [OPTIONS] bdr {register|unregister}\n"), progname());
printf(_(" %s [OPTIONS] node status\n"), progname());
printf(_(" %s [OPTIONS] cluster {show|event|matrix|crosscheck}\n"), progname());
puts("");
printf(_(" Execute \"%s {primary|standby|bdr|node|cluster} --help\" to see command-specific options\n"), progname());
puts("");
printf(_("General options:\n"));
printf(_(" -?, --help show this help, then exit\n"));
printf(_(" -V, --version output version information, then exit\n"));
puts("");
printf(_("General configuration options:\n"));
printf(_(" -b, --pg_bindir=PATH path to PostgreSQL binaries (optional)\n"));
printf(_(" -f, --config-file=PATH path to the repmgr configuration file\n"));
printf(_(" -F, --force force potentially dangerous operations to happen\n"));
puts("");
printf(_("Connection options:\n"));
printf(_(" -S, --superuser=USERNAME superuser to use if repmgr user is not superuser\n"));
puts("");
printf(_("Node-specific options:\n"));
printf(_(" -D, --pgdata=DIR location of the node's data directory \n"));
printf(_(" --node-id specify a node by id (only available for some operations)\n"));
printf(_(" --node-name specify a node by name (only available for some operations)\n"));
puts("");
printf(_("Logging options:\n"));
printf(_(" --dry-run show what would happen for action, but don't execute it\n"));
printf(_(" -L, --log-level set log level (overrides configuration file; default: NOTICE)\n"));
printf(_(" --log-to-file log to file (or logging facility) defined in repmgr.conf\n"));
printf(_(" -t, --terse don't display detail, hints and other non-critical output\n"));
printf(_(" -v, --verbose display additional log output (useful for debugging)\n"));
puts("");
}
/*
* Create the repmgr extension, and grant access for the repmgr
* user if not a superuser.
*
* Note:
* This is one of two places where superuser rights are required.
* We should also consider possible scenarious where a non-superuser
* has sufficient privileges to install the extension.
*/
bool
create_repmgr_extension(PGconn *conn)
{
PQExpBufferData query;
PGresult *res;
ExtensionStatus extension_status = REPMGR_UNKNOWN;
t_connection_user userinfo = T_CONNECTION_USER_INITIALIZER;
bool is_superuser = false;
PGconn *superuser_conn = NULL;
PGconn *schema_create_conn = NULL;
extension_status = get_repmgr_extension_status(conn);
switch(extension_status)
{
case REPMGR_UNKNOWN:
log_error(_("unable to determine status of \"repmgr\" extension"));
return false;
case REPMGR_UNAVAILABLE:
log_error(_("\"repmgr\" extension is not available"));
return false;
case REPMGR_INSTALLED:
/* TODO: check version */
log_info(_("\"repmgr\" extension is already installed"));
return true;
case REPMGR_AVAILABLE:
if (runtime_options.dry_run == true)
{
log_notice(_("would now attempt to install extension \"repmgr\""));
}
else
{
log_notice(_("attempting to install extension \"repmgr\""));
}
break;
}
/* 3. Attempt to get a superuser connection */
is_superuser = is_superuser_connection(conn, &userinfo);
get_superuser_connection(&conn, &superuser_conn, &schema_create_conn);
if (runtime_options.dry_run == true)
return true;
/* 4. Create extension */
initPQExpBuffer(&query);
wrap_ddl_query(&query, config_file_options.replication_type,
"CREATE EXTENSION repmgr");
res = PQexec(schema_create_conn, query.data);
termPQExpBuffer(&query);
if ((PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK))
{
log_error(_("unable to create \"repmgr\" extension:\n %s"),
PQerrorMessage(schema_create_conn));
log_hint(_("check that the provided user has sufficient privileges for CREATE EXTENSION"));
PQclear(res);
if (superuser_conn != NULL)
PQfinish(superuser_conn);
return false;
}
PQclear(res);
/* 5. If not superuser, grant usage */
if (is_superuser == false)
{
initPQExpBuffer(&query);
wrap_ddl_query(&query, config_file_options.replication_type,
"GRANT USAGE ON SCHEMA repmgr TO %s",
userinfo.username);
res = PQexec(schema_create_conn, query.data);
termPQExpBuffer(&query);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
log_error(_("unable to grant usage on \"repmgr\" extension to %s:\n %s"),
userinfo.username,
PQerrorMessage(schema_create_conn));
PQclear(res);
if (superuser_conn != 0)
PQfinish(superuser_conn);
return false;
}
initPQExpBuffer(&query);
wrap_ddl_query(&query, config_file_options.replication_type,
"GRANT ALL ON ALL TABLES IN SCHEMA repmgr TO %s",
userinfo.username);
res = PQexec(schema_create_conn, query.data);
termPQExpBuffer(&query);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
log_error(_("unable to grant permission on tables on \"repmgr\" extension to %s:\n %s"),
userinfo.username,
PQerrorMessage(schema_create_conn));
PQclear(res);
if (superuser_conn != NULL)
PQfinish(superuser_conn);
return false;
}
}
if (superuser_conn != NULL)
PQfinish(superuser_conn);
log_notice(_("\"repmgr\" extension successfully installed"));
create_event_notification(conn,
&config_file_options,
config_file_options.node_id,
"cluster_created",
true,
NULL);
return true;
}
/**
* check_server_version()
*
* Verify that the server is MIN_SUPPORTED_VERSION_NUM or later
*
* PGconn *conn:
* the connection to check
*
* char *server_type:
* either "primary" or "standby"; used to format error message
*
* bool exit_on_error:
* exit if reported server version is too low; optional to enable some callers
* to perform additional cleanup
*
* char *server_version_string
* passed to get_server_version(), which will place the human-readable
* server version string there (e.g. "9.4.0")
*/
int
check_server_version(PGconn *conn, char *server_type, bool exit_on_error, char *server_version_string)
{
int conn_server_version_num = UNKNOWN_SERVER_VERSION_NUM;
conn_server_version_num = get_server_version(conn, server_version_string);
if (conn_server_version_num < MIN_SUPPORTED_VERSION_NUM)
{
if (conn_server_version_num > 0)
log_error(_("%s requires %s to be PostgreSQL %s or later"),
progname(),
server_type,
MIN_SUPPORTED_VERSION
);
if (exit_on_error == true)
{
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
return -1;
}
return conn_server_version_num;
}
int
test_ssh_connection(char *host, char *remote_user)
{
char script[MAXLEN] = "";
int r = 1, i;
/* On some OS, true is located in a different place than in Linux
* we have to try them all until all alternatives are gone or we
* found `true' because the target OS may differ from the source
* OS
*/
const char *bin_true_paths[] = {
"/bin/true",
"/usr/bin/true",
NULL
};
for (i = 0; bin_true_paths[i] && r != 0; ++i)
{
if (!remote_user[0])
maxlen_snprintf(script, "ssh -o Batchmode=yes %s %s %s 2>/dev/null",
config_file_options.ssh_options, host, bin_true_paths[i]);
else
maxlen_snprintf(script, "ssh -o Batchmode=yes %s %s -l %s %s 2>/dev/null",
config_file_options.ssh_options, host, remote_user,
bin_true_paths[i]);
log_verbose(LOG_DEBUG, _("test_ssh_connection(): executing %s"), script);
r = system(script);
}
if (r != 0)
log_warning(_("unable to connect to remote host '%s' via SSH"), host);
return r;
}
/*
* Execute a command locally. "outputbuf" should either be an
* initialised PQexpbuffer, or NULL
*/
bool
local_command(const char *command, PQExpBufferData *outputbuf)
{
FILE *fp;
char output[MAXLEN];
int retval = 0;
if (outputbuf == NULL)
{
retval = system(command);
return (retval == 0) ? true : false;
}
fp = popen(command, "r");
if (fp == NULL)
{
log_error(_("unable to execute local command:\n%s"), command);
return false;
}
/* TODO: better error handling */
while (fgets(output, MAXLEN, fp) != NULL)
{
appendPQExpBuffer(outputbuf, "%s", output);
}
pclose(fp);
if (outputbuf->data != NULL)
log_verbose(LOG_DEBUG, "local_command(): output returned was:\n%s", outputbuf->data);
else
log_verbose(LOG_DEBUG, "local_command(): no output returned");
return true;
}
void
get_superuser_connection(PGconn **conn, PGconn **superuser_conn, PGconn **privileged_conn)
{
t_connection_user userinfo = T_CONNECTION_USER_INITIALIZER;
bool is_superuser = false;
/* this should never happen */
if (PQstatus(*conn) != CONNECTION_OK)
{
log_error(_("no database connection available"));
exit(ERR_INTERNAL);
}
is_superuser = is_superuser_connection(*conn, &userinfo);
if (is_superuser == true)
{
*privileged_conn = *conn;
return;
}
if (runtime_options.superuser[0] == '\0')
{
log_error(_("\"%s\" is not a superuser and no superuser name supplied"), userinfo.username);
log_hint(_("supply a valid superuser name with -S/--superuser"));
PQfinish(*conn);
exit(ERR_BAD_CONFIG);
}
*superuser_conn = establish_db_connection_as_user(config_file_options.conninfo,
runtime_options.superuser,
false);
if (PQstatus(*superuser_conn) != CONNECTION_OK)
{
log_error(_("unable to establish superuser connection as \"%s\""),
runtime_options.superuser);
PQfinish(*conn);
exit(ERR_BAD_CONFIG);
}
/* check provided superuser really is superuser */
if (!is_superuser_connection(*superuser_conn, NULL))
{
log_error(_("\"%s\" is not a superuser"), runtime_options.superuser);
PQfinish(*superuser_conn);
PQfinish(*conn);
exit(ERR_BAD_CONFIG);
}
*privileged_conn = *superuser_conn;
return;
}
/*
* check_upstream_config()
*
* Perform sanity check on upstream server configuration before starting cloning
* process
*
* TODO:
* - check user is qualified to perform base backup
*/
bool
check_upstream_config(PGconn *conn, int server_version_num, bool exit_on_error)
{
int i;
bool config_ok = true;
char *wal_error_message = NULL;
t_basebackup_options backup_options = T_BASEBACKUP_OPTIONS_INITIALIZER;
bool backup_options_ok = true;
ItemList backup_option_errors = { NULL, NULL };
bool xlog_stream = true;
standy_clone_mode mode;
/*
* Detecting the intended cloning mode
*/
mode = get_standby_clone_mode();
/*
* Parse `pg_basebackup_options`, if set, to detect whether --xlog-method
* has been set to something other than `stream` (i.e. `fetch`), as
* this will influence some checks
*/
backup_options_ok = parse_pg_basebackup_options(
config_file_options.pg_basebackup_options,
&backup_options, server_version_num,
&backup_option_errors);
if (backup_options_ok == false)
{
if (exit_on_error == true)
{
log_error(_("error(s) encountered parsing 'pg_basebackup_options'"));
print_error_list(&backup_option_errors, LOG_ERR);
log_hint(_("'pg_basebackup_options' is: '%s'"),
config_file_options.pg_basebackup_options);
exit(ERR_BAD_CONFIG);
}
config_ok = false;
}
if (strlen(backup_options.xlog_method) && strcmp(backup_options.xlog_method, "stream") != 0)
xlog_stream = false;
/* Check that WAL level is set correctly */
{
char *levels_pre96[] = {
"hot_standby",
"logical",
NULL,
};
/*
* Note that in 9.6+, "hot_standby" and "archive" are accepted as aliases
* for "replica", but current_setting() will of course always return "replica"
*/
char *levels_96plus[] = {
"replica",
"logical",
NULL,
};
char **levels;
int j = 0;
if (server_version_num < 90600)
{
levels = (char **)levels_pre96;
wal_error_message = _("parameter 'wal_level' must be set to 'hot_standby' or 'logical'");
}
else
{
levels = (char **)levels_96plus;
wal_error_message = _("parameter 'wal_level' must be set to 'replica' or 'logical'");
}
do
{
i = guc_set(conn, "wal_level", "=", levels[j]);
if (i)
{
break;
}
j++;
} while (levels[j] != NULL);
}
if (i == 0 || i == -1)
{
if (i == 0)
{
log_error("%s", wal_error_message);
}
if (exit_on_error == true)
{
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
config_ok = false;
}
if (config_file_options.use_replication_slots)
{
i = guc_set_typed(conn, "max_replication_slots", ">",
"0", "integer");
if (i == 0 || i == -1)
{
if (i == 0)
{
log_error(_("parameter \"max_replication_slots\" must be set to at least 1 to enable replication slots"));
log_hint(_("\"max_replication_slots\" should be set to at least the number of expected standbys"));
if (exit_on_error == true)
{
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
config_ok = false;
}
}
}
/*
* physical replication slots not available or not requested - check if
* there are any circumstances where `wal_keep_segments` should be set
*/
else if (mode != barman)
{
bool check_wal_keep_segments = false;
char min_wal_keep_segments[MAXLEN] = "1";
/*
* -w/--wal-keep-segments was supplied - check against that value
*/
if (runtime_options.wal_keep_segments_used == true)
{
check_wal_keep_segments = true;
strncpy(min_wal_keep_segments, runtime_options.wal_keep_segments, MAXLEN);
}
/*
* A non-zero `wal_keep_segments` value will almost certainly be required
* if pg_basebackup is being used with --xlog-method=fetch,
* *and* no restore command has been specified
*/
else if (xlog_stream == false
&& strcmp(config_file_options.restore_command, "") == 0)
{
check_wal_keep_segments = true;
}
if (check_wal_keep_segments == true)
{
i = guc_set_typed(conn, "wal_keep_segments", ">=", min_wal_keep_segments, "integer");
if (i == 0 || i == -1)
{
if (i == 0)
{
log_error(_("parameter \"wal_keep_segments\" on the upstream server must be be set to %s or greater"),
min_wal_keep_segments);
log_hint(_("Choose a value sufficiently high enough to retain enough WAL "
"until the standby has been cloned and started.\n "
"Alternatively set up WAL archiving using e.g. PgBarman and configure "
"'restore_command' in repmgr.conf to fetch WALs from there."));
if (server_version_num >= 90400)
{
log_hint(_("In PostgreSQL 9.4 and later, replication slots can be used, which "
"do not require \"wal_keep_segments\" to be set "
"(set parameter \"use_replication_slots\" in repmgr.conf to enable)\n"
));
}
}
if (exit_on_error == true)
{
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
config_ok = false;
}
}
}
/*
* If archive_mode is enabled, check that 'archive_command' is non empty
* (however it's not practical to check that it actually represents a valid
* command).
*
* From PostgreSQL 9.5, archive_mode can be one of 'off', 'on' or 'always'
* so for ease of backwards compatibility, rather than explicitly check for an
* enabled mode, check that it's not "off".
*/
if (guc_set(conn, "archive_mode", "!=", "off"))
{
i = guc_set(conn, "archive_command", "!=", "");
if (i == 0 || i == -1)
{
if (i == 0)
log_error(_("parameter \"archive_command\" must be set to a valid command"));
if (exit_on_error == true)
{
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
config_ok = false;
}
}
/*
* Check that 'hot_standby' is on. This isn't strictly necessary
* for the primary server, however the assumption is that we'll be
* cloning standbys and thus copying the primary configuration;
* this way the standby will be correctly configured by default.
*/
i = guc_set(conn, "hot_standby", "=", "on");
if (i == 0 || i == -1)
{
if (i == 0)
{
log_error(_("parameter 'hot_standby' must be set to 'on'"));
}
if (exit_on_error == true)
{
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
config_ok = false;
}
i = guc_set_typed(conn, "max_wal_senders", ">", "0", "integer");
if (i == 0 || i == -1)
{
if (i == 0)
{
log_error(_("parameter \"max_wal_senders\" must be set to be at least 1"));
log_hint(_("\"max_wal_senders\" should be set to at least the number of expected standbys"));
}
if (exit_on_error == true)
{
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
config_ok = false;
}
/*
* If using pg_basebackup, ensure sufficient replication connections can be made.
* There's no guarantee they'll still be available by the time pg_basebackup
* is executed, but there's nothing we can do about that.
*/
if (mode == pg_basebackup)
{
PGconn **connections;
int i;
int min_replication_connections = 1,
possible_replication_connections = 0;
t_conninfo_param_list repl_conninfo;
/* Make a copy of the connection parameter arrays, and append "replication" */
initialize_conninfo_params(&repl_conninfo, false);
conn_to_param_list(conn, &repl_conninfo);
param_set(&repl_conninfo, "replication", "1");
if (*runtime_options.replication_user)
param_set(&repl_conninfo, "user", runtime_options.replication_user);
/*
* work out how many replication connections are required (1 or 2)
*/
if (xlog_stream == true)
min_replication_connections += 1;
log_verbose(LOG_NOTICE, "checking for available walsenders on upstream node (%i required)",
min_replication_connections);
connections = pg_malloc0(sizeof(PGconn *) * min_replication_connections);
/* Attempt to create the minimum number of required concurrent connections */
for (i = 0; i < min_replication_connections; i++)
{
PGconn *replication_conn;
replication_conn = establish_db_connection_by_params(&repl_conninfo, false);
if (PQstatus(replication_conn) == CONNECTION_OK)
{
connections[i] = replication_conn;
possible_replication_connections++;
}
}
/* Close previously created connections */
for (i = 0; i < possible_replication_connections; i++)
{
PQfinish(connections[i]);
}
pfree(connections);
free_conninfo_params(&repl_conninfo);
if (possible_replication_connections < min_replication_connections)
{
config_ok = false;
/*
* XXX at this point we could check current_setting('max_wal_senders) - COUNT(*) FROM pg_stat_replication;
* if >= min_replication_connections we could infer possible authentication error.
*
* Alternatively call PQconnectStart() and poll for presence/absence of CONNECTION_AUTH_OK ?
*/
log_error(_("unable to establish necessary replication connections"));
log_hint(_("increase \"max_wal_senders\" by at least %i"),
min_replication_connections - possible_replication_connections);
if (exit_on_error == true)
{
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
}
log_verbose(LOG_INFO, "sufficient walsenders available on upstream node (%i required)",
min_replication_connections);
}
return config_ok;
}
standy_clone_mode
get_standby_clone_mode(void)
{
standy_clone_mode mode;
if (strcmp(config_file_options.barman_host, "") != 0 && ! runtime_options.without_barman)
mode = barman;
else
mode = pg_basebackup;
return mode;
}
char *
make_pg_path(const char *file)
{
maxlen_snprintf(path_buf, "%s%s", pg_bindir, file);
return path_buf;
}
int
copy_remote_files(char *host, char *remote_user, char *remote_path,
char *local_path, bool is_directory, int server_version_num)
{
PQExpBufferData rsync_flags;
char script[MAXLEN] = "";
char host_string[MAXLEN] = "";
int r = 0;
initPQExpBuffer(&rsync_flags);
if (*config_file_options.rsync_options == '\0')
{
appendPQExpBuffer(&rsync_flags, "%s",
"--archive --checksum --compress --progress --rsh=ssh");
}
else
{
appendPQExpBuffer(&rsync_flags, "%s",
config_file_options.rsync_options);
}
if (runtime_options.force)
{
appendPQExpBuffer(&rsync_flags, "%s",
" --delete --checksum");
}
if (!remote_user[0])
{
maxlen_snprintf(host_string, "%s", host);
}
else
{
maxlen_snprintf(host_string, "%s@%s", remote_user, host);
}
/*
* When copying the main PGDATA directory, certain files and contents
* of certain directories need to be excluded.
*
* See function 'sendDir()' in 'src/backend/replication/basebackup.c' -
* we're basically simulating what pg_basebackup does, but with rsync rather
* than the BASEBACKUP replication protocol command.
*
* *However* currently we'll always copy the contents of the 'pg_replslot'
* directory and delete later if appropriate.
*/
if (is_directory)
{
/* Files which we don't want */
appendPQExpBuffer(&rsync_flags, "%s",
" --exclude=postmaster.pid --exclude=postmaster.opts --exclude=global/pg_control");
appendPQExpBuffer(&rsync_flags, "%s",
" --exclude=recovery.conf --exclude=recovery.done");
if (server_version_num >= 90400)
{
/*
* Ideally we'd use PG_AUTOCONF_FILENAME from utils/guc.h, but
* that has too many dependencies for a mere client program.
*/
appendPQExpBuffer(&rsync_flags, "%s",
" --exclude=postgresql.auto.conf.tmp");
}
/* Temporary files which we don't want, if they exist */
appendPQExpBuffer(&rsync_flags, " --exclude=%s*",
PG_TEMP_FILE_PREFIX);
/* Directories which we don't want */
if (server_version_num >= 100000)
{
appendPQExpBuffer(&rsync_flags, "%s",
" --exclude=pg_wal/*");
}
else
{
appendPQExpBuffer(&rsync_flags, "%s",
" --exclude=pg_xlog/*");
}
appendPQExpBuffer(&rsync_flags, "%s",
" --exclude=pg_log/* --exclude=pg_stat_tmp/*");
maxlen_snprintf(script, "rsync %s %s:%s/* %s",
rsync_flags.data, host_string, remote_path, local_path);
}
else
{
maxlen_snprintf(script, "rsync %s %s:%s %s",
rsync_flags.data, host_string, remote_path, local_path);
}
log_info(_("rsync command line: '%s'"), script);
r = system(script);
log_debug("copy_remote_files(): r = %i; WIFEXITED: %i; WEXITSTATUS: %i", r, WIFEXITED(r), WEXITSTATUS(r));
/* exit code 24 indicates vanished files, which isn't a problem for us */
if (WIFEXITED(r) && WEXITSTATUS(r) && WEXITSTATUS(r) != 24)
log_verbose(LOG_WARNING, "copy_remote_files(): rsync returned unexpected exit status %i", WEXITSTATUS(r));
return r;
}
/*
* Creates a recovery.conf file for a standby
*
* A database connection pointer is required for escaping primary_conninfo
* parameters. When cloning from Barman and --no-upstream-connection ) this
* might not be available.
*/
bool
create_recovery_file(t_node_info *node_record, t_conninfo_param_list *recovery_conninfo, const char *data_dir)
{
FILE *recovery_file;
char recovery_file_path[MAXPGPATH] = "";
char line[MAXLEN] = "";
mode_t um;
maxpath_snprintf(recovery_file_path, "%s/%s", data_dir, RECOVERY_COMMAND_FILE);
/* Set umask to 0600 */
um = umask((~(S_IRUSR | S_IWUSR)) & (S_IRWXG | S_IRWXO));
recovery_file = fopen(recovery_file_path, "w");
umask(um);
if (recovery_file == NULL)
{
log_error(_("unable to create recovery.conf file at \"%s\""),
recovery_file_path);
return false;
}
log_debug("create_recovery_file(): creating \"%s\"...",
recovery_file_path);
/* standby_mode = 'on' */
maxlen_snprintf(line, "standby_mode = 'on'\n");
if (write_recovery_file_line(recovery_file, recovery_file_path, line) == false)
return false;
log_debug("recovery.conf: %s", line);
/* primary_conninfo = '...' */
/*
* the user specified --upstream-conninfo string - copy that
*/
if (strlen(runtime_options.upstream_conninfo))
{
char *escaped = escape_recovery_conf_value(runtime_options.upstream_conninfo);
maxlen_snprintf(line, "primary_conninfo = '%s'\n",
escaped);
free(escaped);
}
/*
* otherwise use the conninfo inferred from the upstream connection
* and/or node record
*/
else
{
write_primary_conninfo(line, recovery_conninfo);
}
if (write_recovery_file_line(recovery_file, recovery_file_path, line) == false)
return false;
log_debug("recovery.conf: %s", line);
/* recovery_target_timeline = 'latest' */
maxlen_snprintf(line, "recovery_target_timeline = 'latest'\n");
if (write_recovery_file_line(recovery_file, recovery_file_path, line) == false)
return false;
log_debug("recovery.conf: %s", line);
/* recovery_min_apply_delay = ... (optional) */
if (*runtime_options.recovery_min_apply_delay)
{
maxlen_snprintf(line, "recovery_min_apply_delay = %s\n",
runtime_options.recovery_min_apply_delay);
if (write_recovery_file_line(recovery_file, recovery_file_path, line) == false)
return false;
log_debug("recovery.conf: %s", line);
}
/* primary_slot_name = '...' (optional, for 9.4 and later) */
if (config_file_options.use_replication_slots)
{
maxlen_snprintf(line, "primary_slot_name = %s\n",
node_record->slot_name);
if (write_recovery_file_line(recovery_file, recovery_file_path, line) == false)
return false;
log_debug("recovery.conf: %s", line);
}
/* If restore_command is set, we use it as restore_command in recovery.conf */
if (strcmp(config_file_options.restore_command, "") != 0)
{
maxlen_snprintf(line, "restore_command = '%s'\n",
config_file_options.restore_command);
if (write_recovery_file_line(recovery_file, recovery_file_path, line) == false)
return false;
log_debug("recovery.conf: %s", line);
}
fclose(recovery_file);
return true;
}
static bool
write_recovery_file_line(FILE *recovery_file, char *recovery_file_path, char *line)
{
if (fputs(line, recovery_file) == EOF)
{
log_error(_("unable to write to recovery file at \"%s\""), recovery_file_path);
fclose(recovery_file);
return false;
}
return true;
}
static void
write_primary_conninfo(char *line, t_conninfo_param_list *param_list)
{
PQExpBufferData conninfo_buf;
bool application_name_provided = false;
int c;
char *escaped = NULL;
initPQExpBuffer(&conninfo_buf);
for (c = 0; c < param_list->size && param_list->keywords[c] != NULL; c++)
{
/*
* Skip empty settings and ones which don't make any sense in
* recovery.conf
*/
if (strcmp(param_list->keywords[c], "dbname") == 0 ||
strcmp(param_list->keywords[c], "replication") == 0 ||
(param_list->values[c] == NULL) ||
(param_list->values[c] != NULL && param_list->values[c][0] == '\0'))
continue;
/* only include "password" if explicitly requested */
if (runtime_options.use_recovery_conninfo_password == false &&
strcmp(param_list->keywords[c], "password") == 0)
continue;
if (conninfo_buf.len != 0)
appendPQExpBufferChar(&conninfo_buf, ' ');
if (strcmp(param_list->keywords[c], "application_name") == 0)
application_name_provided = true;
appendPQExpBuffer(&conninfo_buf, "%s=", param_list->keywords[c]);
appendConnStrVal(&conninfo_buf, param_list->values[c]);
}
/* `application_name` not provided - default to repmgr node name */
if (application_name_provided == false)
{
if (strlen(config_file_options.node_name))
{
appendPQExpBuffer(&conninfo_buf, " application_name=");
appendConnStrVal(&conninfo_buf, config_file_options.node_name);
}
else
{
appendPQExpBuffer(&conninfo_buf, " application_name=repmgr");
}
}
escaped = escape_recovery_conf_value(conninfo_buf.data);
maxlen_snprintf(line, "primary_conninfo = '%s'\n", escaped);
free(escaped);
termPQExpBuffer(&conninfo_buf);
}
/*
* Execute a command via ssh on the remote host.
*
* TODO: implement SSH calls using libssh2.
*/
bool
remote_command(const char *host, const char *user, const char *command, PQExpBufferData *outputbuf)
{
FILE *fp;
char ssh_command[MAXLEN] = "";
PQExpBufferData ssh_host;
char output[MAXLEN] = "";
initPQExpBuffer(&ssh_host);
if (*user != '\0')
{
appendPQExpBuffer(&ssh_host, "%s@", user);
}
appendPQExpBuffer(&ssh_host, "%s", host);
maxlen_snprintf(ssh_command,
"ssh -o Batchmode=yes %s %s %s",
config_file_options.ssh_options,
ssh_host.data,
command);
termPQExpBuffer(&ssh_host);
log_debug("remote_command():\n %s", ssh_command);
fp = popen(ssh_command, "r");
if (fp == NULL)
{
log_error(_("unable to execute remote command:\n %s"), ssh_command);
return false;
}
if (outputbuf != NULL)
{
/* TODO: better error handling */
while (fgets(output, MAXLEN, fp) != NULL)
{
appendPQExpBuffer(outputbuf, "%s", output);
}
}
else
{
/*
* When executed remotely, repmgr commands which execute pg_ctl (particularly
* `repmgr standby follow`) will see the pg_ctl command appear to fail with a
* non-zero return code when the output from the executed pg_ctl command
* has nowhere to go, even though the command actually succeeds. We'll consume an
* arbitrary amount of output and throw it away to work around this.
*/
int i = 0;
while (fgets(output, MAXLEN, fp) != NULL && i < 10)
{
i++;
}
}
pclose(fp);
if (outputbuf != NULL)
log_verbose(LOG_DEBUG, "remote_command(): output returned was:\n %s", outputbuf->data);
return true;
}
void
make_remote_repmgr_path(PQExpBufferData *output_buf, t_node_info *remote_node_record)
{
appendPQExpBuffer(output_buf,
"%s -f %s ",
make_pg_path("repmgr"),
remote_node_record->config_file);
}
/* ======================== */
/* server control functions */
/* ======================== */
void
get_server_action(t_server_action action, char *script, char *data_dir)
{
PQExpBufferData command;
if (data_dir == NULL || data_dir[0] == '\0')
data_dir = "(none provided)";
switch(action)
{
case ACTION_NONE:
script[0] = '\0';
return;
case ACTION_START:
{
if (config_file_options.service_start_command[0] != '\0')
{
maxlen_snprintf(script, "%s",
config_file_options.service_start_command);
}
else
{
initPQExpBuffer(&command);
appendPQExpBuffer(
&command,
"%s %s -w -D ",
make_pg_path("pg_ctl"),
config_file_options.pg_ctl_options);
appendShellString(
&command,
data_dir);
appendPQExpBuffer(
&command,
" start");
strncpy(script, command.data, MAXLEN);
termPQExpBuffer(&command);
}
return;
}
case ACTION_STOP:
{
if (config_file_options.service_stop_command[0] != '\0')
{
maxlen_snprintf(script, "%s",
config_file_options.service_stop_command);
}
else
{
initPQExpBuffer(&command);
appendPQExpBuffer(
&command,
"%s %s -D ",
make_pg_path("pg_ctl"),
config_file_options.pg_ctl_options);
appendShellString(
&command,
data_dir);
appendPQExpBuffer(
&command,
" -m fast -W stop");
strncpy(script, command.data, MAXLEN);
termPQExpBuffer(&command);
}
return;
}
case ACTION_RESTART:
{
if (config_file_options.service_restart_command[0] != '\0')
{
maxlen_snprintf(script, "%s",
config_file_options.service_restart_command);
}
else
{
initPQExpBuffer(&command);
appendPQExpBuffer(
&command,
"%s %s -w -D ",
make_pg_path("pg_ctl"),
config_file_options.pg_ctl_options);
appendShellString(
&command,
data_dir);
appendPQExpBuffer(
&command,
" restart");
strncpy(script, command.data, MAXLEN);
termPQExpBuffer(&command);
}
return;
}
case ACTION_RELOAD:
{
if (config_file_options.service_reload_command[0] != '\0')
{
maxlen_snprintf(script, "%s",
config_file_options.service_reload_command);
}
else
{
initPQExpBuffer(&command);
appendPQExpBuffer(
&command,
"%s %s -w -D ",
make_pg_path("pg_ctl"),
config_file_options.pg_ctl_options);
appendShellString(
&command,
data_dir);
appendPQExpBuffer(
&command,
" reload");
strncpy(script, command.data, MAXLEN);
termPQExpBuffer(&command);
}
return;
}
case ACTION_PROMOTE:
{
if (config_file_options.service_promote_command[0] != '\0')
{
maxlen_snprintf(script, "%s",
config_file_options.service_promote_command);
}
else
{
initPQExpBuffer(&command);
appendPQExpBuffer(
&command,
"%s %s -w -D ",
make_pg_path("pg_ctl"),
config_file_options.pg_ctl_options);
appendShellString(
&command,
data_dir);
appendPQExpBuffer(
&command,
" promote");
strncpy(script, command.data, MAXLEN);
termPQExpBuffer(&command);
}
return;
}
default:
return;
}
return;
}
bool
data_dir_required_for_action(t_server_action action)
{
switch(action)
{
case ACTION_NONE:
return false;
case ACTION_START:
if (config_file_options.service_start_command[0] != '\0')
{
return false;
}
return true;
case ACTION_STOP:
if (config_file_options.service_stop_command[0] != '\0')
{
return false;
}
return true;
case ACTION_RESTART:
if (config_file_options.service_restart_command[0] != '\0')
{
return false;
}
return true;
case ACTION_RELOAD:
if (config_file_options.service_reload_command[0] != '\0')
{
return false;
}
return true;
case ACTION_PROMOTE:
if (config_file_options.service_promote_command[0] != '\0')
{
return false;
}
return true;
default:
return false;
}
return false;
}
void
get_node_data_directory(char *data_dir_buf)
{
/*
* the configuration file setting has priority, and will always be
* set when a configuration file was provided
*/
if (config_file_options.data_directory[0] != '\0')
{
strncpy(data_dir_buf, config_file_options.data_directory, MAXPGPATH);
return;
}
if (runtime_options.data_dir[0] != '\0')
{
strncpy(data_dir_buf, runtime_options.data_dir, MAXPGPATH);
return;
}
return;
}
/*
* initialise a node record from the provided configuration
* parameters
*/
void
init_node_record(t_node_info *node_record)
{
node_record->node_id = config_file_options.node_id;
node_record->upstream_node_id = runtime_options.upstream_node_id;
node_record->priority = config_file_options.priority;
node_record->active = true;
strncpy(node_record->location, config_file_options.location, MAXLEN);
strncpy(node_record->node_name, config_file_options.node_name, MAXLEN);
strncpy(node_record->conninfo, config_file_options.conninfo, MAXLEN);
strncpy(node_record->config_file, config_file_path, MAXLEN);
if (config_file_options.replication_user[0] != '\0')
{
/* replication user explicitly provided */
strncpy(node_record->repluser, config_file_options.replication_user, NAMEDATALEN);
}
else
{
/* use the "user" value from "conninfo" */
(void)get_conninfo_value(config_file_options.conninfo, "user", node_record->repluser);
}
if (config_file_options.use_replication_slots == true)
{
maxlen_snprintf(node_record->slot_name, "repmgr_slot_%i", config_file_options.node_id);
}
}