]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add unlang_edit_push() for external use of the new edit functionality
authorAlan T. DeKok <aland@freeradius.org>
Fri, 20 Oct 2023 12:50:07 +0000 (08:50 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 20 Oct 2023 12:50:07 +0000 (08:50 -0400)
src/lib/unlang/edit.c
src/lib/unlang/edit.h [new file with mode: 0644]

index be329ea928eb250edef318d925669c734b65c196..b34a1c9a64150d9f12ae3e1cc8545fe4a3f600a0 100644 (file)
@@ -31,6 +31,7 @@ RCSID("$Id$")
 #include <freeradius-devel/util/edit.h>
 #include <freeradius-devel/util/calc.h>
 #include <freeradius-devel/unlang/tmpl.h>
+#include <freeradius-devel/unlang/edit.h>
 #include <freeradius-devel/unlang/unlang_priv.h>
 #include "edit_priv.h"
 
@@ -62,7 +63,7 @@ struct edit_map_s {
        edit_map_t              *parent;
        edit_map_t              *child;
 
-       map_list_t const        *map_head;
+       map_list_t const        *map_list;
        map_t const             *map;                   //!< the map to evaluate
 
        bool                    temporary_pair_list;
@@ -79,10 +80,12 @@ struct edit_map_s {
  *
  */
 struct unlang_frame_state_edit_s {
-       fr_edit_list_t          *el;                            //!< edit list
+       fr_edit_list_t          *el;                    //!< edit list
+       bool                    *success;               //!< whether or not the edit succeeded
+
        rindent_t               indent;
 
-       edit_map_t              *current;                       //!< what we're currently doing.
+       edit_map_t              *current;               //!< what we're currently doing.
        edit_map_t              first;
 };
 
@@ -824,7 +827,7 @@ static int next_map(UNUSED request_t *request, UNUSED unlang_frame_state_edit_t
        current->lhs.vpt = NULL;
        current->rhs.vpt = NULL;
 
-       current->map = map_list_next(current->map_head, current->map);
+       current->map = map_list_next(current->map_list, current->map);
        current->func = expand_lhs;
 
        /*
@@ -1007,8 +1010,8 @@ static int expand_rhs_list(request_t *request, unlang_frame_state_edit_t *state,
         *      parent vp.  Any child pairs which aren't used will be freed.
         */
        child->el = NULL;
-       child->map_head = &map->child;
-       child->map = map_list_head(child->map_head);
+       child->map_list = &map->child;
+       child->map = map_list_head(child->map_list);
        child->func = expand_lhs;
 
        if (fr_type_is_leaf(tmpl_attr_tail_da(current->lhs.vpt)->type)) {
@@ -1499,10 +1502,12 @@ static unlang_action_t process_edit(rlm_rcode_t *p_result, request_t *request, u
                                *p_result = RLM_MODULE_NOOP;
 
                                /*
-                                *      Expansions, etc. are SOFT
-                                *      failures, which simply don't
-                                *      apply the operations.
+                                *      Expansions, etc. failures are SOFT failures, which undo the edit
+                                *      operations, but otherwise do not affect the interpreter.
+                                *
+                                *      However, if the caller asked for the actual result, return that, too.
                                 */
+                               if (state->success) *state->success = false;
                                return UNLANG_ACTION_CALCULATE_RESULT;
                        }
 
@@ -1522,29 +1527,17 @@ static unlang_action_t process_edit(rlm_rcode_t *p_result, request_t *request, u
        }
 
        /*
-        *      Freeing the edit list will automatically commit the edits.
+        *      Freeing the edit list will automatically commit the edits.  i.e. trash the undo list, and
+        *      leave the edited pairs in place.
         */
 
        *p_result = RLM_MODULE_NOOP;
+       if (state->success) *state->success = true;
        return UNLANG_ACTION_CALCULATE_RESULT;
 }
 
-/** Execute an update block
- *
- * Update blocks execute in two phases, first there's an evaluation phase where
- * each input map is evaluated, outputting one or more modification maps. The modification
- * maps detail a change that should be made to a list in the current request.
- * The request is not modified during this phase.
- *
- * The second phase applies those modification maps to the current request.
- * This re-enables the atomic functionality of update blocks provided in v2.x.x.
- * If one map fails in the evaluation phase, no more maps are processed, and the current
- * result is discarded.
- */
-static unlang_action_t unlang_edit_state_init(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
+static void edit_state_init_internal(request_t *request, unlang_frame_state_edit_t *state, fr_edit_list_t *el, map_list_t const *map_list)
 {
-       unlang_edit_t                   *edit = unlang_generic_to_edit(frame->instruction);
-       unlang_frame_state_edit_t       *state = talloc_get_type_abort(frame->state, unlang_frame_state_edit_t);
        edit_map_t                      *current = &state->first;
 
        state->current = current;
@@ -1555,12 +1548,14 @@ static unlang_action_t unlang_edit_state_init(rlm_rcode_t *p_result, request_t *
         *      The edit list creates a local pool which should
         *      generally be large enough for most edits.
         */
-       MEM(state->el = fr_edit_list_alloc(state, map_list_num_elements(&edit->maps)));
+       if (!el) {
+               MEM(state->el = fr_edit_list_alloc(state, map_list_num_elements(map_list)));
+       }
 
        current->ctx = state;
        current->el = state->el;
-       current->map_head = &edit->maps;
-       current->map = map_list_head(current->map_head);
+       current->map_list = map_list;
+       current->map = map_list_head(current->map_list);
        fr_pair_list_init(&current->rhs.pair_list);
        current->func = expand_lhs;
        current->check_lhs = check_lhs;
@@ -1570,6 +1565,26 @@ static unlang_action_t unlang_edit_state_init(rlm_rcode_t *p_result, request_t *
         *      Save current indentation for the error path.
         */
        RINDENT_SAVE(&state->indent, request);
+}
+
+/** Execute an update block
+ *
+ * Update blocks execute in two phases, first there's an evaluation phase where
+ * each input map is evaluated, outputting one or more modification maps. The modification
+ * maps detail a change that should be made to a list in the current request.
+ * The request is not modified during this phase.
+ *
+ * The second phase applies those modification maps to the current request.
+ * This re-enables the atomic functionality of update blocks provided in v2.x.x.
+ * If one map fails in the evaluation phase, no more maps are processed, and the current
+ * result is discarded.
+ */
+static unlang_action_t unlang_edit_state_init(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
+{
+       unlang_edit_t                   *edit = unlang_generic_to_edit(frame->instruction);
+       unlang_frame_state_edit_t       *state = talloc_get_type_abort(frame->state, unlang_frame_state_edit_t);
+
+       edit_state_init_internal(request, state, NULL, &edit->maps);
 
        /*
         *      Call process_edit to do all of the work.
@@ -1579,6 +1594,64 @@ static unlang_action_t unlang_edit_state_init(rlm_rcode_t *p_result, request_t *
 }
 
 
+/** Push a map onto the stack for edit evaluation
+ *
+ *  If the "success" variable returns "false", the caller should free the edit list.  At which point all edits will be undone.
+ *
+ * @param[in] request          The current request.
+ * @param[out] success         Whether or not the edit succeeded
+ * @param[in] el               Edit list which can be used to apply multiple edits
+ * @param[in] map_list         The map list to process
+ */
+int unlang_edit_push(request_t *request, bool *success, fr_edit_list_t *el, map_list_t const *map_list)
+{
+       unlang_stack_t                  *stack = request->stack;
+       unlang_stack_frame_t            *frame;
+       unlang_frame_state_edit_t       *state;
+
+       unlang_edit_t                   *edit;
+
+       static unlang_t edit_instruction = {
+               .type = UNLANG_TYPE_EDIT,
+               .name = "edit",
+               .debug_name = "edit",
+               .actions = {
+                       .actions = {
+                               [RLM_MODULE_REJECT]     = 0,
+                               [RLM_MODULE_FAIL]       = 0,
+                               [RLM_MODULE_OK]         = 0,
+                               [RLM_MODULE_HANDLED]    = 0,
+                               [RLM_MODULE_INVALID]    = 0,
+                               [RLM_MODULE_DISALLOW]   = 0,
+                               [RLM_MODULE_NOTFOUND]   = 0,
+                               [RLM_MODULE_NOOP]       = 0,
+                               [RLM_MODULE_UPDATED]    = 0
+                       },
+                       .retry = RETRY_INIT,
+               },
+       };
+
+       MEM(edit = talloc(stack, unlang_edit_t));
+       *edit = (unlang_edit_t) {
+               .self = edit_instruction,
+       };
+       map_list_init(&edit->maps);
+
+       /*
+        *      Push a new edit frame onto the stack
+        */
+       if (unlang_interpret_push(request, unlang_edit_to_generic(edit),
+                                 RLM_MODULE_NOT_SET, UNLANG_NEXT_STOP, false) < 0) return -1;
+
+       frame = &stack->frame[stack->depth];
+       state = talloc_get_type_abort(frame->state, unlang_frame_state_edit_t);
+
+       edit_state_init_internal(request, state, el, map_list);
+       state->success = success;
+
+       return 0;
+}
+
 void unlang_edit_init(void)
 {
        unlang_register(UNLANG_TYPE_EDIT,
diff --git a/src/lib/unlang/edit.h b/src/lib/unlang/edit.h
new file mode 100644 (file)
index 0000000..af42825
--- /dev/null
@@ -0,0 +1,36 @@
+#pragma once
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software Foundation,
+ *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * $Id$
+ *
+ * @file unlang/edit.h
+ *
+ * @brief Functions to perform edits on maps
+ *
+ * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int            unlang_edit_push(request_t *request, bool *success, fr_edit_list_t *el, map_list_t const *map_list)
+                               CC_HINT(warn_unused_result) CC_HINT(nonnull(1,2,4));
+
+#ifdef __cplusplus
+}
+#endif