mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2026-01-19 00:51:37 +01:00
MINOR: cfgcond: start to split the condition parser to introduce terms
The purpose is to build a descendent parser that will split conditions into expressions made of terms. There are two phases, a parsing phase and an evaluation phase. Strictly speaking it's not required to cut that in two right now, but it's likely that in the future we won't want certain predicates to be evaluated during the parsing (e.g. file system checks or execution of some external commands). The cfg_eval_condition() function is now much simpler, it just tries to parse a single term, and if OK evaluates it, then returns the result. Errors are unchanged and may still be reported during parsing or evaluation. It's worth noting that some invalid expressions such as streq(a,b)zzz continue to parse correctly for now (what remains after the parenthesis is simply ignored as not necessary).
This commit is contained in:
parent
66243b4273
commit
f869095df9
@ -52,6 +52,14 @@ enum cond_predicate {
|
||||
CFG_PRED_VERSION_BEFORE, // "version_before"
|
||||
};
|
||||
|
||||
/* types for condition terms */
|
||||
enum cfg_cond_term_type {
|
||||
CCTT_NONE = 0,
|
||||
CCTT_FALSE,
|
||||
CCTT_TRUE,
|
||||
CCTT_PRED,
|
||||
};
|
||||
|
||||
/* keyword for a condition predicate */
|
||||
struct cond_pred_kw {
|
||||
const char *word; // NULL marks the end of the list
|
||||
@ -59,4 +67,13 @@ struct cond_pred_kw {
|
||||
uint64_t arg_mask; // mask of supported arguments (strings only)
|
||||
};
|
||||
|
||||
/* condition term */
|
||||
struct cfg_cond_term {
|
||||
enum cfg_cond_term_type type; // CCTT_*
|
||||
struct arg *args; // arguments for predicates
|
||||
union {
|
||||
const struct cond_pred_kw *pred; // predicate (function)
|
||||
};
|
||||
};
|
||||
|
||||
#endif /* _HAPROXY_CFGCOND_T_H */
|
||||
|
||||
@ -26,6 +26,8 @@
|
||||
#include <haproxy/cfgcond-t.h>
|
||||
|
||||
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);
|
||||
int cfg_eval_condition(char **args, char **err, const char **errptr);
|
||||
|
||||
#endif
|
||||
|
||||
226
src/cfgcond.c
226
src/cfgcond.c
@ -45,6 +45,138 @@ const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 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. <term> 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 must free term->args using free_args()
|
||||
* and free the array itself. 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_term(const char **text, struct cfg_cond_term *term, char **err, const char **errptr)
|
||||
{
|
||||
const char *in = *text;
|
||||
const char *end_ptr;
|
||||
int err_arg;
|
||||
int nbargs;
|
||||
char *end;
|
||||
long val;
|
||||
|
||||
term->type = CCTT_NONE;
|
||||
term->args = NULL;
|
||||
|
||||
while (*in == ' ' || *in == '\t')
|
||||
in++;
|
||||
|
||||
if (!*in) /* empty term does not parse */
|
||||
return 0;
|
||||
|
||||
val = strtol(in, &end, 0);
|
||||
if (end != in) {
|
||||
term->type = val ? CCTT_TRUE : CCTT_FALSE;
|
||||
*text = end;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* below we'll likely all make_arg_list() so we must return only via
|
||||
* the <done> label which frees the arg list.
|
||||
*/
|
||||
term->pred = cfg_lookup_cond_pred(in);
|
||||
if (term->pred) {
|
||||
term->type = CCTT_PRED;
|
||||
nbargs = make_arg_list(in + strlen(term->pred->word), -1,
|
||||
term->pred->arg_mask, &term->args, err,
|
||||
&end_ptr, &err_arg, NULL);
|
||||
if (nbargs < 0) {
|
||||
free_args(term->args);
|
||||
ha_free(&term->args);
|
||||
memprintf(err, "%s in argument %d of predicate '%s' used in conditional expression", *err, err_arg, term->pred->word);
|
||||
if (errptr)
|
||||
*errptr = end_ptr;
|
||||
return -1;
|
||||
}
|
||||
*text = end_ptr;
|
||||
return 1;
|
||||
}
|
||||
|
||||
memprintf(err, "unparsable conditional expression '%s'", *text);
|
||||
if (errptr)
|
||||
*errptr = *text;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* evaluate a condition term on a .if/.elif line. The condition was already
|
||||
* parsed in <term>. 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_term(const struct cfg_cond_term *term, char **err)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
if (term->type == CCTT_FALSE)
|
||||
ret = 0;
|
||||
else if (term->type == CCTT_TRUE)
|
||||
ret = 1;
|
||||
else if (term->type == CCTT_PRED) {
|
||||
/* here we know we have a valid predicate with valid arguments
|
||||
* placed in term->args (which the caller will free).
|
||||
*/
|
||||
switch (term->pred->prd) {
|
||||
case CFG_PRED_DEFINED: // checks if arg exists as an environment variable
|
||||
ret = getenv(term->args[0].data.str.area) != NULL;
|
||||
break;
|
||||
|
||||
case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature
|
||||
const char *p;
|
||||
|
||||
ret = 0; // assume feature not found
|
||||
for (p = build_features; (p = strstr(p, term->args[0].data.str.area)); p++) {
|
||||
if (p > build_features &&
|
||||
(p[term->args[0].data.str.data] == ' ' ||
|
||||
p[term->args[0].data.str.data] == 0)) {
|
||||
if (*(p-1) == '+') { // e.g. "+OPENSSL"
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
else if (*(p-1) == '-') { // e.g. "-OPENSSL"
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
/* it was a sub-word, let's restart from next place */
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CFG_PRED_STREQ: // checks if the two arg are equal
|
||||
ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) == 0;
|
||||
break;
|
||||
|
||||
case CFG_PRED_STRNEQ: // checks if the two arg are different
|
||||
ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) != 0;
|
||||
break;
|
||||
|
||||
case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one
|
||||
ret = compare_current_version(term->args[0].data.str.area) <= 0;
|
||||
break;
|
||||
|
||||
case CFG_PRED_VERSION_BEFORE: // checks if the current version is older than this one
|
||||
ret = compare_current_version(term->args[0].data.str.area) > 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
memprintf(err, "internal error: unhandled conditional expression predicate '%s'", term->pred->word);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
memprintf(err, "internal error: unhandled condition term type %d", (int)term->type);
|
||||
}
|
||||
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
|
||||
@ -52,96 +184,28 @@ const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str)
|
||||
*/
|
||||
int cfg_eval_condition(char **args, char **err, const char **errptr)
|
||||
{
|
||||
const struct cond_pred_kw *cond_pred = NULL;
|
||||
const char *end_ptr;
|
||||
struct arg *argp = NULL;
|
||||
int err_arg;
|
||||
int nbargs;
|
||||
struct cfg_cond_term term = { };
|
||||
const char *text = args[0];
|
||||
int ret = -1;
|
||||
char *end;
|
||||
long val;
|
||||
|
||||
if (!*args[0]) /* note: empty = false */
|
||||
if (!*text) /* note: empty = false */
|
||||
return 0;
|
||||
|
||||
val = strtol(args[0], &end, 0);
|
||||
if (end && *end == '\0')
|
||||
return val != 0;
|
||||
|
||||
/* below we'll likely all make_arg_list() so we must return only via
|
||||
* the <done> label which frees the arg list.
|
||||
*/
|
||||
cond_pred = cfg_lookup_cond_pred(args[0]);
|
||||
if (cond_pred) {
|
||||
nbargs = make_arg_list(args[0] + strlen(cond_pred->word), -1,
|
||||
cond_pred->arg_mask, &argp, err,
|
||||
&end_ptr, &err_arg, NULL);
|
||||
|
||||
if (nbargs < 0) {
|
||||
memprintf(err, "%s in argument %d of predicate '%s' used in conditional expression", *err, err_arg, cond_pred->word);
|
||||
if (errptr)
|
||||
*errptr = end_ptr;
|
||||
ret = cfg_parse_cond_term(&text, &term, err, errptr);
|
||||
if (ret != 0) {
|
||||
if (ret == -1) // parse error, error already reported
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* here we know we have a valid predicate with <nbargs> valid
|
||||
* arguments, placed in <argp> (which we'll need to free).
|
||||
*/
|
||||
switch (cond_pred->prd) {
|
||||
case CFG_PRED_DEFINED: // checks if arg exists as an environment variable
|
||||
ret = getenv(argp[0].data.str.area) != NULL;
|
||||
goto done;
|
||||
|
||||
case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature
|
||||
const char *p;
|
||||
|
||||
for (p = build_features; (p = strstr(p, argp[0].data.str.area)); p++) {
|
||||
if ((p[argp[0].data.str.data] == ' ' || p[argp[0].data.str.data] == 0) &&
|
||||
p > build_features) {
|
||||
if (*(p-1) == '+') { // "+OPENSSL"
|
||||
ret = 1;
|
||||
goto done;
|
||||
}
|
||||
else if (*(p-1) == '-') { // "-OPENSSL"
|
||||
ret = 0;
|
||||
goto done;
|
||||
}
|
||||
/* it was a sub-word, let's restart from next place */
|
||||
}
|
||||
}
|
||||
/* not found */
|
||||
ret = 0;
|
||||
goto done;
|
||||
}
|
||||
case CFG_PRED_STREQ: // checks if the two arg are equal
|
||||
ret = strcmp(argp[0].data.str.area, argp[1].data.str.area) == 0;
|
||||
goto done;
|
||||
|
||||
case CFG_PRED_STRNEQ: // checks if the two arg are different
|
||||
ret = strcmp(argp[0].data.str.area, argp[1].data.str.area) != 0;
|
||||
goto done;
|
||||
|
||||
case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one
|
||||
ret = compare_current_version(argp[0].data.str.area) <= 0;
|
||||
goto done;
|
||||
|
||||
case CFG_PRED_VERSION_BEFORE: // checks if the current version is older than this one
|
||||
ret = compare_current_version(argp[0].data.str.area) > 0;
|
||||
goto done;
|
||||
|
||||
default:
|
||||
memprintf(err, "internal error: unhandled conditional expression predicate '%s'", cond_pred->word);
|
||||
if (errptr)
|
||||
*errptr = args[0];
|
||||
goto done;
|
||||
}
|
||||
ret = cfg_eval_cond_term(&term, err);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* ret == 0, no other way to parse this */
|
||||
ret = -1;
|
||||
memprintf(err, "unparsable conditional expression '%s'", args[0]);
|
||||
if (errptr)
|
||||
*errptr = args[0];
|
||||
*errptr = text;
|
||||
done:
|
||||
free_args(argp);
|
||||
ha_free(&argp);
|
||||
free_args(term.args);
|
||||
ha_free(&term.args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user