mirror of
https://github.com/EnterpriseDB/repmgr.git
synced 2026-03-23 07:06:30 +00:00
Rather than parse the configuration file into a new structure and copy changed values from that into the main structure, we'll copy the existing structure before parsing the changed configuration file directly into the nmain structure, and revert using the copy if any issues are encountered. This is necessary as preparation for further reworking of the configuration file structure handling. It also makes the reload idempotent. While we're at it, make some general improvements to the reload handling, particularly: - improve logging to show "before" and "after" values - collate change notifications and only display if no errors were found - remove unnecessary double-logging of errors - various bugfixes
2369 lines
69 KiB
C
2369 lines
69 KiB
C
/*
|
|
* config.c - parse repmgr.conf and other configuration-related functionality
|
|
*
|
|
* Copyright (c) 2ndQuadrant, 2010-2020
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <sys/stat.h> /* for stat() */
|
|
|
|
#include "repmgr.h"
|
|
#include "configfile.h"
|
|
#include "log.h"
|
|
|
|
#include <utils/elog.h>
|
|
|
|
#if (PG_ACTUAL_VERSION_NUM >= 100000)
|
|
#include <storage/fd.h> /* for durable_rename() */
|
|
#endif
|
|
|
|
const static char *_progname = NULL;
|
|
char config_file_path[MAXPGPATH] = "";
|
|
static bool config_file_provided = false;
|
|
bool config_file_found = false;
|
|
t_configuration_options config_file_options;
|
|
|
|
static void parse_config(bool terse);
|
|
static void _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *warning_list);
|
|
|
|
static void _parse_line(char *buf, char *name, char *value);
|
|
static void parse_event_notifications_list(t_configuration_options *options, const char *arg);
|
|
static void clear_event_notification_list(t_configuration_options *options);
|
|
|
|
static void parse_time_unit_parameter(const char *name, const char *value, char *dest, ItemList *errors);
|
|
|
|
static void copy_config_file_options(t_configuration_options *original, t_configuration_options *copy);
|
|
|
|
static void tablespace_list_append(t_configuration_options *options, const char *arg);
|
|
static void tablespace_list_copy(t_configuration_options *original, t_configuration_options *copy);
|
|
static void tablespace_list_free(t_configuration_options *options);
|
|
|
|
static void exit_with_config_file_errors(ItemList *config_errors, ItemList *config_warnings, bool terse);
|
|
|
|
|
|
void
|
|
set_progname(const char *argv0)
|
|
{
|
|
_progname = get_progname(argv0);
|
|
}
|
|
|
|
const char *
|
|
progname(void)
|
|
{
|
|
return _progname;
|
|
}
|
|
|
|
void
|
|
load_config(const char *config_file, bool verbose, bool terse, t_configuration_options *options, char *argv0)
|
|
{
|
|
struct stat stat_config;
|
|
|
|
/*
|
|
* If a configuration file was provided, check it exists, otherwise emit
|
|
* an error and terminate. We assume that if a user explicitly provides a
|
|
* configuration file, they'll want to make sure it's used and not fall
|
|
* back to any of the defaults.
|
|
*/
|
|
if (config_file != NULL && config_file[0] != '\0')
|
|
{
|
|
strncpy(config_file_path, config_file, MAXPGPATH);
|
|
canonicalize_path(config_file_path);
|
|
|
|
/* relative path supplied - convert to absolute path */
|
|
if (config_file_path[0] != '/')
|
|
{
|
|
PQExpBufferData fullpath;
|
|
char *pwd = NULL;
|
|
|
|
initPQExpBuffer(&fullpath);
|
|
|
|
/*
|
|
* we'll attempt to use $PWD to derive the effective path; getcwd()
|
|
* will likely resolve symlinks, which may result in a path which
|
|
* isn't permanent (e.g. if filesystem mountpoints change).
|
|
*/
|
|
pwd = getenv("PWD");
|
|
|
|
if (pwd != NULL)
|
|
{
|
|
appendPQExpBufferStr(&fullpath, pwd);
|
|
}
|
|
else
|
|
{
|
|
/* $PWD not available - fall back to getcwd() */
|
|
char cwd[MAXPGPATH] = "";
|
|
|
|
if (getcwd(cwd, MAXPGPATH) == NULL)
|
|
{
|
|
log_error(_("unable to execute getcwd()"));
|
|
log_detail("%s", strerror(errno));
|
|
|
|
termPQExpBuffer(&fullpath);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
appendPQExpBufferStr(&fullpath, cwd);
|
|
}
|
|
|
|
appendPQExpBuffer(&fullpath,
|
|
"/%s", config_file_path);
|
|
|
|
log_debug("relative configuration file converted to:\n \"%s\"",
|
|
fullpath.data);
|
|
|
|
strncpy(config_file_path, fullpath.data, MAXPGPATH);
|
|
|
|
termPQExpBuffer(&fullpath);
|
|
|
|
canonicalize_path(config_file_path);
|
|
}
|
|
|
|
|
|
if (stat(config_file_path, &stat_config) != 0)
|
|
{
|
|
log_error(_("provided configuration file \"%s\" not found"),
|
|
config_file);
|
|
log_detail("%s", strerror(errno));
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
|
|
if (verbose == true)
|
|
{
|
|
log_notice(_("using provided configuration file \"%s\""), config_file);
|
|
}
|
|
|
|
config_file_provided = true;
|
|
config_file_found = true;
|
|
}
|
|
|
|
|
|
/*-----------
|
|
* If no configuration file was provided, attempt to find a default file
|
|
* in this order:
|
|
* - location provided by packager
|
|
* - current directory
|
|
* - /etc/repmgr.conf
|
|
* - default sysconfdir
|
|
*
|
|
* here we just check for the existence of the file; parse_config() will
|
|
* handle read errors etc.
|
|
*
|
|
*-----------
|
|
*/
|
|
if (config_file_provided == false)
|
|
{
|
|
/* packagers: if feasible, patch configuration file path into "package_conf_file" */
|
|
char package_conf_file[MAXPGPATH] = "";
|
|
char my_exec_path[MAXPGPATH] = "";
|
|
char sysconf_etc_path[MAXPGPATH] = "";
|
|
|
|
/* 1. location provided by packager */
|
|
if (package_conf_file[0] != '\0')
|
|
{
|
|
if (verbose == true)
|
|
fprintf(stdout, _("INFO: checking for package configuration file \"%s\"\n"), package_conf_file);
|
|
|
|
if (stat(package_conf_file, &stat_config) == 0)
|
|
{
|
|
strncpy(config_file_path, package_conf_file, MAXPGPATH);
|
|
config_file_found = true;
|
|
goto end_search;
|
|
}
|
|
}
|
|
|
|
/* 2 "./repmgr.conf" */
|
|
log_verbose(LOG_INFO, _("looking for configuration file in current directory\n"));
|
|
|
|
maxpath_snprintf(config_file_path, "./%s", CONFIG_FILE_NAME);
|
|
canonicalize_path(config_file_path);
|
|
|
|
if (stat(config_file_path, &stat_config) == 0)
|
|
{
|
|
config_file_found = true;
|
|
goto end_search;
|
|
}
|
|
|
|
/* 3. "/etc/repmgr.conf" */
|
|
if (verbose == true)
|
|
fprintf(stdout, _("INFO: looking for configuration file in /etc\n"));
|
|
|
|
maxpath_snprintf(config_file_path, "/etc/%s", CONFIG_FILE_NAME);
|
|
if (stat(config_file_path, &stat_config) == 0)
|
|
{
|
|
config_file_found = true;
|
|
goto end_search;
|
|
}
|
|
|
|
/* 4. default sysconfdir */
|
|
if (find_my_exec(argv0, my_exec_path) < 0)
|
|
{
|
|
fprintf(stderr, _("ERROR: %s: could not find own program executable\n"), argv0);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
get_etc_path(my_exec_path, sysconf_etc_path);
|
|
|
|
if (verbose == true)
|
|
fprintf(stdout, _("INFO: looking for configuration file in \"%s\"\n"), sysconf_etc_path);
|
|
|
|
maxpath_snprintf(config_file_path, "%s/%s", sysconf_etc_path, CONFIG_FILE_NAME);
|
|
if (stat(config_file_path, &stat_config) == 0)
|
|
{
|
|
config_file_found = true;
|
|
goto end_search;
|
|
}
|
|
|
|
end_search:
|
|
if (verbose == true)
|
|
{
|
|
if (config_file_found == true)
|
|
{
|
|
fprintf(stdout, _("INFO: configuration file found at: \"%s\"\n"), config_file_path);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stdout, _("INFO: no configuration file provided or found\n"));
|
|
}
|
|
}
|
|
}
|
|
|
|
parse_config(terse);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
static void
|
|
parse_config(bool terse)
|
|
{
|
|
/* Collate configuration file errors here for friendlier reporting */
|
|
static ItemList config_errors = {NULL, NULL};
|
|
static ItemList config_warnings = {NULL, NULL};
|
|
|
|
_parse_config(&config_file_options, &config_errors, &config_warnings);
|
|
|
|
/* errors found - exit after printing details, and any warnings */
|
|
if (config_errors.head != NULL)
|
|
{
|
|
exit_with_config_file_errors(&config_errors, &config_warnings, terse);
|
|
}
|
|
|
|
if (terse == false && config_warnings.head != NULL)
|
|
{
|
|
log_warning(_("the following problems were found in the configuration file:"));
|
|
|
|
print_item_list(&config_warnings);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This creates a parsed representation of the configuration file in a location provided
|
|
* by the caller.
|
|
*/
|
|
static void
|
|
_parse_config(t_configuration_options *options, ItemList *error_list, ItemList *warning_list)
|
|
{
|
|
FILE *fp;
|
|
|
|
/*
|
|
* Clear lists pointing to allocated memory
|
|
*/
|
|
|
|
clear_event_notification_list(options);
|
|
tablespace_list_free(options);
|
|
|
|
/* Initialize configuration options with sensible defaults */
|
|
|
|
/*-----------------
|
|
* node information
|
|
*-----------------
|
|
*/
|
|
options->node_id = UNKNOWN_NODE_ID;
|
|
memset(options->node_name, 0, sizeof(options->node_name));
|
|
memset(options->conninfo, 0, sizeof(options->conninfo));
|
|
memset(options->replication_user, 0, sizeof(options->replication_user));
|
|
memset(options->data_directory, 0, sizeof(options->data_directory));
|
|
memset(options->config_directory, 0, sizeof(options->data_directory));
|
|
memset(options->pg_bindir, 0, sizeof(options->pg_bindir));
|
|
memset(options->repmgr_bindir, 0, sizeof(options->repmgr_bindir));
|
|
options->replication_type = REPLICATION_TYPE_PHYSICAL;
|
|
|
|
/*-------------
|
|
* log settings
|
|
*
|
|
* NOTE: the default for "log_level" is set in log.c and does not need
|
|
* to be initialised here
|
|
*-------------
|
|
*/
|
|
memset(options->log_facility, 0, sizeof(options->log_facility));
|
|
memset(options->log_file, 0, sizeof(options->log_file));
|
|
options->log_status_interval = DEFAULT_LOG_STATUS_INTERVAL;
|
|
|
|
/*-----------------------
|
|
* standby clone settings
|
|
*------------------------
|
|
*/
|
|
options->use_replication_slots = false;
|
|
memset(options->pg_basebackup_options, 0, sizeof(options->pg_basebackup_options));
|
|
memset(options->restore_command, 0, sizeof(options->restore_command));
|
|
options->tablespace_mapping.head = NULL;
|
|
options->tablespace_mapping.tail = NULL;
|
|
memset(options->recovery_min_apply_delay, 0, sizeof(options->recovery_min_apply_delay));
|
|
options->recovery_min_apply_delay_provided = false;
|
|
memset(options->archive_cleanup_command, 0, sizeof(options->archive_cleanup_command));
|
|
options->use_primary_conninfo_password = false;
|
|
memset(options->passfile, 0, sizeof(options->passfile));
|
|
|
|
/*-------------------------
|
|
* standby promote settings
|
|
*-------------------------
|
|
*/
|
|
options->promote_check_timeout = DEFAULT_PROMOTE_CHECK_TIMEOUT;
|
|
options->promote_check_interval = DEFAULT_PROMOTE_CHECK_INTERVAL;
|
|
|
|
/*------------------------
|
|
* standby follow settings
|
|
*------------------------
|
|
*/
|
|
options->primary_follow_timeout = DEFAULT_PRIMARY_FOLLOW_TIMEOUT;
|
|
options->standby_follow_timeout = DEFAULT_STANDBY_FOLLOW_TIMEOUT;
|
|
|
|
/*------------------------
|
|
* standby switchover settings
|
|
*------------------------
|
|
*/
|
|
options->shutdown_check_timeout = DEFAULT_SHUTDOWN_CHECK_TIMEOUT;
|
|
options->standby_reconnect_timeout = DEFAULT_STANDBY_RECONNECT_TIMEOUT;
|
|
options->wal_receive_check_timeout = DEFAULT_WAL_RECEIVE_CHECK_TIMEOUT;
|
|
|
|
/*------------------------
|
|
* node rejoin settings
|
|
*------------------------
|
|
*/
|
|
|
|
options->node_rejoin_timeout = DEFAULT_NODE_REJOIN_TIMEOUT;
|
|
|
|
/*------------------------
|
|
* node check settings
|
|
*------------------------
|
|
*/
|
|
options->archive_ready_warning = DEFAULT_ARCHIVE_READY_WARNING;
|
|
options->archive_ready_critical = DEFAULT_ARCHIVE_READY_CRITICAL;
|
|
options->replication_lag_warning = DEFAULT_REPLICATION_LAG_WARNING;
|
|
options->replication_lag_critical = DEFAULT_REPLICATION_LAG_CRITICAL;
|
|
|
|
/*-------------
|
|
* witness settings
|
|
*-------------
|
|
*/
|
|
options->witness_sync_interval = DEFAULT_WITNESS_SYNC_INTERVAL;
|
|
|
|
/*-----------------
|
|
* repmgrd settings
|
|
*-----------------
|
|
*/
|
|
options->failover = FAILOVER_MANUAL;
|
|
memset(options->location, 0, sizeof(options->location));
|
|
strncpy(options->location, DEFAULT_LOCATION, sizeof(options->location));
|
|
options->priority = DEFAULT_PRIORITY;
|
|
memset(options->promote_command, 0, sizeof(options->promote_command));
|
|
memset(options->follow_command, 0, sizeof(options->follow_command));
|
|
options->monitor_interval_secs = DEFAULT_MONITORING_INTERVAL;
|
|
/* default to 6 reconnection attempts at intervals of 10 seconds */
|
|
options->reconnect_attempts = DEFAULT_RECONNECTION_ATTEMPTS;
|
|
options->reconnect_interval = DEFAULT_RECONNECTION_INTERVAL;
|
|
options->monitoring_history = false;
|
|
options->degraded_monitoring_timeout = -1;
|
|
options->async_query_timeout = DEFAULT_ASYNC_QUERY_TIMEOUT;
|
|
options->primary_notification_timeout = DEFAULT_PRIMARY_NOTIFICATION_TIMEOUT;
|
|
options->repmgrd_standby_startup_timeout = -1; /* defaults to "standby_reconnect_timeout" if not set */
|
|
memset(options->repmgrd_pid_file, 0, sizeof(options->repmgrd_pid_file));
|
|
options->standby_disconnect_on_failover = false;
|
|
options->sibling_nodes_disconnect_timeout = DEFAULT_SIBLING_NODES_DISCONNECT_TIMEOUT;
|
|
options->connection_check_type = CHECK_PING;
|
|
options->primary_visibility_consensus = false;
|
|
memset(options->failover_validation_command, 0, sizeof(options->failover_validation_command));
|
|
options->election_rerun_interval = DEFAULT_ELECTION_RERUN_INTERVAL;
|
|
|
|
options->child_nodes_check_interval = DEFAULT_CHILD_NODES_CHECK_INTERVAL;
|
|
options->child_nodes_disconnect_min_count = DEFAULT_CHILD_NODES_DISCONNECT_MIN_COUNT;
|
|
options->child_nodes_connected_min_count = DEFAULT_CHILD_NODES_CONNECTED_MIN_COUNT;
|
|
options->child_nodes_connected_include_witness = DEFAULT_CHILD_NODES_CONNECTED_INCLUDE_WITNESS;
|
|
options->child_nodes_disconnect_timeout = DEFAULT_CHILD_NODES_DISCONNECT_TIMEOUT;
|
|
memset(options->child_nodes_disconnect_command, 0, sizeof(options->child_nodes_disconnect_command));
|
|
|
|
/*-------------------------
|
|
* service command settings
|
|
*-------------------------
|
|
*/
|
|
memset(options->pg_ctl_options, 0, sizeof(options->pg_ctl_options));
|
|
memset(options->service_start_command, 0, sizeof(options->service_start_command));
|
|
memset(options->service_stop_command, 0, sizeof(options->service_stop_command));
|
|
memset(options->service_restart_command, 0, sizeof(options->service_restart_command));
|
|
memset(options->service_reload_command, 0, sizeof(options->service_reload_command));
|
|
memset(options->service_promote_command, 0, sizeof(options->service_promote_command));
|
|
|
|
/*---------------------------------
|
|
* repmgrd service command settings
|
|
*---------------------------------
|
|
*/
|
|
memset(options->repmgrd_service_start_command, 0, sizeof(options->repmgrd_service_start_command));
|
|
memset(options->repmgrd_service_stop_command, 0, sizeof(options->repmgrd_service_stop_command));
|
|
|
|
/*----------------------------
|
|
* event notification settings
|
|
*----------------------------
|
|
*/
|
|
memset(options->event_notification_command, 0, sizeof(options->event_notification_command));
|
|
memset(options->event_notifications_orig, 0, sizeof(options->event_notifications_orig));
|
|
options->event_notifications.head = NULL;
|
|
options->event_notifications.tail = NULL;
|
|
|
|
/*----------------
|
|
* barman settings
|
|
* ---------------
|
|
*/
|
|
memset(options->barman_host, 0, sizeof(options->barman_host));
|
|
memset(options->barman_server, 0, sizeof(options->barman_server));
|
|
memset(options->barman_config, 0, sizeof(options->barman_config));
|
|
|
|
/*-------------------
|
|
* rsync/ssh settings
|
|
* ------------------
|
|
*/
|
|
memset(options->rsync_options, 0, sizeof(options->rsync_options));
|
|
memset(options->ssh_options, 0, sizeof(options->ssh_options));
|
|
strncpy(options->ssh_options, "-q -o ConnectTimeout=10", sizeof(options->ssh_options));
|
|
|
|
/*---------------------------
|
|
* undocumented test settings
|
|
*---------------------------
|
|
*/
|
|
options->promote_delay = 0;
|
|
|
|
/*
|
|
* If no configuration file available (user didn't specify and none found
|
|
* in the default locations), return with default values
|
|
*/
|
|
if (config_file_found == false)
|
|
{
|
|
log_verbose(LOG_NOTICE,
|
|
_("no configuration file provided and no default file found - "
|
|
"continuing with default values"));
|
|
return;
|
|
}
|
|
|
|
fp = fopen(config_file_path, "r");
|
|
|
|
/*
|
|
* A configuration file has been found, either provided by the user or
|
|
* found in one of the default locations. If we can't open it, fail with
|
|
* an error.
|
|
*/
|
|
if (fp == NULL)
|
|
{
|
|
if (config_file_provided)
|
|
{
|
|
log_error(_("unable to open provided configuration file \"%s\"; terminating"),
|
|
config_file_path);
|
|
}
|
|
else
|
|
{
|
|
log_error(_("unable to open default configuration file \"%s\"; terminating"),
|
|
config_file_path);
|
|
}
|
|
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
(void) ProcessRepmgrConfigFile(fp, config_file_path, options, error_list, warning_list);
|
|
|
|
fclose(fp);
|
|
|
|
/* check required parameters */
|
|
if (options->node_id == UNKNOWN_NODE_ID)
|
|
{
|
|
item_list_append(error_list, _("\"node_id\": required parameter was not found"));
|
|
}
|
|
|
|
if (!strlen(options->node_name))
|
|
{
|
|
item_list_append(error_list, _("\"node_name\": required parameter was not found"));
|
|
}
|
|
|
|
if (!strlen(options->data_directory))
|
|
{
|
|
item_list_append(error_list, _("\"data_directory\": required parameter was not found"));
|
|
}
|
|
|
|
if (!strlen(options->conninfo))
|
|
{
|
|
item_list_append(error_list, _("\"conninfo\": required parameter was not found"));
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Basic sanity check of provided conninfo string; this will catch any
|
|
* invalid parameters (but not values).
|
|
*/
|
|
char *conninfo_errmsg = NULL;
|
|
|
|
if (validate_conninfo_string(options->conninfo, &conninfo_errmsg) == false)
|
|
{
|
|
PQExpBufferData error_message_buf;
|
|
initPQExpBuffer(&error_message_buf);
|
|
|
|
appendPQExpBuffer(&error_message_buf,
|
|
_("\"conninfo\": %s (provided: \"%s\")"),
|
|
conninfo_errmsg,
|
|
options->conninfo);
|
|
|
|
item_list_append(error_list, error_message_buf.data);
|
|
termPQExpBuffer(&error_message_buf);
|
|
}
|
|
}
|
|
|
|
/* set values for parameters which default to other parameters */
|
|
|
|
/*
|
|
* From 4.1, "repmgrd_standby_startup_timeout" replaces "standby_reconnect_timeout"
|
|
* in repmgrd; fall back to "standby_reconnect_timeout" if no value explicitly provided
|
|
*/
|
|
if (options->repmgrd_standby_startup_timeout == -1)
|
|
{
|
|
options->repmgrd_standby_startup_timeout = options->standby_reconnect_timeout;
|
|
}
|
|
|
|
/* add warning about changed "barman_" parameter meanings */
|
|
if ((options->barman_host[0] == '\0' && options->barman_server[0] != '\0') ||
|
|
(options->barman_host[0] != '\0' && options->barman_server[0] == '\0'))
|
|
{
|
|
item_list_append(error_list,
|
|
_("use \"barman_host\" for the hostname of the Barman server"));
|
|
item_list_append(error_list,
|
|
_("use \"barman_server\" for the name of the [server] section in the Barman configuration file"));
|
|
|
|
}
|
|
|
|
/* other sanity checks */
|
|
|
|
if (options->archive_ready_warning >= options->archive_ready_critical)
|
|
{
|
|
item_list_append(error_list,
|
|
_("\"archive_ready_critical\" must be greater than \"archive_ready_warning\""));
|
|
}
|
|
|
|
if (options->replication_lag_warning >= options->replication_lag_critical)
|
|
{
|
|
item_list_append(error_list,
|
|
_("\"replication_lag_critical\" must be greater than \"replication_lag_warning\""));
|
|
}
|
|
|
|
if (options->standby_reconnect_timeout < options->node_rejoin_timeout)
|
|
{
|
|
item_list_append(error_list,
|
|
_("\"standby_reconnect_timeout\" must be equal to or greater than \"node_rejoin_timeout\""));
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
parse_configuration_item(t_configuration_options *options, ItemList *error_list, ItemList *warning_list, const char *name, const char *value)
|
|
{
|
|
if (strcmp(name, "node_id") == 0)
|
|
{
|
|
options->node_id = repmgr_atoi(value, name, error_list, MIN_NODE_ID);
|
|
}
|
|
else if (strcmp(name, "node_name") == 0)
|
|
{
|
|
if (strlen(value) < sizeof(options->node_name))
|
|
strncpy(options->node_name, value, sizeof(options->node_name));
|
|
else
|
|
item_list_append_format(error_list,
|
|
_("value for \"node_name\" must contain fewer than %lu characters"),
|
|
sizeof(options->node_name));
|
|
}
|
|
else if (strcmp(name, "conninfo") == 0)
|
|
{
|
|
strncpy(options->conninfo, value, MAXLEN);
|
|
}
|
|
else if (strcmp(name, "data_directory") == 0)
|
|
{
|
|
strncpy(options->data_directory, value, MAXPGPATH);
|
|
canonicalize_path(options->data_directory);
|
|
}
|
|
else if (strcmp(name, "config_directory") == 0)
|
|
{
|
|
strncpy(options->config_directory, value, MAXPGPATH);
|
|
canonicalize_path(options->config_directory);
|
|
}
|
|
else if (strcmp(name, "replication_user") == 0)
|
|
{
|
|
if (strlen(value) < sizeof(options->replication_user))
|
|
strncpy(options->replication_user, value, sizeof(options->replication_user));
|
|
else
|
|
item_list_append_format(error_list,
|
|
_("value for \"replication_user\" must contain fewer than %lu characters"),
|
|
sizeof(options->replication_user));
|
|
}
|
|
else if (strcmp(name, "pg_bindir") == 0)
|
|
strncpy(options->pg_bindir, value, MAXPGPATH);
|
|
else if (strcmp(name, "repmgr_bindir") == 0)
|
|
strncpy(options->repmgr_bindir, value, MAXPGPATH);
|
|
|
|
else if (strcmp(name, "replication_type") == 0)
|
|
{
|
|
if (strcmp(value, "physical") == 0)
|
|
options->replication_type = REPLICATION_TYPE_PHYSICAL;
|
|
else
|
|
item_list_append(error_list, _("value for \"replication_type\" must be \"physical\""));
|
|
}
|
|
|
|
/* log settings */
|
|
else if (strcmp(name, "log_file") == 0)
|
|
strncpy(options->log_file, value, MAXLEN);
|
|
else if (strcmp(name, "log_level") == 0)
|
|
strncpy(options->log_level, value, MAXLEN);
|
|
else if (strcmp(name, "log_facility") == 0)
|
|
strncpy(options->log_facility, value, MAXLEN);
|
|
else if (strcmp(name, "log_status_interval") == 0)
|
|
options->log_status_interval = repmgr_atoi(value, name, error_list, 0);
|
|
|
|
/* standby clone settings */
|
|
else if (strcmp(name, "use_replication_slots") == 0)
|
|
options->use_replication_slots = parse_bool(value, name, error_list);
|
|
else if (strcmp(name, "pg_basebackup_options") == 0)
|
|
strncpy(options->pg_basebackup_options, value, MAXLEN);
|
|
else if (strcmp(name, "tablespace_mapping") == 0)
|
|
tablespace_list_append(options, value);
|
|
else if (strcmp(name, "restore_command") == 0)
|
|
strncpy(options->restore_command, value, MAXLEN);
|
|
else if (strcmp(name, "recovery_min_apply_delay") == 0)
|
|
{
|
|
parse_time_unit_parameter(name, value, options->recovery_min_apply_delay, error_list);
|
|
options->recovery_min_apply_delay_provided = true;
|
|
}
|
|
else if (strcmp(name, "archive_cleanup_command") == 0)
|
|
strncpy(options->archive_cleanup_command, value, MAXLEN);
|
|
else if (strcmp(name, "use_primary_conninfo_password") == 0)
|
|
options->use_primary_conninfo_password = parse_bool(value, name, error_list);
|
|
else if (strcmp(name, "passfile") == 0)
|
|
strncpy(options->passfile, value, sizeof(options->passfile));
|
|
|
|
/* standby promote settings */
|
|
else if (strcmp(name, "promote_check_timeout") == 0)
|
|
options->promote_check_timeout = repmgr_atoi(value, name, error_list, 1);
|
|
|
|
else if (strcmp(name, "promote_check_interval") == 0)
|
|
options->promote_check_interval = repmgr_atoi(value, name, error_list, 1);
|
|
|
|
/* standby follow settings */
|
|
else if (strcmp(name, "primary_follow_timeout") == 0)
|
|
options->primary_follow_timeout = repmgr_atoi(value, name, error_list, 0);
|
|
else if (strcmp(name, "standby_follow_timeout") == 0)
|
|
options->standby_follow_timeout = repmgr_atoi(value, name, error_list, 0);
|
|
|
|
/* standby switchover settings */
|
|
else if (strcmp(name, "shutdown_check_timeout") == 0)
|
|
options->shutdown_check_timeout = repmgr_atoi(value, name, error_list, 0);
|
|
else if (strcmp(name, "standby_reconnect_timeout") == 0)
|
|
options->standby_reconnect_timeout = repmgr_atoi(value, name, error_list, 0);
|
|
else if (strcmp(name, "wal_receive_check_timeout") == 0)
|
|
options->wal_receive_check_timeout = repmgr_atoi(value, name, error_list, 0);
|
|
|
|
/* node rejoin settings */
|
|
else if (strcmp(name, "node_rejoin_timeout") == 0)
|
|
options->node_rejoin_timeout = repmgr_atoi(value, name, error_list, 0);
|
|
|
|
/* node check settings */
|
|
else if (strcmp(name, "archive_ready_warning") == 0)
|
|
options->archive_ready_warning = repmgr_atoi(value, name, error_list, 1);
|
|
else if (strcmp(name, "archive_ready_critical") == 0)
|
|
options->archive_ready_critical = repmgr_atoi(value, name, error_list, 1);
|
|
else if (strcmp(name, "replication_lag_warning") == 0)
|
|
options->replication_lag_warning = repmgr_atoi(value, name, error_list, 1);
|
|
else if (strcmp(name, "replication_lag_critical") == 0)
|
|
options->replication_lag_critical = repmgr_atoi(value, name, error_list, 1);
|
|
|
|
/* repmgrd settings */
|
|
else if (strcmp(name, "failover") == 0)
|
|
{
|
|
if (strcmp(value, "manual") == 0)
|
|
{
|
|
options->failover = FAILOVER_MANUAL;
|
|
}
|
|
else if (strcmp(value, "automatic") == 0)
|
|
{
|
|
options->failover = FAILOVER_AUTOMATIC;
|
|
}
|
|
else
|
|
{
|
|
item_list_append(error_list,
|
|
_("value for \"failover\" must be \"automatic\" or \"manual\"\n"));
|
|
}
|
|
}
|
|
else if (strcmp(name, "priority") == 0)
|
|
options->priority = repmgr_atoi(value, name, error_list, 0);
|
|
else if (strcmp(name, "location") == 0)
|
|
strncpy(options->location, value, sizeof(options->location));
|
|
else if (strcmp(name, "promote_command") == 0)
|
|
strncpy(options->promote_command, value, sizeof(options->promote_command));
|
|
else if (strcmp(name, "follow_command") == 0)
|
|
strncpy(options->follow_command, value, sizeof(options->follow_command));
|
|
else if (strcmp(name, "reconnect_attempts") == 0)
|
|
options->reconnect_attempts = repmgr_atoi(value, name, error_list, 0);
|
|
else if (strcmp(name, "reconnect_interval") == 0)
|
|
options->reconnect_interval = repmgr_atoi(value, name, error_list, 0);
|
|
else if (strcmp(name, "monitor_interval_secs") == 0)
|
|
options->monitor_interval_secs = repmgr_atoi(value, name, error_list, 1);
|
|
else if (strcmp(name, "monitoring_history") == 0)
|
|
options->monitoring_history = parse_bool(value, name, error_list);
|
|
else if (strcmp(name, "degraded_monitoring_timeout") == 0)
|
|
options->degraded_monitoring_timeout = repmgr_atoi(value, name, error_list, -1);
|
|
else if (strcmp(name, "async_query_timeout") == 0)
|
|
options->async_query_timeout = repmgr_atoi(value, name, error_list, 0);
|
|
else if (strcmp(name, "primary_notification_timeout") == 0)
|
|
options->primary_notification_timeout = repmgr_atoi(value, name, error_list, 0);
|
|
else if (strcmp(name, "repmgrd_standby_startup_timeout") == 0)
|
|
options->repmgrd_standby_startup_timeout = repmgr_atoi(value, name, error_list, 0);
|
|
else if (strcmp(name, "repmgrd_pid_file") == 0)
|
|
strncpy(options->repmgrd_pid_file, value, MAXPGPATH);
|
|
else if (strcmp(name, "standby_disconnect_on_failover") == 0)
|
|
options->standby_disconnect_on_failover = parse_bool(value, name, error_list);
|
|
else if (strcmp(name, "sibling_nodes_disconnect_timeout") == 0)
|
|
options->sibling_nodes_disconnect_timeout = repmgr_atoi(value, name, error_list, 0);
|
|
else if (strcmp(name, "connection_check_type") == 0)
|
|
{
|
|
if (strcasecmp(value, "ping") == 0)
|
|
{
|
|
options->connection_check_type = CHECK_PING;
|
|
}
|
|
else if (strcasecmp(value, "connection") == 0)
|
|
{
|
|
options->connection_check_type = CHECK_CONNECTION;
|
|
}
|
|
else if (strcasecmp(value, "query") == 0)
|
|
{
|
|
options->connection_check_type = CHECK_QUERY;
|
|
}
|
|
else
|
|
{
|
|
item_list_append(error_list,
|
|
_("value for \"connection_check_type\" must be \"ping\", \"connection\" or \"query\"\n"));
|
|
}
|
|
}
|
|
else if (strcmp(name, "primary_visibility_consensus") == 0)
|
|
options->primary_visibility_consensus = parse_bool(value, name, error_list);
|
|
else if (strcmp(name, "failover_validation_command") == 0)
|
|
strncpy(options->failover_validation_command, value, sizeof(options->failover_validation_command));
|
|
else if (strcmp(name, "election_rerun_interval") == 0)
|
|
options->election_rerun_interval = repmgr_atoi(value, name, error_list, 0);
|
|
else if (strcmp(name, "child_nodes_check_interval") == 0)
|
|
options->child_nodes_check_interval = repmgr_atoi(value, name, error_list, 1);
|
|
else if (strcmp(name, "child_nodes_disconnect_command") == 0)
|
|
snprintf(options->child_nodes_disconnect_command, sizeof(options->child_nodes_disconnect_command), "%s", value);
|
|
else if (strcmp(name, "child_nodes_disconnect_min_count") == 0)
|
|
options->child_nodes_disconnect_min_count = repmgr_atoi(value, name, error_list, -1);
|
|
else if (strcmp(name, "child_nodes_connected_min_count") == 0)
|
|
options->child_nodes_connected_min_count = repmgr_atoi(value, name, error_list, -1);
|
|
else if (strcmp(name, "child_nodes_connected_include_witness") == 0)
|
|
options->child_nodes_connected_include_witness = parse_bool(value, name, error_list);
|
|
else if (strcmp(name, "child_nodes_disconnect_timeout") == 0)
|
|
options->child_nodes_disconnect_timeout = repmgr_atoi(value, name, error_list, 0);
|
|
|
|
/* witness settings */
|
|
else if (strcmp(name, "witness_sync_interval") == 0)
|
|
options->witness_sync_interval = repmgr_atoi(value, name, error_list, 1);
|
|
|
|
/* service settings */
|
|
else if (strcmp(name, "pg_ctl_options") == 0)
|
|
strncpy(options->pg_ctl_options, value, sizeof(options->pg_ctl_options));
|
|
else if (strcmp(name, "service_start_command") == 0)
|
|
strncpy(options->service_start_command, value, sizeof(options->service_start_command));
|
|
else if (strcmp(name, "service_stop_command") == 0)
|
|
strncpy(options->service_stop_command, value, sizeof(options->service_stop_command));
|
|
else if (strcmp(name, "service_restart_command") == 0)
|
|
strncpy(options->service_restart_command, value, sizeof(options->service_restart_command));
|
|
else if (strcmp(name, "service_reload_command") == 0)
|
|
strncpy(options->service_reload_command, value, sizeof(options->service_reload_command));
|
|
else if (strcmp(name, "service_promote_command") == 0)
|
|
strncpy(options->service_promote_command, value, sizeof(options->service_promote_command));
|
|
|
|
/* repmgrd service settings */
|
|
else if (strcmp(name, "repmgrd_service_start_command") == 0)
|
|
strncpy(options->repmgrd_service_start_command, value, sizeof(options->repmgrd_service_start_command));
|
|
else if (strcmp(name, "repmgrd_service_stop_command") == 0)
|
|
strncpy(options->repmgrd_service_stop_command, value, sizeof(options->repmgrd_service_stop_command));
|
|
|
|
|
|
/* event notification settings */
|
|
else if (strcmp(name, "event_notification_command") == 0)
|
|
strncpy(options->event_notification_command, value, sizeof(options->event_notification_command));
|
|
else if (strcmp(name, "event_notifications") == 0)
|
|
{
|
|
/* store unparsed value for comparison when reloading config */
|
|
strncpy(options->event_notifications_orig, value, sizeof(options->event_notifications_orig));
|
|
parse_event_notifications_list(options, value);
|
|
}
|
|
|
|
/* barman settings */
|
|
else if (strcmp(name, "barman_host") == 0)
|
|
strncpy(options->barman_host, value, sizeof(options->barman_host));
|
|
else if (strcmp(name, "barman_server") == 0)
|
|
strncpy(options->barman_server, value, sizeof(options->barman_server));
|
|
else if (strcmp(name, "barman_config") == 0)
|
|
strncpy(options->barman_config, value, sizeof(options->barman_config));
|
|
|
|
/* rsync/ssh settings */
|
|
else if (strcmp(name, "rsync_options") == 0)
|
|
strncpy(options->rsync_options, value, sizeof(options->rsync_options));
|
|
else if (strcmp(name, "ssh_options") == 0)
|
|
strncpy(options->ssh_options, value, sizeof(options->ssh_options));
|
|
|
|
/* undocumented settings for testing */
|
|
else if (strcmp(name, "promote_delay") == 0)
|
|
options->promote_delay = repmgr_atoi(value, name, error_list, 1);
|
|
|
|
/*
|
|
* Following parameters have been deprecated or renamed from 3.x -
|
|
* issue a warning
|
|
*/
|
|
else if (strcmp(name, "cluster") == 0)
|
|
{
|
|
item_list_append(warning_list,
|
|
_("parameter \"cluster\" is deprecated and will be ignored"));
|
|
}
|
|
else if (strcmp(name, "node") == 0)
|
|
{
|
|
item_list_append(warning_list,
|
|
_("parameter \"node\" has been renamed to \"node_id\""));
|
|
}
|
|
else if (strcmp(name, "upstream_node") == 0)
|
|
{
|
|
item_list_append(warning_list,
|
|
_("parameter \"upstream_node\" has been removed; use \"--upstream-node-id\" when cloning a standby"));
|
|
}
|
|
else if (strcmp(name, "loglevel") == 0)
|
|
{
|
|
item_list_append(warning_list,
|
|
_("parameter \"loglevel\" has been renamed to \"log_level\""));
|
|
}
|
|
else if (strcmp(name, "logfacility") == 0)
|
|
{
|
|
item_list_append(warning_list,
|
|
_("parameter \"logfacility\" has been renamed to \"log_facility\""));
|
|
}
|
|
else if (strcmp(name, "logfile") == 0)
|
|
{
|
|
item_list_append(warning_list,
|
|
_("parameter \"logfile\" has been renamed to \"log_file\""));
|
|
}
|
|
else if (strcmp(name, "master_reponse_timeout") == 0)
|
|
{
|
|
item_list_append(warning_list,
|
|
_("parameter \"master_reponse_timeout\" has been removed; use \"async_query_timeout\" instead"));
|
|
}
|
|
else if (strcmp(name, "retry_promote_interval_secs") == 0)
|
|
{
|
|
item_list_append(warning_list,
|
|
_("parameter \"retry_promote_interval_secs\" has been removed; use \"primary_notification_timeout\" instead"));
|
|
}
|
|
else
|
|
{
|
|
log_warning(_("%s/%s: unknown name/value pair provided; ignoring"), name, value);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
bool
|
|
parse_recovery_conf(const char *data_dir, t_recovery_conf *conf)
|
|
{
|
|
char recovery_conf_path[MAXPGPATH] = "";
|
|
FILE *fp;
|
|
char *s = NULL,
|
|
buf[MAXLINELENGTH] = "";
|
|
char name[MAXLEN] = "";
|
|
char value[MAXLEN] = "";
|
|
|
|
snprintf(recovery_conf_path, MAXPGPATH,
|
|
"%s/%s",
|
|
data_dir,
|
|
RECOVERY_COMMAND_FILE);
|
|
|
|
fp = fopen(recovery_conf_path, "r");
|
|
|
|
if (fp == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* Read file */
|
|
while ((s = fgets(buf, sizeof buf, fp)) != NULL)
|
|
{
|
|
|
|
/* Parse name/value pair from line */
|
|
_parse_line(buf, name, value);
|
|
|
|
/* Skip blank lines */
|
|
if (!strlen(name))
|
|
continue;
|
|
|
|
/* Skip comments */
|
|
if (name[0] == '#')
|
|
continue;
|
|
|
|
/* archive recovery settings */
|
|
if (strcmp(name, "restore_command") == 0)
|
|
strncpy(conf->restore_command, value, MAXLEN);
|
|
else if (strcmp(name, "archive_cleanup_command") == 0)
|
|
strncpy(conf->archive_cleanup_command, value, MAXLEN);
|
|
else if (strcmp(name, "recovery_end_command") == 0)
|
|
strncpy(conf->recovery_end_command, value, MAXLEN);
|
|
/* recovery target settings */
|
|
else if (strcmp(name, "recovery_target_name") == 0)
|
|
strncpy(conf->recovery_target_name, value, MAXLEN);
|
|
else if (strcmp(name, "recovery_target_time") == 0)
|
|
strncpy(conf->recovery_target_time, value, MAXLEN);
|
|
else if (strcmp(name, "recovery_target_xid") == 0)
|
|
strncpy(conf->recovery_target_xid, value, MAXLEN);
|
|
else if (strcmp(name, "recovery_target_inclusive") == 0)
|
|
conf->recovery_target_inclusive = parse_bool(value, NULL, NULL);
|
|
else if (strcmp(name, "recovery_target_timeline") == 0)
|
|
{
|
|
if (strncmp(value, "latest", MAXLEN) == 0)
|
|
{
|
|
conf->recovery_target_timeline = TARGET_TIMELINE_LATEST;
|
|
}
|
|
else
|
|
{
|
|
conf->recovery_target_timeline = atoi(value);
|
|
}
|
|
}
|
|
else if (strcmp(name, "recovery_target_action") == 0)
|
|
{
|
|
if (strcmp(value, "pause") == 0)
|
|
conf->recovery_target_action = RTA_PAUSE;
|
|
else if (strcmp(value, "promote") == 0)
|
|
conf->recovery_target_action = RTA_PROMOTE;
|
|
else if (strcmp(value, "shutdown") == 0)
|
|
conf->recovery_target_action = RTA_SHUTDOWN;
|
|
}
|
|
|
|
/* standby server settings */
|
|
|
|
else if (strcmp(name, "standby_mode") == 0)
|
|
conf->standby_mode = parse_bool(value, NULL, NULL);
|
|
else if (strcmp(name, "primary_conninfo") == 0)
|
|
strncpy(conf->primary_conninfo, value, MAXLEN);
|
|
else if (strcmp(name, "primary_slot_name") == 0)
|
|
strncpy(conf->trigger_file, value, MAXLEN);
|
|
else if (strcmp(name, "trigger_file") == 0)
|
|
strncpy(conf->trigger_file, value, MAXLEN);
|
|
else if (strcmp(name, "recovery_min_apply_delay") == 0)
|
|
parse_time_unit_parameter(name, value, conf->recovery_min_apply_delay, NULL);
|
|
|
|
}
|
|
fclose(fp);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void
|
|
_parse_line(char *buf, char *name, char *value)
|
|
{
|
|
int i = 0;
|
|
int j = 0;
|
|
|
|
/*
|
|
* Extract parameter name, if present
|
|
*/
|
|
for (; i < MAXLEN; ++i)
|
|
{
|
|
if (buf[i] == '=')
|
|
break;
|
|
|
|
switch (buf[i])
|
|
{
|
|
/* Ignore whitespace */
|
|
case ' ':
|
|
case '\n':
|
|
case '\r':
|
|
case '\t':
|
|
continue;
|
|
default:
|
|
name[j++] = buf[i];
|
|
}
|
|
}
|
|
name[j] = '\0';
|
|
|
|
/*
|
|
* Ignore any whitespace following the '=' sign
|
|
*/
|
|
for (; i < MAXLEN; ++i)
|
|
{
|
|
if (buf[i + 1] == ' ')
|
|
continue;
|
|
if (buf[i + 1] == '\t')
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Extract parameter value
|
|
*/
|
|
j = 0;
|
|
for (++i; i < MAXLEN; ++i)
|
|
if (buf[i] == '\'')
|
|
continue;
|
|
else if (buf[i] == '#')
|
|
break;
|
|
else if (buf[i] != '\n')
|
|
value[j++] = buf[i];
|
|
else
|
|
break;
|
|
value[j] = '\0';
|
|
trim(value);
|
|
}
|
|
|
|
|
|
static void
|
|
parse_time_unit_parameter(const char *name, const char *value, char *dest, ItemList *errors)
|
|
{
|
|
char *ptr = NULL;
|
|
int targ = strtol(value, &ptr, 10);
|
|
|
|
if (targ < 0)
|
|
{
|
|
if (errors != NULL)
|
|
{
|
|
item_list_append_format(errors,
|
|
_("invalid value provided for \"%s\""),
|
|
name);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (ptr && *ptr)
|
|
{
|
|
if (strcmp(ptr, "ms") != 0 && strcmp(ptr, "s") != 0 &&
|
|
strcmp(ptr, "min") != 0 && strcmp(ptr, "h") != 0 &&
|
|
strcmp(ptr, "d") != 0)
|
|
{
|
|
if (errors != NULL)
|
|
{
|
|
item_list_append_format(
|
|
errors,
|
|
_("value provided for \"%s\" must be one of ms/s/min/h/d"),
|
|
name);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
strncpy(dest, value, MAXLEN);
|
|
}
|
|
|
|
/*
|
|
* reload_config()
|
|
*
|
|
* This is only called by repmgrd after receiving a SIGHUP or when a monitoring
|
|
* loop is started up; it therefore only needs to reload options required
|
|
* by repmgrd, which are as follows:
|
|
*
|
|
* changeable options (keep the list in "doc/repmgrd-configuration.xml" in sync
|
|
* with these):
|
|
*
|
|
* - async_query_timeout
|
|
* - child_nodes_check_interval
|
|
* - child_nodes_connected_min_count
|
|
* - child_nodes_connected_include_witness
|
|
* - child_nodes_disconnect_command
|
|
* - child_nodes_disconnect_min_count
|
|
* - child_nodes_disconnect_timeout
|
|
* - connection_check_type
|
|
* - conninfo
|
|
* - degraded_monitoring_timeout
|
|
* - event_notification_command
|
|
* - event_notifications
|
|
* - failover
|
|
* - failover_validation_command
|
|
* - follow_command
|
|
* - log_facility
|
|
* - log_file
|
|
* - log_level
|
|
* - log_status_interval
|
|
* - monitor_interval_secs
|
|
* - monitoring_history
|
|
* - primary_notification_timeout
|
|
* - primary_visibility_consensus
|
|
* - promote_command
|
|
* - reconnect_attempts
|
|
* - reconnect_interval
|
|
* - repmgrd_standby_startup_timeout
|
|
* - retry_promote_interval_secs
|
|
* - sibling_nodes_disconnect_timeout
|
|
* - standby_disconnect_on_failover
|
|
*
|
|
*
|
|
* Not publicly documented:
|
|
* - promote_delay
|
|
*
|
|
* non-changeable options (repmgrd references these from the "repmgr.nodes"
|
|
* table, not the configuration file)
|
|
*
|
|
* - node_id
|
|
* - node_name
|
|
* - data_directory
|
|
* - location
|
|
* - priority
|
|
* - replication_type
|
|
*
|
|
* extract with something like:
|
|
* grep config_file_options\\. repmgrd*.c | perl -n -e '/config_file_options\.([\w_]+)/ && print qq|$1\n|;' | sort | uniq
|
|
*
|
|
* Returns "true" if the configuration was successfully changed, otherwise "false".
|
|
*/
|
|
bool
|
|
reload_config(t_server_type server_type)
|
|
{
|
|
bool log_config_changed = false;
|
|
|
|
ItemList config_errors = {NULL, NULL};
|
|
ItemList config_warnings = {NULL, NULL};
|
|
ItemList config_changes = {NULL, NULL};
|
|
|
|
t_configuration_options orig_config_file_options;
|
|
|
|
copy_config_file_options(&config_file_options, &orig_config_file_options);
|
|
|
|
log_info(_("reloading configuration file"));
|
|
log_detail(_("using file \"%s\""), config_file_path);
|
|
|
|
/*
|
|
* _parse_config() will sanity-check the provided values and put any
|
|
* errors/warnings in the provided lists; no need to add further sanity
|
|
* checks here. We do still need to check for repmgrd-specific
|
|
* requirements.
|
|
*/
|
|
_parse_config(&config_file_options, &config_errors, &config_warnings);
|
|
|
|
if (config_file_options.failover == FAILOVER_AUTOMATIC
|
|
&& (server_type == PRIMARY || server_type == STANDBY))
|
|
{
|
|
if (config_file_options.promote_command[0] == '\0')
|
|
{
|
|
item_list_append(&config_errors, _("\"promote_command\": required parameter was not found"));
|
|
}
|
|
|
|
if (config_file_options.follow_command[0] == '\0')
|
|
{
|
|
item_list_append(&config_errors, _("\"follow_command\": required parameter was not found"));
|
|
}
|
|
}
|
|
|
|
|
|
/* The following options cannot be changed */
|
|
|
|
if (config_file_options.node_id != orig_config_file_options.node_id)
|
|
{
|
|
item_list_append_format(&config_errors,
|
|
_("\"node_id\" cannot be changed, retaining current configuration %i %i"),
|
|
config_file_options.node_id,
|
|
orig_config_file_options.node_id);
|
|
}
|
|
|
|
if (strncmp(config_file_options.node_name, orig_config_file_options.node_name, sizeof(config_file_options.node_name)) != 0)
|
|
{
|
|
item_list_append(&config_errors,
|
|
_("\"node_name\" cannot be changed, keeping current configuration"));
|
|
}
|
|
|
|
|
|
/*
|
|
* conninfo
|
|
*
|
|
* _parse_config() will already have sanity-checked the string; we do that here
|
|
* again so we can avoid trying to connect with a known bad string
|
|
*/
|
|
if (strncmp(config_file_options.conninfo, orig_config_file_options.conninfo, sizeof(config_file_options.conninfo)) != 0 && validate_conninfo_string(config_file_options.conninfo, NULL))
|
|
{
|
|
PGconn *conn;
|
|
|
|
/* Test conninfo string works */
|
|
conn = establish_db_connection(config_file_options.conninfo, false);
|
|
|
|
if (!conn || (PQstatus(conn) != CONNECTION_OK))
|
|
{
|
|
item_list_append_format(&config_errors,
|
|
_("provided \"conninfo\" string \"%s\" is not valid"),
|
|
config_file_options.conninfo);
|
|
}
|
|
else
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"conninfo\" changed from \"%s\" to \"%s\""),
|
|
orig_config_file_options.conninfo,
|
|
config_file_options.conninfo);
|
|
}
|
|
|
|
PQfinish(conn);
|
|
}
|
|
|
|
|
|
/*
|
|
* If any issues encountered, raise an error and roll back to the original
|
|
* configuration
|
|
*/
|
|
if (config_errors.head != NULL)
|
|
{
|
|
ItemListCell *cell = NULL;
|
|
PQExpBufferData errors;
|
|
|
|
log_error(_("one or more errors encountered while parsing the configuration file"));
|
|
|
|
initPQExpBuffer(&errors);
|
|
|
|
appendPQExpBufferStr(&errors,
|
|
"following errors were detected:\n");
|
|
|
|
for (cell = config_errors.head; cell; cell = cell->next)
|
|
{
|
|
appendPQExpBuffer(&errors,
|
|
" %s\n", cell->string);
|
|
}
|
|
|
|
log_detail("%s", errors.data);
|
|
termPQExpBuffer(&errors);
|
|
|
|
log_notice(_("the current configuration has been retained unchanged"));
|
|
|
|
copy_config_file_options(&orig_config_file_options, &config_file_options);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* No configuration problems detected - log any changed values.
|
|
*
|
|
* NB: keep these in the same order as in configfile.h to make it easier
|
|
* to manage them
|
|
*/
|
|
|
|
|
|
/* async_query_timeout */
|
|
if (config_file_options.async_query_timeout != orig_config_file_options.async_query_timeout)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"async_query_timeout\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.async_query_timeout,
|
|
config_file_options.async_query_timeout);
|
|
}
|
|
|
|
/* child_nodes_check_interval */
|
|
if (config_file_options.child_nodes_check_interval != orig_config_file_options.child_nodes_check_interval)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"child_nodes_check_interval\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.child_nodes_check_interval,
|
|
config_file_options.child_nodes_check_interval);
|
|
}
|
|
|
|
/* child_nodes_disconnect_command */
|
|
if (strncmp(config_file_options.child_nodes_disconnect_command, orig_config_file_options.child_nodes_disconnect_command, sizeof(config_file_options.child_nodes_disconnect_command)) != 0)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"child_nodes_disconnect_command\" changed from \"%s\" to \"%s\""),
|
|
orig_config_file_options.child_nodes_disconnect_command,
|
|
config_file_options.child_nodes_disconnect_command);
|
|
}
|
|
|
|
/* child_nodes_disconnect_min_count */
|
|
if (config_file_options.child_nodes_disconnect_min_count != orig_config_file_options.child_nodes_disconnect_min_count)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"child_nodes_disconnect_min_count\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.child_nodes_disconnect_min_count,
|
|
config_file_options.child_nodes_disconnect_min_count);
|
|
}
|
|
|
|
/* child_nodes_connected_min_count */
|
|
if (config_file_options.child_nodes_connected_min_count != orig_config_file_options.child_nodes_connected_min_count)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"child_nodes_connected_min_count\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.child_nodes_connected_min_count,
|
|
config_file_options.child_nodes_connected_min_count);
|
|
}
|
|
|
|
/* child_nodes_connected_include_witness */
|
|
if (config_file_options.child_nodes_connected_include_witness != orig_config_file_options.child_nodes_connected_include_witness)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"child_nodes_connected_include_witness\" changed from \"%s\" to \"%s\""),
|
|
format_bool(orig_config_file_options.child_nodes_connected_include_witness),
|
|
format_bool(config_file_options.child_nodes_connected_include_witness));
|
|
}
|
|
|
|
/* child_nodes_disconnect_timeout */
|
|
if (config_file_options.child_nodes_disconnect_timeout != orig_config_file_options.child_nodes_disconnect_timeout)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"child_nodes_disconnect_timeout\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.child_nodes_disconnect_timeout,
|
|
config_file_options.child_nodes_disconnect_timeout);
|
|
}
|
|
|
|
|
|
/* degraded_monitoring_timeout */
|
|
if (config_file_options.degraded_monitoring_timeout != orig_config_file_options.degraded_monitoring_timeout)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"degraded_monitoring_timeout\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.degraded_monitoring_timeout,
|
|
config_file_options.degraded_monitoring_timeout);
|
|
}
|
|
|
|
/* event_notification_command */
|
|
if (strncmp(config_file_options.event_notification_command, orig_config_file_options.event_notification_command, sizeof(config_file_options.event_notification_command)) != 0)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"event_notification_command\" changed from \"%s\" to \"%s\""),
|
|
orig_config_file_options.event_notification_command,
|
|
config_file_options.event_notification_command);
|
|
}
|
|
|
|
/* event_notifications */
|
|
if (strncmp(config_file_options.event_notifications_orig, orig_config_file_options.event_notifications_orig, sizeof(config_file_options.event_notifications_orig)) != 0)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"event_notifications\" changed from \"%s\" to \"%s\""),
|
|
orig_config_file_options.event_notifications_orig,
|
|
config_file_options.event_notifications_orig);
|
|
}
|
|
|
|
/* failover */
|
|
if (config_file_options.failover != orig_config_file_options.failover)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"failover\" changed from \"%s\" to \"%s\""),
|
|
format_failover_mode(orig_config_file_options.failover),
|
|
format_failover_mode(config_file_options.failover));
|
|
}
|
|
|
|
/* follow_command */
|
|
if (strncmp(config_file_options.follow_command, orig_config_file_options.follow_command, sizeof(config_file_options.follow_command)) != 0)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"follow_command\" changed from \"%s\" to \"%s\""),
|
|
orig_config_file_options.follow_command,
|
|
config_file_options.follow_command);
|
|
}
|
|
|
|
/* monitor_interval_secs */
|
|
if (config_file_options.monitor_interval_secs != orig_config_file_options.monitor_interval_secs)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"monitor_interval_secs\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.monitor_interval_secs,
|
|
config_file_options.monitor_interval_secs);
|
|
}
|
|
|
|
/* monitoring_history */
|
|
if (config_file_options.monitoring_history != orig_config_file_options.monitoring_history)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"monitoring_history\" changed from \"%s\" to \"%s\""),
|
|
format_bool(orig_config_file_options.monitoring_history),
|
|
format_bool(config_file_options.monitoring_history));
|
|
}
|
|
|
|
/* primary_notification_timeout */
|
|
if (config_file_options.primary_notification_timeout != orig_config_file_options.primary_notification_timeout)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"primary_notification_timeout\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.primary_notification_timeout,
|
|
config_file_options.primary_notification_timeout);
|
|
}
|
|
|
|
/* promote_command */
|
|
if (strncmp(config_file_options.promote_command, orig_config_file_options.promote_command, sizeof(config_file_options.promote_command)) != 0)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"promote_command\" changed from \"%s\" to \"%s\""),
|
|
orig_config_file_options.promote_command,
|
|
config_file_options.promote_command);
|
|
}
|
|
|
|
/* promote_delay (for testing use only; not documented */
|
|
if (config_file_options.promote_delay != orig_config_file_options.promote_delay)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"promote_delay\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.promote_delay,
|
|
config_file_options.promote_delay);
|
|
}
|
|
|
|
/* reconnect_attempts */
|
|
if (config_file_options.reconnect_attempts != orig_config_file_options.reconnect_attempts)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"reconnect_attempts\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.reconnect_attempts,
|
|
config_file_options.reconnect_attempts);
|
|
}
|
|
|
|
/* reconnect_interval */
|
|
if (config_file_options.reconnect_interval != orig_config_file_options.reconnect_interval)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"reconnect_interval\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.reconnect_interval,
|
|
config_file_options.reconnect_interval);
|
|
}
|
|
|
|
/* repmgrd_standby_startup_timeout */
|
|
if (config_file_options.repmgrd_standby_startup_timeout != orig_config_file_options.repmgrd_standby_startup_timeout)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"repmgrd_standby_startup_timeout\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.repmgrd_standby_startup_timeout,
|
|
config_file_options.repmgrd_standby_startup_timeout);
|
|
}
|
|
|
|
/* standby_disconnect_on_failover */
|
|
if (config_file_options.standby_disconnect_on_failover != orig_config_file_options.standby_disconnect_on_failover)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"standby_disconnect_on_failover\" changed from \"%s\" to \"%s\""),
|
|
format_bool(orig_config_file_options.standby_disconnect_on_failover),
|
|
format_bool(config_file_options.standby_disconnect_on_failover));
|
|
}
|
|
|
|
/* sibling_nodes_disconnect_timeout */
|
|
if (config_file_options.sibling_nodes_disconnect_timeout != orig_config_file_options.sibling_nodes_disconnect_timeout)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"sibling_nodes_disconnect_timeout\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.sibling_nodes_disconnect_timeout,
|
|
config_file_options.sibling_nodes_disconnect_timeout);
|
|
}
|
|
|
|
/* connection_check_type */
|
|
if (config_file_options.connection_check_type != orig_config_file_options.connection_check_type)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"connection_check_type\" changed from \"%s\" to \"%s\""),
|
|
print_connection_check_type(orig_config_file_options.connection_check_type),
|
|
print_connection_check_type(config_file_options.connection_check_type));
|
|
}
|
|
|
|
/* primary_visibility_consensus */
|
|
if (config_file_options.primary_visibility_consensus != orig_config_file_options.primary_visibility_consensus)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"primary_visibility_consensus\" changed from \"%s\" to \"%s\""),
|
|
format_bool(orig_config_file_options.primary_visibility_consensus),
|
|
format_bool(config_file_options.primary_visibility_consensus));
|
|
}
|
|
|
|
/* failover_validation_command */
|
|
if (strncmp(config_file_options.failover_validation_command, orig_config_file_options.failover_validation_command, sizeof(config_file_options.failover_validation_command)) != 0)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"failover_validation_command\" changed from \"%s\" to \"%s\""),
|
|
orig_config_file_options.failover_validation_command,
|
|
config_file_options.failover_validation_command);
|
|
}
|
|
|
|
/*
|
|
* Handle changes to logging configuration
|
|
*/
|
|
|
|
/* log_facility */
|
|
if (strncmp(config_file_options.log_facility, orig_config_file_options.log_facility, sizeof(config_file_options.log_facility)) != 0)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"log_facility\" changed from \"%s\" to \"%s\""),
|
|
orig_config_file_options.log_facility,
|
|
config_file_options.log_facility);
|
|
}
|
|
|
|
/* log_file */
|
|
if (strncmp(config_file_options.log_file, orig_config_file_options.log_file, sizeof(config_file_options.log_file)) != 0)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"log_file\" changed from \"%s\" to \"%s\""),
|
|
orig_config_file_options.log_file,
|
|
config_file_options.log_file);
|
|
}
|
|
|
|
|
|
/* log_level */
|
|
if (strncmp(config_file_options.log_level, orig_config_file_options.log_level, sizeof(config_file_options.log_level)) != 0)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"log_level\" changed from \"%s\" to \"%s\""),
|
|
orig_config_file_options.log_level,
|
|
config_file_options.log_level);
|
|
}
|
|
|
|
/* log_status_interval */
|
|
if (config_file_options.log_status_interval != orig_config_file_options.log_status_interval)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"log_status_interval\" changed from \"%i\" to \"%i\""),
|
|
orig_config_file_options.log_status_interval,
|
|
config_file_options.log_status_interval);
|
|
}
|
|
|
|
|
|
if (log_config_changed == true)
|
|
{
|
|
log_notice(_("restarting logging with changed parameters"));
|
|
logger_shutdown();
|
|
logger_init(&config_file_options, progname());
|
|
log_notice(_("configuration file reloaded with changed parameters"));
|
|
}
|
|
|
|
if (config_changes.head != NULL)
|
|
{
|
|
ItemListCell *cell = NULL;
|
|
PQExpBufferData detail;
|
|
|
|
log_notice(_("configuration was successfully changed"));
|
|
|
|
initPQExpBuffer(&detail);
|
|
|
|
appendPQExpBufferStr(&detail,
|
|
_("following configuration items were changed:\n"));
|
|
for (cell = config_changes.head; cell; cell = cell->next)
|
|
{
|
|
appendPQExpBuffer(&detail,
|
|
" %s\n", cell->string);
|
|
}
|
|
|
|
log_detail("%s", detail.data);
|
|
|
|
termPQExpBuffer(&detail);
|
|
}
|
|
else
|
|
{
|
|
log_info(_("configuration has not changed"));
|
|
}
|
|
|
|
/*
|
|
* parse_configuration_item() (called from _parse_config()) will add warnings
|
|
* about any deprecated configuration parameters; we'll dump these here as a reminder.
|
|
*/
|
|
if (config_warnings.head != NULL)
|
|
{
|
|
ItemListCell *cell = NULL;
|
|
PQExpBufferData detail;
|
|
|
|
log_warning(_("configuration file contains deprecated parameters"));
|
|
|
|
initPQExpBuffer(&detail);
|
|
|
|
appendPQExpBufferStr(&detail,
|
|
_("following parameters are deprecated:\n"));
|
|
for (cell = config_warnings.head; cell; cell = cell->next)
|
|
{
|
|
appendPQExpBuffer(&detail,
|
|
" %s\n", cell->string);
|
|
}
|
|
|
|
log_detail("%s", detail.data);
|
|
|
|
termPQExpBuffer(&detail);
|
|
}
|
|
|
|
return config_changes.head == NULL ? false : true;
|
|
}
|
|
|
|
|
|
static void
|
|
exit_with_config_file_errors(ItemList *config_errors, ItemList *config_warnings, bool terse)
|
|
{
|
|
log_error(_("following errors were found in the configuration file:"));
|
|
|
|
print_item_list(config_errors);
|
|
item_list_free(config_errors);
|
|
|
|
if (terse == false && config_warnings->head != NULL)
|
|
{
|
|
puts("");
|
|
log_warning(_("the following problems were also found in the configuration file:"));
|
|
|
|
print_item_list(config_warnings);
|
|
item_list_free(config_warnings);
|
|
}
|
|
|
|
if (config_file_provided == false)
|
|
log_detail(_("configuration file is: \"%s\""), config_file_path);
|
|
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
|
|
void
|
|
exit_with_cli_errors(ItemList *error_list, const char *repmgr_command)
|
|
{
|
|
fprintf(stderr, _("The following command line errors were encountered:\n"));
|
|
|
|
print_item_list(error_list);
|
|
|
|
if (repmgr_command != NULL)
|
|
{
|
|
fprintf(stderr, _("Try \"%s --help\" or \"%s %s --help\" for more information.\n"),
|
|
progname(),
|
|
progname(),
|
|
repmgr_command);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname());
|
|
}
|
|
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
void
|
|
print_item_list(ItemList *item_list)
|
|
{
|
|
ItemListCell *cell = NULL;
|
|
|
|
for (cell = item_list->head; cell; cell = cell->next)
|
|
{
|
|
fprintf(stderr, " %s\n", cell->string);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Convert provided string to an integer using strtol;
|
|
* on error, if a callback is provided, pass the error message to that,
|
|
* otherwise exit
|
|
*/
|
|
int
|
|
repmgr_atoi(const char *value, const char *config_item, ItemList *error_list, int minval)
|
|
{
|
|
char *endptr = NULL;
|
|
long longval = 0;
|
|
PQExpBufferData errors;
|
|
|
|
initPQExpBuffer(&errors);
|
|
|
|
/*
|
|
* It's possible that some versions of strtol() don't treat an empty
|
|
* string as an error.
|
|
*/
|
|
if (*value == '\0')
|
|
{
|
|
/* don't log here - empty values will be caught later */
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
errno = 0;
|
|
longval = strtol(value, &endptr, 10);
|
|
|
|
if (value == endptr || errno)
|
|
{
|
|
appendPQExpBuffer(&errors,
|
|
_("\"%s\": invalid value (provided: \"%s\")"),
|
|
config_item, value);
|
|
}
|
|
else if ((int32) longval < longval)
|
|
{
|
|
appendPQExpBuffer(&errors,
|
|
_("\"%s\": must be a positive signed 32 bit integer, i.e. 2147483647 or less (provided: \"%s\")"),
|
|
config_item,
|
|
value);
|
|
}
|
|
else if ((int32) longval < minval)
|
|
/* Disallow negative values for most parameters */
|
|
{
|
|
appendPQExpBuffer(&errors,
|
|
_("\"%s\": must be %i or greater (provided: \"%s\")"),
|
|
config_item,
|
|
minval,
|
|
value);
|
|
}
|
|
}
|
|
|
|
/* Error message buffer is set */
|
|
if (errors.data[0] != '\0')
|
|
{
|
|
if (error_list == NULL)
|
|
{
|
|
log_error("%s", errors.data);
|
|
termPQExpBuffer(&errors);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
item_list_append(error_list, errors.data);
|
|
}
|
|
|
|
termPQExpBuffer(&errors);
|
|
return (int32) longval;
|
|
}
|
|
|
|
|
|
/*
|
|
* Interpret a parameter value as a boolean. Currently accepts:
|
|
*
|
|
* - true/false
|
|
* - 1/0
|
|
* - on/off
|
|
* - yes/no
|
|
|
|
* Returns 'false' if unable to determine the booleanness of the value
|
|
* and adds an entry to the error list, which will result in the program
|
|
* erroring out before it proceeds to do anything.
|
|
*
|
|
* TODO: accept "any unambiguous prefix of one of these" as per postgresql.conf:
|
|
*
|
|
* https://www.postgresql.org/docs/current/config-setting.html
|
|
*/
|
|
bool
|
|
parse_bool(const char *s, const char *config_item, ItemList *error_list)
|
|
{
|
|
PQExpBufferData errors;
|
|
|
|
if (s == NULL)
|
|
return true;
|
|
|
|
if (strcasecmp(s, "0") == 0)
|
|
return false;
|
|
|
|
if (strcasecmp(s, "1") == 0)
|
|
return true;
|
|
|
|
if (strcasecmp(s, "false") == 0)
|
|
return false;
|
|
|
|
if (strcasecmp(s, "true") == 0)
|
|
return true;
|
|
|
|
if (strcasecmp(s, "off") == 0)
|
|
return false;
|
|
|
|
if (strcasecmp(s, "on") == 0)
|
|
return true;
|
|
|
|
if (strcasecmp(s, "no") == 0)
|
|
return false;
|
|
|
|
if (strcasecmp(s, "yes") == 0)
|
|
return true;
|
|
|
|
if (error_list != NULL)
|
|
{
|
|
initPQExpBuffer(&errors);
|
|
|
|
appendPQExpBuffer(&errors,
|
|
"\"%s\": unable to interpret \"%s\" as a boolean value",
|
|
config_item, s);
|
|
item_list_append(error_list, errors.data);
|
|
termPQExpBuffer(&errors);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Copy a configuration file struct
|
|
*/
|
|
|
|
void
|
|
copy_config_file_options(t_configuration_options *original, t_configuration_options *copy)
|
|
{
|
|
memcpy(copy, original, (int)sizeof(t_configuration_options));
|
|
|
|
/* Copy structures which point to allocated memory */
|
|
|
|
if (original->event_notifications.head != NULL)
|
|
{
|
|
/* For the event notifications, we can just reparse the string */
|
|
parse_event_notifications_list(copy, original->event_notifications_orig);
|
|
}
|
|
|
|
if (original->tablespace_mapping.head != NULL)
|
|
{
|
|
/*
|
|
* We allow multiple instances of "tablespace_mapping" in the configuration file
|
|
* which are appended to the list as they're encountered.
|
|
*/
|
|
tablespace_list_copy(original, copy);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Split argument into old_dir and new_dir and append to tablespace mapping
|
|
* list.
|
|
*
|
|
* Adapted from pg_basebackup.c
|
|
*/
|
|
static void
|
|
tablespace_list_append(t_configuration_options *options, const char *arg)
|
|
{
|
|
TablespaceListCell *cell = NULL;
|
|
char *dst = NULL;
|
|
char *dst_ptr = NULL;
|
|
const char *arg_ptr = NULL;
|
|
|
|
cell = (TablespaceListCell *) pg_malloc0(sizeof(TablespaceListCell));
|
|
if (cell == NULL)
|
|
{
|
|
log_error(_("unable to allocate memory; terminating"));
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
dst_ptr = dst = cell->old_dir;
|
|
for (arg_ptr = arg; *arg_ptr; arg_ptr++)
|
|
{
|
|
if (dst_ptr - dst >= MAXPGPATH)
|
|
{
|
|
log_error(_("directory name too long"));
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
if (*arg_ptr == '\\' && *(arg_ptr + 1) == '=')
|
|
; /* skip backslash escaping = */
|
|
else if (*arg_ptr == '=' && (arg_ptr == arg || *(arg_ptr - 1) != '\\'))
|
|
{
|
|
if (*cell->new_dir)
|
|
{
|
|
log_error(_("multiple \"=\" signs in tablespace mapping"));
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
else
|
|
{
|
|
dst = dst_ptr = cell->new_dir;
|
|
}
|
|
}
|
|
else
|
|
*dst_ptr++ = *arg_ptr;
|
|
}
|
|
|
|
if (!*cell->old_dir || !*cell->new_dir)
|
|
{
|
|
log_error(_("invalid tablespace mapping format \"%s\", must be \"OLDDIR=NEWDIR\""),
|
|
arg);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
canonicalize_path(cell->old_dir);
|
|
canonicalize_path(cell->new_dir);
|
|
|
|
if (options->tablespace_mapping.tail)
|
|
options->tablespace_mapping.tail->next = cell;
|
|
else
|
|
options->tablespace_mapping.head = cell;
|
|
|
|
options->tablespace_mapping.tail = cell;
|
|
}
|
|
|
|
|
|
static void
|
|
tablespace_list_copy(t_configuration_options *original, t_configuration_options *copy)
|
|
{
|
|
TablespaceListCell *orig_cell = original->tablespace_mapping.head;
|
|
|
|
for (orig_cell = config_file_options.tablespace_mapping.head; orig_cell; orig_cell = orig_cell->next)
|
|
{
|
|
TablespaceListCell *copy_cell = (TablespaceListCell *) pg_malloc0(sizeof(TablespaceListCell));
|
|
|
|
strncpy(copy_cell->old_dir, orig_cell->old_dir, sizeof(orig_cell->old_dir));
|
|
strncpy(copy_cell->new_dir, orig_cell->new_dir, sizeof(orig_cell->new_dir));
|
|
|
|
if (copy->tablespace_mapping.tail)
|
|
copy->tablespace_mapping.tail->next = copy_cell;
|
|
else
|
|
copy->tablespace_mapping.head = copy_cell;
|
|
|
|
copy->tablespace_mapping.tail = copy_cell;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
tablespace_list_free(t_configuration_options *options)
|
|
{
|
|
TablespaceListCell *cell = NULL;
|
|
TablespaceListCell *next_cell = NULL;
|
|
|
|
cell = options->tablespace_mapping.head;
|
|
|
|
while (cell != NULL)
|
|
{
|
|
next_cell = cell->next;
|
|
pfree(cell);
|
|
cell = next_cell;
|
|
}
|
|
|
|
options->tablespace_mapping.head = NULL;
|
|
options->tablespace_mapping.tail = NULL;
|
|
}
|
|
|
|
|
|
bool
|
|
modify_auto_conf(const char *data_dir, KeyValueList *items)
|
|
{
|
|
PQExpBufferData auto_conf;
|
|
PQExpBufferData auto_conf_tmp;
|
|
PQExpBufferData auto_conf_contents;
|
|
|
|
FILE *fp;
|
|
mode_t um;
|
|
struct stat auto_conf_st;
|
|
|
|
KeyValueList config = {NULL, NULL};
|
|
KeyValueListCell *cell = NULL;
|
|
|
|
bool success = true;
|
|
|
|
initPQExpBuffer(&auto_conf);
|
|
appendPQExpBuffer(&auto_conf, "%s/%s",
|
|
data_dir, PG_AUTOCONF_FILENAME);
|
|
|
|
fp = fopen(auto_conf.data, "r");
|
|
|
|
if (fp == NULL)
|
|
{
|
|
fprintf(stderr, "unable to open \"%s\": %s\n",
|
|
auto_conf.data,
|
|
strerror(errno));
|
|
termPQExpBuffer(&auto_conf);
|
|
return false;
|
|
}
|
|
|
|
success = ProcessPostgresConfigFile(fp, auto_conf.data, &config, NULL, NULL);
|
|
fclose(fp);
|
|
|
|
if (success == false)
|
|
{
|
|
fprintf(stderr, "unable to process \"%s\"\n",
|
|
auto_conf.data);
|
|
termPQExpBuffer(&auto_conf);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Append requested items to items extracted from the existing file.
|
|
*/
|
|
for (cell = items->head; cell; cell = cell->next)
|
|
{
|
|
key_value_list_replace_or_set(&config,
|
|
cell->key,
|
|
cell->value);
|
|
}
|
|
|
|
initPQExpBuffer(&auto_conf_tmp);
|
|
appendPQExpBuffer(&auto_conf_tmp, "%s.tmp",
|
|
auto_conf.data);
|
|
|
|
initPQExpBuffer(&auto_conf_contents);
|
|
|
|
/*
|
|
* Keep this in sync with src/backend/utils/misc/guc.c:write_auto_conf_file()
|
|
*/
|
|
appendPQExpBufferStr(&auto_conf_contents,
|
|
"# Do not edit this file manually!\n"
|
|
"# It will be overwritten by the ALTER SYSTEM command.\n");
|
|
|
|
for (cell = config.head; cell; cell = cell->next)
|
|
{
|
|
appendPQExpBuffer(&auto_conf_contents,
|
|
"%s = '%s'\n",
|
|
cell->key, cell->value);
|
|
}
|
|
|
|
stat(auto_conf.data, &auto_conf_st);
|
|
|
|
/*
|
|
* Set umask so the temporary file is created in the same mode as the original
|
|
* postgresql.auto.conf file.
|
|
*/
|
|
um = umask(~(auto_conf_st.st_mode));
|
|
fp = fopen(auto_conf_tmp.data, "w");
|
|
umask(um);
|
|
|
|
if (fp == NULL)
|
|
{
|
|
fprintf(stderr, "unable to open \"%s\": %s\n",
|
|
auto_conf_tmp.data,
|
|
strerror(errno));
|
|
}
|
|
else
|
|
{
|
|
if (fwrite(auto_conf_contents.data, strlen(auto_conf_contents.data), 1, fp) != 1)
|
|
{
|
|
fclose(fp);
|
|
}
|
|
else
|
|
{
|
|
fclose(fp);
|
|
|
|
/*
|
|
* Note: durable_rename() is not exposed to frontend code before Pg 10.
|
|
* We only really need to be modifying postgresql.auto.conf from Pg 12,
|
|
* but provide backwards compatibitilty for Pg 9.6 and earlier for the
|
|
* (unlikely) event that a repmgr built against one of those versions
|
|
* is being used against Pg 12 and later.
|
|
*/
|
|
|
|
#if (PG_ACTUAL_VERSION_NUM >= 100000)
|
|
if (durable_rename(auto_conf_tmp.data, auto_conf.data, LOG) != 0)
|
|
{
|
|
success = false;
|
|
}
|
|
#else
|
|
if (rename(auto_conf_tmp.data, auto_conf.data) < 0)
|
|
{
|
|
success = false;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
termPQExpBuffer(&auto_conf);
|
|
termPQExpBuffer(&auto_conf_tmp);
|
|
termPQExpBuffer(&auto_conf_contents);
|
|
|
|
key_value_list_free(&config);
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
/*
|
|
* parse_event_notifications_list()
|
|
*
|
|
*
|
|
*/
|
|
|
|
static void
|
|
parse_event_notifications_list(t_configuration_options *options, const char *arg)
|
|
{
|
|
const char *arg_ptr = NULL;
|
|
char event_type_buf[MAXLEN] = "";
|
|
char *dst_ptr = event_type_buf;
|
|
|
|
for (arg_ptr = arg; arg_ptr <= (arg + strlen(arg)); arg_ptr++)
|
|
{
|
|
/* ignore whitespace */
|
|
if (*arg_ptr == ' ' || *arg_ptr == '\t')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* comma (or end-of-string) should mark the end of an event type -
|
|
* just as long as there was something preceding it
|
|
*/
|
|
if ((*arg_ptr == ',' || *arg_ptr == '\0') && event_type_buf[0] != '\0')
|
|
{
|
|
EventNotificationListCell *cell;
|
|
|
|
cell = (EventNotificationListCell *) pg_malloc0(sizeof(EventNotificationListCell));
|
|
|
|
if (cell == NULL)
|
|
{
|
|
log_error(_("unable to allocate memory; terminating"));
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
strncpy(cell->event_type, event_type_buf, MAXLEN);
|
|
|
|
if (options->event_notifications.tail)
|
|
{
|
|
options->event_notifications.tail->next = cell;
|
|
}
|
|
else
|
|
{
|
|
options->event_notifications.head = cell;
|
|
}
|
|
|
|
options->event_notifications.tail = cell;
|
|
|
|
memset(event_type_buf, 0, MAXLEN);
|
|
dst_ptr = event_type_buf;
|
|
}
|
|
/* ignore duplicated commas */
|
|
else if (*arg_ptr == ',')
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
*dst_ptr++ = *arg_ptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
clear_event_notification_list(t_configuration_options *options)
|
|
{
|
|
if (options->event_notifications.head != NULL)
|
|
{
|
|
EventNotificationListCell *cell;
|
|
EventNotificationListCell *next_cell;
|
|
|
|
cell = options->event_notifications.head;
|
|
|
|
while (cell != NULL)
|
|
{
|
|
next_cell = cell->next;
|
|
pfree(cell);
|
|
cell = next_cell;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
parse_output_to_argv(const char *string, char ***argv_array)
|
|
{
|
|
int options_len = 0;
|
|
char *options_string = NULL;
|
|
char *options_string_ptr = NULL;
|
|
int c = 1,
|
|
argc_item = 1;
|
|
char *argv_item = NULL;
|
|
char **local_argv_array = NULL;
|
|
ItemListCell *cell;
|
|
|
|
/*
|
|
* Add parsed options to this list, then copy to an array to pass to
|
|
* getopt
|
|
*/
|
|
ItemList option_argv = {NULL, NULL};
|
|
|
|
options_len = strlen(string) + 1;
|
|
options_string = pg_malloc0(options_len);
|
|
options_string_ptr = options_string;
|
|
|
|
/* Copy the string before operating on it with strtok() */
|
|
strncpy(options_string, string, options_len);
|
|
|
|
/* Extract arguments into a list and keep a count of the total */
|
|
while ((argv_item = strtok(options_string_ptr, " ")) != NULL)
|
|
{
|
|
item_list_append(&option_argv, trim(argv_item));
|
|
|
|
argc_item++;
|
|
|
|
if (options_string_ptr != NULL)
|
|
options_string_ptr = NULL;
|
|
}
|
|
|
|
pfree(options_string);
|
|
|
|
/*
|
|
* Array of argument values to pass to getopt_long - this will need to
|
|
* include an empty string as the first value (normally this would be the
|
|
* program name)
|
|
*/
|
|
local_argv_array = pg_malloc0(sizeof(char *) * (argc_item + 2));
|
|
|
|
/* Insert a blank dummy program name at the start of the array */
|
|
local_argv_array[0] = pg_malloc0(1);
|
|
|
|
/*
|
|
* Copy the previously extracted arguments from our list to the array
|
|
*/
|
|
for (cell = option_argv.head; cell; cell = cell->next)
|
|
{
|
|
int argv_len = strlen(cell->string) + 1;
|
|
|
|
local_argv_array[c] = (char *)pg_malloc0(argv_len);
|
|
|
|
strncpy(local_argv_array[c], cell->string, argv_len);
|
|
|
|
c++;
|
|
}
|
|
|
|
local_argv_array[c] = NULL;
|
|
|
|
item_list_free(&option_argv);
|
|
|
|
*argv_array = local_argv_array;
|
|
|
|
return argc_item;
|
|
}
|
|
|
|
|
|
void
|
|
free_parsed_argv(char ***argv_array)
|
|
{
|
|
char **local_argv_array = *argv_array;
|
|
int i = 0;
|
|
|
|
while (local_argv_array[i] != NULL)
|
|
{
|
|
pfree((char *)local_argv_array[i]);
|
|
i++;
|
|
}
|
|
|
|
pfree((char **)local_argv_array);
|
|
*argv_array = NULL;
|
|
}
|
|
|
|
|
|
bool
|
|
parse_pg_basebackup_options(const char *pg_basebackup_options, t_basebackup_options *backup_options, int server_version_num, ItemList *error_list)
|
|
{
|
|
bool backup_options_ok = true;
|
|
|
|
int c = 0,
|
|
argc_item = 0;
|
|
|
|
char **argv_array = NULL;
|
|
|
|
int optindex = 0;
|
|
|
|
struct option *long_options = NULL;
|
|
|
|
|
|
/*
|
|
* We're only interested in these options.
|
|
*/
|
|
|
|
static struct option long_options_10[] =
|
|
{
|
|
{"slot", required_argument, NULL, 'S'},
|
|
{"wal-method", required_argument, NULL, 'X'},
|
|
{"no-slot", no_argument, NULL, 1},
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
|
|
/*
|
|
* Pre-PostgreSQL 10 options
|
|
*/
|
|
static struct option long_options_legacy[] =
|
|
{
|
|
{"slot", required_argument, NULL, 'S'},
|
|
{"xlog-method", required_argument, NULL, 'X'},
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
|
|
|
|
/* Don't attempt to tokenise an empty string */
|
|
if (!strlen(pg_basebackup_options))
|
|
return backup_options_ok;
|
|
|
|
if (server_version_num >= 100000)
|
|
long_options = long_options_10;
|
|
else
|
|
long_options = long_options_legacy;
|
|
|
|
argc_item = parse_output_to_argv(pg_basebackup_options, &argv_array);
|
|
|
|
/* Reset getopt's optind variable */
|
|
optind = 0;
|
|
|
|
/* Prevent getopt from emitting errors */
|
|
opterr = 0;
|
|
|
|
while ((c = getopt_long(argc_item, argv_array, "S:X:", long_options,
|
|
&optindex)) != -1)
|
|
{
|
|
switch (c)
|
|
{
|
|
case 'S':
|
|
strncpy(backup_options->slot, optarg, MAXLEN);
|
|
break;
|
|
case 'X':
|
|
strncpy(backup_options->wal_method, optarg, MAXLEN);
|
|
break;
|
|
case 1:
|
|
backup_options->no_slot = true;
|
|
break;
|
|
case '?':
|
|
if (server_version_num >= 100000 && optopt == 1)
|
|
{
|
|
if (error_list != NULL)
|
|
{
|
|
item_list_append(error_list, "invalid use of --no-slot");
|
|
}
|
|
backup_options_ok = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (backup_options->no_slot == true && backup_options->slot[0] != '\0')
|
|
{
|
|
if (error_list != NULL)
|
|
{
|
|
item_list_append(error_list, "--no-slot cannot be used with -S/--slot");
|
|
}
|
|
backup_options_ok = false;
|
|
}
|
|
|
|
free_parsed_argv(&argv_array);
|
|
|
|
return backup_options_ok;
|
|
}
|
|
|
|
|
|
const char *
|
|
print_connection_check_type(ConnectionCheckType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case CHECK_PING:
|
|
return "ping";
|
|
case CHECK_QUERY:
|
|
return "query";
|
|
case CHECK_CONNECTION:
|
|
return "connection";
|
|
}
|
|
|
|
/* should never reach here */
|
|
return "UNKNOWN";
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *
|
|
format_failover_mode(failover_mode_opt failover)
|
|
{
|
|
switch (failover)
|
|
{
|
|
case FAILOVER_MANUAL:
|
|
return "manual";
|
|
case FAILOVER_AUTOMATIC:
|
|
return "automatic";
|
|
default:
|
|
return "unknown failover mode";
|
|
}
|
|
}
|