diff --git a/.gitignore b/.gitignore
index 2791c583..757faefe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,3 +53,6 @@ repmgr
repmgrd
repmgr4
repmgrd4
+
+# generated files
+configfile-scan.c
diff --git a/HISTORY b/HISTORY
index 4da2f336..9dc01de3 100644
--- a/HISTORY
+++ b/HISTORY
@@ -1,3 +1,6 @@
+4.5 2019-??-??
+ general: parse configuration file using flex (Ian)
+
4.4 2019-06-27
repmgr: improve "daemon status" output (Ian)
repmgr: add "--siblings-follow" option to "standby promote" (Ian)
diff --git a/Makefile.global.in b/Makefile.global.in
index 49904cb6..76f1260a 100644
--- a/Makefile.global.in
+++ b/Makefile.global.in
@@ -2,6 +2,7 @@
# Makefile.global.in
# @configure_input@
+
# Can only be built using pgxs
USE_PGXS=1
@@ -25,3 +26,11 @@ include $(PGXS)
REPMGR_VERSION=$(shell awk '/^\#define REPMGR_VERSION / { print $3; }' ${repmgr_abs_srcdir}/repmgr_version.h.in | cut -d '"' -f 2)
+FLEX = flex
+
+##########################################################################
+#
+# Global targets and rules
+
+%.c: %.l
+ $(FLEX) $(FLEXFLAGS) -o'$@' $<
diff --git a/Makefile.in b/Makefile.in
index 70df4036..325703dd 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -54,13 +54,15 @@ $(info Building against PostgreSQL $(MAJORVERSION))
REPMGR_CLIENT_OBJS = repmgr-client.o \
repmgr-action-primary.o repmgr-action-standby.o repmgr-action-witness.o \
repmgr-action-bdr.o repmgr-action-cluster.o repmgr-action-node.o repmgr-action-daemon.o \
- configfile.o log.o strutil.o controldata.o dirutil.o compat.o dbutils.o sysutils.o
-REPMGRD_OBJS = repmgrd.o repmgrd-physical.o repmgrd-bdr.o configfile.o log.o dbutils.o strutil.o controldata.o compat.o sysutils.o
+ configfile.o configfile-scan.o log.o strutil.o controldata.o dirutil.o compat.o dbutils.o sysutils.o
+REPMGRD_OBJS = repmgrd.o repmgrd-physical.o repmgrd-bdr.o configfile.o configfile-scan.o log.o dbutils.o strutil.o controldata.o compat.o sysutils.o
DATE=$(shell date "+%Y-%m-%d")
repmgr_version.h: repmgr_version.h.in
sed '0,/REPMGR_VERSION_DATE/s,\(REPMGR_VERSION_DATE\).*,\1 "$(DATE)",' $< >$@
+configfile-scan.c: configfile-scan.l
+
$(REPMGR_CLIENT_OBJS): repmgr-client.h repmgr_version.h
repmgr: $(REPMGR_CLIENT_OBJS)
diff --git a/configfile-scan.l b/configfile-scan.l
new file mode 100644
index 00000000..a1ef07b5
--- /dev/null
+++ b/configfile-scan.l
@@ -0,0 +1,346 @@
+/*
+ * Scanner for the configuration file
+ */
+
+%{
+
+#define FRONTEND
+
+#include "postgres.h"
+
+#include "repmgr.h"
+#include "configfile.h"
+
+/*
+ * flex emits a yy_fatal_error() function that it calls in response to
+ * critical errors like malloc failure, file I/O errors, and detection of
+ * internal inconsistency. That function prints a message and calls exit().
+ * Mutate it to instead call our handler, which jumps out of the parser.
+ */
+#undef fprintf
+#define fprintf(file, fmt, msg) CONF_flex_fatal(msg)
+
+enum
+{
+ CONF_ID = 1,
+ CONF_STRING = 2,
+ CONF_INTEGER = 3,
+ CONF_REAL = 4,
+ CONF_EQUALS = 5,
+ CONF_UNQUOTED_STRING = 6,
+ CONF_QUALIFIED_ID = 7,
+ CONF_EOL = 99,
+ CONF_ERROR = 100
+};
+
+static unsigned int ConfigFileLineno;
+static const char *CONF_flex_fatal_errmsg;
+static sigjmp_buf *CONF_flex_fatal_jmp;
+
+static char *CONF_scanstr(const char *s);
+static int CONF_flex_fatal(const char *msg);
+
+%}
+
+%option 8bit
+%option never-interactive
+%option nodefault
+%option noinput
+%option nounput
+%option noyywrap
+%option warn
+%option prefix="CONF_yy"
+
+
+SIGN ("-"|"+")
+DIGIT [0-9]
+HEXDIGIT [0-9a-fA-F]
+
+UNIT_LETTER [a-zA-Z]
+
+INTEGER {SIGN}?({DIGIT}+|0x{HEXDIGIT}+){UNIT_LETTER}*
+
+EXPONENT [Ee]{SIGN}?{DIGIT}+
+REAL {SIGN}?{DIGIT}*"."{DIGIT}*{EXPONENT}?
+
+LETTER [A-Za-z_\200-\377]
+LETTER_OR_DIGIT [A-Za-z_0-9\200-\377]
+
+ID {LETTER}{LETTER_OR_DIGIT}*
+QUALIFIED_ID {ID}"."{ID}
+
+UNQUOTED_STRING {LETTER}({LETTER_OR_DIGIT}|[-._:/])*
+STRING \'([^'\\\n]|\\.|\'\')*\'
+
+%%
+
+\n ConfigFileLineno++; return CONF_EOL;
+[ \t\r]+ /* eat whitespace */
+#.* /* eat comment (.* matches anything until newline) */
+
+{ID} return CONF_ID;
+{QUALIFIED_ID} return CONF_QUALIFIED_ID;
+{STRING} return CONF_STRING;
+{UNQUOTED_STRING} return CONF_UNQUOTED_STRING;
+{INTEGER} return CONF_INTEGER;
+{REAL} return CONF_REAL;
+= return CONF_EQUALS;
+
+. return CONF_ERROR;
+
+%%
+
+
+extern bool
+ProcessConfigFile(FILE *fp, const char *config_file, t_configuration_options *options, ItemList *error_list, ItemList *warning_list)
+{
+ volatile bool OK = true;
+ volatile YY_BUFFER_STATE lex_buffer = NULL;
+ sigjmp_buf flex_fatal_jmp;
+ int errorcount;
+ int token;
+
+ if (sigsetjmp(flex_fatal_jmp, 1) == 0)
+ {
+ CONF_flex_fatal_jmp = &flex_fatal_jmp;
+ }
+ else
+ {
+ /*
+ * Regain control after a fatal, internal flex error. It may have
+ * corrupted parser state. Consequently, abandon the file, but trust
+ * that the state remains sane enough for yy_delete_buffer().
+ */
+ item_list_append_format(error_list,
+ "%s at file \"%s\" line %u",
+ CONF_flex_fatal_errmsg, config_file, ConfigFileLineno);
+ OK = false;
+ goto cleanup;
+ }
+
+ /*
+ * Parse
+ */
+ ConfigFileLineno = 1;
+ errorcount = 0;
+
+ lex_buffer = yy_create_buffer(fp, YY_BUF_SIZE);
+ yy_switch_to_buffer(lex_buffer);
+
+ /* This loop iterates once per logical line */
+ while ((token = yylex()))
+ {
+ char *opt_name = NULL;
+ char *opt_value = NULL;
+
+ if (token == CONF_EOL) /* empty or comment line */
+ continue;
+
+ /* first token on line is option name */
+ if (token != CONF_ID && token != CONF_QUALIFIED_ID)
+ goto parse_error;
+ opt_name = pstrdup(yytext);
+
+ /* next we have an optional equal sign; discard if present */
+ token = yylex();
+ if (token == CONF_EQUALS)
+ token = yylex();
+
+ /* now we must have the option value */
+ if (token != CONF_ID &&
+ token != CONF_STRING &&
+ token != CONF_INTEGER &&
+ token != CONF_REAL &&
+ token != CONF_UNQUOTED_STRING)
+ goto parse_error;
+ if (token == CONF_STRING) /* strip quotes and escapes */
+ opt_value = CONF_scanstr(yytext);
+ else
+ opt_value = pstrdup(yytext);
+
+ /* now we'd like an end of line, or possibly EOF */
+ token = yylex();
+ if (token != CONF_EOL)
+ {
+ if (token != 0)
+ goto parse_error;
+ /* treat EOF like \n for line numbering purposes, cf bug 4752 */
+ ConfigFileLineno++;
+ }
+
+ /* OK, process the option name and value */
+
+ parse_configuration_item(options,
+ error_list,
+ warning_list,
+ opt_name,
+ opt_value);
+
+ /* break out of loop if read EOF, else loop for next line */
+ if (token == 0)
+ break;
+ continue;
+
+parse_error:
+ /* release storage if we allocated any on this line */
+ if (opt_name)
+ pfree(opt_name);
+ if (opt_value)
+ pfree(opt_value);
+
+ /* report the error */
+ if (token == CONF_EOL || token == 0)
+ {
+ item_list_append_format(error_list,
+ _("syntax error in file \"%s\" line %u, near end of line"),
+ config_file, ConfigFileLineno - 1);
+ }
+ else
+ {
+ item_list_append_format(error_list,
+ _("syntax error in file \"%s\" line %u, near token \"%s\""),
+ config_file, ConfigFileLineno, yytext);
+ }
+ OK = false;
+ errorcount++;
+
+ /*
+ * To avoid producing too much noise when fed a totally bogus file,
+ * give up after 100 syntax errors per file (an arbitrary number).
+ * Also, if we're only logging the errors at DEBUG level anyway, might
+ * as well give up immediately. (This prevents postmaster children
+ * from bloating the logs with duplicate complaints.)
+ */
+ if (errorcount >= 100)
+ {
+ fprintf(stderr,
+ _("too many syntax errors found, abandoning file \"%s\"\n"),
+ config_file);
+ break;
+ }
+
+ /* resync to next end-of-line or EOF */
+ while (token != CONF_EOL && token != 0)
+ token = yylex();
+ /* break out of loop on EOF */
+ if (token == 0)
+ break;
+ }
+
+cleanup:
+ yy_delete_buffer(lex_buffer);
+
+ return OK;
+}
+
+
+/*
+ * scanstr
+ *
+ * Strip the quotes surrounding the given string, and collapse any embedded
+ * '' sequences and backslash escapes.
+ *
+ * the string returned is palloc'd and should eventually be pfree'd by the
+ * caller.
+ */
+static char *
+CONF_scanstr(const char *s)
+{
+ char *newStr;
+ int len,
+ i,
+ j;
+
+ Assert(s != NULL && s[0] == '\'');
+ len = strlen(s);
+ Assert(s != NULL);
+
+ Assert(len >= 2);
+ Assert(s[len - 1] == '\'');
+
+ /* Skip the leading quote; we'll handle the trailing quote below */
+ s++, len--;
+
+ /* Since len still includes trailing quote, this is enough space */
+ newStr = palloc(len);
+
+ for (i = 0, j = 0; i < len; i++)
+ {
+ if (s[i] == '\\')
+ {
+ i++;
+ switch (s[i])
+ {
+ case 'b':
+ newStr[j] = '\b';
+ break;
+ case 'f':
+ newStr[j] = '\f';
+ break;
+ case 'n':
+ newStr[j] = '\n';
+ break;
+ case 'r':
+ newStr[j] = '\r';
+ break;
+ case 't':
+ newStr[j] = '\t';
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ {
+ int k;
+ long octVal = 0;
+
+ for (k = 0;
+ s[i + k] >= '0' && s[i + k] <= '7' && k < 3;
+ k++)
+ octVal = (octVal << 3) + (s[i + k] - '0');
+ i += k - 1;
+ newStr[j] = ((char) octVal);
+ }
+ break;
+ default:
+ newStr[j] = s[i];
+ break;
+ } /* switch */
+ }
+ else if (s[i] == '\'' && s[i + 1] == '\'')
+ {
+ /* doubled quote becomes just one quote */
+ newStr[j] = s[++i];
+ }
+ else
+ newStr[j] = s[i];
+ j++;
+ }
+
+ /* We copied the ending quote to newStr, so replace with \0 */
+ Assert(j > 0 && j <= len);
+ newStr[--j] = '\0';
+
+ return newStr;
+}
+
+
+/*
+ * Flex fatal errors bring us here. Stash the error message and jump back to
+ * ParseConfigFp(). Assume all msg arguments point to string constants; this
+ * holds for flex 2.5.31 (earliest we support) and flex 2.5.35 (latest as of
+ * this writing). Otherwise, we would need to copy the message.
+ *
+ * We return "int" since this takes the place of calls to fprintf().
+*/
+static int
+CONF_flex_fatal(const char *msg)
+{
+ CONF_flex_fatal_errmsg = msg;
+ siglongjmp(*CONF_flex_fatal_jmp, 1);
+ return 0; /* keep compiler quiet */
+}
diff --git a/configfile.c b/configfile.c
index d974895f..812ba908 100644
--- a/configfile.c
+++ b/configfile.c
@@ -266,12 +266,6 @@ static void
_parse_config(t_configuration_options *options, ItemList *error_list, ItemList *warning_list)
{
FILE *fp;
- char *s = NULL,
- buf[MAXLINELENGTH] = "";
- char name[MAXLEN] = "";
- char value[MAXLEN] = "";
-
- bool node_id_found = false;
/* Initialize configuration options with sensible defaults */
@@ -468,365 +462,12 @@ _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *
exit(ERR_BAD_CONFIG);
}
- /* Read file */
- while ((s = fgets(buf, sizeof buf, fp)) != NULL)
- {
- bool known_parameter = true;
-
- /* Parse name/value pair from line */
- _parse_line(buf, name, value);
-
- /* Skip blank lines */
- if (!strlen(name))
- continue;
-
- /* Skip comments */
- if (name[0] == '#')
- continue;
-
- /* Copy into correct entry in parameters struct */
- if (strcmp(name, "node_id") == 0)
- {
- options->node_id = repmgr_atoi(value, name, error_list, MIN_NODE_ID);
- node_id_found = true;
- }
- 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 if (strcmp(value, "bdr") == 0)
- options->replication_type = REPLICATION_TYPE_BDR;
- else
- item_list_append(error_list, _("value for \"replication_type\" must be \"physical\" or \"bdr\""));
- }
-
- /* 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);
-
- /* BDR settings */
- else if (strcmp(name, "bdr_local_monitoring_only") == 0)
- options->bdr_local_monitoring_only = parse_bool(value, name, error_list);
- else if (strcmp(name, "bdr_recovery_timeout") == 0)
- options->bdr_recovery_timeout = repmgr_atoi(value, name, error_list, 0);
-
- /* 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"));
- known_parameter = false;
- }
- else if (strcmp(name, "node") == 0)
- {
- item_list_append(warning_list,
- _("parameter \"node\" has been renamed to \"node_id\""));
- known_parameter = false;
- }
- 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"));
- known_parameter = false;
- }
- else if (strcmp(name, "loglevel") == 0)
- {
- item_list_append(warning_list,
- _("parameter \"loglevel\" has been renamed to \"log_level\""));
- known_parameter = false;
- }
- else if (strcmp(name, "logfacility") == 0)
- {
- item_list_append(warning_list,
- _("parameter \"logfacility\" has been renamed to \"log_facility\""));
- known_parameter = false;
- }
- else if (strcmp(name, "logfile") == 0)
- {
- item_list_append(warning_list,
- _("parameter \"logfile\" has been renamed to \"log_file\""));
- known_parameter = false;
- }
- 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"));
- known_parameter = false;
- }
- 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"));
- known_parameter = false;
- }
- else
- {
- known_parameter = false;
- log_warning(_("%s/%s: unknown name/value pair provided; ignoring"), name, value);
- }
-
- /*
- * Raise an error if a known parameter is provided with an empty
- * value. Currently there's no reason why empty parameters are needed;
- * if we want to accept those, we'd need to add stricter default
- * checking, as currently e.g. an empty `node_id` value will be converted
- * to '0'.
- */
- if (known_parameter == true && !strlen(value))
- {
- char error_message_buf[MAXLEN] = "";
-
- maxlen_snprintf(error_message_buf,
- _("\"%s\": no value provided"),
- name);
-
- item_list_append(error_list, error_message_buf);
- }
- }
+ (void) ProcessConfigFile(fp, config_file_path, options, error_list, warning_list);
fclose(fp);
/* check required parameters */
- if (node_id_found == false)
+ if (options->node_id == UNKNOWN_NODE_ID)
{
item_list_append(error_list, _("\"node_id\": required parameter was not found"));
}
@@ -919,6 +560,349 @@ _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *
}
+void
+parse_configuration_item(t_configuration_options *options, ItemList *error_list, ItemList *warning_list, const char *name, const char *value)
+{
+ bool known_parameter = true;
+
+ 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 if (strcmp(value, "bdr") == 0)
+ options->replication_type = REPLICATION_TYPE_BDR;
+ else
+ item_list_append(error_list, _("value for \"replication_type\" must be \"physical\" or \"bdr\""));
+ }
+
+ /* 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);
+
+ /* BDR settings */
+ else if (strcmp(name, "bdr_local_monitoring_only") == 0)
+ options->bdr_local_monitoring_only = parse_bool(value, name, error_list);
+ else if (strcmp(name, "bdr_recovery_timeout") == 0)
+ options->bdr_recovery_timeout = repmgr_atoi(value, name, error_list, 0);
+
+ /* 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"));
+ known_parameter = false;
+ }
+ else if (strcmp(name, "node") == 0)
+ {
+ item_list_append(warning_list,
+ _("parameter \"node\" has been renamed to \"node_id\""));
+ known_parameter = false;
+ }
+ 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"));
+ known_parameter = false;
+ }
+ else if (strcmp(name, "loglevel") == 0)
+ {
+ item_list_append(warning_list,
+ _("parameter \"loglevel\" has been renamed to \"log_level\""));
+ known_parameter = false;
+ }
+ else if (strcmp(name, "logfacility") == 0)
+ {
+ item_list_append(warning_list,
+ _("parameter \"logfacility\" has been renamed to \"log_facility\""));
+ known_parameter = false;
+ }
+ else if (strcmp(name, "logfile") == 0)
+ {
+ item_list_append(warning_list,
+ _("parameter \"logfile\" has been renamed to \"log_file\""));
+ known_parameter = false;
+ }
+ 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"));
+ known_parameter = false;
+ }
+ 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"));
+ known_parameter = false;
+ }
+ else
+ {
+ known_parameter = false;
+ log_warning(_("%s/%s: unknown name/value pair provided; ignoring"), name, value);
+ }
+
+ /*
+ * Raise an error if a known parameter is provided with an empty
+ * value. Currently there's no reason why empty parameters are needed;
+ * if we want to accept those, we'd need to add stricter default
+ * checking, as currently e.g. an empty `node_id` value will be converted
+ * to '0'.
+ */
+ if (known_parameter == true && !strlen(value))
+ {
+ char error_message_buf[MAXLEN] = "";
+
+ maxlen_snprintf(error_message_buf,
+ _("\"%s\": no value provided"),
+ name);
+
+ item_list_append(error_list, error_message_buf);
+ }
+}
bool
diff --git a/configfile.h b/configfile.h
index 5759de7d..f446b8db 100644
--- a/configfile.h
+++ b/configfile.h
@@ -317,6 +317,8 @@ const char *progname(void);
void load_config(const char *config_file, bool verbose, bool terse, t_configuration_options *options, char *argv0);
bool reload_config(t_configuration_options *orig_options, t_server_type server_type);
+void parse_configuration_item(t_configuration_options *options, ItemList *error_list, ItemList *warning_list, const char *name, const char *value);
+
bool parse_recovery_conf(const char *data_dir, t_recovery_conf *conf);
bool parse_bool(const char *s,
@@ -342,4 +344,7 @@ void exit_with_cli_errors(ItemList *error_list, const char *repmgr_command);
void print_item_list(ItemList *item_list);
const char *print_connection_check_type(ConnectionCheckType type);
+
+extern bool ProcessConfigFile(FILE *fp, const char *config_file, t_configuration_options *options, ItemList *error_list, ItemList *warning_list);
+
#endif /* _REPMGR_CONFIGFILE_H_ */
diff --git a/doc/appendix-release-notes.xml b/doc/appendix-release-notes.xml
index 3ba2b46d..5030268f 100644
--- a/doc/appendix-release-notes.xml
+++ b/doc/appendix-release-notes.xml
@@ -15,6 +15,44 @@
See also:
+
+ Release 4.5
+ ?? ???, 2019
+
+
+ General enhancements
+
+
+
+
+
+ The &repmgr; configuration file is now parsed using
+ flex, meaning it will be parsed in
+ the same way as PostgreSQL parses its own configuration
+ files.
+
+
+ This makes configuration file parsing more robust
+ and consistent.
+
+
+
+ This change makes configuration file parsing somewhat stricter
+ than previously. When upgrading, be sure to check your
+ configuration file syntax.
+
+
+
+ In particular, all string values containing spaces
+ must be contained within single quotes.
+
+
+
+
+
+
+
+
Release 4.4
27 June, 2019