]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add merge with RHS priority
authorAlan T. DeKok <aland@freeradius.org>
Tue, 14 Dec 2021 15:54:20 +0000 (10:54 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Thu, 16 Dec 2021 19:25:22 +0000 (14:25 -0500)
src/lib/util/edit.c
src/tests/keywords/edit-merge-lhs [new file with mode: 0644]

index 28ce81ed0a233cf15907b3bec32352cb55fb1c4a..e792282f8f55acb678c6dab447f7d2c15fde1c33 100644 (file)
@@ -891,6 +891,102 @@ static int list_merge_lhs(fr_edit_list_t *el, fr_pair_t *dst, fr_pair_list_t *sr
        return 0;
 }
 
+/** A MERGE B
+ *
+ * with priority to B
+ */
+static int list_merge_rhs(fr_edit_list_t *el, fr_pair_t *dst, fr_pair_list_t *src)
+{
+       fr_pair_t *a, *b;
+       fr_dcursor_t cursor1, cursor2;
+
+       fr_pair_list_sort(&dst->children, fr_pair_cmp_by_parent_num);
+       fr_pair_list_sort(src, fr_pair_cmp_by_parent_num);
+
+       fr_pair_dcursor_init(&cursor1, &dst->children);
+       fr_pair_dcursor_init(&cursor2, src);
+
+       while (true) {
+               int rcode;
+
+               a = fr_dcursor_current(&cursor1);
+               b = fr_dcursor_current(&cursor2);
+
+               /*
+                *      B is done, so we stop processing the merge.
+                */
+               if (!b) break;
+
+               /*
+                *      A is done, so we always merge in B at the end of A.
+                */
+               if (!a) {
+                       if (fr_edit_list_insert_pair_tail(el, &dst->children, fr_pair_copy(dst, b)) < 0) {
+                               return -1;
+                       }
+
+                       fr_dcursor_next(&cursor2);
+                       continue;
+               }
+
+               rcode = fr_pair_cmp_by_parent_num(a, b);
+
+               /*
+                *      a > b
+                *
+                *      A stays in its list, but we advance to the
+                *      next item.  Maybe at that point we will be
+                *      able to merge A and B.
+                */
+               if (rcode > 0) {
+                       fr_dcursor_next(&cursor1);
+                       continue;
+               }
+
+               /*
+                *      a < b
+                *
+                *      This means that in the ordered set, the
+                *      equivalent to B does not exist.  So we copy B
+                *      to before A.
+                */
+               if (rcode < 0) {
+                       if (fr_edit_list_insert_pair_before(el, &dst->children, a, fr_pair_copy(dst, b)) < 0) {
+                               return -1;
+                       }
+
+                       fr_dcursor_next(&cursor2);
+                       continue;
+               }
+
+               fr_assert(rcode == 0);
+
+               /*
+                *      They're the same.  Recurse if necessary.
+                *
+                *      Then, ignore B, because we already have A.
+                *
+                *      The attributes are the same.  Keep A, but also
+                *      check if we have to merge the children of A
+                *      and B.
+                */
+               fr_assert(a->da == b->da);
+
+               if (fr_type_is_structural(a->vp_type)) {
+                       rcode = list_merge_rhs(el, a, &b->children);
+                       if (rcode < 0) return rcode;
+               }
+
+               /*
+                *      We have both A and B, so we prefer A.
+                */
+               fr_dcursor_next(&cursor1);
+               fr_dcursor_next(&cursor2);
+       }
+
+       return 0;
+}
+
 /** Apply operators to lists.
  *
  *   = is "if found vp, do nothing.  Otherwise call fr_edit_list_insert_pair_tail()
@@ -952,6 +1048,11 @@ int fr_edit_list_apply_list_assignment(fr_edit_list_t *el, fr_pair_t *dst, fr_to
 
                return list_merge_lhs(el, dst, src);
 
+       case T_OP_LE:
+               if (&dst->children == src) return 0; /* A MERGE A == A */
+
+               return list_merge_rhs(el, dst, src);
+
        default:
                break;
        }
diff --git a/src/tests/keywords/edit-merge-lhs b/src/tests/keywords/edit-merge-lhs
new file mode 100644 (file)
index 0000000..800e062
--- /dev/null
@@ -0,0 +1,38 @@
+#
+# PRE: edit-merge
+#
+#  A MERGE B, priority B
+#
+#      = A if there's no B
+#      = B if A exists
+#      = A' MERGE B' if A and B are lists
+#
+
+update request {
+       &Tmp-String-0 := "foo"
+}
+
+update control {
+       &Tmp-String-0 := "bar"
+}
+
+# merge
+&request <= &control
+
+if (!&request.Tmp-String-0) {
+       test_fail
+}
+
+# we want the *control* version
+if (!(&request.Tmp-String-0 == "bar")) {
+       %(debug_attr:request[*])
+       test_fail
+}
+
+#  and the original value should not be there
+if (&request.Tmp-String-0 == "foo") {
+       %(debug_attr:request[*])
+       test_fail
+}
+
+success