]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: cfgcond: insert an expression between the condition and the term
authorWilly Tarreau <w@1wt.eu>
Fri, 16 Jul 2021 12:46:09 +0000 (14:46 +0200)
committerWilly Tarreau <w@1wt.eu>
Fri, 16 Jul 2021 17:18:41 +0000 (19:18 +0200)
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

doc/configuration.txt
include/haproxy/cfgcond-t.h
include/haproxy/cfgcond.h
src/cfgcond.c

index ccb77a511f2ad8e3746a3041d646dfe2dbbbf2b6..a66350fd11d15a1d65ee18e662502196398d07bc 100644 (file)
@@ -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
index b154cb2582b14b9fa85c6a2fce20571059a91a0c..f4f2796f07d87268597f7cec80f06f1d927d0d76 100644 (file)
@@ -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 */
index d17772d31d20486b0eec2677739a74321bf22ad6..6af88a2727b063491c991a55792075c64b2ef866 100644 (file)
@@ -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
index 5b0aeecf5fa5fa00749cf096be2863ac32fa5fd8..a96832ff2f0e98e7b303e0ba1d91ac77f51eeb53 100644 (file)
@@ -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;
 }