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:
parent
ee0d727989
commit
66243b4273
2
Makefile
2
Makefile
@ -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 \
|
||||
|
62
include/haproxy/cfgcond-t.h
Normal file
62
include/haproxy/cfgcond-t.h
Normal 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
31
include/haproxy/cfgcond.h
Normal 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
|
@ -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
147
src/cfgcond.c
Normal 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;
|
||||
}
|
165
src/cfgparse.c
165
src/cfgparse.c
@ -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,
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user