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 46f3efee..83f875ab 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 @@ -29,3 +30,11 @@ include $(PGXS) REPMGR_VERSION=$(shell awk '/^\#define REPMGR_VERSION / { print $3; }' ${repmgr_abs_srcdir}/repmgr_version.h.in | cut -d '"' -f 2) REPMGR_RELEASE_DATE=$(shell awk '/^\#define REPMGR_RELEASE_DATE / { 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 e3be004d..04f27a9e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -54,14 +54,16 @@ $(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) -E 's/REPMGR_VERSION_DATE.*""/REPMGR_VERSION_DATE "$(DATE)"/' $< >$@; \ $(SED) -i -E 's/PG_ACTUAL_VERSION_NUM/PG_ACTUAL_VERSION_NUM $(VERSION_NUM)/' $@ +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..003c8149 --- /dev/null +++ b/configfile-scan.l @@ -0,0 +1,344 @@ +/* + * Scanner for the configuration file + */ + +%{ + +#include + +#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 7ba5284f..0ed1737a 100644 --- a/doc/appendix-release-notes.xml +++ b/doc/appendix-release-notes.xml @@ -24,6 +24,33 @@ 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. + + + +