Improve configuration file parsing and error detection

- enable provision of various boolean values
- improve enforcement of minimum values for integers
This commit is contained in:
Ian Barwick
2017-04-21 13:12:28 +09:00
parent 0b0a0c69fc
commit 073afbce54
2 changed files with 109 additions and 61 deletions

164
config.c
View File

@@ -159,6 +159,7 @@ load_config(const char *config_file, bool verbose, t_configuration_options *opti
return parse_config(options); return parse_config(options);
} }
bool bool
parse_config(t_configuration_options *options) parse_config(t_configuration_options *options)
{ {
@@ -189,6 +190,7 @@ parse_config(t_configuration_options *options)
return true; return true;
} }
static void static void
_parse_config(t_configuration_options *options, ItemList *error_list, ItemList *warning_list) _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *warning_list)
{ {
@@ -326,11 +328,11 @@ _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *
/* Copy into correct entry in parameters struct */ /* Copy into correct entry in parameters struct */
if (strcmp(name, "node_id") == 0) if (strcmp(name, "node_id") == 0)
{ {
options->node_id = repmgr_atoi(value, name, error_list, false); options->node_id = repmgr_atoi(value, name, error_list, 1);
node_id_found = true; node_id_found = true;
} }
else if (strcmp(name, "upstream_node_id") == 0) else if (strcmp(name, "upstream_node_id") == 0)
options->upstream_node_id = repmgr_atoi(value, name, error_list, false); options->upstream_node_id = repmgr_atoi(value, name, error_list, 1);
else if (strcmp(name, "conninfo") == 0) else if (strcmp(name, "conninfo") == 0)
strncpy(options->conninfo, value, MAXLEN); strncpy(options->conninfo, value, MAXLEN);
else if (strcmp(name, "pg_bindir") == 0) else if (strcmp(name, "pg_bindir") == 0)
@@ -342,7 +344,7 @@ _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *
else if (strcmp(value, "bdr") == 0) else if (strcmp(value, "bdr") == 0)
options->replication_type = REPLICATION_TYPE_BDR; options->replication_type = REPLICATION_TYPE_BDR;
else else
item_list_append(error_list, _("value for 'replication_type' must be 'physical' or 'bdr'\n")); item_list_append(error_list, _("value for 'replication_type' must be 'physical' or 'bdr'"));
} }
/* log settings */ /* log settings */
@@ -355,8 +357,7 @@ _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *
/* standby clone settings */ /* standby clone settings */
else if (strcmp(name, "use_replication_slots") == 0) else if (strcmp(name, "use_replication_slots") == 0)
/* XXX we should have a dedicated boolean argument format */ options->use_replication_slots = parse_bool(value, name, error_list);
options->use_replication_slots = repmgr_atoi(value, name, error_list, false);
else if (strcmp(name, "rsync_options") == 0) else if (strcmp(name, "rsync_options") == 0)
strncpy(options->rsync_options, value, MAXLEN); strncpy(options->rsync_options, value, MAXLEN);
else if (strcmp(name, "ssh_options") == 0) else if (strcmp(name, "ssh_options") == 0)
@@ -385,26 +386,25 @@ _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *
} }
} }
else if (strcmp(name, "priority") == 0) else if (strcmp(name, "priority") == 0)
options->priority = repmgr_atoi(value, name, error_list, true); options->priority = repmgr_atoi(value, name, error_list, 0);
else if (strcmp(name, "promote_command") == 0) else if (strcmp(name, "promote_command") == 0)
strncpy(options->promote_command, value, MAXLEN); strncpy(options->promote_command, value, MAXLEN);
else if (strcmp(name, "follow_command") == 0) else if (strcmp(name, "follow_command") == 0)
strncpy(options->follow_command, value, MAXLEN); strncpy(options->follow_command, value, MAXLEN);
else if (strcmp(name, "reconnect_attempts") == 0) else if (strcmp(name, "reconnect_attempts") == 0)
options->reconnect_attempts = repmgr_atoi(value, "reconnect_attempts", error_list, false); options->reconnect_attempts = repmgr_atoi(value, name, error_list, 0);
else if (strcmp(name, "reconnect_interval") == 0) else if (strcmp(name, "reconnect_interval") == 0)
options->reconnect_interval = repmgr_atoi(value, "reconnect_interval", error_list, false); options->reconnect_interval = repmgr_atoi(value, name, error_list, 0);
else if (strcmp(name, "monitor_interval_secs") == 0) else if (strcmp(name, "monitor_interval_secs") == 0)
options->monitor_interval_secs = repmgr_atoi(value, name, error_list, false); options->monitor_interval_secs = repmgr_atoi(value, name, error_list, 1);
else if (strcmp(name, "retry_promote_interval_secs") == 0) else if (strcmp(name, "retry_promote_interval_secs") == 0)
options->retry_promote_interval_secs = repmgr_atoi(value, name, error_list, false); options->retry_promote_interval_secs = repmgr_atoi(value, name, error_list, 1);
else if (strcmp(name, "monitoring_history") == 0) else if (strcmp(name, "monitoring_history") == 0)
/* XXX boolean */ options->monitoring_history = parse_bool(value, name, error_list);
options->monitoring_history = repmgr_atoi(value, name, error_list, false);
/* witness settings */ /* witness settings */
else if (strcmp(name, "witness_repl_nodes_sync_interval_secs") == 0) else if (strcmp(name, "witness_repl_nodes_sync_interval_secs") == 0)
options->witness_repl_nodes_sync_interval_secs = repmgr_atoi(value, name, error_list, false); options->witness_repl_nodes_sync_interval_secs = repmgr_atoi(value, name, error_list, 1);
/* service settings */ /* service settings */
else if (strcmp(name, "pg_ctl_options") == 0) else if (strcmp(name, "pg_ctl_options") == 0)
@@ -453,25 +453,25 @@ _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *
else if (strcmp(name, "cluster") == 0) else if (strcmp(name, "cluster") == 0)
{ {
item_list_append(warning_list, item_list_append(warning_list,
_("parameter 'cluster' is deprecated and will be ignored")); _("parameter \"cluster\" is deprecated and will be ignored"));
known_parameter = false; known_parameter = false;
} }
else if (strcmp(name, "failover") == 0) else if (strcmp(name, "failover") == 0)
{ {
item_list_append(warning_list, item_list_append(warning_list,
_("parameter 'failover' has been renamed 'failover_mode'")); _("parameter \"failover\" has been renamed 'failover_mode'"));
known_parameter = false; known_parameter = false;
} }
else if (strcmp(name, "node") == 0) else if (strcmp(name, "node") == 0)
{ {
item_list_append(warning_list, item_list_append(warning_list,
_("parameter 'node' has been renamed 'node_id'")); _("parameter \"node\" has been renamed 'node_id'"));
known_parameter = false; known_parameter = false;
} }
else if (strcmp(name, "upstream_node") == 0) else if (strcmp(name, "upstream_node") == 0)
{ {
item_list_append(warning_list, item_list_append(warning_list,
_("parameter 'upstream_node' has been renamed 'upstream_node_id'")); _("parameter \"upstream_node\" has been renamed 'upstream_node_id'"));
known_parameter = false; known_parameter = false;
} }
else else
@@ -489,7 +489,7 @@ _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *
char error_message_buf[MAXLEN] = ""; char error_message_buf[MAXLEN] = "";
snprintf(error_message_buf, snprintf(error_message_buf,
MAXLEN, MAXLEN,
_("no value provided for parameter \"%s\""), _("\"%s\": no value provided"),
name); name);
item_list_append(error_list, error_message_buf); item_list_append(error_list, error_message_buf);
@@ -500,25 +500,17 @@ _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *
/* check required parameters */ /* check required parameters */
if (node_id_found == false) if (node_id_found == false)
{ {
item_list_append(error_list, _("\"node_id\": parameter was not found")); item_list_append(error_list, _("\"node_id\": required parameter was not found"));
}
else if (options->node_id == 0)
{
item_list_append(error_list, _("\"node_id\": must be greater than zero"));
}
else if (options->node_id < 0)
{
item_list_append(error_list, _("\"node_id\": must be a positive signed 32 bit integer, i.e. 2147483647 or less"));
} }
if (!strlen(options->node_name)) if (!strlen(options->node_name))
{ {
item_list_append(error_list, _("\"node_name\": parameter was not found")); item_list_append(error_list, _("\"node_name\": required parameter was not found"));
} }
if (!strlen(options->conninfo)) if (!strlen(options->conninfo))
{ {
item_list_append(error_list, _("\"conninfo\": parameter was not found")); item_list_append(error_list, _("\"conninfo\": required parameter was not found"));
} }
else else
{ {
@@ -541,8 +533,6 @@ _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *
PQconninfoFree(conninfo_options); PQconninfoFree(conninfo_options);
} }
} }
@@ -644,12 +634,11 @@ exit_with_errors(ItemList *config_errors, ItemList *config_warnings)
{ {
ItemListCell *cell; ItemListCell *cell;
log_error(_("%s: following errors were found in the configuration file:"), progname()); log_error(_("following errors were found in the configuration file:"));
for (cell = config_errors->head; cell; cell = cell->next) for (cell = config_errors->head; cell; cell = cell->next)
{ {
fprintf(stderr, " "); fprintf(stderr, " %s\n", cell->string);
log_error("%s", cell->string);
} }
if (config_warnings->head != NULL) if (config_warnings->head != NULL)
@@ -657,12 +646,13 @@ exit_with_errors(ItemList *config_errors, ItemList *config_warnings)
log_warning(_("the following problems were also found in the configuration file:")); log_warning(_("the following problems were also found in the configuration file:"));
for (cell = config_warnings->head; cell; cell = cell->next) for (cell = config_warnings->head; cell; cell = cell->next)
{ {
fprintf(stderr, " "); fprintf(stderr, " %s\n", cell->string);
log_warning("%s", cell->string);
} }
} }
exit(ERR_BAD_CONFIG);
} }
void void
item_list_append(ItemList *item_list, char *error_message) item_list_append(ItemList *item_list, char *error_message)
{ {
@@ -698,22 +688,21 @@ item_list_append(ItemList *item_list, char *error_message)
* otherwise exit * otherwise exit
*/ */
int int
repmgr_atoi(const char *value, const char *config_item, ItemList *error_list, bool allow_negative) repmgr_atoi(const char *value, const char *config_item, ItemList *error_list, int minval)
{ {
char *endptr; char *endptr;
long longval = 0; long longval = 0;
char error_message_buf[MAXLEN] = ""; PQExpBufferData errors;
initPQExpBuffer(&errors);
/* It's possible that some versions of strtol() don't treat an empty /* It's possible that some versions of strtol() don't treat an empty
* string as an error. * string as an error.
*/ */
if (*value == '\0') if (*value == '\0')
{ {
snprintf(error_message_buf, /* don't log here - empty values will be caught later */
MAXLEN, return 0;
_("no value provided for \"%s\""),
config_item);
} }
else else
{ {
@@ -722,38 +711,93 @@ repmgr_atoi(const char *value, const char *config_item, ItemList *error_list, bo
if (value == endptr || errno) if (value == endptr || errno)
{ {
snprintf(error_message_buf, appendPQExpBuffer(&errors,
MAXLEN, _("\"%s\": invalid value (provided: \"%s\")"),
_("\"%s\": invalid value (provided: \"%s\")"), config_item, value);
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);
} }
}
/* Disallow negative values for most parameters */
if (allow_negative == false && longval < 0)
{
snprintf(error_message_buf,
MAXLEN,
_("\"%s\" must be zero or greater (provided: %s)"),
config_item, value);
} }
/* Error message buffer is set */ /* Error message buffer is set */
if (error_message_buf[0] != '\0') if (errors.data[0] != '\0')
{ {
if (error_list == NULL) if (error_list == NULL)
{ {
log_error("%s", error_message_buf); log_error("%s", errors.data);
termPQExpBuffer(&errors);
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
item_list_append(error_list, error_message_buf); item_list_append(error_list, errors.data);
termPQExpBuffer(&errors);
} }
return (int32) longval; return (int32) longval;
} }
/*
* Interpret a parameter value as a boolean. Currently accepts:
*
* - true/false
* - 0/1
* - on/off
*
* 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: make this work like in postgresql.conf
*/
bool
parse_bool(const char *s, const char *config_item, ItemList *error_list)
{
PQExpBufferData errors;
if (strcmp(s, "0") == 0)
return false;
if (strcmp(s, "1") == 0)
return true;
if (strcmp(s, "false") == 0)
return false;
if (strcmp(s, "true") == 0)
return true;
if (strcmp(s, "off") == 0)
return false;
if (strcmp(s, "on") == 0)
return true;
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;
}
/* /*
* Split argument into old_dir and new_dir and append to tablespace mapping * Split argument into old_dir and new_dir and append to tablespace mapping
* list. * list.

View File

@@ -150,6 +150,10 @@ void item_list_append(ItemList *item_list, char *error_message);
int repmgr_atoi(const char *s, int repmgr_atoi(const char *s,
const char *config_item, const char *config_item,
ItemList *error_list, ItemList *error_list,
bool allow_negative); int minval);
bool parse_bool(const char *s,
const char *config_item,
ItemList *error_list);
#endif #endif