mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-11-28 14:21:00 +01:00
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:
parent
087b2d018f
commit
ca81887599
@ -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
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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
|
||||
|
||||
180
src/cfgcond.c
180
src/cfgcond.c
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user