]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
move xlat_exp_head_t to it's own data structure
authorAlan T. DeKok <aland@freeradius.org>
Wed, 27 Apr 2022 17:18:22 +0000 (13:18 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Wed, 27 Apr 2022 20:57:42 +0000 (16:57 -0400)
fmt and flags are not yet filled out

13 files changed:
src/bin/unit_test_module.c
src/lib/server/tmpl_tokenize.c
src/lib/unlang/module.c
src/lib/unlang/module.h
src/lib/unlang/xlat.c
src/lib/unlang/xlat.h
src/lib/unlang/xlat_builtin.c
src/lib/unlang/xlat_ctx.h
src/lib/unlang/xlat_eval.c
src/lib/unlang/xlat_expr.c
src/lib/unlang/xlat_inst.c
src/lib/unlang/xlat_priv.h
src/lib/unlang/xlat_tokenize.c

index 62cd7d144c399411f8c371ef2db3ab3142f22646..8326882713c7935488a86c0d4a580413dacb5e82 100644 (file)
@@ -446,7 +446,7 @@ static bool do_xlats(fr_event_list_t *el, char const *filename, FILE *fp)
                                continue;
                        }
 
-                       if (xlat_resolve(&head, &flags, NULL) < 0) {
+                       if (xlat_resolve(head, &flags, NULL) < 0) {
                                talloc_free(xlat_ctx);
                                snprintf(output, sizeof(output), "ERROR resolving xlat: %s", fr_strerror());
                                continue;
index 3aac7abaae921fdd49a29064efc7882b47d4623a..d17990331402b8aa362bb61d91c79aa90e37aca9 100644 (file)
@@ -2895,7 +2895,7 @@ ssize_t tmpl_afrom_substr(TALLOC_CTX *ctx, tmpl_t **out,
                         *      the string for conversion later.
                         */
                        if (xlat_to_string(vpt, &str, &head)) {
-                               xlat_exp_free(&head);           /* Free up any memory */
+                               TALLOC_FREE(head);
 
                                tmpl_init(vpt, TMPL_TYPE_UNRESOLVED, quote,
                                         fr_sbuff_start(&our_in), slen, t_rules);
@@ -3568,7 +3568,7 @@ static inline CC_HINT(always_inline) int tmpl_attr_resolve(tmpl_t *vpt, tmpl_res
 static inline CC_HINT(always_inline)
 int tmpl_xlat_resolve(tmpl_t *vpt, tmpl_res_rules_t const *tr_rules)
 {
-       if (xlat_resolve(&vpt->data.xlat.ex, &vpt->data.xlat.flags,
+       if (xlat_resolve(vpt->data.xlat.ex, &vpt->data.xlat.flags,
                         &(xlat_res_rules_t){
                                .tr_rules = tr_rules,
                                .allow_unresolved = false
@@ -3682,7 +3682,7 @@ void tmpl_unresolve(tmpl_t *vpt)
        case TMPL_TYPE_EXEC_UNRESOLVED:
        case TMPL_TYPE_XLAT_UNRESOLVED:
        case TMPL_TYPE_REGEX_XLAT_UNRESOLVED:
-               xlat_exp_free(&vpt->data.xlat.ex);
+               TALLOC_FREE(vpt->data.xlat.ex);
                break;
 
        case TMPL_TYPE_REGEX:
index 06a3d75a070b56255ea8ef4238794021abea8ccc..c88762d30c88400eadac9cb0cd15d68625e21872 100644 (file)
@@ -442,7 +442,7 @@ int unlang_module_set_resume(request_t *request, unlang_module_resume_t resume)
  *     - UNLANG_ACTION_YIELD
  */
 unlang_action_t unlang_module_yield_to_xlat(TALLOC_CTX *ctx, bool *p_success, fr_value_box_list_t *out,
-                                           request_t *request, xlat_exp_t const *exp,
+                                           request_t *request, xlat_exp_head_t const *exp,
                                            unlang_module_resume_t resume,
                                            unlang_module_signal_t signal, void *rctx)
 {
index 156dc55f795dc35e46dfc6e21bc010de4eca799b..6f0d52498b96a0503858b33b08613a37cb213776 100644 (file)
@@ -122,7 +122,7 @@ unlang_action_t     unlang_module_yield_to_section(rlm_rcode_t *p_result,
                                               unlang_module_signal_t signal, void *rctx);
 
 unlang_action_t        unlang_module_yield_to_xlat(TALLOC_CTX *ctx, bool *p_success, fr_value_box_list_t *out,
-                                           request_t *request, xlat_exp_t const *xlat,
+                                           request_t *request, xlat_exp_head_t const *xlat,
                                            unlang_module_resume_t resume,
                                            unlang_module_signal_t signal, void *rctx);
 
index 8b5b4db5e9f40b01864a508856f24d4aacc13cc7..7edace23758f1615c00cc98ec1586047d0adce45 100644 (file)
@@ -226,7 +226,7 @@ int unlang_xlat_push(TALLOC_CTX *ctx, bool *p_success, fr_value_box_list_t *out,
         *      Allocate its state, and setup a cursor for the xlat nodes
         */
        MEM(frame->state = state = talloc_zero(stack, unlang_frame_state_xlat_t));
-       state->head = talloc_get_type_abort_const(xlat, xlat_exp_t);    /* Ensure the node is valid */
+       state->head = talloc_get_type_abort_const(xlat, xlat_exp_head_t);       /* Ensure the node is valid */
        state->exp = xlat_exp_head(state->head);
        state->success = p_success;
        state->ctx = ctx;
index 43cb1528858c51cefa799d635fef70232c64e410..68da41a4c8158cc9cb17626a9ed7b634e5e479c7 100644 (file)
@@ -326,7 +326,7 @@ bool                xlat_is_literal(xlat_exp_head_t const *head);
 
 bool           xlat_to_string(TALLOC_CTX *ctx, char **str, xlat_exp_head_t **head);
 
-int            xlat_resolve(xlat_exp_head_t **head, xlat_flags_t *flags, xlat_res_rules_t const *xr_rules);
+int            xlat_resolve(xlat_exp_head_t *head, xlat_flags_t *flags, xlat_res_rules_t const *xr_rules);
 
 xlat_t         *xlat_register_module(TALLOC_CTX *ctx, module_inst_ctx_t const *mctx,
                                      char const *name, xlat_func_t func, xlat_flags_t const *flags);
@@ -384,7 +384,7 @@ tmpl_t              *xlat_to_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_head_t *xlat);
 
 int            xlat_from_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *flags, tmpl_t **vpt_p);
 
-int            xlat_copy(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_exp_t const *in);
+int            xlat_copy(TALLOC_CTX *ctx, xlat_exp_head_t **out, xlat_exp_head_t const *in);
 
 /*
  *     xlat_inst.c
@@ -401,7 +401,7 @@ void                xlat_thread_detach(void);
 
 int            xlat_bootstrap_func(xlat_exp_t *node);
 
-int            xlat_bootstrap(xlat_exp_t *root);
+int            xlat_bootstrap(xlat_exp_head_t *root);
 
 void           xlat_instances_free(void);
 
index 0829e20575cbef0019c271bb51eea874da7d1a99..e08e1a382cc7f2e668409767f7ff6debea9ff4ba 100644 (file)
@@ -548,7 +548,7 @@ typedef struct {
 
 typedef struct {
        xlat_redundant_t                *xr;            //!< Information about the redundant xlat.
-       xlat_exp_t                      **ex;           //!< Array of xlat expressions created by
+       xlat_exp_head_t                 **ex;           //!< Array of xlat expressions created by
                                                        ///< tokenizing the arguments to the redundant
                                                        ///< xlat, then duplicating them multiple times,
                                                        ///< one for each xlat function that may be called.
@@ -557,9 +557,9 @@ typedef struct {
 typedef struct {
        bool                            last_success;   //!< Did the last call succeed?
 
-       xlat_exp_t                      **first;        //!< First function called.
+       xlat_exp_head_t                 **first;        //!< First function called.
                                                        ///< Used for redundant-load-balance.
-       xlat_exp_t                      **current;      //!< Last function called, used for redundant xlats.
+       xlat_exp_head_t                 **current;      //!< Last function called, used for redundant xlats.
 } xlat_redundant_rctx_t;
 
 /** Pass back the result from a single redundant child call
@@ -682,12 +682,12 @@ static int xlat_redundant_instantiate(xlat_inst_ctx_t const *xctx)
        xlat_redundant_t                *xr = talloc_get_type_abort(xctx->uctx, xlat_redundant_t);
        xlat_redundant_inst_t           *xri = talloc_get_type_abort(xctx->inst, xlat_redundant_inst_t);
        unsigned int                    num = 0;
-       xlat_redundant_func_t const     *head;
+       xlat_redundant_func_t const     *first;
 
-       MEM(xri->ex = talloc_array(xri, xlat_exp_t *, fr_dlist_num_elements(&xr->funcs)));
+       MEM(xri->ex = talloc_array(xri, xlat_exp_head_t *, fr_dlist_num_elements(&xr->funcs)));
        xri->xr = xr;
 
-       head = talloc_get_type_abort(fr_dlist_head(&xr->funcs), xlat_redundant_func_t);
+       first = talloc_get_type_abort(fr_dlist_head(&xr->funcs), xlat_redundant_func_t);
 
        /*
         *      Check the calling style matches the first
@@ -702,7 +702,7 @@ static int xlat_redundant_instantiate(xlat_inst_ctx_t const *xctx)
                break;
 
        case XLAT_INPUT_MONO:
-               if (head->func->input_type == XLAT_INPUT_ARGS) {
+               if (first->func->input_type == XLAT_INPUT_ARGS) {
                        PERROR("Expansion function \"%s\" takes defined arguments and should "
                               "be called using %%(func:args) syntax",
                                xctx->ex->call.func->name);
@@ -712,7 +712,7 @@ static int xlat_redundant_instantiate(xlat_inst_ctx_t const *xctx)
                break;
 
        case XLAT_INPUT_ARGS:
-               if (head->func->input_type == XLAT_INPUT_MONO) {
+               if (first->func->input_type == XLAT_INPUT_MONO) {
                        PERROR("Expansion function \"%s\" should be called using %%{func:arg} syntax",
                               xctx->ex->call.func->name);
                        return -1;
@@ -726,15 +726,16 @@ static int xlat_redundant_instantiate(xlat_inst_ctx_t const *xctx)
         */
        fr_dlist_foreach(&xr->funcs, xlat_redundant_func_t, xrf) {
                xlat_exp_t *node;
+               xlat_exp_head_t *head;
 
                /*
                 *      We have to do this here as it only
                 *      becomes an error when the user tries
                 *      to use the redundant xlat.
                 */
-               if (head->func->input_type != xrf->func->input_type) {
+               if (first->func->input_type != xrf->func->input_type) {
                        cf_log_err(xr->cs, "Expansion functions \"%s\" and \"%s\" use different argument styles "
-                                  "cannot be used in the same redundant section", head->func->name, xrf->func->name);
+                                  "cannot be used in the same redundant section", first->func->name, xrf->func->name);
                error:
                        talloc_free(xri->ex);
                        return -1;
@@ -746,7 +747,8 @@ static int xlat_redundant_instantiate(xlat_inst_ctx_t const *xctx)
                 *      for the new node can operate
                 *      correctly.
                 */
-               MEM(node = xlat_exp_func_alloc(xri->ex, xrf->func, xctx->ex->call.args));
+               MEM(head = xlat_exp_head_alloc(xri->ex));
+               MEM(head->next = node = xlat_exp_func_alloc(head, xrf->func, xctx->ex->call.args));
 
                switch (xrf->func->input_type) {
                case XLAT_INPUT_UNPROCESSED:
@@ -775,8 +777,8 @@ static int xlat_redundant_instantiate(xlat_inst_ctx_t const *xctx)
                 *      they'll get called at some point after
                 *      we return.
                 */
-               xlat_bootstrap(node);
-               xri->ex[num++] = node;
+               xlat_bootstrap(head);
+               xri->ex[num++] = head;
        }
 
        /*
@@ -1523,7 +1525,7 @@ static xlat_action_t xlat_func_next_time(TALLOC_CTX *ctx, fr_dcursor_t *out,
 
 typedef struct {
        bool            last_success;
-       xlat_exp_t      *ex;
+       xlat_exp_head_t *ex;
 } xlat_eval_rctx_t;
 
 /** Just serves to push the result up the stack
@@ -1611,7 +1613,7 @@ static xlat_action_t xlat_func_eval(TALLOC_CTX *ctx, fr_dcursor_t *out,
         *      unresolved.
         */
        if (flags.needs_resolving &&
-           (xlat_resolve(&rctx->ex, &flags, &(xlat_res_rules_t){ .allow_unresolved = false }) < 0)) {
+           (xlat_resolve(rctx->ex, &flags, &(xlat_res_rules_t){ .allow_unresolved = false }) < 0)) {
                RPEDEBUG("Unresolved expansion functions in expansion");
                goto error;
 
index 59bbab437baa8467ff712fe1f7f86cf756b1e964..8aae87a1f10bf91eb76c59fb879896772e51bd58 100644 (file)
@@ -33,7 +33,7 @@ extern "C" {
 
 /* So we don't need to include xlat.h */
 typedef struct xlat_exp xlat_exp_t;
-typedef struct xlat_exp xlat_exp_head_t;
+typedef struct xlat_exp_head xlat_exp_head_t;
 
 /** An xlat calling ctx
  *
index 5caef96fe8d43c985cceed98c3b6feba9b59aa26..c638baaae40eb73e8a519be16dcaeb8676f33cae 100644 (file)
@@ -1619,8 +1619,6 @@ int xlat_aeval_compiled_argv(TALLOC_CTX *ctx, char ***argv, request_t *request,
        char                    **my_argv;
        size_t                  count;
 
-       if (!xlat_exp_is_head(head)) return -1;
-
        count = 0;
        xlat_exp_foreach(head, node) {
                count++;
@@ -1654,8 +1652,6 @@ int xlat_flatten_compiled_argv(TALLOC_CTX *ctx, xlat_exp_head_t const ***argv, x
        xlat_exp_head_t const   **my_argv;
        size_t                  count;
 
-       if (!xlat_exp_is_head(head)) return -1;
-
        count = 0;
        xlat_exp_foreach(head, node) {
                count++;
index eac448efe989165e402921da76318757ff975a45..4925da3f9db128a03bd0a44d14cd908262cfbd14 100644 (file)
@@ -30,6 +30,13 @@ RCSID("$Id$")
 #include <freeradius-devel/unlang/xlat_priv.h>
 #include <freeradius-devel/util/calc.h>
 
+#undef XLAT_DEBUG
+#ifdef DEBUG_XLAT
+#  define XLAT_DEBUG(_fmt, ...)                        DEBUG3("%s[%i] "_fmt, __FILE__, __LINE__, ##__VA_ARGS__)
+#else
+#  define XLAT_DEBUG(...)
+#endif
+
 /*
  *     The new tokenizer accepts most things which are accepted by the old one.  Many of the errors will be
  *     different, though.
@@ -99,9 +106,10 @@ static fr_slen_t xlat_expr_print_unary(fr_sbuff_t *out, xlat_exp_t const *node,
 static fr_slen_t xlat_expr_print_binary(fr_sbuff_t *out, xlat_exp_t const *node, UNUSED void *inst, fr_sbuff_escape_rules_t const *e_rules)
 {
        size_t  at_in = fr_sbuff_used_total(out);
+       xlat_exp_t *child = xlat_exp_head(node->call.args);
 
        FR_SBUFF_IN_CHAR_RETURN(out, '(');
-       xlat_print_node(out, node->call.args, xlat_exp_head(node->call.args), e_rules); /* prints a space after the first argument */
+       xlat_print_node(out, node->call.args, child, e_rules); /* prints a space after the first argument */
 
        /*
         *      @todo - when things like "+" support more than 2 arguments, print them all out
@@ -109,7 +117,8 @@ static fr_slen_t xlat_expr_print_binary(fr_sbuff_t *out, xlat_exp_t const *node,
         */
        FR_SBUFF_IN_STRCPY_RETURN(out, fr_tokens[node->call.func->token]);
        FR_SBUFF_IN_CHAR_RETURN(out, ' ');
-       xlat_print_node(out, node->call.args, xlat_exp_next(node->call.args, xlat_exp_head(node->call.args)), e_rules);
+       child = xlat_exp_next(node->call.args, child);
+       xlat_print_node(out, node->call.args, child, e_rules);
 
        FR_SBUFF_IN_CHAR_RETURN(out, ')');
 
@@ -157,7 +166,7 @@ static int CC_HINT(nonnull) xlat_purify_expr(xlat_exp_t *node)
        xlat_exp_foreach(node->call.args, arg) {
                fr_assert(arg->type == XLAT_GROUP);
 
-               if (!xlat_is_box(arg->group)) return 0;
+               if (!xlat_is_box(xlat_exp_head(arg->group))) return 0;
 
                if (xlat_exp_next(arg->group, xlat_exp_head(arg->group))) return 0;
        }
@@ -175,9 +184,9 @@ static int CC_HINT(nonnull) xlat_purify_expr(xlat_exp_t *node)
                MEM(box = fr_value_box_alloc_null(node));
 
                if ((arg_p->type != FR_TYPE_VOID) && (arg_p->type != box->type)) {
-                       if (fr_value_box_cast(node, box, arg_p->type, NULL, xlat_box(arg->group)) < 0) goto fail;
+                       if (fr_value_box_cast(node, box, arg_p->type, NULL, xlat_box(xlat_exp_head(arg->group))) < 0) goto fail;
 
-               } else if (fr_value_box_copy(node, box, xlat_box(arg->group)) < 0) {
+               } else if (fr_value_box_copy(node, box, xlat_box(xlat_exp_head(arg->group))) < 0) {
                fail:
                        talloc_free(box);
                        goto cleanup;
@@ -236,7 +245,8 @@ static xlat_exp_t *xlat_groupify_node(TALLOC_CTX *ctx, xlat_exp_t *node)
        group->quote = T_BARE_WORD;
 
        group->fmt = node->fmt; /* not entirely correct, but good enough for now */
-       group->group = talloc_steal(group, node);
+       MEM(group->group = xlat_exp_head_alloc(group));
+       group->group->next = talloc_steal(group->group, node);
        group->flags = node->flags;
 
        if (node->next) {
@@ -247,30 +257,6 @@ static xlat_exp_t *xlat_groupify_node(TALLOC_CTX *ctx, xlat_exp_t *node)
        return group;
 }
 
-/*
- *     Any function requires each argument to be in it's own XLAT_GROUP.  But we can't create an XLAT_GROUP
- *     from the start of parsing, as we might need to return an XLAT_FUNC, or another type of xlat.  Instead,
- *     we just work on the bare nodes, and then later groupify them.  For now, it's just easier to do it this way.
- */
-static void xlat_groupify_expr(xlat_exp_t *node)
-{
-       xlat_t const *func;
-
-       if (node->type != XLAT_FUNC) return;
-
-       func = node->call.func;
-
-       if (!func->internal) return;
-
-       if (func->token == T_INVALID) return;
-
-       /*
-        *      It's already been groupified, don't do anything.
-        */
-       if (node->call.args->type == XLAT_GROUP) return;
-
-       node->call.args = xlat_groupify_node(node, node->call.args);
-}
 
 static xlat_arg_parser_t const binary_op_xlat_args[] = {
        { .required = true, .type = FR_TYPE_VOID },
@@ -395,13 +381,14 @@ static void cast_to_bool(bool *out, fr_value_box_t const *in)
 }
 
 typedef struct {
-       xlat_exp_t      *args;
        bool            sense;
+       int             argc;
+       xlat_exp_head_t **argv;
 } xlat_logical_inst_t;
 
 typedef struct {
        bool                    last_success;
-       xlat_exp_t              *current;
+       int                     current;
        fr_value_box_list_t     list;
 } xlat_logical_rctx_t;
 
@@ -409,24 +396,36 @@ static fr_slen_t xlat_expr_print_logical(fr_sbuff_t *out, xlat_exp_t const *node
 {
        size_t  at_in = fr_sbuff_used_total(out);
        xlat_logical_inst_t *inst = instance;
-       xlat_exp_head_t *head = inst->args;
+       xlat_exp_head_t *head;
+
+       FR_SBUFF_IN_CHAR_RETURN(out, '(');
 
        /*
         *      We might get called before the node is instantiated.
         */
-       if (!head) head = node->call.args;
+       if (!inst->argv) {
+               head = node->call.args;
 
-       fr_assert(head != NULL);
+               fr_assert(head != NULL);
 
-       FR_SBUFF_IN_CHAR_RETURN(out, '(');
+               xlat_exp_foreach(head, child) {
+                       xlat_print_node(out, head, child, e_rules);
 
-       xlat_exp_foreach(head, child) {
-               xlat_print_node(out, head, child, e_rules);
+                       if (!xlat_exp_next(head, child)) break;
+
+                       FR_SBUFF_IN_STRCPY_RETURN(out, fr_tokens[node->call.func->token]);
+                       FR_SBUFF_IN_CHAR_RETURN(out, ' ');
+               }
+       } else {
+               int i;
 
-               if (!xlat_exp_next(head, child)) break;
+               for (i = 0; i < inst->argc; i++) {
+                       xlat_print(out, inst->argv[i], e_rules);
+                       if (i == (inst->argc - 1)) break;
 
-               FR_SBUFF_IN_STRCPY_RETURN(out, fr_tokens[node->call.func->token]);
-               FR_SBUFF_IN_CHAR_RETURN(out, ' ');
+                       FR_SBUFF_IN_STRCPY_RETURN(out, fr_tokens[node->call.func->token]);
+                       FR_SBUFF_IN_CHAR_RETURN(out, ' ');
+               }
        }
 
        FR_SBUFF_IN_CHAR_RETURN(out, ')');
@@ -434,12 +433,37 @@ static fr_slen_t xlat_expr_print_logical(fr_sbuff_t *out, xlat_exp_t const *node
        return fr_sbuff_used_total(out) - at_in;
 }
 
+/*
+ *     Each argument is it's own head, because we do NOT always want
+ *     to go to the next argument.
+ */
 static int xlat_logical_instantiate(xlat_inst_ctx_t const *xctx)
 {
        xlat_logical_inst_t     *inst = talloc_get_type_abort(xctx->inst, xlat_logical_inst_t);
+       int i;
+
+       xlat_exp_foreach(xctx->ex->call.args, arg) {
+               inst->argc++;
+       }
+
+       inst->argv = talloc_array(inst, xlat_exp_head_t *, inst->argc);
+
+       i = 0;
+       xlat_exp_foreach(xctx->ex->call.args, arg) {
+               MEM(inst->argv[i] = xlat_exp_head_alloc(inst->argv));
+               inst->argv[i++]->next = talloc_steal(inst->argv[i], arg);
+               fr_assert(arg->type == XLAT_GROUP);
+       }
+
+       /*
+        *      The arguments are in a linked list, so unlink them,
+        */
+       for (i = 0; i < inst->argc; i++) {
+               inst->argv[i]->next->next = NULL;
+       }
 
-       inst->args = xctx->ex->call.args;
-       xctx->ex->call.args = NULL;
+       xctx->ex->call.args->next = NULL;
+       TALLOC_FREE(xctx->ex->call.args);
        inst->sense = (xctx->ex->call.func->token == T_LOR);
 
        return 0;
@@ -508,12 +532,12 @@ static xlat_action_t xlat_logical_resume(TALLOC_CTX *ctx, fr_dcursor_t *out,
        }
 
        fr_dlist_talloc_free(&rctx->list);
-       rctx->current = rctx->current->next;
+       rctx->current++;
 
        /*
         *      Nothing to expand, return the final value we saw.
         */
-       if (!rctx->current) goto done;
+       if (rctx->current >= inst->argc) goto done;
 
        /*
         *      Push the xlat onto the stack for expansion.
@@ -521,7 +545,7 @@ static xlat_action_t xlat_logical_resume(TALLOC_CTX *ctx, fr_dcursor_t *out,
        if (unlang_xlat_yield(request, xlat_logical_resume, NULL, rctx) != XLAT_ACTION_YIELD) goto fail;
 
        if (unlang_xlat_push(rctx, &rctx->last_success, &rctx->list,
-                            request, rctx->current, UNLANG_SUB_FRAME) < 0) goto fail;
+                            request, inst->argv[rctx->current], UNLANG_SUB_FRAME) < 0) goto fail;
 
        return XLAT_ACTION_PUSH_UNLANG;
 }
@@ -535,7 +559,7 @@ static xlat_action_t xlat_func_logical(TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out
        xlat_logical_rctx_t     *rctx;
 
        MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), xlat_logical_rctx_t));
-       rctx->current = inst->args;
+       rctx->current = 0;
        fr_value_box_list_init(&rctx->list);
 
        if (unlang_xlat_yield(request, xlat_logical_resume, NULL, rctx) != XLAT_ACTION_YIELD) {
@@ -545,7 +569,7 @@ static xlat_action_t xlat_func_logical(TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out
        }
 
        if (unlang_xlat_push(ctx, &rctx->last_success, &rctx->list,
-                            request, rctx->current, UNLANG_SUB_FRAME) < 0) goto fail;
+                            request, inst->argv[rctx->current], UNLANG_SUB_FRAME) < 0) goto fail;
 
        return XLAT_ACTION_PUSH_UNLANG;
 }
@@ -770,7 +794,7 @@ static const int precedence[T_TOKEN_LAST] = {
                while (isspace((int) *fr_sbuff_current(_x))) fr_sbuff_advance(_x, 1); \
        } while (0)
 
-static ssize_t tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *flags, fr_sbuff_t *in,
+static ssize_t tokenize_expression(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_flags_t *flags, fr_sbuff_t *in,
                                   fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules,
                                   fr_token_t prev, fr_sbuff_parse_rules_t const *bracket_rules);
 
@@ -784,7 +808,7 @@ static fr_table_num_sorted_t const expr_quote_table[] = {
 static size_t expr_quote_table_len = NUM_ELEMENTS(expr_quote_table);
 
 #ifdef HAVE_REGEX
-static ssize_t tokenize_regex(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *flags, fr_sbuff_t *in,
+static ssize_t tokenize_regex(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_flags_t *flags, fr_sbuff_t *in,
                              fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules)
 {
        ssize_t         slen;
@@ -850,7 +874,7 @@ static ssize_t tokenize_regex(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flag
                if (slen <= 0) goto error;
        }
 
-       *head = node;
+       *out = node;
        xlat_flags_merge(flags, &node->flags);
 
        return fr_sbuff_used(&our_in);
@@ -871,7 +895,7 @@ static ssize_t tokenize_regex(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flag
  *     to parse the next thing we get.  Otherwise, parse the thing as
  *     int64_t.
  */
-static ssize_t tokenize_field(TALLOC_CTX *input_ctx, xlat_exp_head_t **head, xlat_flags_t *flags, fr_sbuff_t *in,
+static ssize_t tokenize_field(TALLOC_CTX *input_ctx, xlat_exp_t **out, xlat_flags_t *flags, fr_sbuff_t *in,
                              fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules,
                              fr_sbuff_parse_rules_t const *bracket_rules)
 {
@@ -887,6 +911,8 @@ static ssize_t tokenize_field(TALLOC_CTX *input_ctx, xlat_exp_head_t **head, xla
        tmpl_t                  *vpt;
        fr_token_t              quote;
 
+       XLAT_DEBUG("FIELD <-- %pV", fr_box_strvalue_len(fr_sbuff_current(in), fr_sbuff_remaining(in)));
+
        /*
         *      Handle !-~ by adding a unary function to the xlat
         *      node, with the first argument being the _next_ thing
@@ -1076,12 +1102,17 @@ done:
         *      @todo - purify the node.
         */
        if (unary) {
-               unary->call.args = node;
+               xlat_exp_head_t *head;
+
+               MEM(head = xlat_exp_head_alloc(unary));
+
+               unary->call.args = head;
+               head->next = xlat_groupify_node(unary, node);
                xlat_flags_merge(&unary->flags, &node->flags);
                node = unary;
        }
 
-       *head = node;
+       *out = node;
        xlat_flags_merge(flags, &node->flags);
 
        return fr_sbuff_set(in, &our_in);
@@ -1127,7 +1158,7 @@ static size_t const expr_assignment_op_table_len = NUM_ELEMENTS(expr_assignment_
  *     !EXPR
  *     A OP B
  */
-static ssize_t tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *flags, fr_sbuff_t *in,
+static ssize_t tokenize_expression(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_flags_t *flags, fr_sbuff_t *in,
                                   fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules,
                                   fr_token_t prev, fr_sbuff_parse_rules_t const *bracket_rules)
 {
@@ -1137,6 +1168,9 @@ static ssize_t tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat
        ssize_t         slen;
        fr_sbuff_marker_t  marker;
        fr_sbuff_t      our_in = FR_SBUFF(in);
+       xlat_exp_head_t *head;
+
+       XLAT_DEBUG("EXPRESSION <-- %pV", fr_box_strvalue_len(fr_sbuff_current(in), fr_sbuff_remaining(in)));
 
        fr_sbuff_skip_whitespace(&our_in);
 
@@ -1160,7 +1194,7 @@ redo:
         */
        if (fr_sbuff_extend(&our_in) == 0) {
        done:
-               *head = lhs;
+               *out = lhs;
                return fr_sbuff_set(in, &our_in);
        }
 
@@ -1189,6 +1223,7 @@ redo:
        /*
         *      Get the operator.
         */
+       XLAT_DEBUG("    operator <-- %pV", fr_box_strvalue_len(fr_sbuff_current(&our_in), fr_sbuff_remaining(&our_in)));
        fr_sbuff_out_by_longest_prefix(&slen, &op, expr_assignment_op_table, &our_in, T_INVALID);
        if (op == T_INVALID) {
                talloc_free(lhs);
@@ -1251,6 +1286,7 @@ redo:
        /*
         *      We now parse the RHS, allowing a (perhaps different) cast on the RHS.
         */
+       XLAT_DEBUG("    recurse RHS <-- %pV", fr_box_strvalue_len(fr_sbuff_current(&our_in), fr_sbuff_remaining(&our_in)));
        slen = tokenize_expression(ctx, &rhs, flags, &our_in, p_rules, t_rules, op, bracket_rules);
        if (slen <= 0) {
                talloc_free(lhs);
@@ -1278,7 +1314,9 @@ redo:
                fr_assert(lhs->call.func == func);
 
                node = xlat_groupify_node(lhs, rhs);
-               last = &lhs->call.args;
+
+               fr_assert(lhs->call.args != NULL);
+               last = &lhs->call.args->next;
 
                /*
                 *      Find the last child.
@@ -1307,11 +1345,12 @@ redo:
        node->call.func = func;
        node->flags = func->flags;
 
-       node->call.args = xlat_groupify_node(node, lhs);
+       MEM(node->call.args = head = xlat_exp_head_alloc(node));
+       head->next = xlat_groupify_node(node, lhs);
        node->call.args->flags = lhs->flags;
 
-       node->call.args->next = xlat_groupify_node(node, rhs);
-       node->call.args->next->flags = rhs->flags;
+       head->next->next = xlat_groupify_node(node, rhs);
+       head->next->next->flags = rhs->flags;
 
        xlat_flags_merge(&node->flags, &lhs->flags);
        xlat_flags_merge(&node->flags, &rhs->flags);
@@ -1326,11 +1365,6 @@ redo:
                }
        }
 
-       /*
-        *      Ensure that the various nodes are grouped properly.
-        */
-       xlat_groupify_expr(node);
-
        lhs = node;
        goto redo;
 }
@@ -1361,7 +1395,7 @@ static const fr_sbuff_term_t operator_terms = FR_SBUFF_TERMS(
        L("<"),
 );
 
-ssize_t xlat_tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *flags, fr_sbuff_t *in,
+ssize_t xlat_tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **out, xlat_flags_t *flags, fr_sbuff_t *in,
                                 fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules)
 {
        ssize_t slen;
@@ -1369,6 +1403,7 @@ ssize_t xlat_tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_f
        fr_sbuff_parse_rules_t *terminal_rules = NULL;
        xlat_flags_t my_flags = { };
        tmpl_rules_t my_rules = { };
+       xlat_exp_head_t *head;
 
        /*
         *      Whatever the caller passes, ensure that we have a
@@ -1399,9 +1434,9 @@ ssize_t xlat_tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_f
 
        if (!t_rules) t_rules = &my_rules;
 
-       *head = NULL;
+       MEM(head = xlat_exp_head_alloc(ctx));
 
-       slen = tokenize_expression(ctx, head, flags, in, terminal_rules, t_rules, T_INVALID, bracket_rules);
+       slen = tokenize_expression(head, &head->next, flags, in, terminal_rules, t_rules, T_INVALID, bracket_rules);
        talloc_free(bracket_rules);
        talloc_free(terminal_rules);
 
@@ -1410,8 +1445,8 @@ ssize_t xlat_tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_f
        /*
         *      Zero length expansion, return a zero length node.
         */
-       if (!*head) {
-               *head = xlat_exp_alloc(ctx, XLAT_BOX, "", 0);
+       if (!head->next) {
+               *out = head;
                return 0;
        }
 
@@ -1419,11 +1454,12 @@ ssize_t xlat_tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_f
         *      Add nodes that need to be bootstrapped to
         *      the registry.
         */
-       if (xlat_bootstrap(*head) < 0) {
-               TALLOC_FREE(*head);
+       if (xlat_bootstrap(head) < 0) {
+               talloc_free(head);
                return 0;
        }
 
+       *out = head;
        return slen;
 }
 
@@ -1433,7 +1469,7 @@ ssize_t xlat_tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_f
  * expressions are integrated into the main xlat parser.
  *
  * @param[in] ctx      to allocate dynamic buffers in.
- * @param[out] head    the head of the xlat list / tree structure.
+ * @param[out] out     the head of the xlat list / tree structure.
  * @param[in] el       for registering any I/O handlers.
  * @param[out] flags   indicating the state of the ephemeral tree.
  * @param[in] in       the format string to expand.
@@ -1445,7 +1481,7 @@ ssize_t xlat_tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_f
  *     - 0 and *head != NULL - Zero length expansion
  *     - <0 the negative offset of the parse failure.
  */
-ssize_t xlat_tokenize_ephemeral_expression(TALLOC_CTX *ctx, xlat_exp_head_t **head,
+ssize_t xlat_tokenize_ephemeral_expression(TALLOC_CTX *ctx, xlat_exp_head_t **out,
                                           fr_event_list_t *el,
                                           xlat_flags_t *flags, fr_sbuff_t *in,
                                           fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules)
@@ -1455,6 +1491,7 @@ ssize_t xlat_tokenize_ephemeral_expression(TALLOC_CTX *ctx, xlat_exp_head_t **he
        fr_sbuff_parse_rules_t *terminal_rules = NULL;
        xlat_flags_t my_flags = { };
        tmpl_rules_t my_rules = { };
+       xlat_exp_head_t *head;
 
        /*
         *      Whatever the caller passes, ensure that we have a
@@ -1488,9 +1525,9 @@ ssize_t xlat_tokenize_ephemeral_expression(TALLOC_CTX *ctx, xlat_exp_head_t **he
        }
        my_rules.xlat.runtime_el = el;
 
-       *head = NULL;
+       MEM(head = xlat_exp_head_alloc(ctx));
 
-       slen = tokenize_expression(ctx, head, flags, in, terminal_rules, &my_rules, T_INVALID, bracket_rules);
+       slen = tokenize_expression(head, &head->next, flags, in, terminal_rules, &my_rules, T_INVALID, bracket_rules);
        talloc_free(bracket_rules);
        talloc_free(terminal_rules);
 
@@ -1499,19 +1536,20 @@ ssize_t xlat_tokenize_ephemeral_expression(TALLOC_CTX *ctx, xlat_exp_head_t **he
        /*
         *      Zero length expansion, return a zero length node.
         */
-       if (!*head) {
-               *head = xlat_exp_alloc(ctx, XLAT_BOX, "", 0);
+       if (!head->next) {
+               *out = head;
                return 0;
        }
 
        /*
         *      Create ephemeral instance data for the xlat
         */
-       if (xlat_instantiate_ephemeral(*head, el) < 0) {
+       if (xlat_instantiate_ephemeral(head, el) < 0) {
                fr_strerror_const("Failed performing ephemeral instantiation for xlat");
-               TALLOC_FREE(*head);
+               talloc_free(head);
                return 0;
        }
 
+       *out = head;
        return slen;
 }
index 529814005a1a10ae54f0edb0e803811a32d660ba..47ecc6154d18ea40cba5ea97b5e619fdc064cf33 100644 (file)
@@ -513,9 +513,9 @@ static int _xlat_bootstrap_walker(xlat_exp_t *node, UNUSED void *uctx)
  *      IF THIS IS CALLED FOR XLATS TOKENIZED AT RUNTIME YOU WILL LEAK LARGE AMOUNTS OF MEMORY.
  *      USE xlat_instantiate_request() INSTEAD.
  *
- * @param[in] root of xlat tree to create instance data for.
+ * @param[in] head of xlat tree to create instance data for.
  */
-int xlat_bootstrap(xlat_exp_t *root)
+int xlat_bootstrap(xlat_exp_head_t *head)
 {
        /*
         *      If thread instantiate has been called, it's too late to
@@ -533,7 +533,7 @@ int xlat_bootstrap(xlat_exp_t *root)
         *      Walk an expression registering all the function calls
         *      so that we can instantiate them later.
         */
-       return xlat_eval_walk(root, _xlat_bootstrap_walker, XLAT_FUNC, NULL);
+       return xlat_eval_walk(head, _xlat_bootstrap_walker, XLAT_FUNC, NULL);
 }
 
 /** Walk over all registered instance data and free them explicitly
index d9942a5f9f97bde780eb536507a6dcf3de6135ca..64b5f61b78d28966ecd17d2ce9db982c27f7f04b 100644 (file)
@@ -148,6 +148,13 @@ struct xlat_exp {
        };
 };
 
+struct xlat_exp_head {
+       char const      *fmt;           //!< The original format string (a talloced buffer).
+       xlat_flags_t    flags;          //!< Flags that control resolution and evaluation.
+       xlat_exp_t      *next;          //!< Next in the list.
+};
+
+
 typedef struct {
        char const      *out;           //!< Output data.
        size_t          len;            //!< Length of the output string.
@@ -314,17 +321,21 @@ int               xlat_register_expressions(void);
 /*
  *     xlat_tokenize.c
  */
-int            xlat_tokenize_expansion(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *flags, fr_sbuff_t *in,
+int            xlat_tokenize_expansion(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_flags_t *flags, fr_sbuff_t *in,
                                        tmpl_attr_rules_t const *t_rules);
 
-int            xlat_tokenize_function_args(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *flags, fr_sbuff_t *in,
+int            xlat_tokenize_function_args(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_flags_t *flags, fr_sbuff_t *in,
                                            tmpl_attr_rules_t const *rules);
 
 ssize_t                xlat_print_node(fr_sbuff_t *out, xlat_exp_head_t const *head, xlat_exp_t const *node, fr_sbuff_escape_rules_t const *e_rules);
 
+
+
 static inline xlat_exp_t *xlat_exp_head(xlat_exp_head_t const *head)
 {
-       return UNCONST(xlat_exp_t *, head);
+       if (!head || !head->next) return NULL;
+
+       return UNCONST(xlat_exp_t *, (head->next));
 }
 
 /** Iterate over the contents of a list, only one level
@@ -343,11 +354,9 @@ static inline xlat_exp_t *xlat_exp_next(UNUSED xlat_exp_head_t const *head, xlat
        return UNCONST(xlat_exp_t *, item->next);
 }
 
-static inline bool xlat_exp_is_head(xlat_exp_head_t const *head)
+static inline xlat_exp_head_t *xlat_exp_head_alloc(TALLOC_CTX *ctx)
 {
-       return (head && (head->type == XLAT_GROUP));
-
-//     return (head && head->next && (head->next->type == XLAT_GROUP));
+       return talloc_zero(ctx, xlat_exp_head_t);
 }
 
 #ifdef __cplusplus
index 398a8aa5d2ae331b3ebf096d01b13409ebbe6e37..a3d8b5e1505a5bf76e31ec579d03b385437dfa66 100644 (file)
@@ -33,7 +33,6 @@ RCSID("$Id$")
 #include <freeradius-devel/server/regex.h>
 #include <freeradius-devel/unlang/xlat_priv.h>
 
-
 #undef XLAT_DEBUG
 #undef XLAT_HEXDUMP
 #ifdef DEBUG_XLAT
@@ -116,7 +115,7 @@ static fr_sbuff_parse_rules_t const xlat_multi_arg_rules = {
  * @param[in] args     Arguments to the function.  Will be copied,
  *                     and freed when the new xlat node is freed.
  */
-xlat_exp_t *xlat_exp_func_alloc(TALLOC_CTX *ctx, xlat_t *func, xlat_exp_t const *args)
+xlat_exp_t *xlat_exp_func_alloc(TALLOC_CTX *ctx, xlat_t *func, xlat_exp_head_t const *args)
 {
        xlat_exp_t *node;
 
@@ -167,11 +166,11 @@ void xlat_exp_free(xlat_exp_head_t **head)
        *head = NULL;
 }
 
-static int xlat_tokenize_string(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_flags_t *flags,
+static int xlat_tokenize_string(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_flags_t *flags,
                                 fr_sbuff_t *in, bool brace,
                                 fr_sbuff_parse_rules_t const *p_rules, tmpl_attr_rules_t const *t_rules);
 
-static inline int xlat_tokenize_alternation(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_flags_t *flags, fr_sbuff_t *in,
+static inline int xlat_tokenize_alternation(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_flags_t *flags, fr_sbuff_t *in,
                                            tmpl_attr_rules_t const *t_rules, bool func_args)
 {
        xlat_exp_t      *node;
@@ -180,16 +179,17 @@ static inline int xlat_tokenize_alternation(TALLOC_CTX *ctx, xlat_exp_t **head,
 
        node = xlat_exp_alloc_null(ctx);
        xlat_exp_set_type(node, XLAT_ALTERNATE);
+       MEM(node->alternate[0] = xlat_exp_head_alloc(node));
+       MEM(node->alternate[1] = xlat_exp_head_alloc(node));
 
        if (func_args) {
-               if (xlat_tokenize_function_args(node, &node->alternate[0], &node->flags, in, t_rules) < 0) {
+               if (xlat_tokenize_function_args(node, &node->alternate[0]->next, &node->flags, in, t_rules) < 0) {
                error:
-                       *head = NULL;
                        talloc_free(node);
                        return -1;
                }
        } else {
-               if (xlat_tokenize_expansion(node, &node->alternate[0], &node->flags, in, t_rules) < 0) goto error;
+               if (xlat_tokenize_expansion(node, &node->alternate[0]->next, &node->flags, in, t_rules) < 0) goto error;
        }
 
        if (!fr_sbuff_adv_past_str_literal(in, ":-")) {
@@ -201,19 +201,18 @@ static inline int xlat_tokenize_alternation(TALLOC_CTX *ctx, xlat_exp_t **head,
         *      Allow the RHS to be empty as a special case.
         */
        if (fr_sbuff_next_if_char(in, '}')) {
-               node->alternate[1] = xlat_exp_alloc(node, XLAT_BOX, "", 0);
                xlat_flags_merge(&node->flags, &node->alternate[1]->flags);
-               *head = node;
+               *out = node;
                return 0;
        }
 
        /*
         *      Parse the alternate expansion.
         */
-       if (xlat_tokenize_string(node, &node->alternate[1], &node->flags, in,
+       if (xlat_tokenize_string(node, &node->alternate[1]->next, &node->flags, in,
                                  true, &xlat_expansion_rules, t_rules) < 0) goto error;
 
-       if (!node->alternate[1]) {
+       if (!node->alternate[1]->next) {
                talloc_free(node);
                fr_strerror_const("Empty expansion is invalid");
                goto error;
@@ -225,7 +224,7 @@ static inline int xlat_tokenize_alternation(TALLOC_CTX *ctx, xlat_exp_t **head,
        }
 
        xlat_flags_merge(flags, &node->flags);
-       *head = node;
+       *out = node;
 
        return 0;
 }
@@ -237,7 +236,7 @@ static inline int xlat_tokenize_alternation(TALLOC_CTX *ctx, xlat_exp_t **head,
  * @verbatim %{<num>} @endverbatim
  *
  */
-static inline int xlat_tokenize_regex(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_flags_t *flags, fr_sbuff_t *in)
+static inline int xlat_tokenize_regex(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_flags_t *flags, fr_sbuff_t *in)
 {
        uint8_t                 num;
        xlat_exp_t              *node;
@@ -280,7 +279,7 @@ static inline int xlat_tokenize_regex(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_f
        fr_sbuff_next(in);      /* Skip '}' */
 
        xlat_flags_merge(flags, &node->flags);
-       *head = node;
+       *out = node;
 
        return 0;
 }
@@ -307,7 +306,7 @@ int xlat_validate_function_mono(xlat_exp_t *node)
  *     - 0 if the string was parsed into a function.
  *     - <0 on parse error.
  */
-static inline int xlat_tokenize_function_mono(TALLOC_CTX *ctx, xlat_exp_t **head,
+static inline int xlat_tokenize_function_mono(TALLOC_CTX *ctx, xlat_exp_t **out,
                                              xlat_flags_t *flags, fr_sbuff_t *in,
                                              tmpl_attr_rules_t const *rules)
 {
@@ -324,7 +323,7 @@ static inline int xlat_tokenize_function_mono(TALLOC_CTX *ctx, xlat_exp_t **head
                                        ['.'] = true, ['-'] = true, ['_'] = true,
                                };
 
-       XLAT_DEBUG("FUNC <-- %pV", fr_box_strvalue_len(fr_sbuff_current(in), fr_sbuff_remaining(in)));
+       XLAT_DEBUG("FUNC-MONO <-- %pV", fr_box_strvalue_len(fr_sbuff_current(in), fr_sbuff_remaining(in)));
 
        /*
         *      %{module:args}
@@ -335,7 +334,7 @@ static inline int xlat_tokenize_function_mono(TALLOC_CTX *ctx, xlat_exp_t **head
        if (!fr_sbuff_is_char(in, ':')) {
                fr_strerror_const("Can't find function/argument separator");
        bad_function:
-               *head = NULL;
+               *out = NULL;
                fr_sbuff_set(in, &m_s);         /* backtrack */
                fr_sbuff_marker_release(&m_s);
                return -1;
@@ -346,7 +345,8 @@ static inline int xlat_tokenize_function_mono(TALLOC_CTX *ctx, xlat_exp_t **head
        /*
         *      Allocate a node to hold the function
         */
-       node = xlat_exp_alloc(ctx, XLAT_FUNC, fr_sbuff_current(&m_s), fr_sbuff_behind(&m_s));
+       MEM(node = xlat_exp_alloc(ctx, XLAT_FUNC, fr_sbuff_current(&m_s), fr_sbuff_behind(&m_s)));
+       MEM(node->call.args = xlat_exp_head_alloc(node));
        if (!func) {
                if (!rules || !rules->allow_unresolved) {
                        fr_strerror_const("Unresolved expansion functions are not allowed here");
@@ -359,7 +359,6 @@ static inline int xlat_tokenize_function_mono(TALLOC_CTX *ctx, xlat_exp_t **head
                        fr_strerror_const("Function takes defined arguments and should "
                                          "be called using %(func:args) syntax");
                error:
-                       head = NULL;
                        talloc_free(node);
                        return -1;
                }
@@ -386,7 +385,7 @@ static inline int xlat_tokenize_function_mono(TALLOC_CTX *ctx, xlat_exp_t **head
         *      Now parse the child nodes that form the
         *      function's arguments.
         */
-       if (xlat_tokenize_string(node, &node->call.args, &node->flags, in, true, &xlat_expansion_rules, rules) < 0) {
+       if (xlat_tokenize_string(node, &node->call.args->next, &node->flags, in, true, &xlat_expansion_rules, rules) < 0) {
                goto error;
        }
 
@@ -401,7 +400,7 @@ static inline int xlat_tokenize_function_mono(TALLOC_CTX *ctx, xlat_exp_t **head
        }
 
        xlat_flags_merge(flags, &node->flags);
-       *head = node;
+       *out = node;
 
        return 0;
 }
@@ -443,7 +442,7 @@ int xlat_validate_function_args(xlat_exp_t *node)
  *     - 0 if the string was parsed into a function.
  *     - <0 on parse error.
  */
-int xlat_tokenize_function_args(TALLOC_CTX *ctx, xlat_exp_t **head,
+int xlat_tokenize_function_args(TALLOC_CTX *ctx, xlat_exp_t **out,
                                xlat_flags_t *flags, fr_sbuff_t *in,
                                tmpl_attr_rules_t const *rules)
 {
@@ -460,7 +459,7 @@ int xlat_tokenize_function_args(TALLOC_CTX *ctx, xlat_exp_t **head,
                                        ['.'] = true, ['-'] = true, ['_'] = true,
                                };
 
-       XLAT_DEBUG("FUNC <-- %pV", fr_box_strvalue_len(fr_sbuff_current(in), fr_sbuff_remaining(in)));
+       XLAT_DEBUG("FUNC-ARGS <-- %pV", fr_box_strvalue_len(fr_sbuff_current(in), fr_sbuff_remaining(in)));
 
        /*
         *      %(module:args)
@@ -471,7 +470,7 @@ int xlat_tokenize_function_args(TALLOC_CTX *ctx, xlat_exp_t **head,
        if (!fr_sbuff_is_char(in, ':')) {
                fr_strerror_const("Can't find function/argument separator");
        bad_function:
-               *head = NULL;
+               *out = NULL;
                fr_sbuff_set(in, &m_s);         /* backtrack */
                fr_sbuff_marker_release(&m_s);
                return -1;
@@ -535,7 +534,7 @@ int xlat_tokenize_function_args(TALLOC_CTX *ctx, xlat_exp_t **head,
        }
 
        xlat_flags_merge(flags, &node->flags);
-       *head = node;
+       *out = node;
 
        return 0;
 }
@@ -565,7 +564,7 @@ static int xlat_resolve_virtual_attribute(xlat_exp_t *node, tmpl_t *vpt)
 /** Parse an attribute ref or a virtual attribute
  *
  */
-static inline int xlat_tokenize_attribute(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_flags_t *flags, fr_sbuff_t *in,
+static inline int xlat_tokenize_attribute(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_flags_t *flags, fr_sbuff_t *in,
                                          fr_sbuff_parse_rules_t const *p_rules, tmpl_attr_rules_t const *t_rules)
 {
        ssize_t                 slen;
@@ -611,7 +610,7 @@ static inline int xlat_tokenize_attribute(TALLOC_CTX *ctx, xlat_exp_t **head, xl
                 */
                if (err == TMPL_ATTR_ERROR_MISSING_TERMINATOR) fr_sbuff_set(in, &m_s);
        error:
-               *head = NULL;
+               *out = NULL;
                fr_sbuff_marker_release(&m_s);
                talloc_free(node);
                return -1;
@@ -686,12 +685,12 @@ done:
        }
 
        xlat_flags_merge(flags, &node->flags);
-       *head = node;
+       *out = node;
        fr_sbuff_marker_release(&m_s);
        return 0;
 }
 
-int xlat_tokenize_expansion(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_flags_t *flags, fr_sbuff_t *in,
+int xlat_tokenize_expansion(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_flags_t *flags, fr_sbuff_t *in,
                            tmpl_attr_rules_t const *t_rules)
 {
        size_t                  len;
@@ -716,14 +715,14 @@ int xlat_tokenize_expansion(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_flags_t *fl
         *      %{...}:-bar}
         */
        if (fr_sbuff_adv_past_str_literal(in, "%{")) {
-               return xlat_tokenize_alternation(ctx, head, flags, in, t_rules, false);
+               return xlat_tokenize_alternation(ctx, out, flags, in, t_rules, false);
        }
 
        /*
         *      %(...):-bar}
         */
        if (fr_sbuff_adv_past_str_literal(in, "%(")) {
-               return xlat_tokenize_alternation(ctx, head, flags, in, t_rules, true);
+               return xlat_tokenize_alternation(ctx, out, flags, in, t_rules, true);
        }
 
        /*
@@ -739,7 +738,7 @@ int xlat_tokenize_expansion(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_flags_t *fl
         *      Handle regex's %{<num>} specially.
         */
        if (fr_sbuff_is_digit(in)) {
-               ret = xlat_tokenize_regex(ctx, head, flags, in);
+               ret = xlat_tokenize_regex(ctx, out, flags, in);
                if (ret <= 0) return ret;
        }
 #endif /* HAVE_REGEX */
@@ -800,7 +799,7 @@ int xlat_tokenize_expansion(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_flags_t *fl
                fr_sbuff_set(in, &s_m);         /* backtrack */
                fr_sbuff_marker_release(&s_m);
 
-               ret = xlat_tokenize_function_mono(ctx, head, flags, in, t_rules);
+               ret = xlat_tokenize_function_mono(ctx, out, flags, in, t_rules);
                if (ret <= 0) return ret;
        }
                break;
@@ -816,7 +815,7 @@ int xlat_tokenize_expansion(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_flags_t *fl
                fr_sbuff_set(in, &s_m);         /* backtrack */
                fr_sbuff_marker_release(&s_m);
 
-               if (xlat_tokenize_attribute(ctx, head, flags, in, &attr_p_rules, t_rules) < 0) return -1;
+               if (xlat_tokenize_attribute(ctx, out, flags, in, &attr_p_rules, t_rules) < 0) return -1;
                break;
 
        /*
@@ -872,7 +871,7 @@ static void xlat_exp_list_free(xlat_exp_t **head)
  *                             allocated in the same ctx.  This is to allow
  *                             manipulation by xlat instantiation functions
  *                             later.
- * @param[out] head            Where to write the first child node.
+ * @param[out] out             Where to write the first child node.
  * @param[out] flags           where we store flags information for the parent.
  * @param[in] in               sbuff to parse.
  * @param[in] brace            true if we're inside a braced expansion, else false.
@@ -882,7 +881,7 @@ static void xlat_exp_list_free(xlat_exp_t **head)
  *     - 0 on success.
  *     - -1 on failure.
  */
-static int xlat_tokenize_string(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_flags_t *flags,
+static int xlat_tokenize_string(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_flags_t *flags,
                                 fr_sbuff_t *in, bool brace,
                                 fr_sbuff_parse_rules_t const *p_rules, tmpl_attr_rules_t const *t_rules)
 {
@@ -911,10 +910,12 @@ static int xlat_tokenize_string(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_flags_t
                                        );
        fr_sbuff_term_t                 *tokens;
        fr_sbuff_unescape_rules_t const *escapes;
-       xlat_exp_t                      **tail;
+       xlat_exp_t                      *head, **tail;
 
-       *head = NULL;
-       tail = head;
+       head = NULL;
+       tail = &head;
+
+       XLAT_DEBUG("STRING <-- %pV", fr_box_strvalue_len(fr_sbuff_current(in), fr_sbuff_remaining(in)));
 
        escapes = p_rules ? p_rules->escapes : NULL;
        tokens = p_rules && p_rules->terminals ?
@@ -949,7 +950,7 @@ static int xlat_tokenize_string(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_flags_t
                } else if (slen < 0) {
                error:
                        talloc_free(node);
-                       xlat_exp_list_free(head);
+                       xlat_exp_list_free(&head);
 
                        /*
                         *      Free our temporary array of terminals
@@ -1028,6 +1029,7 @@ static int xlat_tokenize_string(TALLOC_CTX *ctx, xlat_exp_t **head, xlat_flags_t
         */
        if (tokens != &expansions) talloc_free(tokens);
 
+       *out = head;
        return 0;
 }
 
@@ -1286,7 +1288,7 @@ done:
  * @param[in] head     First node to print.
  * @param[in] e_rules  Specifying how to escape literal values.
  */
-ssize_t xlat_print(fr_sbuff_t *out, xlat_exp_t const *head, fr_sbuff_escape_rules_t const *e_rules)
+ssize_t xlat_print(fr_sbuff_t *out, xlat_exp_head_t const *head, fr_sbuff_escape_rules_t const *e_rules)
 {
        ssize_t                 slen;
        size_t                  at_in = fr_sbuff_used_total(out);
@@ -1306,7 +1308,7 @@ ssize_t xlat_print(fr_sbuff_t *out, xlat_exp_t const *head, fr_sbuff_escape_rule
  * like LDAP or SQL.
  *
  * @param[in] ctx      to allocate dynamic buffers in.
- * @param[out] head    the head of the xlat list / tree structure.
+ * @param[out] out     the head of the xlat list / tree structure.
  * @param[in] el       for registering any I/O handlers.
  * @param[out] flags   indicating the state of the ephemeral tree.
  * @param[in] in       the format string to expand.
@@ -1318,7 +1320,7 @@ ssize_t xlat_print(fr_sbuff_t *out, xlat_exp_t const *head, fr_sbuff_escape_rule
  *     - 0 and *head != NULL - Zero length expansion
  *     - <0 the negative offset of the parse failure.
  */
-ssize_t xlat_tokenize_ephemeral(TALLOC_CTX *ctx, xlat_exp_head_t **head,
+ssize_t xlat_tokenize_ephemeral(TALLOC_CTX *ctx, xlat_exp_head_t **out,
                                fr_event_list_t *el,
                                xlat_flags_t *flags, fr_sbuff_t *in,
                                fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules)
@@ -1326,36 +1328,38 @@ ssize_t xlat_tokenize_ephemeral(TALLOC_CTX *ctx, xlat_exp_head_t **head,
        fr_sbuff_t      our_in = FR_SBUFF(in);
        tmpl_rules_t    our_t_rules = {};
        xlat_flags_t    tmp_flags = {};
+       xlat_exp_head_t *head;
 
        if (!flags) flags = &tmp_flags;
 
-       *head = NULL;
+       MEM(head = xlat_exp_head_alloc(ctx));
 
        if (t_rules) our_t_rules = *t_rules;
 
        our_t_rules.xlat.runtime_el = el;
 
        fr_strerror_clear();    /* Clear error buffer */
-       if (xlat_tokenize_string(ctx, head, flags, &our_in,
+       if (xlat_tokenize_string(head, &head->next, flags, &our_in,
                                 false, p_rules, &our_t_rules.attr) < 0) return -fr_sbuff_used(&our_in);
 
        /*
         *      Zero length expansion, return a zero length node.
         */
-       if (!*head) {
-               *head = xlat_exp_alloc(ctx, XLAT_BOX, "", 0);
+       if (!head->next) {
+               *out = head;
                return 0;
        }
 
        /*
         *      Create ephemeral instance data for the xlat
         */
-       if (xlat_instantiate_ephemeral(*head, el) < 0) {
+       if (xlat_instantiate_ephemeral(head, el) < 0) {
                fr_strerror_const("Failed performing ephemeral instantiation for xlat");
-               TALLOC_FREE(*head);
+               talloc_free(head);
                return 0;
        }
 
+       *out = head;
        return fr_sbuff_set(in, &our_in);
 }
 
@@ -1365,7 +1369,7 @@ ssize_t xlat_tokenize_ephemeral(TALLOC_CTX *ctx, xlat_exp_head_t **head,
  *                             allocated in the same ctx.  This is to allow
  *                             manipulation by xlat instantiation functions
  *                             later.
- * @param[out] head            the head of the xlat list / tree structure.
+ * @param[out] out             the head of the xlat list / tree structure.
  * @param[out] flags           Populated with parameters that control xlat
  *                             evaluation and multi-pass parsing.
  * @param[in] in               the format string to expand.
@@ -1376,7 +1380,7 @@ ssize_t xlat_tokenize_ephemeral(TALLOC_CTX *ctx, xlat_exp_head_t **head,
  *     - <=0 on error.
  *     - >0  on success which is the number of characters parsed.
  */
-ssize_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *flags, fr_sbuff_t *in,
+ssize_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **out, xlat_flags_t *flags, fr_sbuff_t *in,
                           fr_sbuff_parse_rules_t const *p_rules, tmpl_attr_rules_t const *t_rules)
 {
        fr_sbuff_t                      our_in = FR_SBUFF(in);
@@ -1386,11 +1390,12 @@ ssize_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t
        fr_sbuff_parse_rules_t          tmp_p_rules;
        xlat_flags_t                    tmp_flags = {};
        xlat_exp_t                      **tail;
+       xlat_exp_head_t                 *head;
 
        if (!flags) flags = &tmp_flags;
 
-       *head = NULL;
-       tail = head;
+       MEM(head = xlat_exp_head_alloc(ctx));
+       tail = &head->next;
 
        if (p_rules && p_rules->terminals) {
                tmp_p_rules = (fr_sbuff_parse_rules_t){ /* Stack allocated due to CL scope */
@@ -1425,8 +1430,9 @@ ssize_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t
                 *      Alloc a new node to hold the child nodes
                 *      that make up the argument.
                 */
-               node = xlat_exp_alloc_null(ctx);
+               MEM(node = xlat_exp_alloc_null(head));
                xlat_exp_set_type(node, XLAT_GROUP);
+               MEM(node->group = xlat_exp_head_alloc(node));
                node->quote = quote;
 
                switch (quote) {
@@ -1434,14 +1440,13 @@ ssize_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t
                 *      Barewords --may-contain=%{expansions}
                 */
                case T_BARE_WORD:
-                       if (xlat_tokenize_string(node, &node->group, &node->flags, &our_in,
+                       if (xlat_tokenize_string(node, &node->group->next, &node->flags, &our_in,
                                                  false, our_p_rules, t_rules) < 0) {
                        error:
                                if (our_p_rules != &value_parse_rules_bareword_quoted) {
                                        talloc_const_free(our_p_rules->terminals);
                                }
-                               talloc_free(node);
-                               xlat_exp_list_free(head);
+                               talloc_free(head);
 
                                return -fr_sbuff_used(&our_in); /* error */
                        }
@@ -1452,7 +1457,7 @@ ssize_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t
                 *      "Double quoted strings may contain %{expansions}"
                 */
                case T_DOUBLE_QUOTED_STRING:
-                       if (xlat_tokenize_string(node, &node->group, &node->flags, &our_in,
+                       if (xlat_tokenize_string(node, &node->group->next, &node->flags, &our_in,
                                                  false, &value_parse_rules_double_quoted, t_rules) < 0) goto error;
                        xlat_flags_merge(flags, &node->flags);
                        break;
@@ -1463,17 +1468,18 @@ ssize_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t
                case T_SINGLE_QUOTED_STRING:
                {
                        char            *str;
+                       xlat_exp_t      *child;
 
-                       node->group = xlat_exp_alloc_null(node);
-                       xlat_exp_set_type(node->group, XLAT_BOX);
+                       node->group->next = child = xlat_exp_alloc_null(node);
+                       xlat_exp_set_type(child, XLAT_BOX);
 
-                       slen = fr_sbuff_out_aunescape_until(node->group, &str, &our_in, SIZE_MAX,
+                       slen = fr_sbuff_out_aunescape_until(child, &str, &our_in, SIZE_MAX,
                                                            value_parse_rules_single_quoted.terminals,
                                                            value_parse_rules_single_quoted.escapes);
                        if (slen < 0) goto error;
 
-                       xlat_exp_set_name_buffer_shallow(node->group, str);
-                       fr_value_box_strdup_shallow(&node->group->data, NULL, str, false);
+                       xlat_exp_set_name_buffer_shallow(child, str);
+                       fr_value_box_strdup_shallow(&child->data, NULL, str, false);
                        xlat_flags_merge(flags, &node->flags);
                }
                        break;
@@ -1528,13 +1534,14 @@ ssize_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t
 
        if (our_p_rules != &value_parse_rules_bareword_quoted) talloc_const_free(our_p_rules->terminals);
 
+       *out = head;
        return fr_sbuff_set(in, &our_in);
 }
 
 /** Tokenize an xlat expansion
  *
  * @param[in] ctx      to allocate dynamic buffers in.
- * @param[out] head    the head of the xlat list / tree structure.
+ * @param[out] out     the head of the xlat list / tree structure.
  * @param[in,out] flags        that control evaluation and parsing.
  * @param[in] in       the format string to expand.
  * @param[in] p_rules  controlling how the string containing the xlat
@@ -1546,25 +1553,27 @@ ssize_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t
  *     - 0 and *head != NULL - Zero length expansion
  *     - < 0 the negative offset of the parse failure.
  */
-ssize_t xlat_tokenize(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *flags, fr_sbuff_t *in,
+ssize_t xlat_tokenize(TALLOC_CTX *ctx, xlat_exp_head_t **out, xlat_flags_t *flags, fr_sbuff_t *in,
                      fr_sbuff_parse_rules_t const *p_rules, tmpl_attr_rules_t const *t_rules)
 {
        fr_sbuff_t      our_in = FR_SBUFF(in);
        xlat_flags_t    tmp_flags = {};
+       xlat_exp_head_t *head;
 
        if (!flags) flags = &tmp_flags;
-       *head = NULL;
+
+       MEM(head = xlat_exp_head_alloc(ctx));
 
        fr_strerror_clear();    /* Clear error buffer */
 
-       if (xlat_tokenize_string(ctx, head, flags, &our_in,
+       if (xlat_tokenize_string(head, &head->next, flags, &our_in,
                                  false, p_rules, t_rules) < 0) return -fr_sbuff_used(&our_in);
 
        /*
         *      Zero length expansion, return a zero length node.
         */
-       if (!*head) {
-               *head = xlat_exp_alloc(ctx, XLAT_BOX, "", 0);
+       if (!head->next) {
+               *out = head;
                return fr_sbuff_set(in, &our_in);
        }
 
@@ -1572,11 +1581,12 @@ ssize_t xlat_tokenize(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *fla
         *      Add nodes that need to be bootstrapped to
         *      the registry.
         */
-       if (xlat_bootstrap(*head) < 0) {
-               TALLOC_FREE(*head);
+       if (xlat_bootstrap(head) < 0) {
+               talloc_free(head);
                return 0;
        }
 
+       *out = head;
        return fr_sbuff_set(in, &our_in);
 }
 
@@ -1647,7 +1657,7 @@ bool xlat_to_string(TALLOC_CTX *ctx, char **str, xlat_exp_head_t **head)
  *     - 0 on success.
  *     - -1 on failure.
  */
-int xlat_resolve(xlat_exp_head_t **head, xlat_flags_t *flags, xlat_res_rules_t const *xr_rules)
+int xlat_resolve(xlat_exp_head_t *head, xlat_flags_t *flags, xlat_res_rules_t const *xr_rules)
 {
        static xlat_res_rules_t         xr_default;
        xlat_flags_t                    our_flags;
@@ -1659,12 +1669,12 @@ int xlat_resolve(xlat_exp_head_t **head, xlat_flags_t *flags, xlat_res_rules_t c
        our_flags = *flags;
        our_flags.needs_resolving = false;                      /* We flip this if not all resolutions are successful */
 
-       xlat_exp_foreach(*head, node) {
+       xlat_exp_foreach(head, node) {
                if (!node->flags.needs_resolving) continue;     /* This node and non of its children need resolving */
 
                switch (node->type) {
                case XLAT_GROUP:
-                       if (xlat_resolve(&node->group, &node->flags, xr_rules) < 0) return -1;
+                       if (xlat_resolve(node->group, &node->flags, xr_rules) < 0) return -1;
                        break;
 
                /*
@@ -1676,8 +1686,8 @@ int xlat_resolve(xlat_exp_head_t **head, xlat_flags_t *flags, xlat_res_rules_t c
                {
                        xlat_flags_t    child_flags = node->flags, alt_flags = node->flags;
 
-                       if ((xlat_resolve(&node->alternate[0], &child_flags, xr_rules) < 0) ||
-                           (xlat_resolve(&node->alternate[1], &alt_flags, xr_rules) < 0)) return -1;
+                       if ((xlat_resolve(node->alternate[0], &child_flags, xr_rules) < 0) ||
+                           (xlat_resolve(node->alternate[1], &alt_flags, xr_rules) < 0)) return -1;
 
                        xlat_flags_merge(&child_flags, &alt_flags);
                        node->flags = child_flags;
@@ -1688,7 +1698,7 @@ int xlat_resolve(xlat_exp_head_t **head, xlat_flags_t *flags, xlat_res_rules_t c
                 *      A resolved function with unresolved args
                 */
                case XLAT_FUNC:
-                       if (xlat_resolve(&node->call.args, &node->flags, xr_rules) < 0) return -1;
+                       if (xlat_resolve(node->call.args, &node->flags, xr_rules) < 0) return -1;
                        break;
 
                /*
@@ -1704,7 +1714,7 @@ int xlat_resolve(xlat_exp_head_t **head, xlat_flags_t *flags, xlat_res_rules_t c
                         *      We can't tell if it's just the function
                         *      that needs resolving or its children too.
                         */
-                       if (xlat_resolve(&node->call.args, &child_flags, xr_rules) < 0) return -1;
+                       if (xlat_resolve(node->call.args, &child_flags, xr_rules) < 0) return -1;
 
                        /*
                         *      Try and find the function
@@ -1831,16 +1841,17 @@ int xlat_resolve(xlat_exp_head_t **head, xlat_flags_t *flags, xlat_res_rules_t c
 /** Try to convert an xlat to a tmpl for efficiency
  *
  * @param ctx to allocate new tmpl_t in.
- * @param node to convert.
+ * @param head to convert.
  * @return
  *     - NULL if unable to convert (not necessarily error).
  *     - A new #tmpl_t.
  */
-tmpl_t *xlat_to_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_t *node)
+tmpl_t *xlat_to_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_head_t *head)
 {
        tmpl_t *vpt;
+       xlat_exp_t *node = xlat_exp_head(head);
 
-       if (node->next || (node->type != XLAT_TMPL) || !tmpl_is_attr(node->vpt)) return NULL;
+       if (node || (node->type != XLAT_TMPL) || !tmpl_is_attr(node->vpt)) return NULL;
 
        /*
         *   Concat means something completely different as an attribute reference
@@ -1861,7 +1872,7 @@ tmpl_t *xlat_to_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_t *node)
 /** Convert attr tmpl to an xlat for &attr[*]
  *
  * @param[in] ctx      to allocate new expansion in.
- * @param[out] head    Where to write new xlat node.
+ * @param[out] out     Where to write new xlat node.
  * @param[out] flags   Where to write xlat resolution flags.
  * @param[in,out] vpt_p        to convert to xlat expansion.
  *                     Will be set to NULL on completion
@@ -1869,14 +1880,17 @@ tmpl_t *xlat_to_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_t *node)
  *     - 0 on success.
  *     - -1 on failure.
  */
-int xlat_from_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *flags, tmpl_t **vpt_p)
+int xlat_from_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_head_t **out, xlat_flags_t *flags, tmpl_t **vpt_p)
 {
        xlat_exp_t      *node;
        xlat_t          *func;
        tmpl_t          *vpt = *vpt_p;
+       xlat_exp_head_t *head;
 
        if (!tmpl_is_attr(vpt) && !tmpl_is_attr_unresolved(vpt)) return 0;
 
+       MEM(head = xlat_exp_head_alloc(ctx));
+
        /*
         *      If it's a single attribute reference
         *      see if it's actually a virtual attribute.
@@ -1886,7 +1900,7 @@ int xlat_from_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *f
                        func = xlat_func_find(tmpl_da(vpt)->name, -1);
                        if (!func) {
                        unresolved:
-                               node = xlat_exp_alloc(ctx, XLAT_VIRTUAL_UNRESOLVED, vpt->name, vpt->len);
+                               node = xlat_exp_alloc(head, XLAT_VIRTUAL_UNRESOLVED, vpt->name, vpt->len);
 
                                /*
                                 *      FIXME - Need a tmpl_copy function to
@@ -1895,18 +1909,16 @@ int xlat_from_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *f
                                 */
                                node->vpt = talloc_move(node, vpt_p);
                                node->flags = (xlat_flags_t) { .needs_resolving = true };
-                               *head = node;
-                               xlat_flags_merge(flags, &node->flags);
-                               return 0;
+                               goto done;
                        }
 
                virtual:
-                       node = xlat_exp_alloc(ctx, XLAT_VIRTUAL, vpt->name, vpt->len);
+                       node = xlat_exp_alloc(head, XLAT_VIRTUAL, vpt->name, vpt->len);
                        node->vpt = talloc_move(node, vpt_p);
                        node->call.func = func;
                        node->flags = func->flags;
+                       goto done;
 
-                       *head = node;
                } else if (tmpl_is_attr_unresolved(vpt)) {
                        func = xlat_func_find(tmpl_attr_unresolved(vpt), -1);
                        if (!func) goto unresolved;
@@ -1914,9 +1926,14 @@ int xlat_from_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *f
                }
        }
 
-       node = xlat_exp_alloc(ctx, XLAT_TMPL, vpt->name, vpt->len);
+       node = xlat_exp_alloc(head, XLAT_TMPL, vpt->name, vpt->len);
        node->vpt = talloc_move(node, vpt_p);
 
+done:
+       head->next = node;
+       xlat_flags_merge(flags, &node->flags);
+
+       *out = head;
        return 0;
 }
 
@@ -1929,17 +1946,18 @@ int xlat_from_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_head_t **head, xlat_flags_t *f
  *     - 0 on success.
  *     - -1 on failure.
  */
-int xlat_copy(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_exp_t const *in)
+int xlat_copy(TALLOC_CTX *ctx, xlat_exp_head_t **out, xlat_exp_head_t const *in)
 {
        xlat_exp_t **tail;
+       xlat_exp_head_t *head;
 
        if (!in) {
                *out = NULL;
                return 0;
        }
 
-       *out = NULL;
-       tail = out;
+       head = xlat_exp_head_alloc(ctx);
+       tail = &head->next;
 
        /*
         *      Copy everything in the list of nodes
@@ -1955,7 +1973,7 @@ int xlat_copy(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_exp_t const *in)
                case XLAT_INVALID:
                        fr_strerror_printf("Cannot copy xlat node of type \"invalid\"");
                error:
-                       xlat_exp_list_free(out);
+                       talloc_free(head);
                        return -1;
 
                case XLAT_BOX:
@@ -2007,5 +2025,6 @@ int xlat_copy(TALLOC_CTX *ctx, xlat_exp_t **out, xlat_exp_t const *in)
                xlat_exp_append(tail, node);
        }
 
+       *out = head;
        return 0;
 }