From: Richard Mudgett Date: Sat, 26 Jul 2014 00:40:18 +0000 (+0000) Subject: features.c: Allow appliationmap to use Gosub. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=07407c1c882a9bf5bf6852e7a3d73eda82ea12e3;p=thirdparty%2Fasterisk.git features.c: Allow appliationmap to use Gosub. Using DYNAMIC_FEATURES with a Gosub application as the mapped application does not work. It does not work because Gosub just pushes the current dialplan context, exten, and priority onto a stack and sets the specified Gosub location. Gosub does not have a dialplan execution loop to run dialplan like Macro. * Made the DYNAMIC_FEATURES application mapping feature call ast_app_exec_macro() and ast_app_exec_sub() for the Macro and Gosub applications respectively. * Backported ast_app_exec_macro() and ast_app_exec_sub() from v11 to execute dialplan routines from the DYNAMIC_FEATURES application mapping feature. NOTE: This issue does not affect v12+ because it already does what this patch implements. AST-1391 #close Reported by: Guenther Kelleter Review: https://reviewboard.asterisk.org/r/3844/ ........ Merged revisions 419630 from http://svn.asterisk.org/svn/asterisk/branches/1.8 git-svn-id: https://origsvn.digium.com/svn/asterisk/certified/branches/1.8.15@419679 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- diff --git a/apps/app_stack.c b/apps/app_stack.c index aa597f0674..2330c60dd5 100644 --- a/apps/app_stack.c +++ b/apps/app_stack.c @@ -26,7 +26,7 @@ */ /*** MODULEINFO - res_agi + res_agi core ***/ @@ -205,14 +205,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") ***/ -static const char * const app_gosub = "Gosub"; -static const char * const app_gosubif = "GosubIf"; -static const char * const app_return = "Return"; -static const char * const app_pop = "StackPop"; +static const char app_gosub[] = "Gosub"; +static const char app_gosubif[] = "GosubIf"; +static const char app_return[] = "Return"; +static const char app_pop[] = "StackPop"; static void gosub_free(void *data); -static struct ast_datastore_info stack_info = { +static const struct ast_datastore_info stack_info = { .type = "GOSUB", .destroy = gosub_free, }; @@ -223,11 +223,14 @@ struct gosub_stack_frame { unsigned char arguments; struct varshead varshead; int priority; - unsigned int is_agi:1; + /*! TRUE if the return location marks the end of a special routine. */ + unsigned int is_special:1; char *context; char extension[0]; }; +AST_LIST_HEAD(gosub_stack_list, gosub_stack_frame); + static int frame_set_var(struct ast_channel *chan, struct gosub_stack_frame *frame, const char *var, const char *value) { struct ast_var_t *variables; @@ -242,8 +245,9 @@ static int frame_set_var(struct ast_channel *chan, struct gosub_stack_frame *fra } if (!found) { - variables = ast_var_assign(var, ""); - AST_LIST_INSERT_HEAD(&frame->varshead, variables, entries); + if ((variables = ast_var_assign(var, ""))) { + AST_LIST_INSERT_HEAD(&frame->varshead, variables, entries); + } pbx_builtin_pushvar_helper(chan, var, value); } else { pbx_builtin_setvar_helper(chan, var, value); @@ -295,8 +299,9 @@ static struct gosub_stack_frame *gosub_allocate_frame(const char *context, const static void gosub_free(void *data) { - AST_LIST_HEAD(, gosub_stack_frame) *oldlist = data; + struct gosub_stack_list *oldlist = data; struct gosub_stack_frame *oldframe; + AST_LIST_LOCK(oldlist); while ((oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries))) { gosub_release_frame(NULL, oldframe); @@ -310,7 +315,8 @@ static int pop_exec(struct ast_channel *chan, const char *data) { struct ast_datastore *stack_store; struct gosub_stack_frame *oldframe; - AST_LIST_HEAD(, gosub_stack_frame) *oldlist; + struct gosub_stack_list *oldlist; + int res = 0; ast_channel_lock(chan); if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) { @@ -321,23 +327,30 @@ static int pop_exec(struct ast_channel *chan, const char *data) oldlist = stack_store->data; AST_LIST_LOCK(oldlist); - oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries); - AST_LIST_UNLOCK(oldlist); - + oldframe = AST_LIST_FIRST(oldlist); if (oldframe) { - gosub_release_frame(chan, oldframe); + if (oldframe->is_special) { + ast_debug(1, "%s attempted to pop special return location.\n", app_pop); + + /* Abort the special routine dialplan execution. Dialplan programming error. */ + res = -1; + } else { + AST_LIST_REMOVE_HEAD(oldlist, entries); + gosub_release_frame(chan, oldframe); + } } else { ast_debug(1, "%s called with an empty gosub stack\n", app_pop); } + AST_LIST_UNLOCK(oldlist); ast_channel_unlock(chan); - return 0; + return res; } static int return_exec(struct ast_channel *chan, const char *data) { struct ast_datastore *stack_store; struct gosub_stack_frame *oldframe; - AST_LIST_HEAD(, gosub_stack_frame) *oldlist; + struct gosub_stack_list *oldlist; const char *retval = data; int res = 0; @@ -357,12 +370,24 @@ static int return_exec(struct ast_channel *chan, const char *data) ast_log(LOG_ERROR, "Return without Gosub: stack is empty\n"); ast_channel_unlock(chan); return -1; - } else if (oldframe->is_agi) { - /* Exit from AGI */ + } + if (oldframe->is_special) { + /* Exit from special routine. */ res = -1; } - ast_explicit_goto(chan, oldframe->context, oldframe->extension, oldframe->priority); + /* + * We cannot use ast_explicit_goto() because we MUST restore + * what was there before. Channels that do not have a PBX may + * not have the context or exten set. + */ + ast_copy_string(chan->context, oldframe->context, sizeof(chan->context)); + ast_copy_string(chan->exten, oldframe->extension, sizeof(chan->exten)); + if (ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP)) { + --oldframe->priority; + } + chan->priority = oldframe->priority; + gosub_release_frame(chan, oldframe); /* Set a return value, if any */ @@ -371,10 +396,96 @@ static int return_exec(struct ast_channel *chan, const char *data) return res; } +/*! + * \internal + * \brief Add missing context and/or exten to Gosub application argument string. + * \since 1.8.30.0 + * \since 11.0 + * + * \param chan Channel to obtain context/exten. + * \param args Gosub application argument string. + * + * \details + * Fills in the optional context and exten from the given channel. + * Convert: [[context,]exten,]priority[(arg1[,...][,argN])] + * To: context,exten,priority[(arg1[,...][,argN])] + * + * \retval expanded Gosub argument string on success. Must be freed. + * \retval NULL on error. + * + * \note The parsing needs to be kept in sync with the + * gosub_exec() argument format. + */ +static const char *expand_gosub_args(struct ast_channel *chan, const char *args) +{ + int len; + char *parse; + char *label; + char *new_args; + const char *context; + const char *exten; + const char *pri; + + /* Separate the context,exten,pri from the optional routine arguments. */ + parse = ast_strdupa(args); + label = strsep(&parse, "("); + if (parse) { + char *endparen; + + endparen = strrchr(parse, ')'); + if (endparen) { + *endparen = '\0'; + } else { + ast_log(LOG_WARNING, "Ouch. No closing paren: '%s'?\n", args); + } + } + + /* Split context,exten,pri */ + context = strsep(&label, ","); + exten = strsep(&label, ","); + pri = strsep(&label, ","); + if (!exten) { + /* Only a priority in this one */ + pri = context; + exten = NULL; + context = NULL; + } else if (!pri) { + /* Only an extension and priority in this one */ + pri = exten; + exten = context; + context = NULL; + } + + ast_channel_lock(chan); + if (ast_strlen_zero(exten)) { + exten = chan->exten; + } + if (ast_strlen_zero(context)) { + context = chan->context; + } + len = strlen(context) + strlen(exten) + strlen(pri) + 3; + if (!ast_strlen_zero(parse)) { + len += 2 + strlen(parse); + } + new_args = ast_malloc(len); + if (new_args) { + if (ast_strlen_zero(parse)) { + snprintf(new_args, len, "%s,%s,%s", context, exten, pri); + } else { + snprintf(new_args, len, "%s,%s,%s(%s)", context, exten, pri, parse); + } + } + ast_channel_unlock(chan); + + ast_debug(4, "Gosub args:%s new_args:%s\n", args, new_args ? new_args : ""); + + return new_args; +} + static int gosub_exec(struct ast_channel *chan, const char *data) { struct ast_datastore *stack_store; - AST_LIST_HEAD(, gosub_stack_frame) *oldlist; + struct gosub_stack_list *oldlist; struct gosub_stack_frame *newframe; struct gosub_stack_frame *lastframe; char argname[15]; @@ -501,7 +612,7 @@ static int gosub_exec(struct ast_channel *chan, const char *data) frame_set_var(chan, newframe, argname, i < args2.argc ? args2.argval[i] : ""); ast_debug(1, "Setting '%s' to '%s'\n", argname, i < args2.argc ? args2.argval[i] : ""); } - snprintf(argname, sizeof(argname), "%d", args2.argc); + snprintf(argname, sizeof(argname), "%u", args2.argc); frame_set_var(chan, newframe, "ARGC", argname); /* And finally, save our return address */ @@ -564,10 +675,15 @@ static int gosubif_exec(struct ast_channel *chan, const char *data) static int local_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) { struct ast_datastore *stack_store; - AST_LIST_HEAD(, gosub_stack_frame) *oldlist; + struct gosub_stack_list *oldlist; struct gosub_stack_frame *frame; struct ast_var_t *variables; + if (!chan) { + ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd); + return -1; + } + ast_channel_lock(chan); if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) { ast_channel_unlock(chan); @@ -599,9 +715,14 @@ static int local_read(struct ast_channel *chan, const char *cmd, char *data, cha static int local_write(struct ast_channel *chan, const char *cmd, char *var, const char *value) { struct ast_datastore *stack_store; - AST_LIST_HEAD(, gosub_stack_frame) *oldlist; + struct gosub_stack_list *oldlist; struct gosub_stack_frame *frame; + if (!chan) { + ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd); + return -1; + } + ast_channel_lock(chan); if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) { ast_log(LOG_ERROR, "Tried to set LOCAL(%s), but we aren't within a Gosub routine\n", var); @@ -644,6 +765,12 @@ static int peek_read(struct ast_channel *chan, const char *cmd, char *data, char } AST_STANDARD_RAW_ARGS(args, data); + + if (ast_strlen_zero(args.n) || ast_strlen_zero(args.name)) { + ast_log(LOG_ERROR, "LOCAL_PEEK requires parameters n and varname\n"); + return -1; + } + n = atoi(args.n); *buf = '\0'; @@ -666,7 +793,7 @@ static struct ast_custom_function peek_function = { static int stackpeek_read(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **str, ssize_t len) { struct ast_datastore *stack_store; - AST_LIST_HEAD(, gosub_stack_frame) *oldlist; + struct gosub_stack_list *oldlist; struct gosub_stack_frame *frame; int n; AST_DECLARE_APP_ARGS(args, @@ -683,6 +810,11 @@ static int stackpeek_read(struct ast_channel *chan, const char *cmd, char *data, data = ast_strdupa(data); AST_STANDARD_APP_ARGS(args, data); + if (ast_strlen_zero(args.n) || ast_strlen_zero(args.which)) { + ast_log(LOG_ERROR, "STACK_PEEK requires parameters n and which\n"); + return -1; + } + n = atoi(args.n); if (n <= 0) { ast_log(LOG_ERROR, "STACK_PEEK must be called with a positive peek value\n"); @@ -712,6 +844,7 @@ static int stackpeek_read(struct ast_channel *chan, const char *cmd, char *data, if (!ast_true(args.suppress)) { ast_log(LOG_ERROR, "Stack peek of '%s' is more stack frames than I have\n", args.n); } + AST_LIST_UNLOCK(oldlist); ast_channel_unlock(chan); return -1; } @@ -733,6 +866,7 @@ static int stackpeek_read(struct ast_channel *chan, const char *cmd, char *data, break; default: ast_log(LOG_ERROR, "Unknown argument '%s' to STACK_PEEK\n", args.which); + break; } AST_LIST_UNLOCK(oldlist); @@ -746,11 +880,206 @@ static struct ast_custom_function stackpeek_function = { .read2 = stackpeek_read, }; +/*! + * \internal + * \brief Pop stack frames until remove a special return location. + * \since 1.8.30.0 + * \since 11.0 + * + * \param chan Channel to balance stack on. + * + * \note The channel is already locked when called. + * + * \return Nothing + */ +static void balance_stack(struct ast_channel *chan) +{ + struct ast_datastore *stack_store; + struct gosub_stack_list *oldlist; + struct gosub_stack_frame *oldframe; + int found; + + stack_store = ast_channel_datastore_find(chan, &stack_info, NULL); + if (!stack_store) { + ast_log(LOG_WARNING, "No %s stack allocated.\n", app_gosub); + return; + } + + oldlist = stack_store->data; + AST_LIST_LOCK(oldlist); + do { + oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries); + if (!oldframe) { + break; + } + found = oldframe->is_special; + gosub_release_frame(chan, oldframe); + } while (!found); + AST_LIST_UNLOCK(oldlist); +} + +/*! + * \internal + * \brief Run a subroutine on a channel. + * \since 1.8.30.0 + * \since 11.0 + * + * \note Absolutely _NO_ channel locks should be held before calling this function. + * + * \param chan Channel to execute subroutine on. + * \param sub_args Gosub application argument string. + * \param ignore_hangup TRUE if a hangup does not stop execution of the routine. + * + * \retval 0 success + * \retval -1 on error + */ +static int gosub_run(struct ast_channel *chan, const char *sub_args, int ignore_hangup) +{ + const char *saved_context; + const char *saved_exten; + int saved_priority; + int saved_hangup_flags; + int saved_autoloopflag; + int res; + + ast_channel_lock(chan); + + ast_verb(3, "%s Internal %s(%s) start\n", chan->name, app_gosub, sub_args); + + /* Save non-hangup softhangup flags. */ + saved_hangup_flags = chan->_softhangup + & (AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE); + if (saved_hangup_flags) { + chan->_softhangup &= ~(AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE); + } + + /* Save autoloop flag */ + saved_autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP); + ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP); + + /* Save current dialplan location */ + saved_context = ast_strdupa(chan->context); + saved_exten = ast_strdupa(chan->exten); + saved_priority = chan->priority; + + ast_debug(4, "%s Original location: %s,%s,%d\n", chan->name, + saved_context, saved_exten, saved_priority); + + ast_channel_unlock(chan); + res = gosub_exec(chan, sub_args); + ast_debug(4, "%s exited with status %d\n", app_gosub, res); + ast_channel_lock(chan); + if (!res) { + struct ast_datastore *stack_store; + + /* Mark the return location as special. */ + stack_store = ast_channel_datastore_find(chan, &stack_info, NULL); + if (!stack_store) { + /* Should never happen! */ + ast_log(LOG_ERROR, "No %s stack!\n", app_gosub); + res = -1; + } else { + struct gosub_stack_list *oldlist; + struct gosub_stack_frame *cur; + + oldlist = stack_store->data; + cur = AST_LIST_FIRST(oldlist); + cur->is_special = 1; + } + } + if (!res) { + int found = 0; /* set if we find at least one match */ + + /* + * Run gosub body autoloop. + * + * Note that this loop is inverted from the normal execution + * loop because we just executed the Gosub application as the + * first extension of the autoloop. + */ + do { + /* Check for hangup. */ + if (chan->_softhangup & AST_SOFTHANGUP_UNBRIDGE) { + saved_hangup_flags |= AST_SOFTHANGUP_UNBRIDGE; + chan->_softhangup &= ~AST_SOFTHANGUP_UNBRIDGE; + } + if (ast_check_hangup(chan)) { + if (chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO) { + ast_log(LOG_ERROR, "%s An async goto just messed up our execution location.\n", + chan->name); + break; + } + if (!ignore_hangup) { + break; + } + } + + /* Next dialplan priority. */ + ++chan->priority; + + ast_channel_unlock(chan); + res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, + S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL), + &found, 1); + ast_channel_lock(chan); + } while (!res); + if (found && res) { + /* Something bad happened, or a hangup has been requested. */ + ast_debug(1, "Spawn extension (%s,%s,%d) exited with %d on '%s'\n", + chan->context, chan->exten, chan->priority, res, chan->name); + ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", + chan->context, chan->exten, chan->priority, chan->name); + } + + /* Did the routine return? */ + if (chan->priority == saved_priority + && !strcmp(chan->context, saved_context) + && !strcmp(chan->exten, saved_exten)) { + ast_verb(3, "%s Internal %s(%s) complete GOSUB_RETVAL=%s\n", + chan->name, app_gosub, sub_args, + S_OR(pbx_builtin_getvar_helper(chan, "GOSUB_RETVAL"), "")); + } else { + ast_log(LOG_NOTICE, "%s Abnormal '%s(%s)' exit. Popping routine return locations.\n", + chan->name, app_gosub, sub_args); + balance_stack(chan); + pbx_builtin_setvar_helper(chan, "GOSUB_RETVAL", ""); + } + + /* We executed the requested subroutine to the best of our ability. */ + res = 0; + } + + ast_debug(4, "%s Ending location: %s,%s,%d\n", chan->name, + chan->context, chan->exten, chan->priority); + + /* Restore dialplan location */ + if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) { + ast_copy_string(chan->context, saved_context, sizeof(chan->context)); + ast_copy_string(chan->exten, saved_exten, sizeof(chan->exten)); + chan->priority = saved_priority; + } + + /* Restore autoloop flag */ + ast_set2_flag(chan, saved_autoloopflag, AST_FLAG_IN_AUTOLOOP); + + /* Restore non-hangup softhangup flags. */ + if (saved_hangup_flags) { + ast_softhangup_nolock(chan, saved_hangup_flags); + } + + ast_channel_unlock(chan); + + return res; +} + static int handle_gosub(struct ast_channel *chan, AGI *agi, int argc, const char * const *argv) { - int old_priority, priority; - char old_context[AST_MAX_CONTEXT], old_extension[AST_MAX_EXTENSION]; - struct ast_app *theapp; + int res; + int priority; + int old_autoloopflag; + int old_priority; + const char *old_context; + const char *old_extension; char *gosub_args; if (argc < 4 || argc > 5) { @@ -774,80 +1103,122 @@ static int handle_gosub(struct ast_channel *chan, AGI *agi, int argc, const char return RESULT_FAILURE; } - /* Save previous location, since we're going to change it */ - ast_copy_string(old_context, chan->context, sizeof(old_context)); - ast_copy_string(old_extension, chan->exten, sizeof(old_extension)); - old_priority = chan->priority; - - if (!(theapp = pbx_findapp("Gosub"))) { - ast_log(LOG_ERROR, "Gosub() cannot be found in the list of loaded applications\n"); - ast_agi_send(agi->fd, chan, "503 result=-2 Gosub is not loaded\n"); - return RESULT_FAILURE; - } - - /* Apparently, if you run ast_pbx_run on a channel that already has a pbx - * structure, you need to add 1 to the priority to get it to go to the - * right place. But if it doesn't have a pbx structure, then leaving off - * the 1 is the right thing to do. See how this code differs when we - * call a Gosub for the CALLEE channel in Dial or Queue. - */ if (argc == 5) { - if (asprintf(&gosub_args, "%s,%s,%d(%s)", argv[1], argv[2], priority + (chan->pbx ? 1 : 0), argv[4]) < 0) { - ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno)); + if (ast_asprintf(&gosub_args, "%s,%s,%d(%s)", argv[1], argv[2], priority, argv[4]) < 0) { gosub_args = NULL; } } else { - if (asprintf(&gosub_args, "%s,%s,%d", argv[1], argv[2], priority + (chan->pbx ? 1 : 0)) < 0) { - ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno)); + if (ast_asprintf(&gosub_args, "%s,%s,%d", argv[1], argv[2], priority) < 0) { gosub_args = NULL; } } + if (!gosub_args) { + ast_agi_send(agi->fd, chan, "503 result=-2 Memory allocation failure\n"); + return RESULT_FAILURE; + } - if (gosub_args) { - int res; + ast_channel_lock(chan); - ast_debug(1, "Trying gosub with arguments '%s'\n", gosub_args); + ast_verb(3, "%s AGI %s(%s) start\n", chan->name, app_gosub, gosub_args); - if ((res = pbx_exec(chan, theapp, gosub_args)) == 0) { - struct ast_pbx *pbx = chan->pbx; - struct ast_pbx_args args; - struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL); - AST_LIST_HEAD(, gosub_stack_frame) *oldlist; + /* Save autoloop flag */ + old_autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP); + ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP); + + /* Save previous location, since we're going to change it */ + old_context = ast_strdupa(chan->context); + old_extension = ast_strdupa(chan->exten); + old_priority = chan->priority; + + ast_debug(4, "%s Original location: %s,%s,%d\n", chan->name, + old_context, old_extension, old_priority); + ast_channel_unlock(chan); + + res = gosub_exec(chan, gosub_args); + if (!res) { + struct ast_datastore *stack_store; + + /* Mark the return location as special. */ + ast_channel_lock(chan); + stack_store = ast_channel_datastore_find(chan, &stack_info, NULL); + if (!stack_store) { + /* Should never happen! */ + ast_log(LOG_ERROR, "No %s stack!\n", app_gosub); + res = -1; + } else { + struct gosub_stack_list *oldlist; struct gosub_stack_frame *cur; - if (!stack_store) { - ast_log(LOG_WARNING, "No GoSub stack remaining after AGI GoSub execution.\n"); - ast_free(gosub_args); - return RESULT_FAILURE; - } + oldlist = stack_store->data; cur = AST_LIST_FIRST(oldlist); - cur->is_agi = 1; - - memset(&args, 0, sizeof(args)); - args.no_hangup_chan = 1; - /* Suppress warning about PBX already existing */ - chan->pbx = NULL; - ast_agi_send(agi->fd, chan, "100 result=0 Trying...\n"); - ast_pbx_run_args(chan, &args); - ast_agi_send(agi->fd, chan, "200 result=0 Gosub complete\n"); - if (chan->pbx) { - ast_free(chan->pbx); - } - chan->pbx = pbx; + cur->is_special = 1; + } + ast_channel_unlock(chan); + } + if (!res) { + struct ast_pbx *pbx; + struct ast_pbx_args args; + int abnormal_exit; + + memset(&args, 0, sizeof(args)); + args.no_hangup_chan = 1; + + ast_channel_lock(chan); + + /* Next dialplan priority. */ + ++chan->priority; + + /* Suppress warning about PBX already existing */ + pbx = chan->pbx; + chan->pbx = NULL; + ast_channel_unlock(chan); + + ast_agi_send(agi->fd, chan, "100 result=0 Trying...\n"); + ast_pbx_run_args(chan, &args); + + ast_channel_lock(chan); + ast_free(chan->pbx); + chan->pbx = pbx; + + /* Did the routine return? */ + if (chan->priority == old_priority + && !strcmp(chan->context, old_context) + && !strcmp(chan->exten, old_extension)) { + ast_verb(3, "%s AGI %s(%s) complete GOSUB_RETVAL=%s\n", + chan->name, app_gosub, gosub_args, + S_OR(pbx_builtin_getvar_helper(chan, "GOSUB_RETVAL"), "")); + abnormal_exit = 0; } else { - ast_agi_send(agi->fd, chan, "200 result=%d Gosub failed\n", res); + ast_log(LOG_NOTICE, "%s Abnormal AGI %s(%s) exit. Popping routine return locations.\n", + chan->name, app_gosub, gosub_args); + balance_stack(chan); + pbx_builtin_setvar_helper(chan, "GOSUB_RETVAL", ""); + abnormal_exit = 1; } - ast_free(gosub_args); + ast_channel_unlock(chan); + + ast_agi_send(agi->fd, chan, "200 result=0 Gosub complete%s\n", + abnormal_exit ? " (abnormal exit)" : ""); } else { - ast_agi_send(agi->fd, chan, "503 result=-2 Memory allocation failure\n"); - return RESULT_FAILURE; + ast_agi_send(agi->fd, chan, "200 result=%d Gosub failed\n", res); } + /* Must use free because the memory was allocated by asprintf(). */ + free(gosub_args); + + ast_channel_lock(chan); + ast_debug(4, "%s Ending location: %s,%s,%d\n", chan->name, + chan->context, chan->exten, chan->priority); + /* Restore previous location */ ast_copy_string(chan->context, old_context, sizeof(chan->context)); ast_copy_string(chan->exten, old_extension, sizeof(chan->exten)); chan->priority = old_priority; + /* Restore autoloop flag */ + ast_set2_flag(chan, old_autoloopflag, AST_FLAG_IN_AUTOLOOP); + ast_channel_unlock(chan); + return RESULT_SUCCESS; } @@ -856,6 +1227,8 @@ static struct agi_command gosub_agi_command = static int unload_module(void) { + ast_install_stack_functions(NULL); + ast_agi_unregister(ast_module_info->self, &gosub_agi_command); ast_unregister_application(app_return); @@ -871,6 +1244,12 @@ static int unload_module(void) static int load_module(void) { + /* Setup the stack application callback functions. */ + static struct ast_app_stack_funcs funcs = { + .run_sub = gosub_run, + .expand_sub_args = expand_gosub_args, + }; + ast_agi_register(ast_module_info->self, &gosub_agi_command); ast_register_application_xml(app_pop, pop_exec); @@ -881,6 +1260,9 @@ static int load_module(void) ast_custom_function_register(&peek_function); ast_custom_function_register(&stackpeek_function); + funcs.module = ast_module_info->self, + ast_install_stack_functions(&funcs); + return 0; } diff --git a/include/asterisk/app.h b/include/asterisk/app.h index 3df813d037..8fe4a51fa6 100644 --- a/include/asterisk/app.h +++ b/include/asterisk/app.h @@ -138,25 +138,162 @@ int ast_app_getdata(struct ast_channel *c, const char *prompt, char *s, int maxl /*! \brief Full version with audiofd and controlfd. NOTE: returns '2' on ctrlfd available, not '1' like other full functions */ int ast_app_getdata_full(struct ast_channel *c, const char *prompt, char *s, int maxlen, int timeout, int audiofd, int ctrlfd); +/*! + * \brief Run a macro on a channel, placing an optional second channel into autoservice. + * \since 1.8.30.0 + * \since 11.0 + * + * \details + * This is a shorthand method that makes it very easy to run a + * macro on any given channel. It is perfectly reasonable to + * supply a NULL autoservice_chan here in case there is no + * channel to place into autoservice. + * + * \note Absolutely _NO_ channel locks should be held before calling this function. + * + * \param autoservice_chan A channel to place into autoservice while the macro is run + * \param macro_chan Channel to execute macro on. + * \param macro_args Macro application argument string. + * + * \retval 0 success + * \retval -1 on error + */ +int ast_app_exec_macro(struct ast_channel *autoservice_chan, struct ast_channel *macro_chan, const char *macro_args); + /*! * \since 1.8 - * \brief Run a macro on a channel, placing a second channel into autoservice. + * \brief Run a macro on a channel, placing an optional second channel into autoservice. * - * This is a shorthand method that makes it very easy to run a macro on any given - * channel. It is perfectly reasonable to supply a NULL autoservice_chan here in case - * there is no channel to place into autoservice. It is very important that the - * autoservice_chan parameter is not locked prior to calling ast_app_run_macro. A - * deadlock could result, otherwise. + * \details + * This is a shorthand method that makes it very easy to run a + * macro on any given channel. It is perfectly reasonable to + * supply a NULL autoservice_chan here in case there is no + * channel to place into autoservice. + * + * \note Absolutely _NO_ channel locks should be held before calling this function. * * \param autoservice_chan A channel to place into autoservice while the macro is run - * \param macro_chan The channel to run the macro on - * \param macro_name The name of the macro to run - * \param macro_args The arguments to pass to the macro + * \param macro_chan Channel to execute macro on. + * \param macro_name The name of the macro to run. + * \param macro_args The arguments to pass to the macro. + * * \retval 0 success - * \retval -1 failure + * \retval -1 on error + */ +int ast_app_run_macro(struct ast_channel *autoservice_chan, + struct ast_channel *macro_chan, const char *macro_name, const char *macro_args); + +/*! + * \brief Stack applications callback functions. + */ +struct ast_app_stack_funcs { + /*! + * Module reference pointer so the module will stick around + * while a callback is active. + */ + void *module; + + /*! + * \brief Callback for the routine to run a subroutine on a channel. + * + * \note Absolutely _NO_ channel locks should be held before calling this function. + * + * \param chan Channel to execute subroutine on. + * \param args Gosub application argument string. + * \param ignore_hangup TRUE if a hangup does not stop execution of the routine. + * + * \retval 0 success + * \retval -1 on error + */ + int (*run_sub)(struct ast_channel *chan, const char *args, int ignore_hangup); + + /*! + * \brief Add missing context/exten to Gosub application argument string. + * + * \param chan Channel to obtain context/exten. + * \param args Gosub application argument string. + * + * \details + * Fills in the optional context and exten from the given channel. + * + * \retval New-args Gosub argument string on success. Must be freed. + * \retval NULL on error. + */ + const char *(*expand_sub_args)(struct ast_channel *chan, const char *args); + + /* Add new API calls to the end here. */ +}; + +/*! + * \since 1.8.30.0 + * \since 11 + * \brief Set stack application function callbacks + * \param funcs Stack applications callback functions. + */ +void ast_install_stack_functions(const struct ast_app_stack_funcs *funcs); + +/*! + * \brief Add missing context/exten to subroutine argument string. + * \since 1.8.30.0 + * + * \param chan Channel to obtain context/exten. + * \param args Gosub application argument string. + * + * \details + * Fills in the optional context and exten from the given channel. + * + * \retval New-args Gosub argument string on success. Must be freed. + * \retval NULL on error. + */ +const char *ast_app_expand_sub_args(struct ast_channel *chan, const char *args); + +/*! + * \since 1.8.30.0 + * \since 11 + * \brief Run a subroutine on a channel, placing an optional second channel into autoservice. + * + * \details + * This is a shorthand method that makes it very easy to run a + * subroutine on any given channel. It is perfectly reasonable + * to supply a NULL autoservice_chan here in case there is no + * channel to place into autoservice. + * + * \note Absolutely _NO_ channel locks should be held before calling this function. + * + * \param autoservice_chan A channel to place into autoservice while the subroutine is run + * \param sub_chan Channel to execute subroutine on. + * \param sub_args Gosub application argument string. + * \param ignore_hangup TRUE if a hangup does not stop execution of the routine. + * + * \retval 0 success + * \retval -1 on error + */ +int ast_app_exec_sub(struct ast_channel *autoservice_chan, struct ast_channel *sub_chan, const char *sub_args, int ignore_hangup); + +/*! + * \since 1.8.30.0 + * \since 11 + * \brief Run a subroutine on a channel, placing an optional second channel into autoservice. + * + * \details + * This is a shorthand method that makes it very easy to run a + * subroutine on any given channel. It is perfectly reasonable + * to supply a NULL autoservice_chan here in case there is no + * channel to place into autoservice. + * + * \note Absolutely _NO_ channel locks should be held before calling this function. + * + * \param autoservice_chan A channel to place into autoservice while the subroutine is run + * \param sub_chan Channel to execute subroutine on. + * \param sub_location The location of the subroutine to run. + * \param sub_args The arguments to pass to the subroutine. + * \param ignore_hangup TRUE if a hangup does not stop execution of the routine. + * + * \retval 0 success + * \retval -1 on error */ -int ast_app_run_macro(struct ast_channel *autoservice_chan, struct ast_channel - *macro_chan, const char * const macro_name, const char * const macro_args); +int ast_app_run_sub(struct ast_channel *autoservice_chan, + struct ast_channel *sub_chan, const char *sub_location, const char *sub_args, int ignore_hangup); enum ast_vm_snapshot_sort_val { AST_VM_SNAPSHOT_SORT_BY_ID = 0, diff --git a/main/app.c b/main/app.c index b429661bec..712be45248 100644 --- a/main/app.c +++ b/main/app.c @@ -59,6 +59,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/linkedlists.h" #include "asterisk/threadstorage.h" #include "asterisk/test.h" +#include "asterisk/module.h" AST_THREADSTORAGE_PUBLIC(ast_str_thread_global_buf); @@ -250,28 +251,145 @@ int ast_app_getdata_full(struct ast_channel *c, const char *prompt, char *s, int return res; } -int ast_app_run_macro(struct ast_channel *autoservice_chan, struct ast_channel *macro_chan, const char * const macro_name, const char * const macro_args) +int ast_app_exec_macro(struct ast_channel *autoservice_chan, struct ast_channel *macro_chan, const char *macro_args) { struct ast_app *macro_app; int res; - char buf[1024]; macro_app = pbx_findapp("Macro"); if (!macro_app) { - ast_log(LOG_WARNING, "Cannot run macro '%s' because the 'Macro' application in not available\n", macro_name); + ast_log(LOG_WARNING, + "Cannot run 'Macro(%s)'. The application is not available.\n", macro_args); return -1; } - snprintf(buf, sizeof(buf), "%s%s%s", macro_name, ast_strlen_zero(macro_args) ? "" : ",", S_OR(macro_args, "")); if (autoservice_chan) { ast_autoservice_start(autoservice_chan); } - res = pbx_exec(macro_chan, macro_app, buf); + + ast_debug(4, "%s Original location: %s,%s,%d\n", macro_chan->name, + macro_chan->context, macro_chan->exten, macro_chan->priority); + + res = pbx_exec(macro_chan, macro_app, macro_args); + ast_debug(4, "Macro exited with status %d\n", res); + + /* + * Assume anything negative from Macro is an error. + * Anything else is success. + */ + if (res < 0) { + res = -1; + } else { + res = 0; + } + + ast_debug(4, "%s Ending location: %s,%s,%d\n", macro_chan->name, + macro_chan->context, macro_chan->exten, macro_chan->priority); + if (autoservice_chan) { ast_autoservice_stop(autoservice_chan); } return res; } +int ast_app_run_macro(struct ast_channel *autoservice_chan, struct ast_channel *macro_chan, const char *macro_name, const char *macro_args) +{ + int res; + char *args_str; + size_t args_len; + + if (ast_strlen_zero(macro_args)) { + return ast_app_exec_macro(autoservice_chan, macro_chan, macro_name); + } + + /* Create the Macro application argument string. */ + args_len = strlen(macro_name) + strlen(macro_args) + 2; + args_str = ast_malloc(args_len); + if (!args_str) { + return -1; + } + snprintf(args_str, args_len, "%s,%s", macro_name, macro_args); + + res = ast_app_exec_macro(autoservice_chan, macro_chan, args_str); + ast_free(args_str); + return res; +} + +static const struct ast_app_stack_funcs *app_stack_callbacks; + +void ast_install_stack_functions(const struct ast_app_stack_funcs *funcs) +{ + app_stack_callbacks = funcs; +} + +const char *ast_app_expand_sub_args(struct ast_channel *chan, const char *args) +{ + const struct ast_app_stack_funcs *funcs; + const char *new_args; + + funcs = app_stack_callbacks; + if (!funcs || !funcs->expand_sub_args) { + ast_log(LOG_WARNING, + "Cannot expand 'Gosub(%s)' arguments. The app_stack module is not available.\n", + args); + return NULL; + } + ast_module_ref(funcs->module); + + new_args = funcs->expand_sub_args(chan, args); + ast_module_unref(funcs->module); + return new_args; +} + +int ast_app_exec_sub(struct ast_channel *autoservice_chan, struct ast_channel *sub_chan, const char *sub_args, int ignore_hangup) +{ + const struct ast_app_stack_funcs *funcs; + int res; + + funcs = app_stack_callbacks; + if (!funcs || !funcs->run_sub) { + ast_log(LOG_WARNING, + "Cannot run 'Gosub(%s)'. The app_stack module is not available.\n", + sub_args); + return -1; + } + ast_module_ref(funcs->module); + + if (autoservice_chan) { + ast_autoservice_start(autoservice_chan); + } + + res = funcs->run_sub(sub_chan, sub_args, ignore_hangup); + ast_module_unref(funcs->module); + + if (autoservice_chan) { + ast_autoservice_stop(autoservice_chan); + } + return res; +} + +int ast_app_run_sub(struct ast_channel *autoservice_chan, struct ast_channel *sub_chan, const char *sub_location, const char *sub_args, int ignore_hangup) +{ + int res; + char *args_str; + size_t args_len; + + if (ast_strlen_zero(sub_args)) { + return ast_app_exec_sub(autoservice_chan, sub_chan, sub_location, ignore_hangup); + } + + /* Create the Gosub application argument string. */ + args_len = strlen(sub_location) + strlen(sub_args) + 3; + args_str = ast_malloc(args_len); + if (!args_str) { + return -1; + } + snprintf(args_str, args_len, "%s(%s)", sub_location, sub_args); + + res = ast_app_exec_sub(autoservice_chan, sub_chan, args_str, ignore_hangup); + ast_free(args_str); + return res; +} + static int (*ast_has_voicemail_func)(const char *mailbox, const char *folder) = NULL; static int (*ast_inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsgs) = NULL; static int (*ast_inboxcount2_func)(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs) = NULL; diff --git a/main/features.c b/main/features.c index 1d91a10413..8f8b8b7b6f 100644 --- a/main/features.c +++ b/main/features.c @@ -3180,7 +3180,13 @@ static int feature_exec_app(struct ast_channel *chan, struct ast_channel *peer, if (!ast_strlen_zero(feature->moh_class)) ast_moh_start(idle, feature->moh_class, NULL); - res = pbx_exec(work, app, feature->app_args); + if (!strcasecmp("Gosub", feature->app)) { + res = ast_app_exec_sub(NULL, work, feature->app_args, 0); + } else if (!strcasecmp("Macro", feature->app)) { + res = ast_app_exec_macro(NULL, work, feature->app_args); + } else { + res = pbx_exec(work, app, feature->app_args); + } if (!ast_strlen_zero(feature->moh_class)) ast_moh_stop(idle);