From 35dffc0dc7b660303f1e52017ec37242e65f149c Mon Sep 17 00:00:00 2001 From: tilghman Date: Wed, 3 Oct 2007 22:14:09 +0000 Subject: [PATCH] Create a universal exception handling extension, "e" (closes issue #9785) git-svn-id: http://svn.digium.com/svn/asterisk/trunk@84580 f38db490-d61c-443f-a65b-d21fe96a405b --- doc/tex/extensions.tex | 8 +++ include/asterisk/pbx.h | 4 ++ main/pbx.c | 137 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 144 insertions(+), 5 deletions(-) diff --git a/doc/tex/extensions.tex b/doc/tex/extensions.tex index 28a49f092..35d84549a 100644 --- a/doc/tex/extensions.tex +++ b/doc/tex/extensions.tex @@ -66,6 +66,14 @@ There are some extensions with important meanings: timeout is reached. See "show function TIMEOUT" for more information on setting timeouts. \end{itemize} + \item e + \begin{itemize} + \item This extension will substitute as a catchall for any of the + 'i', 't', or 'T' extensions, if any of them do not exist and + catching the error in a single routine is desired. The + function EXCEPTION may be used to query the type of exception + or the location where it occurred. + \end{itemize} \end{itemize} And finally, the extension context "default" is used when either a) an diff --git a/include/asterisk/pbx.h b/include/asterisk/pbx.h index b18b85b07..6faea62be 100644 --- a/include/asterisk/pbx.h +++ b/include/asterisk/pbx.h @@ -37,6 +37,9 @@ extern "C" { #define AST_PBX_REPLACE 1 /*! \brief Special return values from applications to the PBX { */ +#define AST_PBX_HANGUP -1 /*!< Jump to the 'h' exten */ +#define AST_PBX_OK 0 /*!< No errors */ +#define AST_PBX_ERROR 1 /*!< Jump to the 'e' exten */ #define AST_PBX_KEEPALIVE 10 /*!< Destroy the thread, but don't hang up the channel */ #define AST_PBX_NO_HANGUP_PEER 11 /*! } */ @@ -840,6 +843,7 @@ void pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const void pbx_retrieve_variable(struct ast_channel *c, const char *var, char **ret, char *workspace, int workspacelen, struct varshead *headp); void pbx_builtin_clear_globals(void); int pbx_builtin_setvar(struct ast_channel *chan, void *data); +int pbx_builtin_raise_exception(struct ast_channel *chan, void *data); void pbx_substitute_variables_helper(struct ast_channel *c,const char *cp1,char *cp2,int count); void pbx_substitute_variables_varshead(struct varshead *headp, const char *cp1, char *cp2, int count); diff --git a/main/pbx.c b/main/pbx.c index a27f78871..5fd72241b 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -248,6 +248,16 @@ static struct { .thread = AST_PTHREADT_NULL, }; +struct pbx_exception { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(context); /*!< Context associated with this exception */ + AST_STRING_FIELD(exten); /*!< Exten associated with this exception */ + AST_STRING_FIELD(type); /*!< The type of exception */ + ); + + int priority; /*!< Priority associated with this exception */ +}; + static int pbx_builtin_answer(struct ast_channel *, void *); static int pbx_builtin_goto(struct ast_channel *, void *); static int pbx_builtin_hangup(struct ast_channel *, void *); @@ -272,6 +282,7 @@ static int pbx_builtin_sayphonetic(struct ast_channel *, void *); int pbx_builtin_setvar(struct ast_channel *, void *); static int pbx_builtin_setvar_multiple(struct ast_channel *, void *); static int pbx_builtin_importvar(struct ast_channel *, void *); +static void set_ext_pri(struct ast_channel *c, const char *exten, int pri); AST_RWLOCK_DEFINE_STATIC(globalslock); static struct varshead globals = AST_LIST_HEAD_NOLOCK_INIT_VALUE; @@ -434,6 +445,13 @@ static struct pbx_builtin { "be provided to the calling channel.\n" }, + { "RaiseException", pbx_builtin_raise_exception, + "Handle an exceptional condition", + " RaiseException(): This application will jump to the \"e\" extension\n" + "in the current context, setting the dialplan function EXCEPTION(). If the \"e\"\n" + "extension does not exist, the call will hangup.\n" + }, + { "ResetCDR", pbx_builtin_resetcdr, "Resets the Call Data Record", " ResetCDR([options]): This application causes the Call Data Record to be\n" @@ -1260,6 +1278,84 @@ void pbx_retrieve_variable(struct ast_channel *c, const char *var, char **ret, c } } +static void exception_store_free(void *data) +{ + struct pbx_exception *exception = data; + ast_string_field_free_pools(exception); + ast_free(exception); +} + +static struct ast_datastore_info exception_store_info = { + .type = "EXCEPTION", + .destroy = exception_store_free, +}; + +int pbx_builtin_raise_exception(struct ast_channel *chan, void *vtype) +{ + const char *type = vtype; + struct ast_datastore *ds = ast_channel_datastore_find(chan, &exception_store_info, NULL); + struct pbx_exception *exception = NULL; + + if (!ds) { + ds = ast_channel_datastore_alloc(&exception_store_info, NULL); + if (!ds) + return -1; + exception = ast_calloc(1, sizeof(struct pbx_exception)); + if (!ds->data) { + ast_channel_datastore_free(ds); + return -1; + } + if (ast_string_field_init(exception, 128)) { + ast_free(exception); + ast_channel_datastore_free(ds); + return -1; + } + ds->data = exception; + ast_channel_datastore_add(chan, ds); + } else + exception = ds->data; + + ast_string_field_set(exception, type, type); + ast_string_field_set(exception, context, chan->context); + ast_string_field_set(exception, exten, chan->exten); + exception->priority = chan->priority; + set_ext_pri(chan, "e", 1); + return 0; +} + +static int acf_exception_read(struct ast_channel *chan, const char *name, char *data, char *buf, size_t buflen) +{ + struct ast_datastore *ds = ast_channel_datastore_find(chan, &exception_store_info, NULL); + struct pbx_exception *exception = NULL; + if (!ds || !ds->data) + return -1; + exception = ds->data; + if (!strcasecmp(data, "TYPE")) + ast_copy_string(buf, exception->type, buflen); + else if (!strcasecmp(data, "CONTEXT")) + ast_copy_string(buf, exception->context, buflen); + else if (!strncasecmp(data, "EXTEN", 5)) + ast_copy_string(buf, exception->exten, buflen); + else if (!strcasecmp(data, "PRIORITY")) + snprintf(buf, buflen, "%d", exception->priority); + else + return -1; + return 0; +} + +static struct ast_custom_function exception_function = { + .name = "EXCEPTION", + .synopsis = "Retrieve the details of the current dialplan exception", + .desc = +"The following fields are available for retrieval:\n" +" type INVALID, ERROR, RESPONSETIMEOUT, or ABSOLUTETIMEOUT\n" +" context The context executing when the exception occurred\n" +" exten The extension executing when the exception occurred\n" +" priority The numeric priority executing when the exception occurred\n", + .syntax = "EXCEPTION()", + .read = acf_exception_read, +}; + static char *handle_show_functions(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ast_custom_function *acf; @@ -2414,8 +2510,22 @@ static int __ast_pbx_run(struct ast_channel *c) } ast_debug(1, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", c->context, c->exten, c->priority, c->name); ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", c->context, c->exten, c->priority, c->name); + + if ((res == AST_PBX_ERROR) && ast_exists_extension(c, c->context, "e", 1, c->cid.cid_num)) { + /* if we are already on the 'e' exten, don't jump to it again */ + if (!strcmp(c->exten, "e")) { + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited ERROR while already on 'e' exten on '%s'\n", c->context, c->exten, c->priority, c->name); + error = 1; + break; + } else { + pbx_builtin_raise_exception(c, "ERROR"); + continue; + } + } + if (c->_softhangup == AST_SOFTHANGUP_ASYNCGOTO) { - c->_softhangup =0; + c->_softhangup = 0; } else if (c->_softhangup == AST_SOFTHANGUP_TIMEOUT) { /* atimeout, nothing bad */ } else { @@ -2425,11 +2535,16 @@ static int __ast_pbx_run(struct ast_channel *c) break; } } - if (c->_softhangup == AST_SOFTHANGUP_TIMEOUT && ast_exists_extension(c,c->context,"T",1,c->cid.cid_num)) { + if (c->_softhangup == AST_SOFTHANGUP_TIMEOUT && ast_exists_extension(c, c->context, "T", 1, c->cid.cid_num)) { set_ext_pri(c, "T", 0); /* 0 will become 1 with the c->priority++; at the end */ /* If the AbsoluteTimeout is not reset to 0, we'll get an infinite loop */ c->whentohangup = 0; c->_softhangup &= ~AST_SOFTHANGUP_TIMEOUT; + } else if (c->_softhangup == AST_SOFTHANGUP_TIMEOUT && ast_exists_extension(c, c->context, "e", 1, c->cid.cid_num)) { + pbx_builtin_raise_exception(c, "ABSOLUTETIMEOUT"); + /* If the AbsoluteTimeout is not reset to 0, we'll get an infinite loop */ + c->whentohangup = 0; + c->_softhangup &= ~AST_SOFTHANGUP_TIMEOUT; } else if (ast_check_hangup(c)) { ast_debug(1, "Extension %s, priority %d returned normally even though call was hung up\n", c->exten, c->priority); @@ -2441,16 +2556,23 @@ static int __ast_pbx_run(struct ast_channel *c) if (error) break; - /* XXX we get here on non-existing extension or a keypress or hangup ? */ + /*!\note + * We get here on a failure of some kind: non-existing extension or + * hangup. We have options, here. We can either catch the failure + * and continue, or we can drop out entirely. */ if (!ast_exists_extension(c, c->context, c->exten, 1, c->cid.cid_num)) { - /* If there is no match at priority 1, it is not a valid extension anymore. - * Try to continue at "i", 1 or exit if the latter does not exist. + /*!\note + * If there is no match at priority 1, it is not a valid extension anymore. + * Try to continue at "i" (for invalid) or "e" (for exception) or exit if + * neither exist. */ if (ast_exists_extension(c, c->context, "i", 1, c->cid.cid_num)) { ast_verb(3, "Sent into invalid extension '%s' in context '%s' on %s\n", c->exten, c->context, c->name); pbx_builtin_setvar_helper(c, "INVALID_EXTEN", c->exten); set_ext_pri(c, "i", 1); + } else if (ast_exists_extension(c, c->context, "e", 1, c->cid.cid_num)) { + pbx_builtin_raise_exception(c, "INVALID"); } else { ast_log(LOG_WARNING, "Channel '%s' sent into invalid extension '%s' in context '%s', but no invalid handler\n", c->name, c->exten, c->context); @@ -2493,6 +2615,8 @@ static int __ast_pbx_run(struct ast_channel *c) ast_verb(3, "Invalid extension '%s' in context '%s' on %s\n", dst_exten, c->context, c->name); pbx_builtin_setvar_helper(c, "INVALID_EXTEN", dst_exten); set_ext_pri(c, "i", 1); + } else if (ast_exists_extension(c, c->context, "e", 1, c->cid.cid_num)) { + pbx_builtin_raise_exception(c, "INVALID"); } else { ast_log(LOG_WARNING, "Invalid extension '%s', but no rule 'i' in context '%s'\n", dst_exten, c->context); found = 1; /* XXX disable message */ @@ -2503,6 +2627,8 @@ static int __ast_pbx_run(struct ast_channel *c) if (ast_exists_extension(c, c->context, "t", 1, c->cid.cid_num)) { ast_verb(3, "Timeout on %s\n", c->name); set_ext_pri(c, "t", 1); + } else if (ast_exists_extension(c, c->context, "e", 1, c->cid.cid_num)) { + pbx_builtin_raise_exception(c, "RESPONSETIMEOUT"); } else { ast_log(LOG_WARNING, "Timeout, but no rule 't' in context '%s'\n", c->context); found = 1; /* XXX disable message */ @@ -6098,6 +6224,7 @@ int load_pbx(void) ast_verb(1, "Registering builtin applications:\n"); ast_cli_register_multiple(pbx_cli, sizeof(pbx_cli) / sizeof(struct ast_cli_entry)); + ast_custom_function_register(&exception_function); /* Register builtin applications */ for (x=0; x