REORG: config: move the condition preprocessing code to its own file

The .if/.else/.endif and condition evaluation code is quite dirty and
was dumped into cfgparse.c because it was easy. But it should be tidied
quite a bit as it will need to evolve.

Let's move all that to cfgcond.{c,h}.
This commit is contained in:
Willy Tarreau 2021-07-16 15:39:28 +02:00
parent ee0d727989
commit 66243b4273
7 changed files with 243 additions and 166 deletions

View File

@ -868,7 +868,7 @@ OBJS += src/mux_h2.o src/mux_fcgi.o src/http_ana.o src/mux_h1.o src/stream.o \
src/listener.o src/dns.o src/connection.o src/tcp_rules.o src/debug.o \
src/sink.o src/payload.o src/mux_pt.o src/filters.o src/fcgi-app.o \
src/server_state.o src/vars.o src/map.o src/cfgparse-global.o \
src/task.o src/flt_http_comp.o src/session.o src/sock.o \
src/task.o src/flt_http_comp.o src/session.o src/sock.o src/cfgcond.o \
src/flt_trace.o src/acl.o src/trace.o src/http_rules.o src/queue.o \
src/mjson.o src/h2.o src/h1.o src/mworker.o src/lb_chash.o src/ring.o \
src/activity.o src/tcp_sample.o src/proto_tcp.o src/htx.o src/h1_htx.o \

View File

@ -0,0 +1,62 @@
/*
* include/haproxy/cfgcond-t.h
* Types for the configuration condition preprocessor
*
* Copyright (C) 2000-2021 Willy Tarreau - w@1wt.eu
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, version 2.1
* exclusively.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _HAPROXY_CFGCOND_T_H
#define _HAPROXY_CFGCOND_T_H
#include <haproxy/api-t.h>
/* 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
/* supported conditional predicates for .if/.elif */
enum cond_predicate {
CFG_PRED_NONE, // none
CFG_PRED_DEFINED, // "defined"
CFG_PRED_FEATURE, // "feature"
CFG_PRED_STREQ, // "streq"
CFG_PRED_STRNEQ, // "strneq"
CFG_PRED_VERSION_ATLEAST, // "version_atleast"
CFG_PRED_VERSION_BEFORE, // "version_before"
};
/* keyword for a condition predicate */
struct cond_pred_kw {
const char *word; // NULL marks the end of the list
enum cond_predicate prd; // one of the CFG_PRED_* above
uint64_t arg_mask; // mask of supported arguments (strings only)
};
#endif /* _HAPROXY_CFGCOND_T_H */

31
include/haproxy/cfgcond.h Normal file
View File

@ -0,0 +1,31 @@
/*
* include/haproxy/cfgcond.h
* Configuration condition preprocessor
*
* Copyright (C) 2000-2021 Willy Tarreau - w@1wt.eu
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, version 2.1
* exclusively.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _HAPROXY_CFGCOND_H
#define _HAPROXY_CFGCOND_H
#include <haproxy/api.h>
#include <haproxy/cfgcond-t.h>
const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str);
int cfg_eval_condition(char **args, char **err, const char **errptr);
#endif

View File

@ -126,7 +126,6 @@ void free_email_alert(struct proxy *p);
const char *cfg_find_best_match(const char *word, const struct list *list, int section, const char **extra);
int warnifnotcap(struct proxy *proxy, int cap, const char *file, int line, const char *arg, const char *hint);
int failifnotcap(struct proxy *proxy, int cap, const char *file, int line, const char *arg, const char *hint);
int cfg_eval_condition(char **args, char **err, const char **errptr);
/* simplified way to define a section parser */
#define REGISTER_CONFIG_SECTION(name, parse, post) \

147
src/cfgcond.c Normal file
View File

