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
This commit is contained in:
Willy Tarreau 2021-07-16 14:46:09 +02:00
parent 087b2d018f
commit ca81887599
4 changed files with 216 additions and 6 deletions

View File

@ -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

View File

@ -77,4 +77,22 @@ struct cfg_cond_term {
};
};
/* condition sub-expression for an AND:
* expr_and = <term> '&&' <expr_and>
* | <term>
*/
struct cfg_cond_and {
struct cfg_cond_term *left;
struct cfg_cond_and *right; // may be NULL
};
/* condition expression:
* expr = <expr_and> '||' <expr>
* | <expr_and>
*/
struct cfg_cond_expr {
struct cfg_cond_and *left;
struct cfg_cond_expr *right; // may be NULL
};
#endif /* _HAPROXY_CFGCOND_T_H */

View File

@ -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

View File

@ -206,6 +206,178 @@ int cfg_eval_cond_term(const struct cfg_cond_term *term, char **err)
}
/* Frees <expr> 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 <expr> 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. <expr> is filled with the parsed info, and <text> 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
* <err> on error, and only in this case. In this case the first bad
* character will be reported in <errptr>.
*/
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. <expr> is filled with the parsed info, and <text> 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
* <err> on error, and only in this case. In this case the first bad
* character will be reported in <errptr>.
*/
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 <expr>. 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 <expr>. 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 <err>. 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;
}