mirror of
https://github.com/EnterpriseDB/repmgr.git
synced 2026-03-22 14:46:29 +00:00
2322 lines
60 KiB
C
2322 lines
60 KiB
C
/*
|
|
* configfile.c - parse repmgr.conf and other configuration-related functionality
|
|
*
|
|
* Copyright (c) EnterpriseDB Corporation, 2010-2021
|
|
*
|
|
* 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;
|
|
|
|
|
|
static void parse_config(bool terse);
|
|
static void _parse_config(ItemList *error_list, ItemList *warning_list);
|
|
|
|
static void _parse_line(char *buf, char *name, char *value);
|
|
static void parse_event_notifications_list(EventNotificationList *event_notifications, const char *arg);
|
|
static void clear_event_notification_list(EventNotificationList *event_notifications);
|
|
|
|
static void copy_config_file_options(t_configuration_options *original, t_configuration_options *copy);
|
|
|
|
static void tablespace_list_append(TablespaceList *tablespace_mapping, 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, 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_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;
|
|
}
|
|
|
|
|
|
static void
|
|
_parse_config(ItemList *error_list, ItemList *warning_list)
|
|
{
|
|
FILE *fp;
|
|
char base_directory[MAXPGPATH];
|
|
bool config_ok;
|
|
ConfigFileSetting *setting;
|
|
int i = 0;
|
|
|
|
/*
|
|
* Clear lists pointing to allocated memory
|
|
*/
|
|
|
|
clear_event_notification_list(&config_file_options.event_notifications);
|
|
tablespace_list_free(&config_file_options);
|
|
|
|
/*
|
|
* Initialise with default values
|
|
*/
|
|
setting = &config_file_settings[0];
|
|
|
|
do {
|
|
switch (setting->type)
|
|
{
|
|
case CONFIG_INT:
|
|
*setting->val.intptr = setting->defval.intdefault;
|
|
break;
|
|
case CONFIG_BOOL:
|
|
*setting->val.boolptr = setting->defval.booldefault;
|
|
break;
|
|
case CONFIG_STRING:
|
|
{
|
|
memset((char *)setting->val.strptr, 0, setting->maxval.strmaxlen);
|
|
if (setting->defval.strdefault != NULL)
|
|
strncpy((char *)setting->val.strptr, setting->defval.strdefault, setting->maxval.strmaxlen);
|
|
break;
|
|
}
|
|
case CONFIG_FAILOVER_MODE:
|
|
*setting->val.failovermodeptr = setting->defval.failovermodedefault;
|
|
break;
|
|
case CONFIG_CONNECTION_CHECK_TYPE:
|
|
*setting->val.checktypeptr = setting->defval.checktypedefault;
|
|
break;
|
|
case CONFIG_REPLICATION_TYPE:
|
|
*setting->val.replicationtypeptr = setting->defval.replicationtypedefault;
|
|
break;
|
|
case CONFIG_EVENT_NOTIFICATION_LIST:
|
|
case CONFIG_TABLESPACE_MAPPING:
|
|
/* no default for these types; lists cleared above */
|
|
break;
|
|
default:
|
|
/* this should never happen */
|
|
log_error("unhandled setting type %i", (int)setting->type);
|
|
}
|
|
|
|
i++;
|
|
setting = &config_file_settings[i];
|
|
} while (setting->name != NULL);
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* A configuration file has been found, either provided by the user or
|
|
* found in one of the default locations. Sanity check whether we
|
|
* can open it, and fail with an error about the nature of the file
|
|
* (provided or default) if not. We do this here rather than having
|
|
* to teach the configuration file parser the difference.
|
|
*/
|
|
|
|
fp = fopen(config_file_path, "r");
|
|
|
|
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);
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
strncpy(base_directory, config_file_path, MAXPGPATH);
|
|
canonicalize_path(base_directory);
|
|
get_parent_directory(base_directory);
|
|
|
|
config_ok = ProcessRepmgrConfigFile(config_file_path, base_directory, error_list, warning_list);
|
|
|
|
/*
|
|
* Perform some more complex checks which the file processing step can't do,
|
|
* including checking for required parameters and sanity-checking parameters
|
|
* with dependencies on other parameters.
|
|
*/
|
|
if (config_ok == true)
|
|
{
|
|
/* check required parameters */
|
|
if (config_file_options.node_id == UNKNOWN_NODE_ID)
|
|
{
|
|
item_list_append(error_list, _("\"node_id\": required parameter was not found"));
|
|
}
|
|
|
|
if (!strlen(config_file_options.node_name))
|
|
{
|
|
item_list_append(error_list, _("\"node_name\": required parameter was not found"));
|
|
}
|
|
|
|
if (!strlen(config_file_options.data_directory))
|
|
{
|
|
item_list_append(error_list, _("\"data_directory\": required parameter was not found"));
|
|
}
|
|
|
|
if (!strlen(config_file_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(config_file_options.conninfo, &conninfo_errmsg) == false)
|
|
{
|
|
PQExpBufferData error_message_buf;
|
|
initPQExpBuffer(&error_message_buf);
|
|
|
|
appendPQExpBuffer(&error_message_buf,
|
|
_("\"conninfo\": %s (provided: \"%s\")"),
|
|
conninfo_errmsg,
|
|
config_file_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 (config_file_options.repmgrd_standby_startup_timeout == -1)
|
|
{
|
|
config_file_options.repmgrd_standby_startup_timeout = config_file_options.standby_reconnect_timeout;
|
|
}
|
|
|
|
/* add warning about changed "barman_" parameter meanings */
|
|
if ((config_file_options.barman_host[0] == '\0' && config_file_options.barman_server[0] != '\0') ||
|
|
(config_file_options.barman_host[0] != '\0' && config_file_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 (config_file_options.archive_ready_warning >= config_file_options.archive_ready_critical)
|
|
{
|
|
item_list_append(error_list,
|
|
_("\"archive_ready_critical\" must be greater than \"archive_ready_warning\""));
|
|
}
|
|
|
|
if (config_file_options.replication_lag_warning >= config_file_options.replication_lag_critical)
|
|
{
|
|
item_list_append(error_list,
|
|
_("\"replication_lag_critical\" must be greater than \"replication_lag_warning\""));
|
|
}
|
|
|
|
if (config_file_options.standby_reconnect_timeout < config_file_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(ItemList *error_list, ItemList *warning_list, const char *name, const char *value)
|
|
{
|
|
ConfigFileSetting *setting = &config_file_settings[0];
|
|
int i = 0;
|
|
do {
|
|
if (strcmp(name, setting->name) == 0)
|
|
{
|
|
switch (setting->type)
|
|
{
|
|
/* Generic types */
|
|
case CONFIG_BOOL:
|
|
{
|
|
*(bool *)setting->val.boolptr = parse_bool(value, name, error_list);
|
|
break;
|
|
}
|
|
case CONFIG_INT:
|
|
{
|
|
*(int *)setting->val.intptr = repmgr_atoi(value, name, error_list, setting->minval.intminval);
|
|
break;
|
|
}
|
|
case CONFIG_STRING:
|
|
{
|
|
if (strlen(value) > setting->maxval.strmaxlen)
|
|
{
|
|
item_list_append_format(error_list,
|
|
_("value for \"%s\" must contain fewer than %i characters (current length: %i)"),
|
|
name,
|
|
setting->maxval.strmaxlen,
|
|
(int)strlen(value));
|
|
}
|
|
else
|
|
{
|
|
/* custom function for processing this string value */
|
|
if (setting->process.process_func != NULL)
|
|
{
|
|
(*setting->process.process_func)(name, value, (char *)setting->val.strptr, error_list);
|
|
}
|
|
/* otherwise copy as-is */
|
|
else
|
|
{
|
|
strncpy((char *)setting->val.strptr, value, setting->maxval.strmaxlen);
|
|
}
|
|
|
|
/* post-processing, e.g. path canonicalisation */
|
|
if (setting->process.postprocess_func != NULL)
|
|
{
|
|
(*setting->process.postprocess_func)(name, value, (char *)setting->val.strptr, error_list);
|
|
}
|
|
|
|
if (setting->process.providedptr != NULL)
|
|
{
|
|
*(bool *)setting->process.providedptr = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
/* repmgr types */
|
|
case CONFIG_FAILOVER_MODE:
|
|
{
|
|
if (strcmp(value, "manual") == 0)
|
|
{
|
|
*(failover_mode_opt *)setting->val.failovermodeptr = FAILOVER_MANUAL;
|
|
}
|
|
else if (strcmp(value, "automatic") == 0)
|
|
{
|
|
*(failover_mode_opt *)setting->val.failovermodeptr = FAILOVER_AUTOMATIC;
|
|
}
|
|
else
|
|
{
|
|
item_list_append_format(error_list,
|
|
_("value for \"%s\" must be \"automatic\" or \"manual\"\n"),
|
|
name);
|
|
}
|
|
break;
|
|
}
|
|
case CONFIG_CONNECTION_CHECK_TYPE:
|
|
{
|
|
if (strcasecmp(value, "ping") == 0)
|
|
{
|
|
*(ConnectionCheckType *)setting->val.checktypeptr = CHECK_PING;
|
|
}
|
|
else if (strcasecmp(value, "connection") == 0)
|
|
{
|
|
*(ConnectionCheckType *)setting->val.checktypeptr = CHECK_CONNECTION;
|
|
}
|
|
else if (strcasecmp(value, "query") == 0)
|
|
{
|
|
*(ConnectionCheckType *)setting->val.checktypeptr = CHECK_QUERY;
|
|
}
|
|
else
|
|
{
|
|
item_list_append_format(error_list,
|
|
_("value for \"%s\" must be \"ping\", \"connection\" or \"query\"\n"),
|
|
name);
|
|
}
|
|
break;
|
|
}
|
|
case CONFIG_REPLICATION_TYPE:
|
|
{
|
|
if (strcasecmp(value, "physical") == 0)
|
|
{
|
|
*(ReplicationType *)setting->val.replicationtypeptr = REPLICATION_TYPE_PHYSICAL;
|
|
}
|
|
else
|
|
{
|
|
item_list_append_format(error_list,
|
|
_("value for \"%s\" must be \"physical\"\n"),
|
|
name);
|
|
}
|
|
break;
|
|
}
|
|
case CONFIG_EVENT_NOTIFICATION_LIST:
|
|
{
|
|
parse_event_notifications_list((EventNotificationList *)setting->val.notificationlistptr,
|
|
value);
|
|
break;
|
|
}
|
|
case CONFIG_TABLESPACE_MAPPING:
|
|
{
|
|
tablespace_list_append((TablespaceList *)setting->val.tablespacemappingptr, value);
|
|
break;
|
|
}
|
|
default:
|
|
/* this should never happen */
|
|
log_error("encountered unknown configuration type %i when processing \"%s\"",
|
|
(int)setting->type,
|
|
setting->name);
|
|
}
|
|
|
|
/* Configuration item found - we can stop processing here */
|
|
return;
|
|
}
|
|
i++;
|
|
setting = &config_file_settings[i];
|
|
} while (setting->name);
|
|
|
|
/* If we reach here, the configuration item is either deprecated or unknown */
|
|
|
|
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
|
|
{
|
|
item_list_append_format(warning_list,
|
|
_("%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);
|
|
}
|
|
|
|
|
|
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 \"%s\" provided for \"%s\""),
|
|
value,
|
|
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 for \"%s\" must be one of ms/s/min/h/d (provided: \"%s\")"),
|
|
name,
|
|
value);
|
|
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
|
|
* - always_promote
|
|
* - 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_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));
|
|
}
|
|
|
|
/* always_promote */
|
|
if (config_file_options.always_promote != orig_config_file_options.always_promote)
|
|
{
|
|
item_list_append_format(&config_changes,
|
|
_("\"always_promote\" changed from \"%s\" to \"%s\""),
|
|
format_bool(orig_config_file_options.always_promote),
|
|
format_bool(config_file_options.always_promote));
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
|
|
/*
|
|
* Dump the parsed configuration
|
|
*/
|
|
void
|
|
dump_config(void)
|
|
{
|
|
ConfigFileSetting *setting;
|
|
int i = 0;
|
|
|
|
setting = &config_file_settings[0];
|
|
|
|
do {
|
|
printf("%s|", setting->name);
|
|
switch (setting->type)
|
|
{
|
|
case CONFIG_INT:
|
|
printf("%i", *setting->val.intptr);
|
|
break;
|
|
case CONFIG_BOOL:
|
|
printf("%s", format_bool(*setting->val.boolptr));
|
|
break;
|
|
case CONFIG_STRING:
|
|
printf("%s", setting->val.strptr);
|
|
break;
|
|
case CONFIG_FAILOVER_MODE:
|
|
printf("%s", format_failover_mode(*setting->val.failovermodeptr));
|
|
break;
|
|
case CONFIG_CONNECTION_CHECK_TYPE:
|
|
printf("%s", print_connection_check_type(*setting->val.checktypeptr));
|
|
break;
|
|
case CONFIG_REPLICATION_TYPE:
|
|
printf("%s", print_replication_type(*setting->val.replicationtypeptr));
|
|
break;
|
|
case CONFIG_EVENT_NOTIFICATION_LIST:
|
|
{
|
|
char *list = print_event_notification_list(setting->val.notificationlistptr);
|
|
printf("%s", list);
|
|
pfree(list);
|
|
}
|
|
break;
|
|
case CONFIG_TABLESPACE_MAPPING:
|
|
{
|
|
char *list = print_tablespace_mapping(setting->val.tablespacemappingptr);
|
|
printf("%s", list);
|
|
pfree(list);
|
|
}
|
|
break;
|
|
default:
|
|
/* this should never happen */
|
|
log_error("unhandled setting type %i", (int)setting->type);
|
|
}
|
|
puts("");
|
|
i++;
|
|
setting = &config_file_settings[i];
|
|
} while (setting->name != NULL);
|
|
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void
|
|
repmgr_canonicalize_path(const char *name, const char *value, char *config_item, ItemList *errors)
|
|
{
|
|
/* NOTE: canonicalize_path does not produce any errors */
|
|
canonicalize_path(config_item);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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(©->event_notifications, 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(TablespaceList *tablespace_mapping, 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 (tablespace_mapping->tail)
|
|
tablespace_mapping->tail->next = cell;
|
|
else
|
|
tablespace_mapping->head = cell;
|
|
|
|
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(copy_cell->old_dir));
|
|
strncpy(copy_cell->new_dir, orig_cell->new_dir, sizeof(copy_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 data_dir_st;
|
|
|
|
KeyValueList config = {NULL, NULL};
|
|
KeyValueListCell *cell = NULL;
|
|
|
|
bool success = true;
|
|
|
|
initPQExpBuffer(&auto_conf);
|
|
appendPQExpBuffer(&auto_conf, "%s/%s",
|
|
data_dir, PG_AUTOCONF_FILENAME);
|
|
|
|
success = ProcessPostgresConfigFile(auto_conf.data,
|
|
NULL,
|
|
false, /* we don't care if the file does not exist */
|
|
&config,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (success == false)
|
|
{
|
|
fprintf(stderr, "unable to process \"%s\"\n",
|
|
auto_conf.data);
|
|
termPQExpBuffer(&auto_conf);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Append requested items to any 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 the data directory for the file mode */
|
|
if (stat(data_dir, &data_dir_st) != 0)
|
|
{
|
|
/*
|
|
* This is highly unlikely to happen, but if it does (e.g. freak
|
|
* race condition with some rogue process which is messing about
|
|
* with the data directory), there's not a lot we can do.
|
|
*/
|
|
log_error(_("error encountered when checking \"%s\""),
|
|
data_dir);
|
|
log_detail("%s", strerror(errno));
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/*
|
|
* Set umask so the temporary file is created in the same mode as the data
|
|
* directory. In PostgreSQL 11 and later this can be 0700 or 0750.
|
|
*/
|
|
um = umask(~(data_dir_st.st_mode));
|
|
|
|
fp = fopen(auto_conf_tmp.data, "w");
|
|
|
|
umask(um);
|
|
|
|
if (fp == NULL)
|
|
{
|
|
fprintf(stderr, "unable to open \"%s\" for writing: %s\n",
|
|
auto_conf_tmp.data,
|
|
strerror(errno));
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
if (fwrite(auto_conf_contents.data, strlen(auto_conf_contents.data), 1, fp) != 1)
|
|
{
|
|
fprintf(stderr, "unable to write to \"%s\": %s\n",
|
|
auto_conf_tmp.data,
|
|
strerror(errno));
|
|
fclose(fp);
|
|
success = false;
|
|
}
|
|
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 compatibility 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(EventNotificationList *event_notifications, 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 (event_notifications->tail)
|
|
{
|
|
event_notifications->tail->next = cell;
|
|
}
|
|
else
|
|
{
|
|
event_notifications->head = cell;
|
|
}
|
|
|
|
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(EventNotificationList *event_notifications)
|
|
{
|
|
if (event_notifications->head != NULL)
|
|
{
|
|
EventNotificationListCell *cell;
|
|
EventNotificationListCell *next_cell;
|
|
|
|
cell = event_notifications->head;
|
|
|
|
while (cell != NULL)
|
|
{
|
|
next_cell = cell->next;
|
|
pfree(cell);
|
|
cell = next_cell;
|
|
}
|
|
}
|
|
|
|
event_notifications->head = NULL;
|
|
event_notifications->tail = NULL;
|
|
}
|
|
|
|
|
|
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'},
|
|
{"waldir", required_argument, NULL, 1},
|
|
{"no-slot", no_argument, NULL, 2},
|
|
{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'},
|
|
{"xlogdir", required_argument, NULL, 1},
|
|
{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:
|
|
strncpy(backup_options->waldir, optarg, MAXPGPATH);
|
|
break;
|
|
case 2:
|
|
backup_options->no_slot = true;
|
|
break;
|
|
case '?':
|
|
if (server_version_num >= 100000 && optopt == 2)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* If --waldir/--xlogdir provided, check it's an absolute path.
|
|
*/
|
|
if (backup_options->waldir[0] != '\0')
|
|
{
|
|
canonicalize_path(backup_options->waldir);
|
|
if (!is_absolute_path(backup_options->waldir))
|
|
{
|
|
if (error_list != NULL)
|
|
{
|
|
item_list_append_format(error_list,
|
|
"--%s must be provided with an absolute path",
|
|
server_version_num >= 100000 ? "waldir" : "xlogdir");
|
|
}
|
|
backup_options_ok = false;
|
|
}
|
|
}
|
|
|
|
free_parsed_argv(&argv_array);
|
|
|
|
return backup_options_ok;
|
|
}
|
|
|
|
|
|
const char *
|
|
print_replication_type(ReplicationType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case REPLICATION_TYPE_PHYSICAL:
|
|
return "physical";
|
|
}
|
|
|
|
/* should never reach here */
|
|
return "UNKNOWN";
|
|
}
|
|
|
|
|
|
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";
|
|
}
|
|
|
|
|
|
|
|
char *
|
|
print_event_notification_list(EventNotificationList *list)
|
|
{
|
|
PQExpBufferData buf;
|
|
char *ptr;
|
|
EventNotificationListCell *cell;
|
|
int ptr_len;
|
|
|
|
initPQExpBuffer(&buf);
|
|
cell = list->head;
|
|
|
|
while (cell != NULL)
|
|
{
|
|
appendPQExpBufferStr(&buf, cell->event_type);
|
|
|
|
if (cell->next)
|
|
appendPQExpBufferChar(&buf, ',');
|
|
|
|
cell = cell->next;
|
|
}
|
|
|
|
ptr_len = strlen(buf.data);
|
|
ptr = palloc0(ptr_len + 1);
|
|
|
|
strncpy(ptr, buf.data, ptr_len);
|
|
|
|
termPQExpBuffer(&buf);
|
|
|
|
return ptr;
|
|
}
|
|
|
|
|
|
char *
|
|
print_tablespace_mapping(TablespaceList *tablespace_mapping)
|
|
{
|
|
TablespaceListCell *cell;
|
|
bool first = true;
|
|
PQExpBufferData buf;
|
|
char *ptr;
|
|
|
|
initPQExpBuffer(&buf);
|
|
|
|
for (cell = tablespace_mapping->head; cell; cell = cell->next)
|
|
{
|
|
if (first == true)
|
|
first = false;
|
|
else
|
|
appendPQExpBufferChar(&buf, ',');
|
|
|
|
appendPQExpBuffer(&buf, "%s=%s",
|
|
cell->old_dir, cell->new_dir);
|
|
}
|
|
|
|
ptr = palloc0(strlen(buf.data) + 1);
|
|
strncpy(ptr, buf.data, strlen(buf.data));
|
|
|
|
termPQExpBuffer(&buf);
|
|
|
|
return ptr;
|
|
}
|
|
|
|
|
|
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";
|
|
}
|
|
}
|