/src/freeradius-server/src/lib/unlang/xlat_alloc.c
Line | Count | Source |
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: a1a34d3c31fa0da7adf92edb518429924ac66cda $ |
19 | | * |
20 | | * @file xlat_alloc.c |
21 | | * @brief Functions to allocate different types of xlat nodes |
22 | | */ |
23 | | |
24 | | RCSID("$Id: a1a34d3c31fa0da7adf92edb518429924ac66cda $") |
25 | | |
26 | | #include <freeradius-devel/server/base.h> |
27 | | |
28 | | |
29 | | #define _XLAT_PRIVATE |
30 | | #include <freeradius-devel/unlang/xlat_priv.h> |
31 | | |
32 | | xlat_exp_head_t *_xlat_exp_head_alloc(NDEBUG_LOCATION_ARGS TALLOC_CTX *ctx) |
33 | 58.4k | { |
34 | 58.4k | xlat_exp_head_t *head; |
35 | | |
36 | 58.4k | MEM(head = talloc_zero(ctx, xlat_exp_head_t)); |
37 | | |
38 | 58.4k | fr_dlist_init(&head->dlist, xlat_exp_t, entry); |
39 | 58.4k | head->flags = XLAT_FLAGS_INIT; |
40 | 58.4k | #ifndef NDEBUG |
41 | 58.4k | head->file = file; |
42 | 58.4k | head->line = line; |
43 | 58.4k | #endif |
44 | | |
45 | 58.4k | return head; |
46 | 58.4k | } |
47 | | |
48 | | /** Set the type of an xlat node |
49 | | * |
50 | | * Also initialises any xlat_exp_head necessary |
51 | | * |
52 | | * @param[in] node to set type for. |
53 | | * @param[in] type to set. |
54 | | */ |
55 | | void _xlat_exp_set_type(NDEBUG_LOCATION_ARGS xlat_exp_t *node, xlat_type_t type) |
56 | 118k | { |
57 | | /* |
58 | | * Do nothing if it's the same type |
59 | | */ |
60 | 118k | if (node->type == type) return; |
61 | | |
62 | | /* |
63 | | * Free existing lists if present |
64 | | */ |
65 | 118k | if (node->type != 0) switch (node->type) { |
66 | 0 | case XLAT_GROUP: |
67 | 0 | TALLOC_FREE(node->group); |
68 | 0 | break; |
69 | | |
70 | 0 | case XLAT_FUNC_UNRESOLVED: |
71 | 0 | if (type == XLAT_FUNC) goto done; /* Just switching from unresolved to resolved */ |
72 | 0 | FALL_THROUGH; |
73 | |
|
74 | 0 | case XLAT_FUNC: |
75 | 0 | TALLOC_FREE(node->call.args); |
76 | 0 | break; |
77 | | |
78 | 19.8k | case XLAT_TMPL: |
79 | 19.8k | if (node->vpt && (node->fmt == node->vpt->name)) (void) talloc_steal(node, node->fmt); |
80 | | |
81 | | /* |
82 | | * Converting a tmpl to a box. If the tmpl is data, we can then just steal the contents |
83 | | * of the box. |
84 | | */ |
85 | 19.8k | if (type == XLAT_BOX) { |
86 | 19.8k | tmpl_t *vpt = node->vpt; |
87 | | |
88 | 19.8k | if (!vpt) break; |
89 | | |
90 | 19.8k | fr_assert(tmpl_rules_cast(vpt) == FR_TYPE_NULL); |
91 | | |
92 | 19.8k | if (!tmpl_is_data(vpt)) { |
93 | 0 | talloc_free(vpt); |
94 | 0 | break; |
95 | 0 | } |
96 | | |
97 | | /* |
98 | | * Initialize the box from the tmpl data. And then do NOT re-initialize the box |
99 | | * later. |
100 | | */ |
101 | 19.8k | node->flags = XLAT_FLAGS_INIT; |
102 | 19.8k | fr_value_box_steal(node, &node->data, tmpl_value(vpt)); |
103 | 19.8k | talloc_free(vpt); |
104 | 19.8k | goto done; |
105 | 19.8k | } |
106 | | |
107 | 19.8k | TALLOC_FREE(node->vpt); |
108 | 0 | break; |
109 | | |
110 | 0 | default: |
111 | 0 | break; |
112 | 19.8k | } |
113 | | |
114 | | /* |
115 | | * Alloc new lists to match the type |
116 | | */ |
117 | 98.2k | switch (type) { |
118 | 33.8k | case XLAT_GROUP: |
119 | 33.8k | node->group = _xlat_exp_head_alloc(NDEBUG_LOCATION_VALS node); |
120 | 33.8k | node->flags = node->group->flags; |
121 | 33.8k | break; |
122 | | |
123 | 15.9k | case XLAT_FUNC: |
124 | 15.9k | node->flags = XLAT_FLAGS_INIT; |
125 | 15.9k | break; |
126 | | |
127 | 0 | case XLAT_FUNC_UNRESOLVED: |
128 | 0 | node->flags = XLAT_FLAGS_INIT; |
129 | 0 | node->flags.needs_resolving = true; |
130 | 0 | break; |
131 | | |
132 | 22.4k | case XLAT_BOX: |
133 | 22.4k | node->flags = XLAT_FLAGS_INIT; |
134 | 22.4k | fr_value_box_init_null(&node->data); |
135 | 22.4k | break; |
136 | | |
137 | 0 | #ifdef HAVE_REGEX |
138 | 140 | case XLAT_REGEX: |
139 | 140 | node->flags = (xlat_flags_t) {}; |
140 | 140 | break; |
141 | 0 | #endif |
142 | | |
143 | 463 | case XLAT_ONE_LETTER: |
144 | | /* |
145 | | * %% is pure. Everything else is not. |
146 | | */ |
147 | 463 | fr_assert(node->fmt); |
148 | | |
149 | 463 | if (node->fmt[0] != '%') { |
150 | 463 | node->flags = (xlat_flags_t) {}; |
151 | 463 | } else { |
152 | 0 | node->flags = XLAT_FLAGS_INIT; |
153 | 0 | } |
154 | 463 | break; |
155 | | |
156 | 25.4k | default: |
157 | 25.4k | node->flags = XLAT_FLAGS_INIT; |
158 | 25.4k | break; |
159 | 98.2k | } |
160 | | |
161 | 118k | done: |
162 | 118k | node->type = type; |
163 | 118k | } |
164 | | |
165 | | static xlat_exp_t *xlat_exp_alloc_pool(NDEBUG_LOCATION_ARGS TALLOC_CTX *ctx, unsigned int extra_hdrs, size_t extra) |
166 | 98.2k | { |
167 | 98.2k | xlat_exp_t *node; |
168 | | |
169 | 98.2k | MEM(node = talloc_zero_pooled_object(ctx, xlat_exp_t, extra_hdrs, sizeof(xlat_exp_t) + extra * extra_hdrs)); |
170 | 98.2k | node->flags = XLAT_FLAGS_INIT; |
171 | 98.2k | node->quote = T_BARE_WORD; |
172 | 98.2k | #ifndef NDEBUG |
173 | 98.2k | node->file = file; |
174 | 98.2k | node->line = line; |
175 | 98.2k | #endif |
176 | | |
177 | 98.2k | return node; |
178 | 98.2k | } |
179 | | |
180 | | /** Allocate an xlat node with no name, and no type set |
181 | | * |
182 | | * @param[in] ctx to allocate node in. |
183 | | * @return A new xlat node. |
184 | | */ |
185 | | xlat_exp_t *_xlat_exp_alloc_null(NDEBUG_LOCATION_ARGS TALLOC_CTX *ctx) |
186 | 466 | { |
187 | 466 | return xlat_exp_alloc_pool(NDEBUG_LOCATION_VALS ctx, 0, 0); |
188 | 466 | } |
189 | | |
190 | | /** Allocate an xlat node |
191 | | * |
192 | | * @param[in] ctx to allocate node in. |
193 | | * @param[in] type of the node. |
194 | | * @param[in] in original input string. |
195 | | * @param[in] inlen the length of the original input string. |
196 | | * @return A new xlat node. |
197 | | */ |
198 | | xlat_exp_t *_xlat_exp_alloc(NDEBUG_LOCATION_ARGS TALLOC_CTX *ctx, xlat_type_t type, char const *in, size_t inlen) |
199 | 97.7k | { |
200 | 97.7k | xlat_exp_t *node; |
201 | 97.7k | unsigned int extra_hdrs; |
202 | 97.7k | size_t extra; |
203 | | |
204 | | /* |
205 | | * Figure out how much extra memory we |
206 | | * need to allocate for this node type. |
207 | | */ |
208 | 97.7k | switch (type) { |
209 | 33.8k | case XLAT_GROUP: |
210 | 33.8k | extra_hdrs = 1; |
211 | 33.8k | extra = sizeof(xlat_exp_head_t); |
212 | 33.8k | break; |
213 | | |
214 | 15.9k | case XLAT_FUNC: |
215 | 15.9k | extra_hdrs = 1; |
216 | 15.9k | extra = sizeof(xlat_exp_head_t); |
217 | 15.9k | break; |
218 | | |
219 | 47.9k | default: |
220 | 47.9k | extra_hdrs = 0; |
221 | 47.9k | extra = 0; |
222 | 97.7k | } |
223 | | |
224 | 97.7k | node = xlat_exp_alloc_pool(NDEBUG_LOCATION_VALS |
225 | 97.7k | ctx, |
226 | 97.7k | (in != NULL) + extra_hdrs, |
227 | 97.7k | inlen + extra); |
228 | 97.7k | _xlat_exp_set_type(NDEBUG_LOCATION_VALS node, type); |
229 | | |
230 | 97.7k | node->quote = T_BARE_WORD; /* ensure that this is always initialized */ |
231 | | |
232 | 97.7k | if (!in) return node; |
233 | | |
234 | 16.0k | node->fmt = talloc_bstrndup(node, in, inlen); |
235 | 16.0k | switch (type) { |
236 | 0 | case XLAT_BOX: |
237 | 0 | fr_value_box_strdup_shallow(&node->data, NULL, node->fmt, false); |
238 | 0 | break; |
239 | | |
240 | 16.0k | default: |
241 | 16.0k | break; |
242 | 16.0k | } |
243 | | |
244 | 16.0k | return node; |
245 | 16.0k | } |
246 | | |
247 | | /** Set the tmpl for a node, along with flags and the name. |
248 | | * |
249 | | * @param[in] node to set fmt for. |
250 | | * @param[in] vpt the tmpl to set |
251 | | */ |
252 | | void xlat_exp_set_vpt(xlat_exp_t *node, tmpl_t *vpt) |
253 | 24.3k | { |
254 | 24.3k | if (tmpl_contains_xlat(vpt)) { |
255 | 3.74k | node->flags = tmpl_xlat(vpt)->flags; |
256 | 3.74k | } |
257 | | |
258 | 24.3k | if (tmpl_is_exec(vpt) || tmpl_contains_attr(vpt)) { |
259 | 629 | node->flags = (xlat_flags_t) {}; |
260 | 629 | } |
261 | | |
262 | 24.3k | node->flags.needs_resolving |= tmpl_needs_resolving(vpt); |
263 | | |
264 | 24.3k | node->vpt = vpt; |
265 | 24.3k | xlat_exp_set_name_shallow(node, vpt->name); |
266 | 24.3k | } |
267 | | |
268 | | /** Set the function for a node |
269 | | * |
270 | | * @param[in] node to set fmt for. |
271 | | * @param[in] func to set |
272 | | * @param[in] dict the dictionary to set |
273 | | */ |
274 | | void xlat_exp_set_func(xlat_exp_t *node, xlat_t const *func, fr_dict_t const *dict) |
275 | 15.9k | { |
276 | 15.9k | node->call.func = func; |
277 | 15.9k | node->call.dict = dict; |
278 | 15.9k | node->flags = func->flags; |
279 | 15.9k | node->flags.impure_func = !func->flags.pure; |
280 | | |
281 | 15.9k | if (!dict) node->flags.needs_resolving = true; |
282 | 15.9k | } |
283 | | |
284 | | void xlat_exp_finalize_func(xlat_exp_t *node) |
285 | 1.20k | { |
286 | 1.20k | if (!node->call.args) return; |
287 | | |
288 | 1.20k | node->call.args->is_argv = true; |
289 | | |
290 | 1.20k | if (node->type == XLAT_FUNC_UNRESOLVED) return; |
291 | | |
292 | 1.20k | xlat_flags_merge(&node->flags, &node->call.args->flags); |
293 | | |
294 | | /* |
295 | | * We might not be able to purify the function call, but perhaps we can purify the arguments to it. |
296 | | */ |
297 | 1.20k | node->flags.can_purify = (node->call.func->flags.pure && node->call.args->flags.pure) | node->call.args->flags.can_purify; |
298 | 1.20k | node->flags.impure_func = !node->call.func->flags.pure; |
299 | 1.20k | } |
300 | | |
301 | | |
302 | | /** Set the format string for an xlat node |
303 | | * |
304 | | * @param[in] node to set fmt for. |
305 | | * @param[in] fmt talloced buffer to set as the fmt string. |
306 | | * @param[in] len of fmt string. |
307 | | */ |
308 | | void xlat_exp_set_name(xlat_exp_t *node, char const *fmt, size_t len) |
309 | 31.6k | { |
310 | 31.6k | fr_assert(node->fmt != fmt); |
311 | | |
312 | 31.6k | if (node->fmt) talloc_const_free(node->fmt); |
313 | 31.6k | MEM(node->fmt = talloc_bstrndup(node, fmt, len)); |
314 | 31.6k | } |
315 | | |
316 | | /** Set the format string for an xlat node, copying from a talloc'd buffer |
317 | | * |
318 | | * @param[in] node to set fmt for. |
319 | | * @param[in] fmt talloced buffer to set as the fmt string. |
320 | | */ |
321 | | void xlat_exp_set_name_buffer(xlat_exp_t *node, char const *fmt) |
322 | | { |
323 | | if (node->fmt) { |
324 | | if (node->fmt == fmt) { |
325 | | (void) talloc_steal(node, fmt); |
326 | | } else { |
327 | | talloc_const_free(node->fmt); |
328 | | } |
329 | | } |
330 | | MEM(node->fmt = talloc_typed_strdup_buffer(node, fmt)); |
331 | | } |
332 | | |
333 | | /** Set the format string for an xlat node from a pre-existing buffer |
334 | | * |
335 | | * @param[in] node to set fmt for. |
336 | | * @param[in] fmt talloced buffer to set as the fmt string. |
337 | | */ |
338 | | void xlat_exp_set_name_shallow(xlat_exp_t *node, char const *fmt) |
339 | 66.2k | { |
340 | 66.2k | fr_assert(node->fmt != fmt); |
341 | | |
342 | 66.2k | if (node->fmt) talloc_const_free(node->fmt); |
343 | 66.2k | node->fmt = talloc_get_type_abort_const(fmt, char); |
344 | 66.2k | } |
345 | | |
346 | | /** Copy all nodes in the input list to the output list |
347 | | * |
348 | | * @param[in] ctx to allocate new nodes in. |
349 | | * @param[out] out Where to write new nodes. |
350 | | * @param[in] in Input nodes. |
351 | | * @return |
352 | | * - 0 on success. |
353 | | * - -1 on failure. |
354 | | */ |
355 | | static int CC_HINT(nonnull) _xlat_copy_internal(NDEBUG_LOCATION_ARGS TALLOC_CTX *ctx, xlat_exp_head_t *out, xlat_exp_head_t const *in) |
356 | 0 | { |
357 | 0 | xlat_flags_merge(&out->flags, &in->flags); |
358 | | |
359 | | /* |
360 | | * Copy everything in the list of nodes |
361 | | */ |
362 | 0 | xlat_exp_foreach(in, p) { |
363 | 0 | xlat_exp_t *node; |
364 | |
|
365 | 0 | (void)talloc_get_type_abort(p, xlat_exp_t); |
366 | | |
367 | | /* |
368 | | * Ensure the format string is valid... At this point |
369 | | * they should all be talloc'd strings. |
370 | | */ |
371 | 0 | MEM(node = xlat_exp_alloc(ctx, p->type, |
372 | 0 | talloc_get_type_abort_const(p->fmt, char), talloc_strlen(p->fmt))); |
373 | |
|
374 | 0 | node->quote = p->quote; |
375 | 0 | node->flags = p->flags; |
376 | |
|
377 | 0 | switch (p->type) { |
378 | 0 | case XLAT_INVALID: |
379 | 0 | fr_strerror_printf("Cannot copy xlat node of type \"invalid\""); |
380 | 0 | error: |
381 | 0 | return -1; |
382 | | |
383 | 0 | case XLAT_BOX: |
384 | 0 | if (unlikely(fr_value_box_copy(node, &node->data, &p->data) < 0)) goto error; |
385 | 0 | break; |
386 | | |
387 | 0 | case XLAT_ONE_LETTER: /* Done with format */ |
388 | 0 | case XLAT_FUNC_UNRESOLVED: |
389 | 0 | break; |
390 | | |
391 | 0 | case XLAT_FUNC: |
392 | | /* |
393 | | * Only copy the function pointer, and whether this |
394 | | * is ephemeral. |
395 | | * |
396 | | * All instance data is specific to the xlat node and |
397 | | * cannot be duplicated. |
398 | | * |
399 | | * The node xlat nodes will need to be registered in |
400 | | * the xlat instantiation table later. |
401 | | */ |
402 | 0 | node->call.func = p->call.func; |
403 | 0 | node->call.dict = p->call.dict; |
404 | 0 | node->call.ephemeral = p->call.ephemeral; |
405 | 0 | node->call.args = xlat_exp_head_alloc(node); |
406 | 0 | node->call.args->is_argv = true; |
407 | 0 | if (unlikely(_xlat_copy_internal(NDEBUG_LOCATION_VALS |
408 | 0 | node, node->call.args, p->call.args) < 0)) goto error; |
409 | 0 | break; |
410 | | |
411 | 0 | case XLAT_TMPL: |
412 | 0 | node->vpt = tmpl_copy(node, p->vpt); |
413 | 0 | break; |
414 | | |
415 | 0 | #ifdef HAVE_REGEX |
416 | 0 | case XLAT_REGEX: |
417 | 0 | node->regex_index = p->regex_index; |
418 | 0 | break; |
419 | 0 | #endif |
420 | | |
421 | 0 | case XLAT_GROUP: |
422 | 0 | if (unlikely(_xlat_copy_internal(NDEBUG_LOCATION_VALS |
423 | 0 | node, node->group, p->group) < 0)) goto error; |
424 | 0 | break; |
425 | 0 | } |
426 | | |
427 | 0 | xlat_exp_insert_tail(out, node); |
428 | 0 | } |
429 | | |
430 | 0 | return 0; |
431 | 0 | } |
432 | | |
433 | | int _xlat_copy(NDEBUG_LOCATION_ARGS TALLOC_CTX *ctx, xlat_exp_head_t *out, xlat_exp_head_t const *in) |
434 | 0 | { |
435 | 0 | int ret; |
436 | |
|
437 | 0 | if (!in) return 0; |
438 | | |
439 | 0 | XLAT_HEAD_VERIFY(in); |
440 | 0 | ret = _xlat_copy_internal(NDEBUG_LOCATION_VALS ctx, out, in); |
441 | 0 | XLAT_HEAD_VERIFY(out); |
442 | |
|
443 | 0 | return ret; |
444 | 0 | } |
445 | | |
446 | | #ifdef WITH_VERIFY_PTR |
447 | | void xlat_exp_verify(xlat_exp_t const *node) |
448 | 12.5M | { |
449 | 12.5M | (void)talloc_get_type_abort_const(node, xlat_exp_t); |
450 | | |
451 | 12.5M | switch (node->type) { |
452 | 6.22M | case XLAT_GROUP: |
453 | 6.22M | xlat_exp_head_verify(node->group); |
454 | 6.22M | (void)talloc_get_type_abort_const(node->fmt, char); |
455 | 6.22M | return; |
456 | | |
457 | 3.18M | case XLAT_FUNC: |
458 | 3.18M | fr_assert(node->call.args->is_argv); |
459 | | |
460 | 6.18M | xlat_exp_foreach(node->call.args, arg) { |
461 | 6.18M | fr_assert(arg->type == XLAT_GROUP); |
462 | | |
463 | | /* |
464 | | * We can't do this yet, because the old function argument parser doesn't do the |
465 | | * right thing. |
466 | | */ |
467 | | // fr_assert(arg->quote == T_BARE_WORD); |
468 | 6.18M | } |
469 | | |
470 | 3.18M | xlat_exp_head_verify(node->call.args); |
471 | 3.18M | (void)talloc_get_type_abort_const(node->fmt, char); |
472 | 3.18M | return; |
473 | | |
474 | 976k | case XLAT_TMPL: { |
475 | 976k | tmpl_t const *vpt = node->vpt; |
476 | | |
477 | 976k | if (node->quote != node->vpt->quote) { |
478 | 1.40k | if (node->vpt->quote == T_SOLIDUS_QUOTED_STRING) { |
479 | | /* |
480 | | * m'foo' versus /foo/ |
481 | | */ |
482 | 1.40k | fr_assert(node->quote != T_BARE_WORD); |
483 | 1.40k | } else { |
484 | | /* |
485 | | * Mismatching quotes are bad. |
486 | | */ |
487 | 0 | fr_assert(node->quote == T_BARE_WORD); |
488 | 0 | } |
489 | 1.40k | } |
490 | | |
491 | 976k | if (tmpl_is_attr(vpt)) { |
492 | 3.19k | fr_dict_attr_t const *da; |
493 | 3.19k | da = tmpl_attr_tail_da(node->vpt); |
494 | | |
495 | 3.19k | if (tmpl_rules_cast(node->vpt) != FR_TYPE_NULL) { |
496 | | /* |
497 | | * Casts must be omitted, unless we're using a cast as a way to get rid |
498 | | * of enum names. |
499 | | */ |
500 | 0 | if (tmpl_rules_cast(node->vpt) == da->type) { |
501 | 0 | fr_assert(da->flags.has_value); |
502 | 0 | } |
503 | |
|
504 | 3.19k | } else if (node->quote != T_BARE_WORD) { |
505 | 0 | fr_assert(da->type != FR_TYPE_STRING); |
506 | 0 | } |
507 | | |
508 | 3.19k | return; |
509 | 3.19k | } |
510 | | |
511 | | /* |
512 | | * Casts should have been hoisted. |
513 | | */ |
514 | 973k | if (tmpl_is_data(node->vpt)) { |
515 | 0 | fr_assert(tmpl_rules_cast(node->vpt) == FR_TYPE_NULL); |
516 | 0 | } |
517 | | |
518 | | #if 0 |
519 | | /* |
520 | | * @todo - xlats SHOULD have been hoisted, unless they're quoted or cast. |
521 | | */ |
522 | | if (tmpl_is_xlat(node->vpt)) { |
523 | | fr_assert((node->vpt->quote != T_BARE_WORD) || |
524 | | (tmpl_rules_cast(node->vpt) != FR_TYPE_NULL)); |
525 | | return; |
526 | | } |
527 | | #endif |
528 | | |
529 | 973k | if (tmpl_is_exec(node->vpt) || tmpl_is_exec_unresolved(node->vpt)) { |
530 | 0 | fr_assert(node->quote == T_BACK_QUOTED_STRING); |
531 | 0 | fr_assert(!node->flags.constant); |
532 | 0 | fr_assert(!node->flags.pure); |
533 | 0 | fr_assert(!node->flags.can_purify); |
534 | 0 | } |
535 | | |
536 | 973k | return; |
537 | 976k | } |
538 | | |
539 | 2.10M | case XLAT_BOX: |
540 | 2.10M | fr_assert(node->flags.constant); |
541 | 2.10M | fr_assert(node->flags.pure); |
542 | | // fr_assert(node->flags.can_purify); |
543 | 2.10M | break; |
544 | | |
545 | 1.30k | default: |
546 | 1.30k | break; |
547 | 12.5M | } |
548 | 12.5M | } |
549 | | |
550 | | /** Performs recursive validation of node lists |
551 | | */ |
552 | | void xlat_exp_head_verify(xlat_exp_head_t const *head) |
553 | 9.41M | { |
554 | 9.41M | (void)talloc_get_type_abort_const(head, xlat_exp_head_t); |
555 | | |
556 | 12.4M | xlat_exp_foreach(head, node) xlat_exp_verify(node); |
557 | 9.41M | } |
558 | | #endif |