The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
edit.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: 3da233f6a2b85c4cf278cd2639a1c1e2e5be076b $
19 *
20 * @brief fr_pair_t editing
21 *
22 * @ingroup AVP
23 *
24 * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
25 */
26RCSID("$Id: 3da233f6a2b85c4cf278cd2639a1c1e2e5be076b $")
27
28#include <freeradius-devel/server/base.h>
29#include <freeradius-devel/server/tmpl_dcursor.h>
30#include <freeradius-devel/util/edit.h>
31#include <freeradius-devel/util/calc.h>
32#include <freeradius-devel/unlang/tmpl.h>
33#include <freeradius-devel/unlang/edit.h>
34#include <freeradius-devel/unlang/transaction.h>
35#include <freeradius-devel/unlang/unlang_priv.h>
36#include "edit_priv.h"
37
38#undef XDEBUG
39#if 1
40#define XDEBUG(...)
41#else
42#define XDEBUG DEBUG2
43#endif
44
45#define RDEBUG_ASSIGN(_name, _op, _box) rdebug_assign(request, _name, _op, _box)
46
47static void rdebug_assign(request_t *request, char const *attr, fr_token_t op, fr_value_box_t const *box)
48{
49 char const *name;
50
51 switch (box->type) {
52 case FR_TYPE_QUOTED:
53 RDEBUG2("%s %s \"%pV\"", attr, fr_tokens[op], box);
54 break;
55
58 fr_assert(0);
59 break;
60
61 default:
62 fr_assert(fr_type_is_leaf(box->type));
63
64 if ((name = fr_value_box_enum_name(box)) != NULL) {
65 RDEBUG2("%s %s ::%s", attr, fr_tokens[op], name);
66 break;
67 }
68
69 RDEBUG2("%s %s %pV", attr, fr_tokens[op], box);
70 break;
71 }
72}
73
74typedef struct {
75 fr_value_box_list_t list; //!< output data
76 tmpl_t const *vpt; //!< expanded tmpl
77 tmpl_t *to_free; //!< tmpl to free.
78 bool create; //!< whether we need to create the VP
79 unlang_result_t result; //!< result of the xlat expansion
80 fr_pair_t *vp; //!< VP referenced by tmpl.
81 fr_pair_t *vp_parent; //!< parent of the current VP
82 fr_pair_list_t pair_list; //!< for structural attributes
84
85typedef struct edit_map_s edit_map_t;
86
88
90
91struct edit_map_s {
92 fr_edit_list_t *el; //!< edit list
93
95 TALLOC_CTX *ctx;
98
99 map_list_t const *map_list;
100 map_t const *map; //!< the map to evaluate
101
103
104 edit_result_t lhs; //!< LHS child entries
105 edit_result_t rhs; //!< RHS child entries
106
107 unlang_edit_expand_t func; //!< for process state
108 unlang_edit_expand_t check_lhs; //!< for special cases
109 unlang_edit_expand_t expanded_lhs; //!< for special cases
110};
111
112/** State of an edit block
113 *
114 */
116 fr_edit_list_t *el; //!< edit list
117 bool *success; //!< whether or not the edit succeeded
118 bool ours;
119
121
122 edit_map_t *current; //!< what we're currently doing.
124};
125
126#define MAP_INFO cf_filename(map->ci), cf_lineno(map->ci)
127
128static fr_pair_t *edit_list_pair_build(fr_pair_t *parent, fr_dcursor_t *cursor, fr_dict_attr_t const *da, void *uctx);
129
130/*
131 * Convert a value-box list to a LHS attribute #tmpl_t
132 */
133static int tmpl_attr_from_result(TALLOC_CTX *ctx, map_t const *map, edit_result_t *out, request_t *request)
134{
135 ssize_t slen;
136 fr_value_box_t *box = fr_value_box_list_head(&out->list);
137
138 if (!box) {
139 RWDEBUG("%s %s ... - Assignment failed - No value on right-hand side", map->lhs->name, fr_tokens[map->op]);
140 return -1;
141 }
142
143 /*
144 * Mash all of the results together.
145 */
146 if (fr_value_box_list_concat_in_place(box, box, &out->list, FR_TYPE_STRING, FR_VALUE_BOX_LIST_FREE, true, SIZE_MAX) < 0) {
147 RWDEBUG("Failed converting result to string");
148 return -1;
149 }
150
151 /*
152 * Parse the LHS as an attribute reference. It can't be
153 * anything else.
154 */
155 slen = tmpl_afrom_attr_str(ctx, NULL, &out->to_free, box->vb_strvalue,
156 &(tmpl_rules_t){
157 .attr = {
158 .dict_def = request->local_dict,
159 .list_def = request_attr_request,
160 .ci = map->ci,
161 }
162 });
163 if (slen <= 0) {
164 RPEDEBUG("Expansion result \"%s\" is not an attribute reference", box->vb_strvalue);
165 return -1;
166 }
167
168 out->vpt = out->to_free;
169 fr_value_box_list_talloc_free(&out->list);
170
171 return 0;
172}
173
174
175/*
176 * Expand a tmpl.
177 */
178static int tmpl_to_values(TALLOC_CTX *ctx, edit_result_t *out, request_t *request, tmpl_t const *vpt)
179{
180 fr_assert(out->vpt == NULL);
181 fr_assert(out->to_free == NULL);
182
183 switch (vpt->type) {
184 case TMPL_TYPE_DATA:
185 return 0;
186
187 case TMPL_TYPE_ATTR:
188 out->vpt = vpt;
189 return 0;
190
191 case TMPL_TYPE_EXEC:
192 if (unlang_tmpl_push(ctx, &out->result, &out->list, request, vpt, NULL, UNLANG_SUB_FRAME) < 0) return -1;
193 return 1;
194
195 case TMPL_TYPE_XLAT:
196 if (unlang_xlat_push(ctx, &out->result, &out->list, request, tmpl_xlat(vpt), false) < 0) return -1;
197 return 1;
198
199 default:
200 /*
201 * The other tmpl types MUST have already been
202 * converted to the "realized" types.
203 */
204 tmpl_debug(stderr, vpt);
205 fr_assert(0);
206 break;
207 }
208
209 return -1;
210}
211
212static void edit_debug_attr_list(request_t *request, fr_pair_list_t const *list, map_t const *map);
213
214static void edit_debug_attr_vp(request_t *request, fr_pair_t *vp, map_t const *map)
215{
216 fr_assert(vp != NULL);
217
218 if (map) {
219 switch (vp->vp_type) {
221 RDEBUG2("%s = {", map->lhs->name);
222 RINDENT();
223 edit_debug_attr_list(request, &vp->vp_group, map_list_head(&map->child));
224 REXDENT();
225 RDEBUG2("}");
226 break;
227
228 default:
229 RDEBUG_ASSIGN(map->lhs->name, vp->op, &vp->data);
230 break;
231 }
232 } else {
233 switch (vp->vp_type) {
235 RDEBUG2("%s = {", vp->da->name);
236 RINDENT();
237 edit_debug_attr_list(request, &vp->vp_group, NULL);
238 REXDENT();
239 RDEBUG2("}");
240 break;
241
242 default:
243 RDEBUG_ASSIGN(vp->da->name, vp->op, &vp->data);
244 break;
245 }
246 }
247}
248
249static void edit_debug_attr_list(request_t *request, fr_pair_list_t const *list, map_t const *map)
250{
251 fr_pair_t *vp;
252 map_t const *child = NULL;
253
254 if (map) child = map_list_head(&map->child);
255
256 for (vp = fr_pair_list_next(list, NULL);
257 vp != NULL;
258 vp = fr_pair_list_next(list, vp)) {
259 edit_debug_attr_vp(request, vp, child);
260 if (map) child = map_list_next(&map->child, child);
261 }
262}
263
264static int edit_create_lhs_vp(request_t *request, TALLOC_CTX *ctx, edit_map_t *current)
265{
266 int err;
267 fr_pair_t *vp;
268 tmpl_dcursor_ctx_t lhs_cc;
269 fr_dcursor_t lhs_cursor;
270
271 fr_assert(current->lhs.create);
272
273 /*
274 * Now that we have the RHS values, go create the LHS vp. We delay creating it until
275 * now, because the RHS might just be nothing. In which case we don't want to create the
276 * LHS, and then discover that we need to delete it.
277 */
279 vp = tmpl_dcursor_build_init(&err, ctx, &lhs_cc, &lhs_cursor, request, current->lhs.vpt, edit_list_pair_build, current);
280 tmpl_dcursor_clear(&lhs_cc);
281 if (!vp) {
282 RPEDEBUG("Failed creating attribute %s", current->lhs.vpt->name);
283 return -1;
284 }
285
286 current->lhs.vp = vp;
287
288 return 0;
289}
290
291/* Apply the edits to a structural attribute..
292 *
293 * Figure out what edits to do, and then do them.
294 */
296{
297 fr_pair_t *vp;
298 fr_pair_list_t *children;
299 int rcode;
300 map_t const *map = current->map;
302 fr_dcursor_t cursor;
303
304 XDEBUG("apply_edits_to_list %s", map->lhs->name);
305
306 /*
307 * RHS is a sublist, go apply that.
308 */
309 if (!map->rhs) {
310 children = &current->rhs.pair_list;
311 goto apply_list;
312 }
313
314 /*
315 * For RHS of data, it should be a string which contains the pairs to use.
316 */
317 if (!current->rhs.vpt) {
318 fr_value_box_t *box;
319 fr_dict_attr_t const *da;
320 fr_pair_parse_t root, relative;
321
322 if (tmpl_is_data(map->rhs)) {
323 box = tmpl_value(map->rhs);
324
325 if (box->type != FR_TYPE_STRING) {
326 REDEBUG("Invalid data type for assignment to list");
327 return -1;
328 }
329
330 } else {
331 box = fr_value_box_list_head(&current->rhs.list);
332
333 /*
334 * Can't concatenate empty results.
335 */
336 if (!box) {
337 RWDEBUG("%s %s ... - Assignment failed to having no value on right-hand side", map->lhs->name, fr_tokens[map->op]);
338 return -1;
339 }
340
341 /*
342 * Mash all of the results together.
343 */
344 if (fr_value_box_list_concat_in_place(box, box, &current->rhs.list, FR_TYPE_STRING, FR_VALUE_BOX_LIST_FREE, true, SIZE_MAX) < 0) {
345 RWDEBUG("Failed converting result to string");
346 return -1;
347 }
348 }
349
350 children = &current->rhs.pair_list;
351
352 /*
353 * For exec, etc., parse the pair list from a string, in the context of the
354 * parent VP. Because we're going to be moving them to the parent VP at some
355 * point. The ones which aren't moved will get deleted in this function.
356 */
357 da = tmpl_attr_tail_da(current->lhs.vpt);
358 if (fr_type_is_group(da->type)) da = fr_dict_root(request->proto_dict);
359
360 root = (fr_pair_parse_t) {
361 .ctx = current->ctx,
362 .da = da,
363 .list = children,
364 .dict = request->proto_dict,
365 .internal = fr_dict_internal(),
366 .allow_compare = true,
367 .tainted = box->tainted,
368 };
369 relative = (fr_pair_parse_t) { };
370
371 if (fr_pair_list_afrom_substr(&root, &relative, &FR_SBUFF_IN(box->vb_strvalue, box->vb_length)) < 0) {
372 RPEDEBUG("Failed parsing string '%pV' as attribute list", box);
373 return -1;
374 }
375
376 goto apply_list;
377 }
378
379 fr_assert(current->rhs.vpt);
380 fr_assert(tmpl_is_attr(current->rhs.vpt));
381
382 /*
383 * Doing no modifications to a list is a NOOP.
384 */
385 vp = tmpl_dcursor_init(NULL, request, &cc, &cursor, request, current->rhs.vpt);
386 if (!vp) {
388 return 0;
389 }
390
391 /*
392 * Remove an attribute from a list. The tmpl_dcursor and tmpl_parser ensures that the RHS
393 * references are done in the context of the LHS attribute.
394 */
395 if (map->op == T_OP_SUB_EQ) {
396 fr_pair_t *next;
397
398 /*
399 * Loop over matching attributes, and delete them.
400 */
401 RDEBUG2("%s %s %s", current->lhs.vpt->name, fr_tokens[T_OP_SUB_EQ], current->rhs.vpt->name);
402
403 for ( ; vp != NULL; vp = next) {
404 fr_pair_list_t *list;
405
406 next = fr_dcursor_next(&cursor);
407
408 list = fr_pair_parent_list(vp);
409 fr_assert(list != NULL);
410
411 /*
412 * @todo - if this attribute is structural, then remove all children which aren't
413 * immutable. For now, this is good enough.
414 */
415 if (fr_pair_immutable(vp)) {
416 RWDEBUG("Not removing immutable %pP", vp);
417 continue;
418 }
419
420 if (vp->vp_edit) {
421 RWDEBUG("Attribute cannot be removed, as it is being used in a 'foreach' loop - %pP", vp);
422 continue;
423 }
424
425 if (fr_edit_list_pair_delete(current->el, list, vp) < 0) {
426 RPEDEBUG("Failed deleting attribute");
428 return -1;
429 }
430 }
431
433 return 0;
434 }
435
436 /*
437 * Check the RHS thing we're copying.
438 */
439 if (fr_type_is_structural(vp->vp_type)) {
441
442 if (tmpl_attr_tail_num(current->rhs.vpt) == NUM_ALL) {
443 REDEBUG("%s[%d] Wildcard for structural attribute %s is not yet implemented.", MAP_INFO, current->rhs.vpt->name);
444 return -1;
445 }
446
447 children = &vp->vp_group;
448 goto apply_list;
449 }
450
451 /*
452 * Copy the attributes from the cursor to a temporary pair list.
453 */
454 fr_pair_list_init(&current->rhs.pair_list);
455 while (vp) {
456 fr_pair_t *copy;
457
458 copy = fr_pair_copy(request, vp);
459 if (!copy) {
460 fr_pair_list_free(&current->rhs.pair_list);
462 return -1;
463 }
464 fr_pair_append(&current->rhs.pair_list, copy);
465
466 vp = fr_dcursor_next(&cursor);
467 }
469
470 children = &current->rhs.pair_list;
471
472 /*
473 * Apply structural thingies!
474 */
475apply_list:
476 fr_assert(children != NULL);
477
478 /*
479 * If we have to create the LHS, then do so now.
480 */
481 if (current->lhs.create && (edit_create_lhs_vp(request, state, current) < 0)) {
482 return -1;
483 }
484
485 fr_assert(current->lhs.vp != NULL);
486
487#ifdef STATIC_ANALYZER
488 if (!current->lhs.vp) return -1;
489#endif
490
491 /*
492 * Print the children before we do the modifications.
493 */
494 if (!current->parent) {
495 RDEBUG2("%s %s {", current->lhs.vpt->name, fr_tokens[map->op]);
496 if (fr_debug_lvl >= L_DBG_LVL_2) {
497 RINDENT();
498 edit_debug_attr_list(request, children, map);
499 REXDENT();
500 }
501 RDEBUG2("}");
502 }
503
504 fr_pair_list_foreach(children, child) {
505 if (!fr_dict_attr_can_contain(current->lhs.vp->da, child->da)) {
506 RDEBUG("Cannot perform assignment: Attribute \"%s\" is not a child of parent \"%s\"",
507 child->da->name, current->lhs.vp->da->name);
508 rcode = -1;
509 goto done;
510 }
511 }
512
513 if (map->op != T_OP_EQ) {
514 fr_assert(current->el != NULL);
515
516 rcode = fr_edit_list_apply_list_assignment(current->el, current->lhs.vp, map->op, children,
517 (children != &current->rhs.pair_list));
518 if (rcode < 0) RPEDEBUG("Failed performing list '%s' operation", fr_tokens[map->op]);
519
520 } else {
521#if 0
522 /*
523 * The RHS list _should_ be a copy of the LHS list. But for some cases it's not. We
524 * should spend time tracking this down, but not today.
525 *
526 * For now, brute-force copy isn't wrong.
527 */
528 if (children == &current->rhs.pair_list) {
529 fr_pair_list_append(&current->lhs.vp->vp_group, children);
530 } else
531#endif
532 (void) fr_pair_list_copy(current->lhs.vp, &current->lhs.vp->vp_group, children);
533
534 PAIR_VERIFY(current->lhs.vp);
535 rcode = 0;
536 }
537
538 /*
539 * If the child list wasn't copied, then we just created it, and we need to free it.
540 */
541done:
542 if (children == &current->rhs.pair_list) fr_pair_list_free(children);
543 return rcode;
544}
545
546static bool pair_is_editable(request_t *request, fr_pair_t *vp)
547{
548 if (vp->vp_edit) {
549 RWDEBUG("Attribute cannot be removed, as it is being used in a 'foreach' loop - %s", vp->da->name);
550 return false;
551 }
552
553 if (!fr_type_is_structural(vp->vp_type)) return true;
554
555 fr_pair_list_foreach(&vp->vp_group, child) {
556 if (!pair_is_editable(request, child)) return false;
557 }
558
559 return true;
560}
561
562static int edit_delete_lhs(request_t *request, edit_map_t *current, bool delete)
563{
565 fr_dcursor_t cursor;
566
567 /*
568 * These are magic.
569 */
570 if (delete) {
571 fr_dict_attr_t const *da = tmpl_attr_tail_da(current->lhs.vpt);
572
573 if (fr_type_is_structural(da->type) &&
574 ((da == request_attr_request) ||
575 (da == request_attr_reply) ||
576 (da == request_attr_control) ||
577 (da == request_attr_state))) {
578 delete = false;
579 }
580 }
581
582 while (true) {
583 int err;
585
586 /*
587 * Reinitialize the cursor for every VP. This is because fr_dcursor_remove() does not
588 * work with tmpl_dcursors, as the tmpl_dcursor code does not set the "remove" callback.
589 * And the tmpl is NUM_UNSPEC, which means "the first one", whereas for T_OP_SET_EQ, we
590 * really mean "delete all except the first one".
591 *
592 * Once that's implemented, we also need to update the edit list API to
593 * allow for "please delete children"?
594 */
595 vp = tmpl_dcursor_init(&err, current->ctx, &cc, &cursor, request, current->lhs.vpt);
596 if (!vp) break;
597
599 fr_assert(parent != NULL);
600
601 if (!pair_is_editable(request, vp)) {
603 return -1;
604 }
605
606 if (!delete) {
607 if (fr_type_is_structural(vp->vp_type)) {
608
609 if (fr_edit_list_free_pair_children(current->el, vp) < 0) return -1;
610 } else {
611 /*
612 * No need to save value, as fr_edit_list_apply_pair_assignment() will do
613 * that for us.
614 */
615 }
616
617 current->lhs.vp = vp;
619 return 0;
620 }
621
622 /*
623 * Delete all of them. We'll create one later for the SET operation.
624 */
625 if (fr_edit_list_pair_delete(current->el, &parent->vp_group, vp) < 0) {
626 RPWDEBUG("Failed deleting attribute");
627 return -1;
628 }
630 }
631
632 return 0;
633}
634
635/*
636 * Apply the edits to a leaf attribute. First we figure out where the results come from:
637 *
638 * single value-box (e.g. tmpl_is_data(vpt)
639 * rhs value-box result list (we create a dcursor)
640 * RHS attribute reference (we create a nested dcursor to get the values from the pair list)
641 *
642 * Then we figure out what to do with those values.
643 *
644 * if it needs to be created, then create it and just mash the results in place
645 * otherwise apply the edits (+=, etc.) to an existing attribute.
646 *
647 * @todo - move to using dcursors for all of the values. The dcursor should exist in current->rhs. It
648 * should be used even for TMPL_DATA and single value-boxes. Once that's done, it becomes easier to use
649 * dcursors for xlats, too.
650 */
652{
653 fr_value_box_t *box = NULL;
655 fr_dcursor_t cursor;
656 fr_dcursor_t pair_cursor;
657 bool single = false, pair = false;
658 map_t const *map = current->map;
659
660 XDEBUG("apply_edits_to_leaf %s", map->lhs->name);
661
662 if (!tmpl_is_attr(current->lhs.vpt)) {
663 REDEBUG("%s[%d] The left side of an assignment must be an attribute reference", MAP_INFO);
664 return -1;
665 }
666
667 /*
668 * &Foo := { a, b, c }
669 *
670 * There should be values in RHS result, all of value boxes.
671 */
672 if (!map->rhs) {
673 fr_assert(current->rhs.vpt == NULL);
674 goto rhs_list;
675
676 }
677
678 if (!current->rhs.vpt) {
679 /*
680 * There's no RHS tmpl, so the result must be in in the parent RHS tmpl as data, OR in
681 * the RHS result list.
682 */
683 if (tmpl_is_data(map->rhs)) {
684 box = tmpl_value(map->rhs);
685 single = true;
686
687 } else if ((map->rhs->quote == T_SINGLE_QUOTED_STRING) || (map->rhs->quote == T_DOUBLE_QUOTED_STRING)) {
688 /*
689 * The caller asked for a string, so instead of returning a list, return a string.
690 *
691 * If there's no output, then it's an empty string.
692 *
693 * We have to check this here, because the quote is part of the tmpl, and we call
694 * xlat_push(), which doesn't know about the quote.
695 *
696 * @todo - we should really push the quote into the xlat, too.
697 */
698 box = fr_value_box_list_head(&current->rhs.list);
699
700 if (!box) {
701 MEM(box = fr_value_box_alloc(state, FR_TYPE_STRING, NULL));
702 fr_value_box_strdup(box, box, NULL, "", false);
703 fr_value_box_list_insert_tail(&current->rhs.list, box);
704
705 } else if (fr_value_box_list_concat_in_place(box, box, &current->rhs.list, FR_TYPE_STRING,
706 FR_VALUE_BOX_LIST_FREE_BOX, true, 8192) < 0) {
707 RWDEBUG("Failed converting result to string");
708 return -1;
709 }
710 box = fr_value_box_list_head(&current->rhs.list);
711 single = true;
712
713 } else {
714 rhs_list:
715 if (fr_value_box_list_num_elements(&current->rhs.list) == 1) {
716 box = fr_value_box_list_head(&current->rhs.list);
717 single = true;
718 } else {
719 box = fr_dcursor_init(&cursor, fr_value_box_list_dlist_head(&current->rhs.list));
720 }
721 }
722 } else {
723 fr_pair_t *vp;
724 int err;
725
726 /*
727 * We have a temporary tmpl on the RHS. It MUST be an attribute, because everything else
728 * was expanded to a value-box list.
729 */
730 fr_assert(tmpl_is_attr(current->rhs.vpt));
731
732 /*
733 * Get a cursor over the RHS pairs.
734 */
735 vp = tmpl_dcursor_init(&err, request, &cc, &pair_cursor, request, current->rhs.vpt);
736 if (!vp) {
738
739 if (map->op != T_OP_SET) return 0;
740
741 /*
742 * No RHS pairs means we can finally delete all of the LHS.
743 */
744 return edit_delete_lhs(request, current, true);
745 }
746
747 box = fr_pair_dcursor_nested_init(&cursor, &pair_cursor); // the list is unused
748 pair = true;
749 }
750
751 if (!box) {
752 if (map->op != T_OP_SET) {
753 RWDEBUG("%s %s ... - Assignment failed - No value on right-hand side", map->lhs->name, fr_tokens[map->op]);
754 return -1;
755 }
756
757 /*
758 * Set is "delete, then add".
759 */
760 RDEBUG2("%s :=", current->lhs.vpt->name);
761 goto done;
762 }
763
764 /*
765 * The parent is a structural type. The RHS is a temporary list or attribute, which we can just
766 * add to the parents pair list. The parent will then take care of merging that pair list into
767 * the appropriate place.
768 */
769 if (current->temporary_pair_list) {
770 fr_pair_list_t *list = &current->parent->rhs.pair_list;
771 fr_pair_t *vp;
772
773 if (!current->parent->lhs.vp) {
774 if (edit_create_lhs_vp(request, request, current->parent) < 0) return -1;
775 }
776
777 while (box) {
778 /*
779 * Create (or find) all intermediate attributes. The LHS map might have multiple
780 * attribute names in it.
781 *
782 * @todo - audit other uses of tmpl_attr_tail_da() and fr_pair_afrom_da() in this file.
783 */
784 if (pair_append_by_tmpl_parent(current->parent->lhs.vp, &vp, list, current->lhs.vpt, true) < 0) {
785 RPEDEBUG("Failed creating attribute %s", current->lhs.vpt->name);
786 return -1;
787 }
788
789 vp->op = map->op;
791
792 if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, box) < 0) return -1;
793
794 if (single) break;
795
796 box = fr_dcursor_next(&cursor);
797 }
798
799 goto done;
800 }
801
802 /*
803 * If we're supposed to create the LHS, then go do that.
804 */
805 if (current->lhs.create) {
806 fr_dict_attr_t const *da = tmpl_attr_tail_da(current->lhs.vpt);
807 fr_pair_t *vp;
808
809 /*
810 * Something went wrong creating the value, it's a failure. Note that we fail _all_
811 * subsequent assignments, too.
812 */
813 if (fr_type_is_null(box->type)) goto fail;
814
815 if (edit_create_lhs_vp(request, state, current) < 0) goto fail;
816
817 fr_assert(current->lhs.vp_parent != NULL);
818 fr_assert(fr_type_is_structural(current->lhs.vp_parent->vp_type));
819
820 vp = current->lhs.vp;
821
822 /*
823 * There's always at least one LHS vp created. So we apply that first.
824 */
825 RDEBUG_ASSIGN(current->lhs.vpt->name, map->op, box);
826
827 /*
828 * The VP has already been inserted into the edit list, so we don't need to edit it's
829 * value, we can just mash it in place.
830 */
831 if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, box) < 0) goto fail;
832 vp->op = T_OP_EQ;
833 if (vp->da->flags.unsafe) fr_value_box_mark_unsafe(&vp->data);
834
835 if (single) goto done;
836
837 /*
838 * Now that the attribute has been created, go apply the rest of the values to the attribute.
839 */
840 if (!((map->op == T_OP_EQ) || (map->op == T_OP_SET))) {
841 box = fr_dcursor_next(&cursor);
842 if (!box) goto done;
843
844 goto apply_op;
845 }
846
847 if (current->lhs.vp->da->flags.local) {
848 if (fr_dcursor_next_peek(&cursor)) RWDEBUG("Ignoring extra values for local variable");
849 goto done;
850 }
851
852 /*
853 * Loop over the remaining items, adding the VPs we've just created.
854 */
855 while ((box = fr_dcursor_next(&cursor)) != NULL) {
856 RDEBUG_ASSIGN(current->lhs.vpt->name, map->op, box);
857
858 MEM(vp = fr_pair_afrom_da(current->lhs.vp_parent, da));
859 if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, box) < 0) goto fail;
860 if (vp->da->flags.unsafe) fr_value_box_mark_unsafe(&vp->data);
861
862 if (fr_edit_list_insert_pair_tail(state->el, &current->lhs.vp_parent->vp_group, vp) < 0) goto fail;
863 vp->op = T_OP_EQ;
865 }
866
867 goto done;
868 }
869
870 /*
871 * If we're not creating a temporary list, we must be editing an existing attribute on the LHS.
872 *
873 * We have two remaining cases. One is the attribute was just created with "=" or ":=", so we
874 * can just mash its value. The second is that the attribute already exists, and we're editing
875 * it's value using something like "+=".
876 */
877 fr_assert(current->lhs.vp != NULL);
878
879#ifdef STATIC_ANALYZER
880 if (!current->lhs.vp) return -1;
881#endif
882
883apply_op:
884 /*
885 * All other operators are "modify in place", of the existing current->lhs.vp
886 */
887 while (box) {
888 RDEBUG_ASSIGN(current->lhs.vpt->name, map->op, box);
889 if (current->lhs.vp->da->flags.unsafe) fr_value_box_mark_unsafe(box);
890
891 /*
892 * The apply function also takes care of doing data type upcasting and conversion. So we don't
893 * have to check for compatibility of the data types on the LHS and RHS.
894 */
896 current->lhs.vp,
897 map->op,
898 box) < 0) {
899 fail:
900 RPEDEBUG("Assigning value to %s failed", map->lhs->name);
901 if (pair) tmpl_dcursor_clear(&cc);
902 return -1;
903 }
904
905 if (single) break;
906
907 box = fr_dcursor_next(&cursor);
908 }
909
910done:
911 if (pair) tmpl_dcursor_clear(&cc);
912 fr_value_box_list_talloc_free(&current->rhs.list);
913
914 return 0;
915}
916
917
918/** Simple pair building callback for use with tmpl_dcursors
919 *
920 * Which always appends the new pair to the tail of the list
921 * since it is only called when no matching pairs were found when
922 * walking the list.
923 *
924 * Note that this function is called for all intermediate nodes which are built!
925 *
926 *
927 *
928 * @param[in] parent to allocate new pair within.
929 * @param[in,out] cursor to append new pair to.
930 * @param[in] da of new pair.
931 * @param[in] uctx unused.
932 * @return
933 * - newly allocated #fr_pair_t.
934 * - NULL on error.
935 */
937{
938 fr_pair_t *vp;
939 edit_map_t *current = uctx;
940
941 if (!fr_type_is_structural(parent->da->type)) {
942 request_t *request = current->request;
943
944 REDEBUG("Cannot create child of leaf data type");
945 return NULL;
946 }
947
949 if (!vp) return NULL;
950
951 current->lhs.vp_parent = parent;
952 current->lhs.vp = vp;
954
955 if (fr_edit_list_insert_pair_tail(current->el, &parent->vp_group, vp) < 0) {
957 return NULL;
958 }
959
960 /*
961 * Tell the cursor that we appended a pair. This
962 * function only gets called when we've ran off of the
963 * end of the list, and can't find the thing we're
964 * looking for. So it's safe at set the current one
965 * here.
966 *
967 * @todo - mainly only because we don't allow creating
968 * foo[4] when there's <3 matching entries. i.e. the
969 * "arrays" here are really lists, so we can't create
970 * "holes" in the list.
971 */
972 fr_dcursor_set_current(cursor, vp);
973
974 return vp;
975}
976
977#define DECLARE(_x) static int _x(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
978
985
986/*
987 * Clean up the current state, and go to the next map.
988 */
990{
991 TALLOC_FREE(current->lhs.to_free);
992 TALLOC_FREE(current->rhs.to_free);
993 fr_pair_list_free(&current->rhs.pair_list);
994 current->lhs.vp = NULL;
995 current->lhs.vp_parent = NULL;
996 current->lhs.vpt = NULL;
997 current->rhs.vpt = NULL;
998
999 current->map = map_list_next(current->map_list, current->map);
1000 current->func = expand_lhs;
1001
1002 /*
1003 * Don't touch the other callbacks.
1004 */
1005
1006 return 0;
1007}
1008
1009/*
1010 * Validate the RHS of an expansion.
1011 */
1013{
1014 map_t const *map = current->map;
1015
1016 if (!XLAT_RESULT_SUCCESS(&current->rhs.result)) {
1017 if (map->rhs) {
1018 RDEBUG("Failed expanding ... %s", map->rhs->name);
1019 } else {
1020 RDEBUG("Failed assigning to %s", map->lhs->name);
1021 }
1022 return -1;
1023 }
1024
1025 XDEBUG("%s map %s %s ...", __FUNCTION__, map->lhs->name, fr_tokens[map->op]);
1026
1027 /*
1028 * := is "remove all matching, and then add". So if even if we don't add anything, we still remove things.
1029 *
1030 * If we deleted the attribute when processing the LHS, then you couldn't reference an attribute
1031 * in it's own assignment:
1032 *
1033 * &foo := %tolower(foo)
1034 *
1035 * so we have to delay the deletion until the RHS has been fully expanded. But we don't always
1036 * delete everything. e.g. if the map is:
1037 *
1038 * &foo[1] := %tolower(foo[1])
1039 *
1040 * The we just apply the assignment to the LHS, over-writing it's value.
1041 */
1042 if ((map->op == T_OP_SET) &&
1043 ((tmpl_attr_tail_num(current->lhs.vpt) == NUM_UNSPEC) || (tmpl_attr_tail_num(current->lhs.vpt) > 0) ||
1044 !current->map->rhs)) {
1045 if (edit_delete_lhs(request, current,
1046 (tmpl_attr_tail_num(current->lhs.vpt) == NUM_UNSPEC) || !current->map->rhs) < 0) return -1;
1047 }
1048
1049 /*
1050 * @todo - Realize the RHS box value. By moving the code in apply_edits_to_leaf() to a common function,
1051 * and getting the box dcursor here.
1052 *
1053 * Then, get a cursor for the LHS vp, and loop over it, applying the edits in the operator, using
1054 * the comparisons in the RHS box.
1055 *
1056 * This lets us use array indexes (or more complex things) on the LHS, and means that we don't
1057 * have to realize the VPs and use horrible hacks.
1058 */
1059 if (current->parent && (current->parent->map->op == T_OP_SUB_EQ)) {
1060 fr_assert(current->temporary_pair_list);
1061 fr_assert(tmpl_is_attr(current->lhs.vpt)); /* can only apply edits to real attributes */
1062 fr_assert(map->rhs); /* can only filter on leaf attributes */
1063
1064#if 0
1065 {
1066 // dcursor_init over current->lhs.vpt, using children of current->parent.lhs_vp
1067 //
1068 // and then use the dcursor from the apply_edits_to_leaf() to get value-boxes
1069 rcode = fr_value_box_cmp_op(map->op, &vp->data, box);
1070 if (rcode < 0) return -1;
1071
1072 if (!rcode) continue;
1073
1074 if (fr_edit_list_pair_delete(el, list, vp) < 0) return -1;
1075 }
1076
1077 return next_map(request, state, current);
1078#endif
1079 }
1080
1081 if (fr_type_is_leaf(tmpl_attr_tail_da(current->lhs.vpt)->type)) {
1082 if (apply_edits_to_leaf(request, state, current) < 0) return -1;
1083 } else {
1085
1086 if (apply_edits_to_list(request, state, current) < 0) return -1;
1087 }
1088
1089 return next_map(request, state, current);
1090}
1091
1092/*
1093 * The RHS map is a sublist. Go expand that by creating a child expansion context, and returning to the
1094 * main loop.
1095 */
1097{
1098 map_t const *map = current->map;
1099 edit_map_t *child;
1100
1101 XDEBUG("%s map %s %s ...", __FUNCTION__, map->lhs->name, fr_tokens[map->op]);
1102
1103 /*
1104 * If there's no RHS tmpl, then the RHS is a child list.
1105 */
1106 fr_assert(!map->rhs);
1107
1108 /*
1109 * Fast path: child is empty, we don't need to do anything.
1110 */
1111 if (fr_dlist_empty(&map->child.head)) {
1112 if (fr_type_is_leaf(tmpl_attr_tail_da(current->lhs.vpt)->type) && (map->op != T_OP_SET)) {
1113 REDEBUG("%s[%d] Cannot assign a list to the '%s' data type", MAP_INFO, fr_type_to_str(tmpl_attr_tail_da(current->lhs.vpt)->type));
1114 return -1;
1115 }
1116
1117 return check_rhs(request, state, current);
1118 }
1119
1120 /*
1121 * Allocate a new child structure if necessary.
1122 */
1123 child = current->child;
1124 if (!child) {
1125 MEM(child = talloc_zero(state, edit_map_t));
1126 current->child = child;
1127 child->parent = current;
1128 }
1129
1130 /*
1131 * Initialize the child structure. There's no edit list here, as we're
1132 * creating a temporary pair list. Any edits to this list aren't
1133 * tracked, as it only exists in current->parent->rhs.pair_list.
1134 *
1135 * The parent edit_state_t will take care of applying any edits to the
1136 * parent vp. Any child pairs which aren't used will be freed.
1137 */
1138 child->el = NULL;
1139 child->map_list = &map->child;
1140 child->map = map_list_head(child->map_list);
1141 child->func = expand_lhs;
1142
1143 if (fr_type_is_leaf(tmpl_attr_tail_da(current->lhs.vpt)->type)) {
1144 child->ctx = child;
1145 child->check_lhs = check_lhs_value;
1147 } else {
1149
1150 child->ctx = current->lhs.vp ? (TALLOC_CTX *) current->lhs.vp : (TALLOC_CTX *) child;
1151 child->check_lhs = check_lhs_nested;
1153 child->temporary_pair_list = true;
1154 }
1155
1156 memset(&child->lhs, 0, sizeof(child->lhs));
1157 memset(&child->rhs, 0, sizeof(child->rhs));
1158
1160 fr_value_box_list_init(&child->lhs.list);
1161 fr_value_box_list_init(&child->rhs.list);
1162
1163 /*
1164 * Continue back with the RHS when we're done processing the
1165 * child. The go process the child.
1166 */
1167 current->func = check_rhs;
1168 state->current = child;
1169 RINDENT();
1170 return 0;
1171}
1172
1173
1174/*
1175 * Expand the RHS of an assignment operation.
1176 */
1178{
1179 int rcode;
1180 map_t const *map = current->map;
1181
1182 if (!map->rhs) return expand_rhs_list(request, state, current);
1183
1184 XDEBUG("%s map %s %s %s", __FUNCTION__, map->lhs->name, fr_tokens[map->op], map->rhs->name);
1185
1186 /*
1187 * Turn the RHS into a tmpl_t. This can involve just referencing an existing
1188 * tmpl in map->rhs, or expanding an xlat to get an attribute name.
1189 */
1190 rcode = tmpl_to_values(state, &current->rhs, request, map->rhs);
1191 if (rcode < 0) return -1;
1192
1193 if (rcode == 1) {
1194 current->func = check_rhs;
1195 return 1;
1196 }
1197
1198 return check_rhs(request, state, current);
1199}
1200
1201/*
1202 * The LHS is a value, and the parent is a leaf. There is no RHS.
1203 *
1204 * Do some validations, and move the value-boxes to the parents result list.
1205 */
1207{
1208 map_t const *map = current->map;
1209 fr_value_box_t *box;
1210 fr_pair_t *vp;
1211 tmpl_t const *vpt;
1213 fr_dcursor_t cursor;
1214
1215 fr_assert(current->parent);
1216
1217 XDEBUG("%s map %s", __FUNCTION__, map->lhs->name);
1218
1219 if (tmpl_is_data(map->lhs)) {
1220 vpt = map->lhs;
1221
1222 data:
1223 MEM(box = fr_value_box_alloc_null(state));
1224 if (unlikely(fr_value_box_copy(box, box, tmpl_value(vpt)) < 0)) return -1;
1225
1226 fr_value_box_list_insert_tail(&current->parent->rhs.list, box);
1227
1228 return next_map(request, state, current);
1229 }
1230
1231 if (!current->lhs.vpt) {
1232 vpt = map->lhs;
1233
1234 /*
1235 *
1236 */
1237 if (tmpl_is_xlat(vpt)) return next_map(request,state, current);
1238
1239 attr:
1241
1242 /*
1243 * Loop over the attributes, copying their value-boxes to the parent list.
1244 */
1245 vp = tmpl_dcursor_init(NULL, request, &cc, &cursor, request, vpt);
1246 while (vp) {
1247 MEM(box = fr_value_box_alloc_null(state));
1248 if (unlikely(fr_value_box_copy(box, box, &vp->data) < 0)) return -1;
1249
1250 fr_value_box_list_insert_tail(&current->parent->rhs.list, box);
1251
1252 vp = fr_dcursor_next(&cursor);
1253 }
1254 tmpl_dcursor_clear(&cc);
1255
1256 return next_map(request, state, current);
1257 }
1258
1259 vpt = current->lhs.vpt;
1260
1261 if (tmpl_is_data(vpt)) goto data;
1262
1263 goto attr;
1264}
1265
1266/*
1267 * We've expanded the LHS (xlat or exec) into a value-box list. The result gets moved to the parent
1268 * result list.
1269 *
1270 * There's no RHS, so once the LHS has been expanded, we jump immediately to the next entry.
1271 */
1273{
1274 fr_dict_attr_t const *da;
1276 fr_value_box_t *box = fr_value_box_list_head(&current->lhs.list);
1277 fr_value_box_t *dst;
1278 fr_sbuff_unescape_rules_t *erules = NULL;
1279
1280 fr_assert(current->parent);
1281
1282 if (!box) {
1283 RWDEBUG("Failed expanding result");
1284 return -1;
1285 }
1286
1287 fr_assert(tmpl_is_attr(current->parent->lhs.vpt));
1288
1289 /*
1290 * There's only one value-box, just use it as-is. We let the parent handler complain about being
1291 * able to parse (or not) the value.
1292 */
1293 if (!fr_value_box_list_next(&current->lhs.list, box)) goto done;
1294
1295 /*
1296 * Figure out how to parse the string.
1297 */
1298 da = tmpl_attr_tail_da(current->parent->lhs.vpt);
1299 if (fr_type_is_structural(da->type)) {
1300 fr_assert(da->type == FR_TYPE_GROUP);
1301
1303
1304 } else if (fr_type_is_variable_size(da->type)) {
1305 type = da->type;
1306
1307 } else {
1309 }
1310
1311 /*
1312 * Mash all of the results together.
1313 */
1314 if (fr_value_box_list_concat_in_place(box, box, &current->lhs.list, type, FR_VALUE_BOX_LIST_FREE, true, SIZE_MAX) < 0) {
1315 RWDEBUG("Failed converting result to '%s' - no memory", fr_type_to_str(type));
1316 return -1;
1317 }
1318
1319 /*
1320 * Strings, etc. get assigned to the parent. Fixed-size things ger parsed according to their values / enums.
1321 */
1322 if (!fr_type_is_fixed_size(da->type)) {
1323 done:
1324 fr_value_box_list_move(&current->parent->rhs.list, &current->lhs.list);
1325 return next_map(request, state, current);
1326 }
1327
1328 /*
1329 * Try to re-parse the box as the destination data type.
1330 */
1331 MEM(dst = fr_value_box_alloc(state, type, da));
1332
1333 erules = fr_value_unescape_by_quote[current->map->lhs->quote];
1334
1335 if (fr_value_box_from_str(dst, dst, da->type, da, box->vb_strvalue, box->vb_length, erules) < 0) {
1336 RWDEBUG("Failed converting result to '%s' - %s", fr_type_to_str(type), fr_strerror());
1337 return -1;
1338 }
1340
1341 fr_value_box_list_talloc_free(&current->lhs.list);
1342 fr_value_box_list_insert_tail(&current->parent->rhs.list, dst);
1343 return next_map(request, state, current);
1344}
1345
1346/*
1347 * Check the LHS of an assignment, for
1348 *
1349 * foo = { bar = baz } LHS bar
1350 *
1351 * There are more limitations here on the attr / op / value format then for the top-level check_lhs().
1352 */
1354{
1355 map_t const *map = current->map;
1356
1357 fr_assert(current->parent != NULL);
1358
1359 XDEBUG("%s map %s", __FUNCTION__, map->lhs->name);
1360
1361 /*
1362 * Don't create the leaf. The apply_edits_to_leaf() function will create them after the RHS has
1363 * been expanded.
1364 */
1365 if (fr_type_is_leaf(tmpl_attr_tail_da(current->lhs.vpt)->type)) {
1366 return expand_rhs(request, state, current);
1367 }
1368
1370
1371 /*
1372 * We have a parent, so we know that attribute exist. Which means that we don't need to call a
1373 * cursor function to create this VP.
1374 */
1375
1376 /*
1377 * We create this VP in the "current" context, so that it's freed on
1378 * error. If we create it in the LHS VP context, then we have to
1379 * manually free rhs.pair_list on any error. Creating it in the
1380 * "current" context means we have to reparent it when we move it to the
1381 * parent list, but fr_edit_list_apply_list_assignment() does that
1382 * anyways.
1383 */
1384 MEM(current->lhs.vp = fr_pair_afrom_da(current->ctx, tmpl_attr_tail_da(current->lhs.vpt)));
1385 fr_pair_append(&current->parent->rhs.pair_list, current->lhs.vp);
1386 current->lhs.vp->op = map->op;
1387 PAIR_ALLOCED(current->lhs.vp);
1388
1389 return expand_rhs(request, state, current);
1390}
1391
1392/*
1393 * The LHS tmpl is now an attribute reference. Do some sanity checks on tmpl_attr_tail_num(), operators, etc.
1394 * Once that's done, go expand the RHS.
1395 */
1397{
1398 map_t const *map = current->map;
1399 int err;
1400 fr_pair_t *vp;
1402 fr_dcursor_t cursor;
1403
1404 if (!XLAT_RESULT_SUCCESS(&current->lhs.result)) {
1405 RDEBUG("Failed expanding %s ...", map->lhs->name);
1406 return -1;
1407 }
1408
1409 current->lhs.create = false;
1410 current->lhs.vp = NULL;
1411
1412 XDEBUG("%s map %s %s ...", __FUNCTION__, map->lhs->name, fr_tokens[map->op]);
1413
1414 /*
1415 * Create the attribute, including any necessary parents.
1416 */
1417 if ((map->op == T_OP_EQ) ||
1418 (fr_type_is_leaf(tmpl_attr_tail_da(current->lhs.vpt)->type) && fr_comparison_op[map->op])) {
1419 if (tmpl_attr_tail_num(current->lhs.vpt) == NUM_UNSPEC) {
1420 current->lhs.create = true;
1421
1422 /*
1423 * Don't go to expand_rhs(), as we have to see if the attribute exists.
1424 */
1425 }
1426
1427 } else if (map->op == T_OP_SET) {
1428 if (tmpl_attr_tail_num(current->lhs.vpt) == NUM_UNSPEC) {
1429 current->lhs.create = true;
1430 return expand_rhs(request, state, current);
1431 }
1432
1433 /*
1434 * Else we're doing something like:
1435 *
1436 * &foo[1] := bar
1437 *
1438 * the attribute has to exist, and we modify its value as a leaf.
1439 *
1440 * If the RHS is a list, we can set the children for a LHS structural type.
1441 * But if the LHS is a leaf, then we can't do:
1442 *
1443 * &foo[3] := { a, b, c}
1444 *
1445 * because foo[3] is a single leaf value, not a list.
1446 */
1447 if (!map->rhs && fr_type_is_leaf(tmpl_attr_tail_da(current->lhs.vpt)->type) &&
1448 (map_list_num_elements(&map->child) > 0)) {
1449 RWDEBUG("Cannot set one entry to multiple values for %s", current->lhs.vpt->name);
1450 return -1;
1451 }
1452
1453 } else if (map->op == T_OP_ADD_EQ) {
1454 /*
1455 * For "+=", if there's no existing attribute, create one, and rewrite the operator we
1456 * apply to ":=". Which also means moving the operator be in edit_map_t, and then updating the
1457 * "apply" functions above to use that for the operations, but map->op for printing.
1458 *
1459 * This allows "foo += 4" to set "foo := 4" when the attribute doesn't exist. It also allows us
1460 * to do list appending to an empty list. But likely only for strings, octets, and numbers.
1461 * Nothing much else makes sense.
1462 */
1463
1464 switch (tmpl_attr_tail_da(current->lhs.vpt)->type) {
1465 case FR_TYPE_NUMERIC:
1466 case FR_TYPE_OCTETS:
1467 case FR_TYPE_STRING:
1468 case FR_TYPE_STRUCTURAL:
1469 current->lhs.create = true;
1470 break;
1471
1472 default:
1473 break;
1474 }
1475 }
1476
1477 /*
1478 * Find the VP. If the operation is "=" or ":=", then it's OK for the VP to not exist.
1479 *
1480 * @todo - put the cursor into the LHS, and then set lhs.vp == NULL
1481 * use the cursor in apply_edits_to_leaf()
1482 */
1484 vp = tmpl_dcursor_init(&err, current->ctx, &cc, &cursor, request, current->lhs.vpt);
1485 tmpl_dcursor_clear(&cc);
1486 if (!vp) {
1487 if (!current->lhs.create) {
1488 RWDEBUG("Failed finding %s", current->lhs.vpt->name);
1489 return -1;
1490 }
1491
1492 /*
1493 * Else we need to create it.
1494 */
1495 return expand_rhs(request, state, current);
1496
1497 } else if (current->lhs.create) {
1498 /*
1499 * &foo[1] := bar
1500 * &foo = bar
1501 */
1502 current->lhs.create = false;
1503
1504 if (map->rhs && fr_type_is_structural(vp->vp_type) && tmpl_is_exec(map->rhs)) {
1505 int rcode;
1506
1507 current->lhs.vp = vp;
1508 current->lhs.vp_parent = fr_pair_parent(vp);
1509
1510 rcode = tmpl_to_values(state, &current->rhs, request, map->rhs);
1511 if (rcode < 0) return -1;
1512
1513 if (rcode == 1) {
1514 current->func = check_rhs;
1515 return 1;
1516 }
1517
1518 return expand_rhs(request, state, current);
1519 }
1520
1521 /*
1522 * We found it, but the attribute already exists. This
1523 * is a NOOP, where we ignore this assignment.
1524 */
1525 if (map->op == T_OP_EQ) {
1526 return next_map(request, state, current);
1527 }
1528
1529 /*
1530 * &foo[1] exists, don't bother deleting it. Just over-write its value.
1531 */
1532 fr_assert((map->op == T_OP_SET) || (map->op == T_OP_ADD_EQ) || fr_comparison_op[map->op]);
1533// fr_assert((map->op == T_OP_ADD_EQ) || tmpl_attr_tail_num(map->lhs) != NUM_UNSPEC);
1534
1535 // &control := ...
1536 }
1537
1538 /*
1539 * We forbid operations on immutable leaf attributes.
1540 *
1541 * If a list contains an immutable attribute, then we can still operate on the list, but instead
1542 * we look at each VP we're operating on.
1543 */
1544 if (fr_type_is_leaf(vp->vp_type) && vp->vp_immutable) {
1545 RWDEBUG("Cannot modify immutable value for %s", current->lhs.vpt->name);
1546 return -1;
1547 }
1548
1549 /*
1550 * We found an existing attribute, with a modification operator.
1551 */
1552 current->lhs.vp = vp;
1553 current->lhs.vp_parent = fr_pair_parent(current->lhs.vp);
1554 return expand_rhs(request, state, current);
1555}
1556
1557/*
1558 * We've expanding the LHS into a string. Now convert it to an attribute.
1559 *
1560 * foo := bar LHS foo
1561 * foo = { bar = baz } LHS bar
1562 */
1564{
1565 REXDENT();
1566
1567 if (tmpl_attr_from_result(state, current->map, &current->lhs, request) < 0) return -1;
1568
1569 return current->check_lhs(request, state, current);
1570}
1571
1572/*
1573 * Take the LHS of a map, and figure out what it is. Data and attributes are immediately processed.
1574 * xlats and execs are expanded, and then their expansion is checked.
1575 *
1576 * This function is called for all variants of the LHS:
1577 *
1578 * foo := bar LHS foo
1579 * foo = { bar = baz } LHS bar
1580 * foo = { 1, 2, 3, 4 } LHS 1, 2, etc.
1581 *
1582 */
1584{
1585 int rcode;
1586 map_t const *map = current->map;
1587
1588 XDEBUG("%s map %s %s ...", __FUNCTION__, map->lhs->name, fr_tokens[map->op]);
1589
1590 fr_assert(fr_value_box_list_empty(&current->lhs.list)); /* Should have been consumed */
1591 fr_assert(fr_value_box_list_empty(&current->rhs.list)); /* Should have been consumed */
1592
1593 rcode = tmpl_to_values(state, &current->lhs, request, map->lhs);
1594 if (rcode < 0) return -1;
1595
1596 if (rcode == 1) {
1597 current->func = current->expanded_lhs;
1598 return 1;
1599 }
1600
1601 return current->check_lhs(request, state, current);
1602}
1603
1604/** Apply a map (recursively) to a request.
1605 *
1606 * @param[out] p_result The rcode indicating what the result
1607 * of the operation was.
1608 * @param[in] request The current request.
1609 * @param[in] frame Current stack frame.
1610 * @return
1611 * - UNLANG_ACTION_CALCULATE_RESULT changes were applied.
1612 * - UNLANG_ACTION_PUSHED_CHILD async execution of an expansion is required.
1613 */
1615{
1616 unlang_frame_state_edit_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_edit_t);
1617
1618 /*
1619 * Keep running the "expand map" function until done.
1620 */
1621 while (state->current) {
1622 while (state->current->map) {
1623 int rcode;
1624
1625 if (!state->current->map->rhs) {
1626 XDEBUG("MAP %s ...", state->current->map->lhs->name);
1627 } else {
1628 XDEBUG("MAP %s ... %s", state->current->map->lhs->name, state->current->map->rhs->name);
1629 }
1630
1632
1633 rcode = state->current->func(request, state, state->current);
1634 if (rcode < 0) {
1635 RINDENT_RESTORE(request, state);
1636
1637 /*
1638 * Expansions, etc. failures are SOFT failures, which undo the edit
1639 * operations, but otherwise do not affect the interpreter.
1640 *
1641 * However, if the caller asked for the actual result, return that, too.
1642 */
1643 if (state->success) *state->success = false;
1644
1645 if (state->ours) fr_edit_list_abort(state->el);
1646 TALLOC_FREE(frame->state);
1647 repeatable_clear(frame);
1648
1650 }
1651
1652 if (rcode == 1) {
1653 repeatable_set(frame);
1655 }
1656 }
1657
1658 /*
1659 * Stop if there's no parent to process.
1660 */
1661 if (!state->current->parent) break;
1662
1663 state->current = state->current->parent;
1664 REXDENT(); /* "push child" has called RINDENT */
1665 }
1666
1667 /*
1668 * Freeing the edit list will automatically commit the edits. i.e. trash the undo list, and
1669 * leave the edited pairs in place.
1670 */
1671
1672 RINDENT_RESTORE(request, state);
1673
1674 if (state->success) *state->success = true;
1676}
1677
1678static 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)
1679{
1680 edit_map_t *current = &state->first;
1681
1682 state->current = current;
1683 fr_value_box_list_init(&current->lhs.list);
1684 fr_value_box_list_init(&current->rhs.list);
1685
1686 /*
1687 * The edit list creates a local pool which should
1688 * generally be large enough for most edits.
1689 */
1690 if (!el) {
1691 MEM(state->el = fr_edit_list_alloc(state, map_list_num_elements(map_list), NULL));
1692 state->ours = true;
1693 } else {
1694 state->el = el;
1695 state->ours = false;
1696 }
1697
1698 current->request = request;
1699 current->ctx = state;
1700 current->el = state->el;
1701 current->map_list = map_list;
1702 current->map = map_list_head(current->map_list);
1703 fr_pair_list_init(&current->rhs.pair_list);
1704 current->func = expand_lhs;
1705 current->check_lhs = check_lhs;
1706 current->expanded_lhs = expanded_lhs_attribute;
1707
1708 /*
1709 * Save current indentation for the error path.
1710 */
1711 RINDENT_SAVE(state, request);
1712}
1713
1714/** Execute an update block
1715 *
1716 * Update blocks execute in two phases, first there's an evaluation phase where
1717 * each input map is evaluated, outputting one or more modification maps. The modification
1718 * maps detail a change that should be made to a list in the current request.
1719 * The request is not modified during this phase.
1720 *
1721 * The second phase applies those modification maps to the current request.
1722 * This re-enables the atomic functionality of update blocks provided in v2.x.x.
1723 * If one map fails in the evaluation phase, no more maps are processed, and the current
1724 * result is discarded.
1725 */
1727{
1729 unlang_frame_state_edit_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_edit_t);
1731
1732 edit_state_init_internal(request, state, el, &edit->maps);
1733
1734 /*
1735 * Call process_edit to do all of the work.
1736 */
1737 frame_repeat(frame, process_edit);
1738 return process_edit(p_result, request, frame);
1739}
1740
1741
1742/** Push a map onto the stack for edit evaluation
1743 *
1744 * If the "success" variable returns "false", the caller should call fr_edit_list_abort().
1745 *
1746 * If the "success" variable returns "true", the caller can free the edit list (or rely on talloc to do that)
1747 * and the transaction will be finalized.
1748 *
1749 * @param[in] request The current request.
1750 * @param[out] success Whether or not the edit succeeded
1751 * @param[in] el Edit list which can be used to apply multiple edits
1752 * @param[in] map_list The map list to process
1753 */
1754int unlang_edit_push(request_t *request, bool *success, fr_edit_list_t *el, map_list_t const *map_list)
1755{
1756 unlang_stack_t *stack = request->stack;
1757 unlang_stack_frame_t *frame;
1759
1760 unlang_edit_t *edit;
1761
1762 static unlang_t edit_instruction = {
1764 .name = "edit",
1765 .debug_name = "edit",
1766 .actions = DEFAULT_MOD_ACTIONS,
1767 };
1768
1769 MEM(edit = talloc(stack, unlang_edit_t));
1770 *edit = (unlang_edit_t) {
1771 .self = edit_instruction,
1772 };
1773
1774 unlang_type_init(&edit->self, NULL, UNLANG_TYPE_EDIT);
1775 map_list_init(&edit->maps);
1776
1777 /*
1778 * Push a new edit frame onto the stack
1779 */
1780 if (unlang_interpret_push(NULL, request, unlang_edit_to_generic(edit),
1782
1783 frame = &stack->frame[stack->depth];
1784 state = talloc_get_type_abort(frame->state, unlang_frame_state_edit_t);
1785
1786 edit_state_init_internal(request, state, el, map_list);
1787 state->success = success;
1788
1789 return 0;
1790}
1791
1793{
1795 .name = "edit",
1796 .type = UNLANG_TYPE_EDIT,
1798
1799 .interpret = unlang_edit_state_init,
1800
1801 .unlang_size = sizeof(unlang_edit_t),
1802 .unlang_name = "unlang_edit_t",
1803
1804 .frame_state_size = sizeof(unlang_frame_state_edit_t),
1805 .frame_state_type = "unlang_frame_state_edit_t",
1806 });
1807}
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
@ UNLANG_ACTION_PUSHED_CHILD
unlang_t pushed a new child onto the stack, execute it instead of continuing.
Definition action.h:39
@ UNLANG_ACTION_CALCULATE_RESULT
Calculate a new section rlm_rcode_t value.
Definition action.h:37
#define RCSID(id)
Definition build.h:485
#define unlikely(_x)
Definition build.h:383
#define UNUSED
Definition build.h:317
static void * fr_dcursor_next(fr_dcursor_t *cursor)
Advanced the cursor to the next item.
Definition dcursor.h:290
static void * fr_dcursor_next_peek(fr_dcursor_t *cursor)
Return the next iterator item without advancing the cursor.
Definition dcursor.h:305
#define fr_dcursor_init(_cursor, _head)
Initialise a cursor.
Definition dcursor.h:710
static void * fr_dcursor_set_current(fr_dcursor_t *cursor, void *item)
Set the cursor to a specified item.
Definition dcursor.h:355
#define MEM(x)
Definition debug.h:36
static fr_slen_t err
Definition dict.h:887
bool fr_dict_attr_can_contain(fr_dict_attr_t const *parent, fr_dict_attr_t const *child)
See if a structural da is allowed to contain another da.
Definition dict_util.c:5213
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition dict_util.c:2695
fr_dict_t const * fr_dict_internal(void)
Definition dict_util.c:4948
static bool fr_dlist_empty(fr_dlist_head_t const *list_head)
Check whether a list has any items.
Definition dlist.h:501
map_list_t maps
Head of the map list.
Definition edit_priv.h:33
static unlang_t * unlang_edit_to_generic(unlang_edit_t const *p)
Definition edit_priv.h:45
static unlang_edit_t * unlang_generic_to_edit(unlang_t const *p)
Cast a generic structure to the edit extension.
Definition edit_priv.h:39
unlang_t self
Definition edit_priv.h:32
int unlang_interpret_push(unlang_result_t *p_result, request_t *request, unlang_t const *instruction, unlang_frame_conf_t const *conf, bool do_next_sibling)
Push a new frame onto the stack.
Definition interpret.c:280
#define FRAME_CONF(_default_rcode, _top_frame)
Definition interpret.h:152
#define UNLANG_SUB_FRAME
Definition interpret.h:37
#define UNLANG_RESULT_RCODE(_x)
Definition interpret.h:140
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition log.h:443
#define RWDEBUG(fmt,...)
Definition log.h:361
#define RINDENT_SAVE(_x, _request)
Save indentation for later restoral.
Definition log.h:388
#define RINDENT_RESTORE(_request, _x)
Definition log.h:392
#define RPEDEBUG(fmt,...)
Definition log.h:376
#define RPWDEBUG(fmt,...)
Definition log.h:366
#define RINDENT()
Indent R* messages by one level.
Definition log.h:430
void unlang_register(unlang_op_t *op)
Register an operation with the interpreter.
Definition base.c:56
talloc_free(reap)
int fr_debug_lvl
Definition log.c:40
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition log.h:71
static char * stack[MAX_STACK]
Definition radmin.c:159
fr_type_t
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_OCTETS
Raw octets.
@ FR_TYPE_GROUP
A grouping of other attributes.
long int ssize_t
#define DEFAULT_MOD_ACTIONS
Definition mod_action.h:68
int fr_pair_list_copy(TALLOC_CTX *ctx, fr_pair_list_t *to, fr_pair_list_t const *from)
Duplicate a list of pairs.
Definition pair.c:2326
fr_pair_list_t * fr_pair_parent_list(fr_pair_t const *vp)
Return a pointer to the parent pair list.
Definition pair.c:937
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1348
fr_pair_t * fr_pair_parent(fr_pair_t const *vp)
Return a pointer to the parent pair.
Definition pair.c:952
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:289
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:46
bool fr_pair_immutable(fr_pair_t const *vp)
Definition pair.c:2283
fr_pair_t * fr_pair_copy(TALLOC_CTX *ctx, fr_pair_t const *vp)
Copy a single valuepair.
Definition pair.c:497
fr_value_box_t * fr_pair_dcursor_nested_init(fr_dcursor_t *cursor, fr_dcursor_t *parent)
Initialises a special dcursor over another cursor which returns fr_pair_t, but we return fr_value_box...
Definition pair.c:1301
fr_slen_t fr_pair_list_afrom_substr(fr_pair_parse_t const *root, fr_pair_parse_t *relative, fr_sbuff_t *in)
Parse a fr_pair_list_t from a substring.
struct fr_pair_parse_s fr_pair_parse_t
TALLOC_CTX * ctx
Definition pair_legacy.h:43
#define fr_assert(_expr)
Definition rad_assert.h:38
static rc_request_t * current
static bool done
Definition radclient.c:83
#define REDEBUG(fmt,...)
Definition radclient.h:52
#define RDEBUG2(fmt,...)
Definition radclient.h:54
#define RDEBUG(fmt,...)
Definition radclient.h:53
char const * name
Test name (as specified in the request).
Definition radclient.h:102
#define RETURN_UNLANG_FAIL
Definition rcode.h:59
@ RLM_MODULE_OK
The module is OK, continue.
Definition rcode.h:45
@ RLM_MODULE_NOT_SET
Error resolving rcode (should not be returned by modules).
Definition rcode.h:41
fr_dict_attr_t const * request_attr_request
Definition request.c:43
fr_dict_attr_t const * request_attr_control
Definition request.c:45
fr_dict_attr_t const * request_attr_state
Definition request.c:46
fr_dict_attr_t const * request_attr_reply
Definition request.c:44
static char const * name
#define FR_SBUFF_IN(_start, _len_or_end)
Set of parsing rules for *unescape_until functions.
static int16_t tmpl_attr_tail_num(tmpl_t const *vpt)
Return the last attribute reference's attribute number.
Definition tmpl.h:889
#define tmpl_is_xlat(vpt)
Definition tmpl.h:210
#define tmpl_value(_tmpl)
Definition tmpl.h:941
#define tmpl_is_attr(vpt)
Definition tmpl.h:208
#define NUM_ALL
Definition tmpl.h:395
#define tmpl_is_exec(vpt)
Definition tmpl.h:211
#define tmpl_xlat(_tmpl)
Definition tmpl.h:934
ssize_t tmpl_afrom_attr_str(TALLOC_CTX *ctx, tmpl_attr_error_t *err, tmpl_t **out, char const *name, tmpl_rules_t const *rules))
Parse a string into a TMPL_TYPE_ATTR_* type tmpl_t.
@ TMPL_TYPE_ATTR
Reference to one or more attributes.
Definition tmpl.h:142
@ TMPL_TYPE_XLAT
Pre-parsed xlat expansion.
Definition tmpl.h:146
@ TMPL_TYPE_EXEC
Callout to an external script or program.
Definition tmpl.h:150
@ TMPL_TYPE_DATA
Value in native boxed format.
Definition tmpl.h:138
#define tmpl_is_data(vpt)
Definition tmpl.h:206
static fr_slen_t vpt
Definition tmpl.h:1272
#define NUM_UNSPEC
Definition tmpl.h:394
void tmpl_debug(FILE *fp, tmpl_t const *vpt)
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition tmpl.h:805
int pair_append_by_tmpl_parent(TALLOC_CTX *ctx, fr_pair_t **out, fr_pair_list_t *list, tmpl_t const *vpt, bool skip_list))
Allocate and insert a leaf vp from a tmpl_t, building the parent vps if needed.
Definition tmpl_eval.c:858
Optional arguments passed to vp_tmpl functions.
Definition tmpl.h:336
fr_aka_sim_id_type_t type
fr_pair_t * vp
Value pair map.
Definition map.h:77
fr_token_t op
The operator that controls insertion of the dst attribute.
Definition map.h:82
tmpl_t * lhs
Typically describes the attribute to add, modify or compare.
Definition map.h:78
map_list_t child
parent map, for nested ones
Definition map.h:89
tmpl_t * rhs
Typically describes a literal value or a src attribute to copy or compare.
Definition map.h:79
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
fr_dict_attr_t const *_CONST da
Dictionary attribute defines the attribute number, vendor and type of the pair.
Definition pair.h:69
int unlang_tmpl_push(TALLOC_CTX *ctx, unlang_result_t *p_result, fr_value_box_list_t *out, request_t *request, tmpl_t const *tmpl, unlang_tmpl_args_t *args, bool top_frame)
Push a tmpl onto the stack for evaluation.
Definition tmpl.c:276
void tmpl_dcursor_clear(tmpl_dcursor_ctx_t *cc)
Clear any temporary state allocations.
#define tmpl_dcursor_build_init(_err, _ctx, _cc, _cursor, _request, _vpt, _build, _uctx)
#define tmpl_dcursor_init(_err, _ctx, _cc, _cursor, _request, _vpt)
Maintains state between cursor calls.
goto success
Definition tmpl_eval.c:1331
char const * fr_tokens[T_TOKEN_LAST]
Definition token.c:79
const bool fr_comparison_op[T_TOKEN_LAST]
Definition token.c:199
enum fr_token fr_token_t
@ T_OP_SUB_EQ
Definition token.h:70
@ T_SINGLE_QUOTED_STRING
Definition token.h:122
@ T_OP_EQ
Definition token.h:83
@ T_OP_SET
Definition token.h:84
@ T_OP_ADD_EQ
Definition token.h:69
@ T_DOUBLE_QUOTED_STRING
Definition token.h:121
fr_edit_list_t * unlang_interpret_edit_list(request_t *request)
static fr_event_list_t * el
TALLOC_CTX * ctx
Definition edit.c:95
fr_pair_t * vp_parent
parent of the current VP
Definition edit.c:81
int(* unlang_edit_expand_t)(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition edit.c:89
static int check_rhs(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition edit.c:1012
tmpl_t const * vpt
expanded tmpl
Definition edit.c:76
edit_map_t * parent
Definition edit.c:96
static int expand_rhs_list(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition edit.c:1096
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)
Definition edit.c:1678
static void edit_debug_attr_vp(request_t *request, fr_pair_t *vp, map_t const *map)
Definition edit.c:214
map_list_t const * map_list
Definition edit.c:99
unlang_result_t result
result of the xlat expansion
Definition edit.c:79
fr_pair_list_t pair_list
for structural attributes
Definition edit.c:82
void unlang_edit_init(void)
Definition edit.c:1792
static int expand_lhs(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition edit.c:1583
unlang_edit_expand_t func
for process state
Definition edit.c:107
static int next_map(UNUSED request_t *request, UNUSED unlang_frame_state_edit_t *state, edit_map_t *current)
Definition edit.c:989
tmpl_t * to_free
tmpl to free.
Definition edit.c:77
static int check_lhs(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition edit.c:1396
int unlang_edit_push(request_t *request, bool *success, fr_edit_list_t *el, map_list_t const *map_list)
Push a map onto the stack for edit evaluation.
Definition edit.c:1754
#define XDEBUG(...)
fr_pair_t editing
Definition edit.c:40
static int check_lhs_value(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition edit.c:1206
#define RDEBUG_ASSIGN(_name, _op, _box)
Definition edit.c:45
static int check_lhs_nested(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition edit.c:1353
static unlang_action_t unlang_edit_state_init(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Execute an update block.
Definition edit.c:1726
static int expand_rhs(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition edit.c:1177
#define MAP_INFO
Definition edit.c:126
static int expanded_lhs_value(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition edit.c:1272
static int apply_edits_to_list(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition edit.c:295
static int edit_delete_lhs(request_t *request, edit_map_t *current, bool delete)
Definition edit.c:562
map_t const * map
the map to evaluate
Definition edit.c:100
static fr_pair_t * edit_list_pair_build(fr_pair_t *parent, fr_dcursor_t *cursor, fr_dict_attr_t const *da, void *uctx)
Simple pair building callback for use with tmpl_dcursors.
Definition edit.c:936
bool temporary_pair_list
Definition edit.c:102
fr_edit_list_t * el
edit list
Definition edit.c:92
fr_edit_list_t * el
edit list
Definition edit.c:116
static bool pair_is_editable(request_t *request, fr_pair_t *vp)
Definition edit.c:546
static void rdebug_assign(request_t *request, char const *attr, fr_token_t op, fr_value_box_t const *box)
Definition edit.c:47
static int tmpl_attr_from_result(TALLOC_CTX *ctx, map_t const *map, edit_result_t *out, request_t *request)
Definition edit.c:133
static void edit_debug_attr_list(request_t *request, fr_pair_list_t const *list, map_t const *map)
Definition edit.c:249
bool create
whether we need to create the VP
Definition edit.c:78
unlang_edit_expand_t check_lhs
for special cases
Definition edit.c:108
static int tmpl_to_values(TALLOC_CTX *ctx, edit_result_t *out, request_t *request, tmpl_t const *vpt)
Definition edit.c:178
static int edit_create_lhs_vp(request_t *request, TALLOC_CTX *ctx, edit_map_t *current)
Definition edit.c:264
static int expanded_lhs_attribute(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition edit.c:1563
fr_value_box_list_t list
output data
Definition edit.c:75
edit_map_t * child
Definition edit.c:97
bool * success
whether or not the edit succeeded
Definition edit.c:117
request_t * request
Definition edit.c:94
unlang_edit_expand_t expanded_lhs
for special cases
Definition edit.c:109
edit_map_t * current
what we're currently doing.
Definition edit.c:122
static int apply_edits_to_leaf(request_t *request, unlang_frame_state_edit_t *state, edit_map_t *current)
Definition edit.c:651
#define DECLARE(_x)
Definition edit.c:977
edit_result_t rhs
RHS child entries.
Definition edit.c:105
edit_result_t lhs
LHS child entries.
Definition edit.c:104
static unlang_action_t process_edit(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Apply a map (recursively) to a request.
Definition edit.c:1614
fr_pair_t * vp
VP referenced by tmpl.
Definition edit.c:80
State of an edit block.
Definition edit.c:115
int unlang_xlat_push(TALLOC_CTX *ctx, unlang_result_t *p_result, fr_value_box_list_t *out, request_t *request, xlat_exp_head_t const *xlat, bool top_frame)
Push a pre-compiled xlat onto the stack for evaluation.
Definition xlat.c:270
#define XLAT_RESULT_SUCCESS(_p_result)
Definition xlat.h:503
static void repeatable_clear(unlang_stack_frame_t *frame)
void * state
Stack frame specialisations.
#define UNLANG_NEXT_STOP
Definition unlang_priv.h:98
static void unlang_type_init(unlang_t *unlang, unlang_t *parent, unlang_type_t type)
@ UNLANG_TYPE_EDIT
edit VPs in place. After 20 years!
Definition unlang_priv.h:82
static void frame_repeat(unlang_stack_frame_t *frame, unlang_process_t process)
Mark the current stack frame up for repeat, and set a new process function.
unlang_t const * instruction
The unlang node we're evaluating.
@ UNLANG_OP_FLAG_INTERNAL
it's not a real keyword
static void repeatable_set(unlang_stack_frame_t *frame)
unlang_type_t type
The specialisation of this node.
An unlang operation.
A node in a graph of unlang_op_t (s) that we execute.
Our interpreter stack, as distinct from the C stack.
An unlang stack associated with a request.
int fr_edit_list_apply_list_assignment(fr_edit_list_t *el, fr_pair_t *dst, fr_token_t op, fr_pair_list_t *src, bool copy)
Apply operators to lists.
Definition edit.c:1491
int fr_edit_list_pair_delete(fr_edit_list_t *el, fr_pair_list_t *list, fr_pair_t *vp)
Delete a VP.
Definition edit.c:598
void fr_edit_list_abort(fr_edit_list_t *el)
Abort the entries in an edit list.
Definition edit.c:193
int fr_edit_list_free_pair_children(fr_edit_list_t *el, fr_pair_t *vp)
Free children of a structural pair.
Definition edit.c:713
int fr_edit_list_apply_pair_assignment(fr_edit_list_t *el, fr_pair_t *vp, fr_token_t op, fr_value_box_t const *in)
Apply operators to pairs.
Definition edit.c:980
fr_edit_list_t * fr_edit_list_alloc(TALLOC_CTX *ctx, int hint, fr_edit_list_t *parent)
Allocate an edit list.
Definition edit.c:794
Track a series of edits.
Definition edit.c:101
#define fr_edit_list_insert_pair_tail(_el, _list, _vp)
Definition edit.h:51
#define PAIR_ALLOCED(_x)
Definition pair.h:212
#define PAIR_VERIFY(_x)
Definition pair.h:204
fr_pair_t * fr_pair_list_next(fr_pair_list_t const *list, fr_pair_t const *item))
Get the next item in a valuepair list after a specific entry.
Definition pair_inline.c:69
#define fr_pair_list_foreach(_list_head, _iter)
Iterate over the contents of a fr_pair_list_t.
Definition pair.h:279
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
void fr_pair_list_append(fr_pair_list_t *dst, fr_pair_list_t *src)
Appends a list of fr_pair_t from a temporary list to a destination list.
static fr_slen_t parent
Definition pair.h:857
char const * fr_strerror(void)
Get the last library error.
Definition strerror.c:553
void fr_strerror_clear(void)
Clears all pending messages from the talloc pools.
Definition strerror.c:576
#define FR_TYPE_QUOTED
Definition types.h:313
#define fr_type_is_group(_x)
Definition types.h:377
#define fr_type_is_variable_size(_x)
Definition types.h:389
#define fr_type_is_structural(_x)
Definition types.h:393
#define FR_TYPE_INTERNAL
Definition types.h:320
#define fr_type_is_fixed_size(_x)
Definition types.h:388
#define FR_TYPE_STRUCTURAL
Definition types.h:317
#define fr_type_is_null(_x)
Definition types.h:348
#define fr_type_is_leaf(_x)
Definition types.h:394
static char const * fr_type_to_str(fr_type_t type)
Return a static string containing the type name.
Definition types.h:455
#define FR_TYPE_NUMERIC
Definition types.h:307
void fr_value_box_mark_unsafe(fr_value_box_t *vb)
Mark a value-box as "unsafe".
Definition value.c:6884
int fr_value_box_cast(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, fr_value_box_t const *src)
Convert one type of fr_value_box_t to another.
Definition value.c:3732
int fr_value_box_copy(TALLOC_CTX *ctx, fr_value_box_t *dst, const fr_value_box_t *src)
Copy value data verbatim duplicating any buffers.
Definition value.c:4176
int fr_value_box_cmp_op(fr_token_t op, fr_value_box_t const *a, fr_value_box_t const *b)
Compare two attributes using an operator.
Definition value.c:1008
ssize_t fr_value_box_from_str(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, char const *in, size_t inlen, fr_sbuff_unescape_rules_t const *erules)
Definition value.c:5782
int fr_value_box_strdup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, bool tainted)
Copy a nul terminated string to a fr_value_box_t.
Definition value.c:4396
void fr_value_box_safety_copy_changed(fr_value_box_t *out, fr_value_box_t const *in)
Copy the safety values from one box to another.
Definition value.c:6927
fr_sbuff_unescape_rules_t * fr_value_unescape_by_quote[T_TOKEN_LAST]
Definition value.c:342
int fr_value_box_list_concat_in_place(TALLOC_CTX *ctx, fr_value_box_t *out, fr_value_box_list_t *list, fr_type_t type, fr_value_box_list_action_t proc_action, bool flatten, size_t max_size)
Concatenate a list of value boxes.
Definition value.c:6302
@ FR_VALUE_BOX_LIST_FREE
Definition value.h:239
@ FR_VALUE_BOX_LIST_FREE_BOX
Free each processed box.
Definition value.h:236
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
Definition value.h:643
static fr_slen_t data
Definition value.h:1322
static char const * fr_value_box_enum_name(fr_value_box_t const *box)
Decide if we need an enum prefix.
Definition value.h:1133
#define fr_value_box_alloc_null(_ctx)
Allocate a value box for later use with a value assignment function.
Definition value.h:654
static size_t char ** out
Definition value.h:1023