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}.
diff --git a/Makefile b/Makefile
index 4b06767..d598e60 100644
--- a/Makefile
+++ b/Makefile
@@ -868,7 +868,7 @@
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 \
diff --git a/include/haproxy/cfgcond-t.h b/include/haproxy/cfgcond-t.h
new file mode 100644
index 0000000..04c8df1
--- /dev/null
+++ b/include/haproxy/cfgcond-t.h
@@ -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 */
diff --git a/include/haproxy/cfgcond.h b/include/haproxy/cfgcond.h
new file mode 100644
index 0000000..bf8f58d
--- /dev/null
+++ b/include/haproxy/cfgcond.h
@@ -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
diff --git a/include/haproxy/cfgparse.h b/include/haproxy/cfgparse.h
index 51e5b9e..1c97a88 100644
--- a/include/haproxy/cfgparse.h
+++ b/include/haproxy/cfgparse.h
@@ -126,7 +126,6 @@
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) \
diff --git a/src/cfgcond.c b/src/cfgcond.c
new file mode 100644
index 0000000..d3c087b
--- /dev/null
+++ b/src/cfgcond.c
@@ -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;
+}
diff --git a/src/cfgparse.c b/src/cfgparse.c
index a1b9a47..4cee8a7 100644
--- a/src/cfgparse.c
+++ b/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 @@
.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 @@
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,
diff --git a/src/haproxy.c b/src/haproxy.c
index 2d32bb8..c863e13 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -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>