@ -0,0 +1,147 @@
/*
* Configuration condition preprocessor
*
* Copyright 2000-2021 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*/
#include <haproxy/api.h>
#include <haproxy/arg.h>
#include <haproxy/cfgcond.h>
#include <haproxy/global.h>
#include <haproxy/tools.h>
/* supported condition predicates */
const struct cond_pred_kw cond_predicates[] = {
{ "defined", CFG_PRED_DEFINED, ARG1(1, STR) },
{ "feature", CFG_PRED_FEATURE, ARG1(1, STR) },
{ "streq", CFG_PRED_STREQ, ARG2(2, STR, STR) },
{ "strneq", CFG_PRED_STRNEQ, ARG2(2, STR, STR) },
{ "version_atleast", CFG_PRED_VERSION_ATLEAST, ARG1(1, STR) },
{ "version_before", CFG_PRED_VERSION_BEFORE, ARG1(1, STR) },
{ NULL, CFG_PRED_NONE, 0 }
};
/* looks up a cond predicate matching the keyword in <str>, possibly followed
* by a parenthesis. Returns a pointer to it or NULL if not found.
*/
const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str)
{
const struct cond_pred_kw *ret;
int len = strcspn(str, " (");
for (ret = &cond_predicates[0]; ret->word; ret++) {
if (len != strlen(ret->word))
continue;
if (strncmp(str, ret->word, len) != 0)
continue;
return ret;
}
return NULL;
}
/* 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
* <errptr> is not NULL, it's set to the first invalid character on error.
*/
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;
int ret = -1;
char *end;
long val;
if (!*args[0]) /* 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;
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;
}
}
memprintf(err, "unparsable conditional expression '%s'", args[0]);
if (errptr)
*errptr = args[0];
done:
free_args(argp);
ha_free(&argp);
return ret;
}

View File

@ -41,6 +41,7 @@
#include <haproxy/auth.h>
#include <haproxy/backend.h>
#include <haproxy/capture.h>
#include <haproxy/cfgcond.h>
#include <haproxy/cfgparse.h>
#include <haproxy/channel.h>
#include <haproxy/check.h>
@ -116,51 +117,6 @@ struct cfg_kw_list cfg_keywords = {
.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
/* supported conditional predicates for .if/.elif */
enum cond_predicate {
CFG_PRED_NONE, // none
CFG_PRED_DEFINED, // "defined"
CFG_PRED_FEATURE, // "feature"
CFG_PRED_STREQ, // "streq"
CFG_PRED_STRNEQ, // "strneq"
CFG_PRED_VERSION_ATLEAST, // "version_atleast"
CFG_PRED_VERSION_BEFORE, // "version_before"
};
struct cond_pred_kw {
const char *word; // NULL marks the end of the list
enum cond_predicate prd; // one of the CFG_PRED_* above
uint64_t arg_mask; // mask of supported arguments (strings only)
};
/* supported condition predicates */
const struct cond_pred_kw cond_predicates[] = {
{ "defined", CFG_PRED_DEFINED, ARG1(1, STR) },
{ "feature", CFG_PRED_FEATURE, ARG1(1, STR) },
{ "streq", CFG_PRED_STREQ, ARG2(2, STR, STR) },
{ "strneq", CFG_PRED_STRNEQ, ARG2(2, STR, STR) },
{ "version_atleast", CFG_PRED_VERSION_ATLEAST, ARG1(1, STR) },
{ "version_before", CFG_PRED_VERSION_BEFORE, ARG1(1, STR) },
{ NULL, CFG_PRED_NONE, 0 }
};
/*
* converts <str> to a list of listeners which are dynamically allocated.
* The format is "{addr|'*'}:port[-end][,{addr|'*'}:port[-end]]*", where :
@ -1713,125 +1669,6 @@ static int cfg_parse_global_def_path(char **args, int section_type, struct proxy
return ret;
}
/* looks up a cond predicate matching the keyword in <str>, possibly followed
* by a parenthesis. Returns a pointer to it or NULL if not found.
*/
static const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str)
{
const struct cond_pred_kw *ret;
int len = strcspn(str, " (");
for (ret = &cond_predicates[0]; ret->word; ret++) {
if (len != strlen(ret->word))
continue;
if (strncmp(str, ret->word, len) != 0)
continue;
return ret;
}
return NULL;
}
/* 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
* <errptr> is not NULL, it's set to the first invalid character on error.
*/
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;
int ret = -1;
char *end;
long val;
if (!*args[0]) /* 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;
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;
}
}
memprintf(err, "unparsable conditional expression '%s'", args[0]);
if (errptr)
*errptr = args[0];
done:
free_args(argp);
ha_free(&argp);
return ret;
}
/*
* This function reads and parses the configuration file given in the argument.
* Returns the error code, 0 if OK, -1 if the config file couldn't be opened,

View File

@ -87,6 +87,7 @@
#include <haproxy/auth.h>
#include <haproxy/base64.h>
#include <haproxy/capture-t.h>
#include <haproxy/cfgcond.h>
#include <haproxy/cfgdiag.h>
#include <haproxy/cfgparse.h>
#include <haproxy/chunk.h>