try {
...
}
-catch {
+catch [ rcodes ] {
[ statements ]
}
----
The `catch` statement runs a series of substatements in a block, but only if the previous xref:unlang/try.adoc[try] failed.
+[ rcodes ]:: Zero or more module return codes. The return codes can be `disallow`, `fail`, `invalid`, or `reject`.
+
[ statements ]:: The `unlang` commands which will be executed. A
`catch` block can be empty.
+If no rcode is given the `catch` statement matches all of the codes listed above. Otherwise, the `catch` statement matches one of the listed rcodes.
+
+Multiple `catch` statements cam ne given
+
.Example
[source,unlang]
}
----
-There is some overlap in functionality between `try` / xref:unlang/catch.adoc[catch] and xref:unlang/redundant.adoc[redundant]. The main difference (TODO) is that a xref:unlang/catch.adoc[catch] statement can catch specific actions.
+There is some overlap in functionality between `try` / xref:unlang/catch.adoc[catch] and xref:unlang/redundant.adoc[redundant]. The main difference is that a xref:unlang/catch.adoc[catch] statement can catch specific failure codes.
+
+The xref:unlang/redundant.adoc[redundant] statement should be used to run
+one of many similar modules. For example, the xref:unlang/redundant.adoc[redundant] statement could be used to choose one of four different `sql` modules in a fail-over fashion.
+
+In contrast, the `try` / `catch` statements should be used for more complex policies, when the intention is to run one policy, and then do something completely different if a failure occurs.
// Copyright (C) 2023 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
// This documentation was developed by Network RADIUS SAS.
return cf_section_to_item(css);
}
+static CONF_ITEM *process_catch(cf_stack_t *stack)
+{
+ CONF_SECTION *css;
+ int argc = 0;
+ char const *ptr = stack->ptr;
+ cf_stack_frame_t *frame = &stack->frame[stack->depth];
+ CONF_SECTION *parent = frame->current;
+ char *name2 = NULL;
+ char *argv[RLM_MODULE_NUMCODES];
+
+ while (true) {
+ char const *p;
+ size_t len;
+
+ fr_skip_whitespace(ptr);
+
+ /*
+ * We have an open bracket, it's the end of the "catch" statement.
+ */
+ if (*ptr == '{') {
+ ptr++;
+ break;
+ }
+
+ /*
+ * The arguments have to be short, unquoted words.
+ */
+ p = ptr;
+ while (isalpha((uint8_t) *ptr)) ptr++;
+
+ len = ptr - p;
+ if (len > 16) {
+ ERROR("%s[%d]: Invalid syntax for 'catch' - unknown rcode '%s'",
+ frame->filename, frame->lineno, p);
+ return NULL;
+ }
+
+ if ((*ptr != '{') && !isspace((uint8_t) *ptr)) {
+ ERROR("%s[%d]: Invalid syntax for 'catch' - unexpected text at '%s'",
+ frame->filename, frame->lineno, ptr);
+ return NULL;
+ }
+
+ if (!name2) {
+ name2 = talloc_strndup(NULL, p, len);
+ continue;
+ }
+
+ if (argc > RLM_MODULE_NUMCODES) {
+ ERROR("%s[%d]: Invalid syntax for 'catch' - too many arguments at'%s'",
+ frame->filename, frame->lineno, ptr);
+ return NULL;
+ }
+
+ argv[argc++] = talloc_strndup(name2, p, len);
+ }
+
+ css = cf_section_alloc(parent, parent, "catch", name2);
+ if (!css) {
+ talloc_free(name2);
+ ERROR("%s[%d]: Failed allocating memory for section",
+ frame->filename, frame->lineno);
+ return NULL;
+ }
+ cf_filename_set(css, frame->filename);
+ cf_lineno_set(css, frame->lineno);
+ css->name2_quote = T_BARE_WORD;
+ css->allow_unlang = 1;
+
+ css->argc = argc;
+ if (argc) {
+ int i;
+
+ css->argv = talloc_array(css, char const *, argc);
+ css->argv_quote = talloc_array(css, fr_token_t, argc);
+ css->argc = argc;
+
+ for (i = 0; i < argc; i++) {
+ css->argv[i] = talloc_typed_strdup(css->argv, argv[i]);
+ css->argv_quote[i] = T_BARE_WORD;
+ }
+
+ css->argv[argc] = NULL;
+ }
+ talloc_free(name2);
+
+ stack->ptr = ptr;
+ frame->special = css;
+
+ return cf_section_to_item(css);
+}
+
static int add_section_pair(CONF_SECTION **parent, char const **attr, char const *dot, char *buffer, size_t buffer_len, char const *filename, int lineno)
{
CONF_SECTION *cs;
}
static fr_table_ptr_sorted_t unlang_keywords[] = {
+ { L("catch"), (void *) process_catch },
{ L("elsif"), (void *) process_if },
{ L("if"), (void *) process_if },
{ L("map"), (void *) process_map },
.type_name = "unlang_transaction_t",
};
+ if (cf_section_name2(cs) != NULL) {
+ cf_log_err(cs, "Unexpected argument to 'transaction' section");
+ return NULL;
+ }
+
/*
* The transaction is empty, ignore it.
*/
if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
+
if (!transaction_ok(cs)) return NULL;
/*
return NULL;
}
+ if (cf_section_name2(cs) != NULL) {
+ cf_log_err(cs, "Unexpected argument to 'try' section");
+ return NULL;
+ }
+
next = cf_section_next(cf_item_to_section(cf_parent(cs)), cs);
if (!next || (strcmp(cf_section_name1(next), "catch") != 0)) {
cf_log_err(cs, "'try' sections must be followed by a 'catch'");
return compile_children(g, unlang_ctx, true);
}
+static int catch_argv(CONF_SECTION *cs, unlang_catch_t *ca, char const *name)
+{
+ int rcode;
+
+ rcode = fr_table_value_by_str(mod_rcode_table, name, -1);
+ if (rcode < 0) {
+ cf_log_err(cs, "Unknown rcode '%s'.", name);
+ return -1;
+ }
+
+ if (ca->catching[rcode]) {
+ cf_log_err(cs, "Duplicate rcode '%s'.", name);
+ return -1;
+ }
+
+ ca->catching[rcode] = true;
+
+ return 0;
+}
+
static unlang_t *compile_catch(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs)
{
unlang_group_t *g;
unlang_t *c;
+ unlang_catch_t *ca;
static unlang_ext_t const ext = {
.type = UNLANG_TYPE_CATCH,
c = unlang_group_to_generic(g);
c->debug_name = c->name = cf_section_name1(cs);
+ ca = unlang_group_to_catch(g);
+
/*
* No arg2: catch all errors
*/
if (!cf_section_name2(cs)) {
- unlang_catch_t *ca = unlang_group_to_catch(g);
-
ca->catching[RLM_MODULE_REJECT] = true;
ca->catching[RLM_MODULE_FAIL] = true;
ca->catching[RLM_MODULE_INVALID] = true;
ca->catching[RLM_MODULE_DISALLOW] = true;
+
+ } else {
+ int i;
+ char const *name = cf_section_name2(cs);
+
+ if (catch_argv(cs, ca, name) < 0) {
+ talloc_free(c);
+ return NULL;
+ }
+
+ for (i = 0; (name = cf_section_argv(cs, i)) != NULL; i++) {
+ if (catch_argv(cs, ca, name) < 0) {
+ talloc_free(c);
+ return NULL;
+ }
+ }
}
/*
&bar := "nope"
}
-catch {
+catch disallow {
+ test_fail
+}
+catch ok reject fail {
if &foo != "hello" {
test_fail
}