From: Alan T. DeKok Date: Tue, 30 Mar 2021 17:15:20 +0000 (-0400) Subject: start of cond_eval_async() X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2b371806a4b209befae332b6570cdbc8c28b6178;p=thirdparty%2Ffreeradius-server.git start of cond_eval_async() --- diff --git a/src/lib/server/cond.h b/src/lib/server/cond.h index 7251a68e302..dbdca305a25 100644 --- a/src/lib/server/cond.h +++ b/src/lib/server/cond.h @@ -105,6 +105,8 @@ bool fr_cond_walk(fr_cond_t *head, bool (*callback)(fr_cond_t *cond, void *uctx) int fr_cond_from_map(TALLOC_CTX *ctx, fr_cond_t **head, map_t *map); +void fr_cond_async_update(fr_cond_t *cond); + #ifdef __cplusplus } #endif diff --git a/src/lib/server/cond_eval.c b/src/lib/server/cond_eval.c index 920f86bf3ff..0a56604e003 100644 --- a/src/lib/server/cond_eval.c +++ b/src/lib/server/cond_eval.c @@ -43,7 +43,7 @@ RCSID("$Id$") static int cond_realize_tmpl(request_t *request, fr_value_box_t **out, fr_value_box_t **to_free, - tmpl_t *in, tmpl_t *other); + tmpl_t *in, tmpl_t *other, fr_value_box_t *async); /** Map keywords to #pair_list_t values */ @@ -121,14 +121,14 @@ void cond_debug(fr_cond_t const *cond) * Converts a tmpl_t to a boolean value. * * @param[in] request the request_t - * @param[in] depth of the recursion (only used for debugging) * @param[in] in the template to evaluate + * @param[in] async the asynchronously evaluated value box, for XLAT and EXEC * @return * - <0 for failure * - 0 for "no match" * - 1 for "match". */ -int cond_eval_tmpl(request_t *request, UNUSED int depth, tmpl_t const *in) +static int cond_eval_tmpl(request_t *request, tmpl_t const *in, fr_value_box_t *async) { int rcode = -1; fr_pair_t *vp = NULL; @@ -172,7 +172,7 @@ int cond_eval_tmpl(request_t *request, UNUSED int depth, tmpl_t const *in) /* * Realize and cast the tmpl. */ - if (cond_realize_tmpl(request, &box, &box_free, vpt, NULL) < 0) { + if (cond_realize_tmpl(request, &box, &box_free, vpt, NULL, async) < 0) { fr_strerror_const("Failed evaluating condition"); return -1; } @@ -238,6 +238,7 @@ done: return rcode; } + #ifdef HAVE_REGEX /** Perform a regular expressions comparison between two operands * @@ -350,7 +351,8 @@ done: */ static int cond_realize_tmpl(request_t *request, fr_value_box_t **out, fr_value_box_t **to_free, - tmpl_t *in, tmpl_t *other) /* both really should be 'const' */ + tmpl_t *in, tmpl_t *other, /* both really should be 'const' */ + fr_value_box_t *async) { fr_value_box_t *box; xlat_escape_legacy_t escape = NULL; @@ -365,6 +367,7 @@ static int cond_realize_tmpl(request_t *request, #ifdef HAVE_REGEX case TMPL_TYPE_REGEX: #endif + fr_assert(!async); return 0; case TMPL_TYPE_ATTR: @@ -375,6 +378,7 @@ static int cond_realize_tmpl(request_t *request, * avoid all of the cost of setting up the * cursors? */ + fr_assert(!async); return 0; /* @@ -384,6 +388,7 @@ static int cond_realize_tmpl(request_t *request, case TMPL_TYPE_DATA: fr_assert((in->cast == FR_TYPE_NULL) || (in->cast == tmpl_value_type(in))); *out = tmpl_value(in); + fr_assert(!async); return 0; #ifdef HAVE_REGEX @@ -399,12 +404,6 @@ static int cond_realize_tmpl(request_t *request, fr_type_t cast_type; fr_dict_attr_t const *da = NULL; - box = NULL; - ret = tmpl_aexpand(request, &box, request, in, escape, NULL); - if (ret < 0) return ret; - - fr_assert(box != NULL); - /* * We can't be TMPL_TYPE_ATTR or TMPL_TYPE_DATA, * because that was caught above. @@ -433,14 +432,27 @@ static int cond_realize_tmpl(request_t *request, cast_type = FR_TYPE_STRING; } + if (!async) { + box = NULL; + ret = tmpl_aexpand(request, &box, request, in, escape, NULL); + if (ret < 0) return ret; + + fr_assert(box != NULL); + *out = *to_free = box; + + } else { + *out = box = async; + *to_free = NULL; + } + if (cast_type != box->type) { if (fr_value_box_cast_in_place(box, box, cast_type, da) < 0) { + *out = *to_free = NULL; RPEDEBUG("Failed casting!"); return -1; } } - *out = *to_free = box; return 0; } @@ -583,14 +595,16 @@ static int cond_compare_virtual(request_t *request, map_t const *map) /** Evaluate a map * * @param[in] request the request_t - * @param[in] depth of the recursion (only used for debugging) * @param[in] c the condition to evaluate + * @param[in] async_lhs the asynchronously evaluated value box, for XLAT and EXEC + * @param[in] async_rhs the asynchronously evaluated value box, for XLAT and EXEC * @return * - -1 on failure. * - 0 for "no match". * - 1 for "match". */ -int cond_eval_map(request_t *request, UNUSED int depth, fr_cond_t const *c) +static int cond_eval_map(request_t *request, fr_cond_t const *c, + fr_value_box_t *async_lhs, fr_value_box_t *async_rhs) { int rcode = 0; map_t const *map = c->data.map; @@ -621,7 +635,7 @@ int cond_eval_map(request_t *request, UNUSED int depth, fr_cond_t const *c) /* * Realize the LHS of a condition. */ - if (cond_realize_tmpl(request, &lhs, &lhs_free, map->lhs, map->rhs) < 0) { + if (cond_realize_tmpl(request, &lhs, &lhs_free, map->lhs, map->rhs, async_lhs) < 0) { fr_strerror_const("Failed evaluating left side of condition"); return -1; } @@ -629,7 +643,7 @@ int cond_eval_map(request_t *request, UNUSED int depth, fr_cond_t const *c) /* * Realize the RHS of a condition. */ - if (cond_realize_tmpl(request, &rhs, &rhs_free, map->rhs, map->lhs) < 0) { + if (cond_realize_tmpl(request, &rhs, &rhs_free, map->rhs, map->lhs, async_rhs) < 0) { fr_strerror_const("Failed evaluating right side of condition"); return -1; } @@ -828,6 +842,7 @@ done: return rcode; } + /** Evaluate a fr_cond_t; * * @param[in] request the request_t @@ -842,7 +857,6 @@ done: int cond_eval(request_t *request, rlm_rcode_t modreturn, fr_cond_t const *c) { int rcode = -1; - int depth = 0; #ifdef WITH_EVAL_DEBUG char buffer[1024]; @@ -854,7 +868,7 @@ int cond_eval(request_t *request, rlm_rcode_t modreturn, fr_cond_t const *c) while (c) { switch (c->type) { case COND_TYPE_TMPL: - rcode = cond_eval_tmpl(request, depth, c->data.vpt); + rcode = cond_eval_tmpl(request, c->data.vpt, NULL); break; case COND_TYPE_RCODE: @@ -862,11 +876,10 @@ int cond_eval(request_t *request, rlm_rcode_t modreturn, fr_cond_t const *c) break; case COND_TYPE_MAP: - rcode = cond_eval_map(request, depth, c); + rcode = cond_eval_map(request, c, NULL, NULL); break; case COND_TYPE_CHILD: - depth++; c = c->data.child; continue; @@ -900,9 +913,6 @@ int cond_eval(request_t *request, rlm_rcode_t modreturn, fr_cond_t const *c) return_to_parent: c = c->parent; if (!c) return rcode; - - depth--; - fr_assert(depth >= 0); } /* @@ -922,6 +932,7 @@ return_to_parent: break; default: + fr_assert(0); c = c->next; break; } @@ -932,3 +943,168 @@ return_to_parent: } return rcode; } + +/** Asynchronous evaluation of conditions. + * + * The caller is expected to clear the structure, and then set + * a->ctx = talloc ctx for ephemeral value boxes + * a->state = COND_EVAL_STATE_INIT + * a->c = condition to evaluate + * a->modreturn the module return code before the condition + * a->result = true + * + * On return, the caller checks a->state + * + * COND_EVAL_STATE_EXPAND - a->tmpl_lhs and/or a->tmpl_rhs are + * asynchronous templates which need to be pushed onto the unlang + * stack in order to be evaluated. The evaluation results should go + * into a->vb_lhs and a->vb_rhs, respectively. The caller should then + * set a->state = COND_EVAL_STATE_EVAL, and call the function again to + * evaluate the results. + * + * COND_EVAL_STATE_DONE - the result of the condition is in a->result. + * + * @param[in] request the request to evaluate + * @param[in,out] a the asynchronous data structure to evaluate + * @return + * - <0 on error + * - 0 on success + */ +int cond_eval_async(request_t *request, fr_cond_async_t *a) +{ + int rcode; + fr_cond_t const *c; + + if (!request || !a || !a->c) return -1; + +redo: + c = a->c; + + if (a->state == COND_EVAL_STATE_INIT) { + while (c->type == COND_TYPE_CHILD) { + c = c->data.child; + } + + /* + * Evaluate synchronous conditions as quickly as + * possible. + */ + if (!c->async_required) { + rcode = cond_eval(request, a->modreturn, a->c); + if (rcode < 0) return rcode; + + a->result = (rcode == 1); + goto return_to_parent; + } + + switch (c->type) { + case COND_TYPE_TMPL: + fr_assert(tmpl_async_required(c->data.vpt)); + a->tmpl_lhs = c->data.vpt; + a->tmpl_rhs = NULL; + break; + + case COND_TYPE_MAP: + a->tmpl_lhs = tmpl_async_required(c->data.map->lhs) ? c->data.map->lhs : NULL; + a->tmpl_rhs = tmpl_async_required(c->data.map->rhs) ? c->data.map->rhs : NULL; + + fr_assert(a->tmpl_lhs || a->tmpl_rhs); + break; + + default: + fr_assert(0); + return -1; + } + + /* + * Tell the caller to expand the tmpls. + * + * The caller should then set + * + * a->state = COND_EVAL_STATE_EVAL + * + * in order to tell us that we need to evaluate + * the expanded tmpls. + */ + a->state = COND_EVAL_STATE_EXPAND; + return 0; + } /* INIT state */ + + if (a->state == COND_EVAL_STATE_EVAL) { + switch (c->type) { + case COND_TYPE_TMPL: + fr_assert(a->vb_lhs); + rcode = cond_eval_tmpl(request, c->data.vpt, a->vb_lhs); + if (rcode < 0) return rcode; + + a->result = (rcode == 1); + break; + + case COND_TYPE_MAP: + fr_assert(a->vb_lhs || a->vb_rhs); + + rcode = cond_eval_map(request, c, a->vb_lhs, a->vb_rhs); + if (rcode < 0) return rcode; + break; + + default: + fr_assert(0); + return -1; + } + + TALLOC_FREE(a->vb_lhs); + TALLOC_FREE(a->vb_rhs); + a->tmpl_lhs = a->tmpl_rhs = NULL; + + if (c->negate) a->result = !a->result; + } /* EVAL state */ + + /* + * We've fallen off of the end of this evaluation + * string. Go back up to the parent, and then to + * the next sibling of the parent. + * + * Do this repeatedly until we have a c->next. + */ + while (!c->next) { +return_to_parent: + c = c->parent; + if (!c) { + a->state = COND_EVAL_STATE_DONE; + return 0; + } + } + c = c->next; + + /* + * Do short-circuit evaluations. + */ + switch (c->type) { + case COND_TYPE_AND: + if (!a->result) goto return_to_parent; + + fr_assert(c->next != NULL); + c = c->next; /* skip the && */ + break; + + case COND_TYPE_OR: + if (a->result) goto return_to_parent; + + fr_assert(c->next != NULL); + c = c->next; /* skip the || */ + break; + + default: + fr_assert(0); + break; + } + + /* + * We now have a new condition which needs to be + * evaluated. Go back to figuring out if it's async or + * not. + */ + a->c = c; + a->state = COND_EVAL_STATE_INIT; + goto redo; +} diff --git a/src/lib/server/cond_eval.h b/src/lib/server/cond_eval.h index 93b27c2d3b1..252fd97ca90 100644 --- a/src/lib/server/cond_eval.h +++ b/src/lib/server/cond_eval.h @@ -38,10 +38,32 @@ typedef struct fr_cond_s fr_cond_t; void cond_debug(fr_cond_t const *cond); -int cond_eval_tmpl(request_t *request, int depth, tmpl_t const *vpt); -int cond_eval_map(request_t *request, int depth, fr_cond_t const *c); int cond_eval(request_t *request, rlm_rcode_t modreturn, fr_cond_t const *c); +typedef struct { + TALLOC_CTX *ctx; //!< for intermediate value boxes + fr_cond_t const *c; //!< the current condition being evaluated + rlm_rcode_t modreturn; //!< the previous module return code; + + tmpl_t const *tmpl_lhs; //!< the LHS async template to evaluate + tmpl_t const *tmpl_rhs; //!< the RHS async template to evaluate + + fr_value_box_t *vb_lhs; //!< the output of the LHS async evaluation + fr_value_box_t *vb_rhs; //!< the output of the RHS async evaluation + + enum { + COND_EVAL_STATE_INVALID = 0, + COND_EVAL_STATE_INIT, + COND_EVAL_STATE_EXPAND, + COND_EVAL_STATE_EVAL, + COND_EVAL_STATE_DONE, + } state; + + bool result; //!< the final conditional result +} fr_cond_async_t; + +int cond_eval_async(request_t *request, fr_cond_async_t *a); + #ifdef __cplusplus } #endif diff --git a/src/lib/server/cond_tokenize.c b/src/lib/server/cond_tokenize.c index 6f5e19818dd..255862a5813 100644 --- a/src/lib/server/cond_tokenize.c +++ b/src/lib/server/cond_tokenize.c @@ -1619,6 +1619,7 @@ bool fr_cond_walk(fr_cond_t *c, bool (*callback)(fr_cond_t *cond, void *uctx), v if (!fr_cond_walk(c->data.child, callback, uctx)) { return false; } + break; } /* @@ -1630,6 +1631,62 @@ bool fr_cond_walk(fr_cond_t *c, bool (*callback)(fr_cond_t *cond, void *uctx), v return true; } +/** Update the condition with "is async required". + * + * This is done only by the compiler, in pass2, after is has resolved + * all of the various TMPLs. Note that we MUST walk over the entire + * condition, as tmpl_async_required() returns "true" for unresolved + * TMPLs. Which means that if the TMPL is resolved to a synchronous + * one, then the flag will be set. So we have to clear the flag, and + * then set it again ourselves. + */ +void fr_cond_async_update(fr_cond_t *c) +{ + while (c) { + c->async_required = false; + + switch (c->type) { + case COND_TYPE_INVALID: + case COND_TYPE_RCODE: + case COND_TYPE_AND: + case COND_TYPE_OR: + case COND_TYPE_TRUE: + case COND_TYPE_FALSE: + break; + + case COND_TYPE_TMPL: + c->async_required = tmpl_async_required(c->data.vpt); + break; + + case COND_TYPE_MAP: + c->async_required = tmpl_async_required(c->data.map->lhs) | tmpl_async_required(c->data.map->rhs); + break; + + case COND_TYPE_CHILD: + c = c->data.child; + continue; + + } + + /* + * Mark up the parent, too. If any of the + * children are async, then the parent is async, + * too. + */ + if (c->parent) c->parent->async_required |= c->async_required; + + /* + * Go up if there's no sibling, or to the next + * sibling if it exists. + */ + while (!c->next) { + c = c->parent; + if (!c) return; + } + c = c->next; + } +} + /** Convert a single map to a condition. * * @param ctx the talloc context where the condition is allocated diff --git a/src/lib/unlang/compile.c b/src/lib/unlang/compile.c index a7ad279205f..00984c1e557 100644 --- a/src/lib/unlang/compile.c +++ b/src/lib/unlang/compile.c @@ -2491,6 +2491,7 @@ static unlang_t *compile_if_subsection(unlang_t *parent, unlang_compile_t *unlan * them up. */ if (!fr_cond_walk(cond, pass2_cond_callback, cs)) return NULL; + fr_cond_async_update(cond); c = compile_section(parent, unlang_ctx, cs, ext); } if (!c) return NULL;