MINOR: cfgparse: implement a simple if/elif/else/endif macro block handler
Very often, especially since reg-tests, it would be desirable to be able
to conditionally comment out a config block, such as removing an SSL
binding when SSL is disabled, or enabling HTX only for certain versions,
etc.
This patch introduces a very simple nested block management which takes
".if", ".elif", ".else" and ".endif" directives to take or ignore a block.
For now the conditions are limited to empty string or "0" for false versus
a non-nul integer for true, which already suffices to test environment
variables. Still, it needs to be a bit more advanced with defines, versions
etc.
A set of ".notice", ".warning" and ".alert" statements are provided to
emit messages, often in order to provide advice about how to fix certain
conditions.
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 0ddbab0..077a584 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -98,6 +98,23 @@
.list = LIST_HEAD_INIT(cfg_keywords.list)
};
+/* nested if/elif/else/endif block states */
+enum nested_cond_state {
+ NESTED_COND_IF_TAKE, // "if" with a true condition
+ NESTED_COND_IF_DROP, // "if" with a false condition
+ NESTED_COND_IF_SKIP, // "if" masked by an outer false condition
+
+ NESTED_COND_ELIF_TAKE, // "elif" with a true condition from a false one
+ NESTED_COND_ELIF_DROP, // "elif" with a false condition from a false one
+ NESTED_COND_ELIF_SKIP, // "elif" masked by an outer false condition or a previously taken if
+
+ NESTED_COND_ELSE_TAKE, // taken "else" after an if false condition
+ NESTED_COND_ELSE_DROP, // "else" masked by outer false condition or an if true condition
+};
+
+/* 100 levels of nested conditions should already be sufficient */
+#define MAXNESTEDCONDS 100
+
/*
* converts <str> to a list of listeners which are dynamically allocated.
* The format is "{addr|'*'}:port[-end][,{addr|'*'}:port[-end]]*", where :
@@ -1798,6 +1815,8 @@
size_t outlinesize = 0;
int fatal = 0;
int missing_lf = -1;
+ int nested_cond_lvl = 0;
+ enum nested_cond_state nested_conds[MAXNESTEDCONDS];
if ((thisline = malloc(sizeof(*thisline) * linesize)) == NULL) {
ha_alert("parsing [%s] : out of memory.\n", file);
@@ -1969,6 +1988,137 @@
if (!**args)
continue;
+ /* check for config macros */
+ if (*args[0] == '.') {
+ if (strcmp(args[0], ".if") == 0) {
+ nested_cond_lvl++;
+ if (nested_cond_lvl >= MAXNESTEDCONDS) {
+ ha_alert("parsing [%s:%d]: too many nested '.if', max is %d.\n", file, linenum, MAXNESTEDCONDS);
+ err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
+ goto err;
+ }
+
+ if (nested_conds[nested_cond_lvl - 1] == NESTED_COND_IF_DROP ||
+ nested_conds[nested_cond_lvl - 1] == NESTED_COND_IF_SKIP ||
+ nested_conds[nested_cond_lvl - 1] == NESTED_COND_ELIF_DROP ||
+ nested_conds[nested_cond_lvl - 1] == NESTED_COND_ELIF_SKIP ||
+ nested_conds[nested_cond_lvl - 1] == NESTED_COND_ELSE_DROP) {
+ nested_conds[nested_cond_lvl] = NESTED_COND_IF_SKIP;
+ } else if (!*args[1] || *args[1] == '0') {
+ /* empty = false */
+ nested_conds[nested_cond_lvl] = NESTED_COND_IF_DROP;
+ } else if (atoi(args[1]) > 0) {
+ /* true */
+ nested_conds[nested_cond_lvl] = NESTED_COND_IF_TAKE;
+ } else {
+ ha_alert("parsing [%s:%d]: unparsable conditional expression '%s'.\n", file, linenum, args[1]);
+ err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
+ goto err;
+ }
+ goto next_line;
+ }
+ else if (strcmp(args[0], ".elif") == 0) {
+ if (!nested_cond_lvl) {
+ ha_alert("parsing [%s:%d]: lone '.elif' with no matching '.if'.\n", file, linenum);
+ err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
+ goto err;
+ }
+
+ if (nested_conds[nested_cond_lvl] == NESTED_COND_ELSE_TAKE ||
+ nested_conds[nested_cond_lvl] == NESTED_COND_ELSE_DROP) {
+ ha_alert("parsing [%s:%d]: '.elif' after '.else' is not permitted.\n", file, linenum);
+ err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
+ goto err;
+ }
+
+ if (nested_conds[nested_cond_lvl] == NESTED_COND_IF_TAKE ||
+ nested_conds[nested_cond_lvl] == NESTED_COND_IF_SKIP ||
+ nested_conds[nested_cond_lvl] == NESTED_COND_ELIF_SKIP) {
+ nested_conds[nested_cond_lvl] = NESTED_COND_ELIF_SKIP;
+ } else if (!*args[1] || *args[1] == '0') {
+ /* empty = false */
+ nested_conds[nested_cond_lvl] = NESTED_COND_ELIF_DROP;
+ } else if (atoi(args[1]) > 0) {
+ /* true */
+ nested_conds[nested_cond_lvl] = NESTED_COND_ELIF_TAKE;
+ } else {
+ ha_alert("parsing [%s:%d]: unparsable conditional expression '%s'.\n", file, linenum, args[1]);
+ err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
+ goto err;
+ }
+ goto next_line;
+ }
+ else if (strcmp(args[0], ".else") == 0) {
+ if (!nested_cond_lvl) {
+ ha_alert("parsing [%s:%d]: lone '.else' with no matching '.if'.\n", file, linenum);
+ err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
+ goto err;
+ }
+
+ if (nested_conds[nested_cond_lvl] == NESTED_COND_ELSE_TAKE ||
+ nested_conds[nested_cond_lvl] == NESTED_COND_ELSE_DROP) {
+ ha_alert("parsing [%s:%d]: '.else' after '.else' is not permitted.\n", file, linenum);
+ err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
+ goto err;
+ }
+
+ if (nested_conds[nested_cond_lvl] == NESTED_COND_IF_TAKE ||
+ nested_conds[nested_cond_lvl] == NESTED_COND_IF_SKIP ||
+ nested_conds[nested_cond_lvl] == NESTED_COND_ELIF_TAKE ||
+ nested_conds[nested_cond_lvl] == NESTED_COND_ELIF_SKIP) {
+ nested_conds[nested_cond_lvl] = NESTED_COND_ELSE_DROP;
+ } else {
+ /* otherwise we take the "else" */
+ nested_conds[nested_cond_lvl] = NESTED_COND_ELSE_TAKE;
+ }
+ goto next_line;
+ }
+ else if (strcmp(args[0], ".endif") == 0) {
+ if (!nested_cond_lvl) {
+ ha_alert("parsing [%s:%d]: lone '.endif' with no matching '.if'.\n", file, linenum);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ fatal++;
+ break;
+ }
+ nested_cond_lvl--;
+ goto next_line;
+ }
+ }
+
+ if (nested_cond_lvl &&
+ (nested_conds[nested_cond_lvl] == NESTED_COND_IF_DROP ||
+ nested_conds[nested_cond_lvl] == NESTED_COND_IF_SKIP ||
+ nested_conds[nested_cond_lvl] == NESTED_COND_ELIF_DROP ||
+ nested_conds[nested_cond_lvl] == NESTED_COND_ELIF_SKIP ||
+ nested_conds[nested_cond_lvl] == NESTED_COND_ELSE_DROP)) {
+ /* The current block is masked out by the conditions */
+ goto next_line;
+ }
+
+ /* .warning/.error/.notice */
+ if (*args[0] == '.') {
+ if (strcmp(args[0], ".alert") == 0) {
+ ha_alert("parsing [%s:%d]: '%s'.\n", file, linenum, args[1]);
+ err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
+ goto err;
+ }
+ else if (strcmp(args[0], ".warning") == 0) {
+ ha_warning("parsing [%s:%d]: '%s'.\n", file, linenum, args[1]);
+ err_code |= ERR_WARN;
+ goto next_line;
+ }
+ else if (strcmp(args[0], ".notice") == 0) {
+ ha_notice("parsing [%s:%d]: '%s'.\n", file, linenum, args[1]);
+ goto next_line;
+ }
+ else {
+ ha_alert("parsing [%s:%d]: unknown directive '%s'.\n", file, linenum, args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ fatal++;
+ break;
+ }
+ }
+
/* check for keyword modifiers "no" and "default" */
if (strcmp(args[0], "no") == 0) {
char *tmp;
@@ -2046,6 +2196,10 @@
if (cs && cs->post_section_parser)
err_code |= cs->post_section_parser();
+ if (nested_cond_lvl) {
+ ha_alert("parsing [%s:%d]: non-terminated '.if' block.\n", file, linenum);
+ err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
+ }
err:
free(cfg_scope);
cfg_scope = NULL;