From: Willy Tarreau Date: Fri, 16 Jul 2021 12:46:09 +0000 (+0200) Subject: MINOR: cfgcond: insert an expression between the condition and the term X-Git-Tag: v2.5-dev2~5 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ca818875990d2b2ac8377f3b418d262164681ef7;p=thirdparty%2Fhaproxy.git MINOR: cfgcond: insert an expression between the condition and the term Now evaluating a condition will rely on an expression (or an empty string), and this expression will support ORing a sub-expression with another optional expression. The sub-expressions ANDs a term with another optional sub-expression. With this alone precedence between && and || is respected, and the following expression: A && B && C || D || E && F || G will naturally evaluate as: (A && B && C) || D || (E && F) || G --- diff --git a/doc/configuration.txt b/doc/configuration.txt index ccb77a511f..a66350fd11 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -805,14 +805,21 @@ as such it is possible to use environment variables in conditions. Conditions can also be evaluated on startup with the -cc parameter. See "3. Starting HAProxy" in the management doc. -The conditions are currently limited to: +The conditions are either an empty string (which then returns false), or an +expression made of any combination of: - - an empty string, always returns "false" - the integer zero ('0'), always returns "false" - a non-nul integer (e.g. '1'), always returns "true". - a predicate optionally followed by argument(s) in parenthesis. - a question mark ('!') preceeding any of the non-empty elements above, and which will negate its status. + - expressions combined with a logical AND ('&&'), which will be evaluated + from left to right until one returns false + - expressions combined with a logical OR ('||'), which will be evaluated + from right to left until one returns true + +Note that like in other languages, the AND operator has precedence over the OR +operator, so that "A && B || C && D" evalues as "(A && B) || (C && D)". The list of currently supported predicates is the following: @@ -854,6 +861,10 @@ Example: .endif .endif + .if streq("$WITH_SSL",yes) && feature(OPENSSL) + bind :443 ssl crt ... + .endif + .if version_atleast(2.4-dev19) profiling.memory on .endif diff --git a/include/haproxy/cfgcond-t.h b/include/haproxy/cfgcond-t.h index b154cb2582..f4f2796f07 100644 --- a/include/haproxy/cfgcond-t.h +++ b/include/haproxy/cfgcond-t.h @@ -77,4 +77,22 @@ struct cfg_cond_term { }; }; +/* condition sub-expression for an AND: + * expr_and = '&&' + * | + */ +struct cfg_cond_and { + struct cfg_cond_term *left; + struct cfg_cond_and *right; // may be NULL +}; + +/* condition expression: + * expr = '||' + * | + */ +struct cfg_cond_expr { + struct cfg_cond_and *left; + struct cfg_cond_expr *right; // may be NULL +}; + #endif /* _HAPROXY_CFGCOND_T_H */ diff --git a/include/haproxy/cfgcond.h b/include/haproxy/cfgcond.h index d17772d31d..6af88a2727 100644 --- a/include/haproxy/cfgcond.h +++ b/include/haproxy/cfgcond.h @@ -29,6 +29,15 @@ const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str); int cfg_parse_cond_term(const char **text, struct cfg_cond_term **term, char **err, const char **errptr); int cfg_eval_cond_term(const struct cfg_cond_term *term, char **err); void cfg_free_cond_term(struct cfg_cond_term **term); + +int cfg_parse_cond_and(const char **text, struct cfg_cond_and **expr, char **err, const char **errptr); +int cfg_eval_cond_and(struct cfg_cond_and *expr, char **err); +void cfg_free_cond_and(struct cfg_cond_and **expr); + +int cfg_parse_cond_expr(const char **text, struct cfg_cond_expr **expr, char **err, const char **errptr); +int cfg_eval_cond_expr(struct cfg_cond_expr *expr, char **err); +void cfg_free_cond_expr(struct cfg_cond_expr **expr); + int cfg_eval_condition(char **args, char **err, const char **errptr); #endif diff --git a/src/cfgcond.c b/src/cfgcond.c index 5b0aeecf5f..a96832ff2f 100644 --- a/src/cfgcond.c +++ b/src/cfgcond.c @@ -206,6 +206,178 @@ int cfg_eval_cond_term(const struct cfg_cond_term *term, char **err) } +/* Frees and its terms and args. NULL is supported and does nothing. */ +void cfg_free_cond_and(struct cfg_cond_and **expr) +{ + while (expr && *expr) { + cfg_free_cond_term(&(*expr)->left); + expr = &(*expr)->right; + } +} + +/* Frees and its terms and args. NULL is supported and does nothing. */ +void cfg_free_cond_expr(struct cfg_cond_expr **expr) +{ + while (expr && *expr) { + cfg_free_cond_and(&(*expr)->left); + expr = &(*expr)->right; + } +} + +/* Parse an indirect input text as a possible config condition sub-expr. + * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on + * success. is filled with the parsed info, and is updated on + * success to point to the first unparsed character, or is left untouched + * on failure. On success, the caller will have to free all lower-level + * allocated structs using cfg_free_cond_expr(). An error will be set in + * on error, and only in this case. In this case the first bad + * character will be reported in . + */ +int cfg_parse_cond_and(const char **text, struct cfg_cond_and **expr, char **err, const char **errptr) +{ + struct cfg_cond_and *e; + const char *in = *text; + int ret = -1; + + if (!*in) /* empty expr does not parse */ + return 0; + + e = *expr = calloc(1, sizeof(**expr)); + if (!e) { + memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text); + goto done; + } + + ret = cfg_parse_cond_term(&in, &e->left, err, errptr); + if (ret == -1) // parse error, error already reported + goto done; + + if (ret == 0) { + /* ret == 0, no other way to parse this */ + memprintf(err, "unparsable conditional sub-expression '%s'", in); + if (errptr) + *errptr = in; + ret = -1; + goto done; + } + + /* ret=1, we have a term in the left hand set */ + + /* find an optionnal '&&' */ + while (*in == ' ' || *in == '\t') + in++; + + *text = in; + if (in[0] != '&' || in[1] != '&') + goto done; + + /* we have a '&&', let's parse the right handset's subexp */ + in += 2; + while (*in == ' ' || *in == '\t') + in++; + + ret = cfg_parse_cond_and(&in, &e->right, err, errptr); + if (ret > 0) + *text = in; + done: + if (ret < 0) + cfg_free_cond_and(expr); + return ret; +} + +/* Parse an indirect input text as a possible config condition term. + * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on + * success. is filled with the parsed info, and is updated on + * success to point to the first unparsed character, or is left untouched + * on failure. On success, the caller will have to free all lower-level + * allocated structs using cfg_free_cond_expr(). An error will be set in + * on error, and only in this case. In this case the first bad + * character will be reported in . + */ +int cfg_parse_cond_expr(const char **text, struct cfg_cond_expr **expr, char **err, const char **errptr) +{ + struct cfg_cond_expr *e; + const char *in = *text; + int ret = -1; + + if (!*in) /* empty expr does not parse */ + return 0; + + e = *expr = calloc(1, sizeof(**expr)); + if (!e) { + memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text); + goto done; + } + + ret = cfg_parse_cond_and(&in, &e->left, err, errptr); + if (ret == -1) // parse error, error already reported + goto done; + + if (ret == 0) { + /* ret == 0, no other way to parse this */ + memprintf(err, "unparsable conditional expression '%s'", in); + if (errptr) + *errptr = in; + ret = -1; + goto done; + } + + /* ret=1, we have a sub-expr in the left hand set */ + + /* find an optionnal '||' */ + while (*in == ' ' || *in == '\t') + in++; + + *text = in; + if (in[0] != '|' || in[1] != '|') + goto done; + + /* we have a '||', let's parse the right handset's subexp */ + in += 2; + while (*in == ' ' || *in == '\t') + in++; + + ret = cfg_parse_cond_expr(&in, &e->right, err, errptr); + if (ret > 0) + *text = in; + done: + if (ret < 0) + cfg_free_cond_expr(expr); + return ret; +} + +/* evaluate an sub-expression on a .if/.elif line. The expression is valid and + * was already parsed in . Returns -1 on error (in which case err is + * filled with a message, and only in this case), 0 if the condition is false, + * 1 if it's true. + */ +int cfg_eval_cond_and(struct cfg_cond_and *expr, char **err) +{ + int ret; + + /* AND: loop on terms and sub-exp's terms as long as they're TRUE + * (stop on FALSE and ERROR). + */ + while ((ret = cfg_eval_cond_term(expr->left, err)) > 0 && expr->right) + expr = expr->right; + return ret; +} + +/* evaluate an expression on a .if/.elif line. The expression is valid and was + * already parsed in . Returns -1 on error (in which case err is filled + * with a message, and only in this case), 0 if the condition is false, 1 if + * it's true. + */ +int cfg_eval_cond_expr(struct cfg_cond_expr *expr, char **err) +{ + int ret; + + /* OR: loop on sub-exps as long as they're FALSE (stop on TRUE and ERROR) */ + while ((ret = cfg_eval_cond_and(expr->left, err)) == 0 && expr->right) + expr = expr->right; + return ret; +} + /* evaluate a condition on a .if/.elif line. The condition is already tokenized * in . Returns -1 on error (in which case err is filled with a message, * and only in this case), 0 if the condition is false, 1 if it's true. If @@ -213,14 +385,14 @@ int cfg_eval_cond_term(const struct cfg_cond_term *term, char **err) */ int cfg_eval_condition(char **args, char **err, const char **errptr) { - struct cfg_cond_term *term = NULL; + struct cfg_cond_expr *expr = NULL; const char *text = args[0]; int ret = -1; if (!*text) /* note: empty = false */ return 0; - ret = cfg_parse_cond_term(&text, &term, err, errptr); + ret = cfg_parse_cond_expr(&text, &expr, err, errptr); if (ret != 0) { if (ret == -1) // parse error, error already reported goto done; @@ -234,7 +406,7 @@ int cfg_eval_condition(char **args, char **err, const char **errptr) goto fail; } - ret = cfg_eval_cond_term(term, err); + ret = cfg_eval_cond_expr(expr, err); goto done; } @@ -245,6 +417,6 @@ int cfg_eval_condition(char **args, char **err, const char **errptr) if (errptr) *errptr = text; done: - cfg_free_cond_term(&term); + cfg_free_cond_expr(&expr); return ret; }