/src/freeradius-server/src/lib/unlang/switch.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: 1118af5632d0bec589a846b9bc001c06c29e9253 $ |
19 | | * |
20 | | * @file unlang/switch.c |
21 | | * @brief Unlang "switch" keyword evaluation. |
22 | | * |
23 | | * @copyright 2006-2019 The FreeRADIUS server project |
24 | | */ |
25 | | RCSID("$Id: 1118af5632d0bec589a846b9bc001c06c29e9253 $") |
26 | | |
27 | | #include <freeradius-devel/server/rcode.h> |
28 | | #include "group_priv.h" |
29 | | #include "switch_priv.h" |
30 | | #include "xlat_priv.h" |
31 | | |
32 | | static unlang_action_t unlang_switch(UNUSED unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame) |
33 | 0 | { |
34 | 0 | unlang_t *found; |
35 | |
|
36 | 0 | unlang_group_t *switch_g; |
37 | 0 | unlang_switch_t *switch_gext; |
38 | |
|
39 | 0 | fr_value_box_t const *box = NULL, *to_free = NULL; |
40 | |
|
41 | 0 | fr_pair_t *vp; |
42 | | |
43 | | /* |
44 | | * Mock up an unlang_cast_t. Note that these on-stack |
45 | | * buffers are the reason why case_cmp(), case_hash(), |
46 | | * and case_to_key() use direct casts, and not the |
47 | | * "generic to x" functions. |
48 | | */ |
49 | 0 | tmpl_t case_vpt = (tmpl_t) { |
50 | 0 | .type = TMPL_TYPE_DATA, |
51 | 0 | }; |
52 | 0 | unlang_case_t my_case = (unlang_case_t) { |
53 | 0 | .group = (unlang_group_t) { |
54 | 0 | .self = (unlang_t) { |
55 | 0 | .type = UNLANG_TYPE_CASE, |
56 | 0 | }, |
57 | 0 | }, |
58 | 0 | .vpt = &case_vpt, |
59 | 0 | }; |
60 | |
|
61 | 0 | switch_g = unlang_generic_to_group(frame->instruction); |
62 | 0 | switch_gext = unlang_group_to_switch(switch_g); |
63 | |
|
64 | 0 | found = NULL; |
65 | | |
66 | | /* |
67 | | * The attribute doesn't exist. We can skip |
68 | | * directly to the default 'case' statement. |
69 | | */ |
70 | 0 | if (tmpl_is_attr(switch_gext->vpt)) { |
71 | 0 | if (tmpl_find_vp(&vp, request, switch_gext->vpt) < 0) { |
72 | 0 | found = switch_gext->default_case; |
73 | 0 | goto do_null_case; |
74 | 0 | } else { |
75 | 0 | box = &vp->data; |
76 | 0 | } |
77 | | |
78 | | /* |
79 | | * Expand the template if necessary, so that it |
80 | | * is evaluated once instead of for each 'case' |
81 | | * statement. |
82 | | */ |
83 | 0 | } else if (tmpl_is_xlat(switch_gext->vpt) || |
84 | 0 | tmpl_is_exec(switch_gext->vpt)) { |
85 | 0 | ssize_t slen; |
86 | |
|
87 | 0 | slen = tmpl_aexpand_type(unlang_interpret_frame_talloc_ctx(request), &box, FR_TYPE_VALUE_BOX, |
88 | 0 | request, switch_gext->vpt); |
89 | 0 | if (slen < 0) { |
90 | 0 | RDEBUG("Switch failed expanding %s - %s", switch_gext->vpt->name, fr_strerror()); |
91 | 0 | goto find_null_case; |
92 | 0 | } |
93 | 0 | to_free = box; |
94 | |
|
95 | 0 | } else if (!fr_cond_assert_msg(0, "Invalid tmpl type %s", tmpl_type_to_str(switch_gext->vpt->type))) { |
96 | 0 | return UNLANG_ACTION_FAIL; |
97 | 0 | } |
98 | | |
99 | | /* |
100 | | * case_gext->vpt.data.literal is an in-line box, so we |
101 | | * have to make a shallow copy of its contents. |
102 | | * |
103 | | * Note: We do not pass a ctx here as we don't want to |
104 | | * create a reference. |
105 | | */ |
106 | 0 | fr_value_box_copy_shallow(NULL, &case_vpt.data.literal, box); |
107 | 0 | fr_htrie_find((void **)&found, switch_gext->ht, &my_case); |
108 | 0 | if (!found) { |
109 | 0 | find_null_case: |
110 | 0 | found = switch_gext->default_case; |
111 | 0 | } |
112 | |
|
113 | 0 | do_null_case: |
114 | | /* |
115 | | * Nothing found. Just continue, and ignore the "switch" |
116 | | * statement. |
117 | | */ |
118 | 0 | if (!found) { |
119 | 0 | if (box) { |
120 | 0 | RWDEBUG("Failed to find 'case' target for value %pV", box); |
121 | 0 | } else { |
122 | 0 | RWDEBUG("Failed to find 'default' target when expansion of %s returning no value", |
123 | 0 | switch_gext->vpt->name); |
124 | 0 | } |
125 | 0 | talloc_const_free(to_free); |
126 | 0 | return UNLANG_ACTION_EXECUTE_NEXT; |
127 | 0 | } |
128 | 0 | talloc_const_free(to_free); |
129 | |
|
130 | 0 | if (unlang_interpret_push(NULL, request, found, FRAME_CONF(RLM_MODULE_NOT_SET, UNLANG_SUB_FRAME), UNLANG_NEXT_STOP) < 0) { |
131 | 0 | RETURN_UNLANG_ACTION_FATAL; |
132 | 0 | } |
133 | | |
134 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
135 | 0 | } |
136 | | |
137 | | |
138 | | static unlang_action_t unlang_case(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame) |
139 | 0 | { |
140 | 0 | unlang_group_t *g = unlang_generic_to_group(frame->instruction); |
141 | |
|
142 | 0 | if (unlang_list_empty(&g->children)) RETURN_UNLANG_NOOP; |
143 | | |
144 | 0 | return unlang_group(p_result, request, frame); |
145 | 0 | } |
146 | | |
147 | | |
148 | | static unlang_t *unlang_compile_case(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci) |
149 | 0 | { |
150 | 0 | CONF_SECTION *cs = cf_item_to_section(ci); |
151 | 0 | int i; |
152 | 0 | char const *name2; |
153 | 0 | unlang_t *c; |
154 | 0 | unlang_group_t *case_g; |
155 | 0 | unlang_case_t *case_gext; |
156 | 0 | tmpl_t *vpt = NULL; |
157 | 0 | tmpl_rules_t t_rules; |
158 | | |
159 | | /* |
160 | | * We allow unknown attributes here. |
161 | | */ |
162 | 0 | t_rules = *(unlang_ctx->rules); |
163 | 0 | t_rules.attr.allow_unknown = true; |
164 | 0 | RULES_VERIFY(&t_rules); |
165 | | |
166 | 0 | if (!parent || (parent->type != UNLANG_TYPE_SWITCH)) { |
167 | 0 | cf_log_err(cs, "\"case\" statements may only appear within a \"switch\" section"); |
168 | 0 | cf_log_err(ci, DOC_KEYWORD_REF(case)); |
169 | 0 | return NULL; |
170 | 0 | } |
171 | | |
172 | | /* |
173 | | * case THING means "match THING" |
174 | | * case means "match anything" |
175 | | */ |
176 | 0 | name2 = cf_section_name2(cs); |
177 | 0 | if (name2) { |
178 | 0 | fr_slen_t slen; |
179 | 0 | fr_token_t quote; |
180 | 0 | unlang_group_t *switch_g; |
181 | 0 | unlang_switch_t *switch_gext; |
182 | |
|
183 | 0 | switch_g = unlang_generic_to_group(parent); |
184 | 0 | switch_gext = unlang_group_to_switch(switch_g); |
185 | |
|
186 | 0 | fr_assert(switch_gext->vpt != NULL); |
187 | | |
188 | | /* |
189 | | * We need to cast case values to match |
190 | | * what we're switching over, otherwise |
191 | | * integers of different widths won't |
192 | | * match. |
193 | | */ |
194 | 0 | t_rules.cast = tmpl_expanded_type(switch_gext->vpt); |
195 | | |
196 | | /* |
197 | | * Need to pass the attribute from switch |
198 | | * to tmpl rules so we can convert the |
199 | | * case string to an integer value. |
200 | | */ |
201 | 0 | if (tmpl_is_attr(switch_gext->vpt)) { |
202 | 0 | fr_dict_attr_t const *da = tmpl_attr_tail_da(switch_gext->vpt); |
203 | 0 | if (da->flags.has_value) t_rules.enumv = da; |
204 | 0 | } |
205 | |
|
206 | 0 | quote = cf_section_name2_quote(cs); |
207 | |
|
208 | 0 | slen = tmpl_afrom_substr(cs, &vpt, |
209 | 0 | &FR_SBUFF_IN_STR(name2), |
210 | 0 | quote, |
211 | 0 | NULL, |
212 | 0 | &t_rules); |
213 | 0 | if (!vpt) { |
214 | 0 | cf_canonicalize_error(cs, slen, "Failed parsing argument to 'case'", name2); |
215 | 0 | return NULL; |
216 | 0 | } |
217 | | |
218 | | /* |
219 | | * Bare word strings are attribute references |
220 | | */ |
221 | 0 | if (tmpl_is_attr(vpt) || tmpl_is_attr_unresolved(vpt)) { |
222 | 0 | cf_log_err(cs, "arguments to 'case' statements MUST NOT be attribute references."); |
223 | 0 | goto fail; |
224 | 0 | } |
225 | | |
226 | 0 | if (!tmpl_is_data(vpt)) { |
227 | 0 | cf_log_err(cs, "arguments to 'case' statements MUST be static data."); |
228 | 0 | fail: |
229 | 0 | talloc_free(vpt); |
230 | 0 | return NULL; |
231 | 0 | } |
232 | | |
233 | | /* |
234 | | * References to unresolved attributes are forbidden. They are no longer "bare word |
235 | | * strings". |
236 | | */ |
237 | 0 | if ((quote == T_BARE_WORD) && (tmpl_value_type(vpt) == FR_TYPE_STRING)) { |
238 | 0 | cf_log_err(cs, "String arguments to 'case' statements MUST be quoted."); |
239 | 0 | goto fail; |
240 | 0 | } |
241 | |
|
242 | 0 | } /* else it's a default 'case' statement */ |
243 | | |
244 | | /* |
245 | | * If we were asked to match something, then we MUST |
246 | | * match it, even if the section is empty. Otherwise we |
247 | | * will silently skip the match, and then fall through to |
248 | | * the "default" statement. |
249 | | */ |
250 | 0 | c = unlang_compile_section(parent, unlang_ctx, cs, UNLANG_TYPE_CASE); |
251 | 0 | if (!c) { |
252 | 0 | talloc_free(vpt); |
253 | 0 | return NULL; |
254 | 0 | } |
255 | | |
256 | 0 | case_g = unlang_generic_to_group(c); |
257 | 0 | case_gext = unlang_group_to_case(case_g); |
258 | 0 | case_gext->vpt = talloc_steal(case_gext, vpt); |
259 | | |
260 | | /* |
261 | | * Set all of its codes to return, so that |
262 | | * when we pick a 'case' statement, we don't |
263 | | * fall through to processing the next one. |
264 | | */ |
265 | 0 | for (i = 0; i < RLM_MODULE_NUMCODES; i++) c->actions.actions[i] = MOD_ACTION_RETURN; |
266 | |
|
267 | 0 | return c; |
268 | 0 | } |
269 | | |
270 | | static fr_cmp_ret_t case_cmp(void const *one, void const *two) |
271 | 0 | { |
272 | 0 | unlang_case_t const *a = (unlang_case_t const *) one; /* may not be talloc'd! See switch.c */ |
273 | 0 | unlang_case_t const *b = (unlang_case_t const *) two; /* may not be talloc'd! */ |
274 | |
|
275 | 0 | return fr_value_box_cmp(tmpl_value(a->vpt), tmpl_value(b->vpt)); |
276 | 0 | } |
277 | | |
278 | | static uint32_t case_hash(void const *data) |
279 | 0 | { |
280 | 0 | unlang_case_t const *a = (unlang_case_t const *) data; /* may not be talloc'd! */ |
281 | |
|
282 | 0 | return fr_value_box_hash(tmpl_value(a->vpt)); |
283 | 0 | } |
284 | | |
285 | | static int case_to_key(uint8_t **out, size_t *outlen, void const *data) |
286 | 0 | { |
287 | 0 | unlang_case_t const *a = (unlang_case_t const *) data; /* may not be talloc'd! */ |
288 | |
|
289 | 0 | return fr_value_box_to_key(out, outlen, tmpl_value(a->vpt)); |
290 | 0 | } |
291 | | |
292 | | static unlang_t *unlang_compile_switch(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci) |
293 | 0 | { |
294 | 0 | CONF_SECTION *cs = cf_item_to_section(ci); |
295 | 0 | CONF_ITEM *subci; |
296 | 0 | fr_token_t token; |
297 | 0 | char const *name1, *name2; |
298 | 0 | char const *type_name; |
299 | |
|
300 | 0 | unlang_group_t *g; |
301 | 0 | unlang_switch_t *gext; |
302 | |
|
303 | 0 | unlang_t *c; |
304 | 0 | fr_slen_t slen; |
305 | |
|
306 | 0 | tmpl_rules_t t_rules; |
307 | |
|
308 | 0 | fr_type_t type; |
309 | 0 | fr_htrie_type_t htype; |
310 | | |
311 | | /* |
312 | | * We allow unknown attributes here. |
313 | | */ |
314 | 0 | t_rules = *(unlang_ctx->rules); |
315 | 0 | t_rules.attr.allow_unknown = true; |
316 | 0 | RULES_VERIFY(&t_rules); |
317 | | |
318 | 0 | name2 = cf_section_name2(cs); |
319 | 0 | if (!name2) { |
320 | 0 | cf_log_err(cs, "You must specify a variable to switch over for 'switch'"); |
321 | 0 | print_url: |
322 | 0 | cf_log_err(ci, DOC_KEYWORD_REF(switch)); |
323 | 0 | return NULL; |
324 | 0 | } |
325 | | |
326 | 0 | if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE; |
327 | | |
328 | 0 | g = unlang_group_allocate(parent, cs, UNLANG_TYPE_SWITCH); |
329 | 0 | if (!g) return NULL; |
330 | | |
331 | 0 | gext = unlang_group_to_switch(g); |
332 | | |
333 | | /* |
334 | | * Create the template. All attributes and xlats are |
335 | | * defined by now. |
336 | | * |
337 | | * The 'case' statements need g->vpt filled out to ensure |
338 | | * that the data types match. |
339 | | */ |
340 | 0 | token = cf_section_name2_quote(cs); |
341 | |
|
342 | 0 | if ((token == T_BARE_WORD) && (name2[0] != '%')) { |
343 | 0 | slen = tmpl_afrom_attr_substr(gext, NULL, &gext->vpt, |
344 | 0 | &FR_SBUFF_IN_STR(name2), |
345 | 0 | NULL, |
346 | 0 | &t_rules); |
347 | 0 | } else { |
348 | 0 | slen = tmpl_afrom_substr(gext, &gext->vpt, |
349 | 0 | &FR_SBUFF_IN_STR(name2), |
350 | 0 | token, |
351 | 0 | NULL, |
352 | 0 | &t_rules); |
353 | 0 | } |
354 | 0 | if (!gext->vpt) { |
355 | 0 | cf_canonicalize_error(cs, slen, "Failed parsing argument to 'switch'", name2); |
356 | 0 | talloc_free(g); |
357 | 0 | return NULL; |
358 | 0 | } |
359 | | |
360 | 0 | c = unlang_group_to_generic(g); |
361 | 0 | c->name = "switch"; |
362 | 0 | c->debug_name = talloc_typed_asprintf(c, "switch %s", name2); |
363 | | |
364 | | /* |
365 | | * Fixup the template before compiling the children. |
366 | | * This is so that compile_case() can do attribute type |
367 | | * checks / casts against us. |
368 | | */ |
369 | 0 | if (!pass2_fixup_tmpl(g, &gext->vpt, cf_section_to_item(cs), unlang_ctx->rules->attr.dict_def)) { |
370 | 0 | talloc_free(g); |
371 | 0 | return NULL; |
372 | 0 | } |
373 | | |
374 | 0 | if (tmpl_is_list(gext->vpt)) { |
375 | 0 | cf_log_err(cs, "Cannot use list for 'switch' statement"); |
376 | 0 | error: |
377 | 0 | talloc_free(g); |
378 | 0 | goto print_url; |
379 | 0 | } |
380 | | |
381 | 0 | if (tmpl_contains_regex(gext->vpt)) { |
382 | 0 | cf_log_err(cs, "Cannot use regular expression for 'switch' statement"); |
383 | 0 | goto error; |
384 | 0 | } |
385 | | |
386 | 0 | if (tmpl_is_data(gext->vpt)) { |
387 | 0 | cf_log_err(cs, "Cannot use constant data for 'switch' statement"); |
388 | 0 | goto error; |
389 | 0 | } |
390 | | |
391 | 0 | if (tmpl_is_xlat(gext->vpt)) { |
392 | 0 | xlat_exp_head_t *xlat = tmpl_xlat(gext->vpt); |
393 | |
|
394 | 0 | fr_assert(xlat != NULL); |
395 | | |
396 | | /* |
397 | | * Pure xlats are allowed, e.g. for %tolower(User-Name). |
398 | | */ |
399 | 0 | if (xlat->flags.constant) { |
400 | 0 | cf_log_err(cs, "Cannot use constant data for 'switch' statement"); |
401 | 0 | goto error; |
402 | 0 | } |
403 | 0 | } |
404 | | |
405 | 0 | if (tmpl_needs_resolving(gext->vpt)) { |
406 | 0 | cf_log_err(cs, "Cannot resolve key for 'switch' statement"); |
407 | 0 | goto error; |
408 | 0 | } |
409 | | |
410 | 0 | type_name = cf_section_argv(cs, 0); /* AFTER name1, name2 */ |
411 | 0 | if (type_name) { |
412 | 0 | type = fr_table_value_by_str(fr_type_table, type_name, FR_TYPE_NULL); |
413 | | |
414 | | /* |
415 | | * Should have been caught in cf_file.c, process_switch() |
416 | | */ |
417 | 0 | fr_assert(type != FR_TYPE_NULL); |
418 | 0 | fr_assert(fr_type_is_leaf(type)); |
419 | |
|
420 | 0 | goto do_cast; |
421 | |
|
422 | 0 | } else { |
423 | | /* |
424 | | * Get the return type of the tmpl. If we don't know, |
425 | | * mash it all to string. |
426 | | */ |
427 | 0 | type = tmpl_data_type(gext->vpt); |
428 | 0 | if ((type == FR_TYPE_NULL) || (type == FR_TYPE_VOID)) { |
429 | 0 | type = FR_TYPE_STRING; |
430 | 0 | } |
431 | |
|
432 | 0 | do_cast: |
433 | 0 | if (tmpl_cast_set(gext->vpt, type) < 0) { |
434 | 0 | cf_log_perr(cs, "Failed setting cast type"); |
435 | 0 | goto error; |
436 | 0 | } |
437 | 0 | } |
438 | | |
439 | 0 | htype = fr_htrie_hint(type); |
440 | 0 | if (htype == FR_HTRIE_INVALID) { |
441 | 0 | cf_log_err(cs, "Invalid data type '%s' used for 'switch' statement", |
442 | 0 | fr_type_to_str(type)); |
443 | 0 | goto error; |
444 | 0 | } |
445 | | |
446 | 0 | gext->ht = fr_htrie_alloc(gext, htype, |
447 | 0 | (fr_hash_t) case_hash, |
448 | 0 | (fr_cmp_t) case_cmp, |
449 | 0 | (fr_trie_key_t) case_to_key, |
450 | 0 | NULL); |
451 | 0 | if (!gext->ht) { |
452 | 0 | cf_log_err(cs, "Failed initializing internal data structures"); |
453 | 0 | talloc_free(g); |
454 | 0 | return NULL; |
455 | 0 | } |
456 | | |
457 | | /* |
458 | | * Walk through the children of the switch section, |
459 | | * ensuring that they're all 'case' statements, and then compiling them. |
460 | | */ |
461 | 0 | for (subci = cf_item_next(cs, NULL); |
462 | 0 | subci != NULL; |
463 | 0 | subci = cf_item_next(cs, subci)) { |
464 | 0 | CONF_SECTION *subcs; |
465 | 0 | unlang_t *single; |
466 | 0 | unlang_case_t *case_gext; |
467 | |
|
468 | 0 | if (!cf_item_is_section(subci)) { |
469 | 0 | if (!cf_item_is_pair(subci)) continue; |
470 | | |
471 | 0 | cf_log_err(subci, "\"switch\" sections can only have \"case\" subsections"); |
472 | 0 | goto error; |
473 | 0 | } |
474 | | |
475 | 0 | subcs = cf_item_to_section(subci); /* can't return NULL */ |
476 | 0 | name1 = cf_section_name1(subcs); |
477 | |
|
478 | 0 | if (strcmp(name1, "case") != 0) { |
479 | | /* |
480 | | * We finally support "default" sections for "switch". |
481 | | */ |
482 | 0 | if (strcmp(name1, "default") == 0) { |
483 | 0 | if (cf_section_name2(subcs) != NULL) { |
484 | 0 | cf_log_err(subci, "\"default\" sections cannot have a match argument"); |
485 | 0 | goto error; |
486 | 0 | } |
487 | 0 | goto handle_default; |
488 | 0 | } |
489 | | |
490 | 0 | cf_log_err(subci, "\"switch\" sections can only have \"case\" subsections"); |
491 | 0 | goto error; |
492 | 0 | } |
493 | | |
494 | 0 | name2 = cf_section_name2(subcs); |
495 | 0 | if (!name2) { |
496 | 0 | handle_default: |
497 | 0 | if (gext->default_case) { |
498 | 0 | cf_log_err(subci, "Cannot have two 'default' case statements"); |
499 | 0 | goto error; |
500 | 0 | } |
501 | 0 | } |
502 | | |
503 | | /* |
504 | | * Compile the subsection. |
505 | | */ |
506 | 0 | single = unlang_compile_case(c, unlang_ctx, subci); |
507 | 0 | if (!single) goto error; |
508 | | |
509 | 0 | fr_assert(single->type == UNLANG_TYPE_CASE); |
510 | | |
511 | | /* |
512 | | * Remember the "default" section, and insert the |
513 | | * non-default "case" into the htrie. |
514 | | */ |
515 | 0 | case_gext = unlang_group_to_case(unlang_generic_to_group(single)); |
516 | 0 | if (!case_gext->vpt) { |
517 | 0 | gext->default_case = single; |
518 | |
|
519 | 0 | } else { |
520 | 0 | int insert_ret; |
521 | |
|
522 | 0 | insert_ret = fr_htrie_insert(gext->ht, single); |
523 | 0 | if (unlikely(insert_ret < 0)) { |
524 | 0 | cf_log_err(subci, "Failed inserting 'case' statement - %s", fr_strerror()); |
525 | 0 | goto error; |
526 | 0 | } |
527 | 0 | if (insert_ret > 0) { |
528 | 0 | fr_htrie_find((void **)&single, gext->ht, single); |
529 | |
|
530 | 0 | cf_log_err(subci, "Failed inserting 'case' statement. Is there a duplicate?"); |
531 | |
|
532 | 0 | if (single) cf_log_err(unlang_generic_to_group(single)->cs, "Duplicate may be here."); |
533 | |
|
534 | 0 | goto error; |
535 | 0 | } |
536 | 0 | } |
537 | | |
538 | 0 | unlang_list_insert_tail(&g->children, single); |
539 | 0 | } |
540 | | |
541 | 0 | return c; |
542 | 0 | } |
543 | | |
544 | | void unlang_switch_init(void) |
545 | 4 | { |
546 | 4 | unlang_register(&(unlang_op_t) { |
547 | 4 | .name = "switch", |
548 | 4 | .type = UNLANG_TYPE_SWITCH, |
549 | 4 | .flag = UNLANG_OP_FLAG_DEBUG_BRACES, |
550 | | |
551 | 4 | .compile = unlang_compile_switch, |
552 | 4 | .interpret = unlang_switch, |
553 | | |
554 | 4 | .unlang_size = sizeof(unlang_switch_t), |
555 | 4 | .unlang_name = "unlang_switch_t", |
556 | | |
557 | 4 | .pool_headers = TMPL_POOL_DEF_HEADERS, |
558 | 4 | .pool_len = TMPL_POOL_DEF_LEN |
559 | 4 | }); |
560 | | |
561 | | |
562 | 4 | unlang_register(&(unlang_op_t){ |
563 | 4 | .name = "case", |
564 | 4 | .type = UNLANG_TYPE_CASE, |
565 | 4 | .flag = (unlang_op_flag_t)(UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_BREAK_POINT), |
566 | | |
567 | 4 | .compile = unlang_compile_case, |
568 | 4 | .interpret = unlang_case, |
569 | | |
570 | 4 | .unlang_size = sizeof(unlang_case_t), |
571 | 4 | .unlang_name = "unlang_case_t", |
572 | 4 | }); |
573 | 4 | } |