diff --git a/apps/app_dial.c b/apps/app_dial.c index 1ebad34128..002d2bef71 100644 --- a/apps/app_dial.c +++ b/apps/app_dial.c @@ -2280,7 +2280,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast if (ast_test_flag64(&opts, OPT_PREDIAL_CALLER) && !ast_strlen_zero(opt_args[OPT_ARG_PREDIAL_CALLER])) { ast_replace_subargument_delimiter(opt_args[OPT_ARG_PREDIAL_CALLER]); - ast_app_exec_sub(NULL, chan, opt_args[OPT_ARG_PREDIAL_CALLER]); + ast_app_exec_sub(NULL, chan, opt_args[OPT_ARG_PREDIAL_CALLER], 0); } /* loop through the list of dial destinations */ @@ -2550,12 +2550,18 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast if (ast_test_flag64(&opts, OPT_PREDIAL_CALLEE) && !ast_strlen_zero(opt_args[OPT_ARG_PREDIAL_CALLEE]) && !AST_LIST_EMPTY(&out_chans)) { - ast_autoservice_start(chan); + const char *predial_callee; + ast_replace_subargument_delimiter(opt_args[OPT_ARG_PREDIAL_CALLEE]); - AST_LIST_TRAVERSE(&out_chans, tmp, node) { - ast_pre_call(tmp->chan, opt_args[OPT_ARG_PREDIAL_CALLEE]); + predial_callee = ast_app_expand_sub_args(chan, opt_args[OPT_ARG_PREDIAL_CALLEE]); + if (predial_callee) { + ast_autoservice_start(chan); + AST_LIST_TRAVERSE(&out_chans, tmp, node) { + ast_pre_call(tmp->chan, predial_callee); + } + ast_autoservice_stop(chan); + ast_free((char *) predial_callee); } - ast_autoservice_stop(chan); } /* Start all outgoing calls */ @@ -2884,7 +2890,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast } } if (gosub_args) { - res9 = ast_app_exec_sub(chan, peer, gosub_args); + res9 = ast_app_exec_sub(chan, peer, gosub_args, 0); ast_free(gosub_args); } else { ast_log(LOG_ERROR, "Could not Allocate string for Gosub arguments -- Gosub Call Aborted!\n"); diff --git a/apps/app_followme.c b/apps/app_followme.c index f44d453ad3..275ed43c51 100644 --- a/apps/app_followme.c +++ b/apps/app_followme.c @@ -1386,14 +1386,15 @@ static int app_exec(struct ast_channel *chan, const char *data) if (ast_test_flag(&targs->followmeflags, FOLLOWMEFLAG_PREDIAL_CALLEE) && !ast_strlen_zero(opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLEE])) { ast_replace_subargument_delimiter(opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLEE]); - targs->predial_callee = opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLEE]; + targs->predial_callee = + ast_app_expand_sub_args(chan, opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLEE]); } /* PREDIAL: Run gosub on the caller's channel */ if (ast_test_flag(&targs->followmeflags, FOLLOWMEFLAG_PREDIAL_CALLER) && !ast_strlen_zero(opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLER])) { ast_replace_subargument_delimiter(opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLER]); - ast_app_exec_sub(NULL, chan, opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLER]); + ast_app_exec_sub(NULL, chan, opt_args[FOLLOWMEFLAG_ARG_PREDIAL_CALLER], 0); } /* Forget the 'N' option if the call is already up. */ @@ -1522,6 +1523,7 @@ outrun: if (!ast_strlen_zero(targs->namerecloc)) { unlink(targs->namerecloc); } + ast_free((char *) targs->predial_callee); ast_party_connected_line_free(&targs->connected_in); ast_party_connected_line_free(&targs->connected_out); ast_free(targs); diff --git a/apps/app_queue.c b/apps/app_queue.c index 245b451733..c7cd40a911 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -5352,7 +5352,7 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char * } } if (gosub_args) { - ast_app_exec_sub(qe->chan, peer, gosub_args); + ast_app_exec_sub(qe->chan, peer, gosub_args, 0); ast_free(gosub_args); } else { ast_log(LOG_ERROR, "Could not Allocate string for Gosub arguments -- Gosub Call Aborted!\n"); diff --git a/apps/app_stack.c b/apps/app_stack.c index 508a43c4e3..70fb7282d0 100644 --- a/apps/app_stack.c +++ b/apps/app_stack.c @@ -200,10 +200,10 @@ 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); @@ -218,11 +218,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; @@ -290,8 +293,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); @@ -305,7 +309,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))) { @@ -316,23 +321,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; @@ -352,12 +364,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_channel_context_set(chan, oldframe->context); + ast_channel_exten_set(chan, oldframe->extension); + if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP)) { + --oldframe->priority; + } + ast_channel_priority_set(chan, oldframe->priority); + gosub_release_frame(chan, oldframe); /* Set a return value, if any */ @@ -366,10 +390,95 @@ 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 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 = ast_channel_exten(chan); + } + if (ast_strlen_zero(context)) { + context = ast_channel_context(chan); + } + 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]; @@ -560,7 +669,7 @@ 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; @@ -595,7 +704,7 @@ 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; ast_channel_lock(chan); @@ -662,7 +771,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, @@ -729,6 +838,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); @@ -742,11 +852,211 @@ static struct ast_custom_function stackpeek_function = { .read2 = stackpeek_read, }; +/*! + * \internal + * \brief Pop stack frames until remove a special return location. + * \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 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", + ast_channel_name(chan), app_gosub, sub_args); + + /* Save non-hangup softhangup flags. */ + saved_hangup_flags = ast_channel_softhangup_internal_flag(chan) + & (AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE); + if (saved_hangup_flags) { + ast_channel_clear_softhangup(chan, + AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE); + } + + /* Save autoloop flag */ + saved_autoloopflag = ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP); + ast_set_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP); + + /* Save current dialplan location */ + saved_context = ast_strdupa(ast_channel_context(chan)); + saved_exten = ast_strdupa(ast_channel_exten(chan)); + saved_priority = ast_channel_priority(chan); + + ast_debug(4, "%s Original location: %s,%s,%d\n", ast_channel_name(chan), + 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 (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_UNBRIDGE) { + saved_hangup_flags |= AST_SOFTHANGUP_UNBRIDGE; + ast_channel_clear_softhangup(chan, AST_SOFTHANGUP_UNBRIDGE); + } + if (ast_check_hangup(chan)) { + if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) { + ast_log(LOG_ERROR, "%s An async goto just messed up our execution location.\n", + ast_channel_name(chan)); + break; + } + if (!ignore_hangup) { + break; + } + } + + /* Next dialplan priority. */ + ast_channel_priority_set(chan, ast_channel_priority(chan) + 1); + + ast_channel_unlock(chan); + res = ast_spawn_extension(chan, ast_channel_context(chan), + ast_channel_exten(chan), ast_channel_priority(chan), + S_COR(ast_channel_caller(chan)->id.number.valid, + ast_channel_caller(chan)->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", + ast_channel_context(chan), ast_channel_exten(chan), + ast_channel_priority(chan), res, ast_channel_name(chan)); + ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", + ast_channel_context(chan), ast_channel_exten(chan), + ast_channel_priority(chan), ast_channel_name(chan)); + } + + /* Did the routine return? */ + if (ast_channel_priority(chan) == saved_priority + && !strcmp(ast_channel_context(chan), saved_context) + && !strcmp(ast_channel_exten(chan), saved_exten)) { + ast_verb(3, "%s Internal %s(%s) complete GOSUB_RETVAL=%s\n", + ast_channel_name(chan), 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", + ast_channel_name(chan), 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", ast_channel_name(chan), + ast_channel_context(chan), ast_channel_exten(chan), + ast_channel_priority(chan)); + + /* Restore dialplan location */ + if (!(ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO)) { + ast_channel_context_set(chan, saved_context); + ast_channel_exten_set(chan, saved_exten); + ast_channel_priority_set(chan, saved_priority); + } + + /* Restore autoloop flag */ + ast_set2_flag(ast_channel_flags(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) { @@ -770,80 +1080,125 @@ 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, ast_channel_context(chan), sizeof(old_context)); - ast_copy_string(old_extension, ast_channel_exten(chan), sizeof(old_extension)); - old_priority = ast_channel_priority(chan); - - 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 + (ast_channel_pbx(chan) ? 1 : 0), argv[4]) < 0) { + if (asprintf(&gosub_args, "%s,%s,%d(%s)", argv[1], argv[2], priority, argv[4]) < 0) { ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno)); gosub_args = NULL; } } else { - if (asprintf(&gosub_args, "%s,%s,%d", argv[1], argv[2], priority + (ast_channel_pbx(chan) ? 1 : 0)) < 0) { + if (asprintf(&gosub_args, "%s,%s,%d", argv[1], argv[2], priority) < 0) { ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno)); gosub_args = NULL; } } - - if (gosub_args) { - int res; - - ast_debug(1, "Trying gosub with arguments '%s'\n", gosub_args); - - if ((res = pbx_exec(chan, theapp, gosub_args)) == 0) { - struct ast_pbx *pbx = ast_channel_pbx(chan); - 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; - 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 */ - ast_channel_pbx_set(chan, 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 (ast_channel_pbx(chan)) { - ast_free(ast_channel_pbx(chan)); - } - ast_channel_pbx_set(chan, pbx); - } else { - ast_agi_send(agi->fd, chan, "200 result=%d Gosub failed\n", res); - } - ast_free(gosub_args); - } else { + if (!gosub_args) { ast_agi_send(agi->fd, chan, "503 result=-2 Memory allocation failure\n"); return RESULT_FAILURE; } + ast_channel_lock(chan); + + ast_verb(3, "%s AGI %s(%s) start\n", ast_channel_name(chan), app_gosub, gosub_args); + + /* Save autoloop flag */ + old_autoloopflag = ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP); + ast_set_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP); + + /* Save previous location, since we're going to change it */ + old_context = ast_strdupa(ast_channel_context(chan)); + old_extension = ast_strdupa(ast_channel_exten(chan)); + old_priority = ast_channel_priority(chan); + + ast_debug(4, "%s Original location: %s,%s,%d\n", ast_channel_name(chan), + 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; + + oldlist = stack_store->data; + cur = AST_LIST_FIRST(oldlist); + 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. */ + ast_channel_priority_set(chan, ast_channel_priority(chan) + 1); + + /* Suppress warning about PBX already existing */ + pbx = ast_channel_pbx(chan); + ast_channel_pbx_set(chan, 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(ast_channel_pbx(chan)); + ast_channel_pbx_set(chan, pbx); + + /* Did the routine return? */ + if (ast_channel_priority(chan) == old_priority + && !strcmp(ast_channel_context(chan), old_context) + && !strcmp(ast_channel_exten(chan), old_extension)) { + ast_verb(3, "%s AGI %s(%s) complete GOSUB_RETVAL=%s\n", + ast_channel_name(chan), app_gosub, gosub_args, + S_OR(pbx_builtin_getvar_helper(chan, "GOSUB_RETVAL"), "")); + abnormal_exit = 0; + } else { + ast_log(LOG_NOTICE, "%s Abnormal AGI %s(%s) exit. Popping routine return locations.\n", + ast_channel_name(chan), app_gosub, gosub_args); + balance_stack(chan); + pbx_builtin_setvar_helper(chan, "GOSUB_RETVAL", ""); + abnormal_exit = 1; + } + 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, "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", ast_channel_name(chan), + ast_channel_context(chan), ast_channel_exten(chan), + ast_channel_priority(chan)); + /* Restore previous location */ ast_channel_context_set(chan, old_context); ast_channel_exten_set(chan, old_extension); ast_channel_priority_set(chan, old_priority); + /* Restore autoloop flag */ + ast_set2_flag(ast_channel_flags(chan), old_autoloopflag, AST_FLAG_IN_AUTOLOOP); + ast_channel_unlock(chan); + return RESULT_SUCCESS; } @@ -852,7 +1207,7 @@ static struct agi_command gosub_agi_command = static int unload_module(void) { - struct ast_context *con; + ast_install_stack_functions(NULL); ast_agi_unregister(ast_module_info->self, &gosub_agi_command); @@ -864,29 +1219,16 @@ static int unload_module(void) ast_custom_function_unregister(&peek_function); ast_custom_function_unregister(&stackpeek_function); - con = ast_context_find("gosub_virtual_context"); - if (con) { - /* leave nothing behind */ - ast_context_remove_extension2(con, "s", 1, NULL, 0); - ast_context_destroy(con, "app_stack"); - } - return 0; } static int load_module(void) { - struct ast_context *con; - - /* Create internal gosub return target to indicate successful completion. */ - con = ast_context_find_or_create(NULL, NULL, "gosub_virtual_context", "app_stack"); - if (!con) { - ast_log(LOG_ERROR, "'gosub_virtual_context' does not exist and unable to create\n"); - } else { - ast_add_extension2(con, 1, "s", 1, NULL, NULL, "NoOp", - ast_strdup("Internal Gosub call complete GOSUB_RETVAL=${GOSUB_RETVAL}"), - ast_free_ptr, "app_stack"); - } + /* 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); @@ -898,6 +1240,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 50f6674469..d10a0a6673 100644 --- a/include/asterisk/app.h +++ b/include/asterisk/app.h @@ -148,8 +148,7 @@ int ast_app_getdata_full(struct ast_channel *c, const char *prompt, char *s, int * supply a NULL autoservice_chan here in case there is no * channel to place into autoservice. * - * \note It is very important that the autoservice_chan is not - * locked prior to calling. Otherwise, a deadlock could result. + * \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. @@ -170,8 +169,7 @@ int ast_app_exec_macro(struct ast_channel *autoservice_chan, struct ast_channel * supply a NULL autoservice_chan here in case there is no * channel to place into autoservice. * - * \note It is very important that the autoservice_chan is not - * locked prior to calling. Otherwise, a deadlock could result. + * \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. @@ -184,27 +182,67 @@ int ast_app_exec_macro(struct ast_channel *autoservice_chan, struct ast_channel 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 11 - * \brief Run a subroutine on a channel, placing an optional second channel into autoservice. + * \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. + * + * \param chan Channel to obtain context/exten. + * \param args Gosub application argument string. * * \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. + * Fills in the optional context and exten from the given channel. * - * \note It is very important that the autoservice_chan is not - * locked prior to calling. Otherwise, a deadlock could result. - * - * \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. - * - * \retval 0 success - * \retval -1 on error + * \retval New-args Gosub argument string on success. Must be freed. + * \retval NULL on error. */ -int ast_app_exec_sub(struct ast_channel *autoservice_chan, struct ast_channel *sub_chan, const char *sub_args); +const char *ast_app_expand_sub_args(struct ast_channel *chan, const char *args); /*! * \since 11 @@ -216,19 +254,41 @@ int ast_app_exec_sub(struct ast_channel *autoservice_chan, struct ast_channel *s * to supply a NULL autoservice_chan here in case there is no * channel to place into autoservice. * - * \note It is very important that the autoservice_chan is not - * locked prior to calling. Otherwise, a deadlock could result. + * \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 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_sub(struct ast_channel *autoservice_chan, - struct ast_channel *sub_chan, const char *sub_location, const char *sub_args); + 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 8cc22c6c70..210ef11685 100644 --- a/main/app.c +++ b/main/app.c @@ -55,6 +55,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); @@ -312,82 +313,52 @@ int ast_app_run_macro(struct ast_channel *autoservice_chan, struct ast_channel * return res; } -int ast_app_exec_sub(struct ast_channel *autoservice_chan, struct ast_channel *sub_chan, const char *sub_args) +static const struct ast_app_stack_funcs *app_stack_callbacks; + +void ast_install_stack_functions(const struct ast_app_stack_funcs *funcs) { - struct ast_app *sub_app; - const char *saved_context; - const char *saved_exten; - int saved_priority; - int saved_autoloopflag; + 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; - sub_app = pbx_findapp("Gosub"); - if (!sub_app) { + funcs = app_stack_callbacks; + if (!funcs || !funcs->run_sub) { ast_log(LOG_WARNING, - "Cannot run 'Gosub(%s)'. The application is not available.\n", sub_args); + "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); } - ast_channel_lock(sub_chan); - - /* Save current dialplan location */ - saved_context = ast_strdupa(ast_channel_context(sub_chan)); - saved_exten = ast_strdupa(ast_channel_exten(sub_chan)); - saved_priority = ast_channel_priority(sub_chan); - - /* - * Save flag to restore at the end so we don't have to play with - * the priority in the gosub location string. - */ - saved_autoloopflag = ast_test_flag(ast_channel_flags(sub_chan), AST_FLAG_IN_AUTOLOOP); - ast_clear_flag(ast_channel_flags(sub_chan), AST_FLAG_IN_AUTOLOOP); - - /* Set known location for Gosub to return - 1 */ - ast_channel_context_set(sub_chan, "gosub_virtual_context"); - ast_channel_exten_set(sub_chan, "s"); - ast_channel_priority_set(sub_chan, 1 - 1); - - ast_debug(4, "%s Original location: %s,%s,%d\n", ast_channel_name(sub_chan), - saved_context, saved_exten, saved_priority); - - ast_channel_unlock(sub_chan); - res = pbx_exec(sub_chan, sub_app, sub_args); - ast_debug(4, "Gosub exited with status %d\n", res); - ast_channel_lock(sub_chan); - if (!res) { - struct ast_pbx_args gosub_args = {{0}}; - struct ast_pbx *saved_pbx; - - /* supress warning about a pbx already being on the channel */ - saved_pbx = ast_channel_pbx(sub_chan); - ast_channel_pbx_set(sub_chan, NULL); - - ast_channel_unlock(sub_chan); - gosub_args.no_hangup_chan = 1; - ast_pbx_run_args(sub_chan, &gosub_args); - ast_channel_lock(sub_chan); - - /* Restore pbx. */ - ast_free(ast_channel_pbx(sub_chan)); - ast_channel_pbx_set(sub_chan, saved_pbx); - } - - ast_debug(4, "%s Ending location: %s,%s,%d\n", ast_channel_name(sub_chan), - ast_channel_context(sub_chan), ast_channel_exten(sub_chan), - ast_channel_priority(sub_chan)); - - /* Restore flag */ - ast_set2_flag(ast_channel_flags(sub_chan), saved_autoloopflag, AST_FLAG_IN_AUTOLOOP); - - /* Restore dialplan location */ - ast_channel_context_set(sub_chan, saved_context); - ast_channel_exten_set(sub_chan, saved_exten); - ast_channel_priority_set(sub_chan, saved_priority); - - ast_channel_unlock(sub_chan); + res = funcs->run_sub(sub_chan, sub_args, ignore_hangup); + ast_module_unref(funcs->module); if (autoservice_chan) { ast_autoservice_stop(autoservice_chan); @@ -395,14 +366,14 @@ int ast_app_exec_sub(struct ast_channel *autoservice_chan, struct ast_channel *s 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 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); + return ast_app_exec_sub(autoservice_chan, sub_chan, sub_location, ignore_hangup); } /* Create the Gosub application argument string. */ @@ -413,7 +384,7 @@ int ast_app_run_sub(struct ast_channel *autoservice_chan, struct ast_channel *su } snprintf(args_str, args_len, "%s(%s)", sub_location, sub_args); - res = ast_app_exec_sub(autoservice_chan, sub_chan, args_str); + res = ast_app_exec_sub(autoservice_chan, sub_chan, args_str, ignore_hangup); ast_free(args_str); return res; } diff --git a/main/ccss.c b/main/ccss.c index fe748e98c5..b27823aaa4 100644 --- a/main/ccss.c +++ b/main/ccss.c @@ -2724,7 +2724,7 @@ static void *generic_recall(void *data) if (!ast_strlen_zero(callback_macro)) { ast_log_dynamic_level(cc_logger_level, "Core %d: There's a callback macro configured for agent %s\n", agent->core_id, agent->device_name); - if (ast_app_run_macro(NULL, chan, callback_macro, NULL)) { + if (ast_app_exec_macro(NULL, chan, callback_macro)) { ast_cc_failed(agent->core_id, "Callback macro to %s failed. Maybe a hangup?", agent->device_name); ast_hangup(chan); return NULL; @@ -2734,7 +2734,7 @@ static void *generic_recall(void *data) if (!ast_strlen_zero(callback_sub)) { ast_log_dynamic_level(cc_logger_level, "Core %d: There's a callback subroutine configured for agent %s\n", agent->core_id, agent->device_name); - if (ast_app_run_sub(NULL, chan, callback_sub, NULL)) { + if (ast_app_exec_sub(NULL, chan, callback_sub, 0)) { ast_cc_failed(agent->core_id, "Callback subroutine to %s failed. Maybe a hangup?", agent->device_name); ast_hangup(chan); return NULL; diff --git a/main/channel.c b/main/channel.c index 6b3621dae3..b9fc0b5d8f 100644 --- a/main/channel.c +++ b/main/channel.c @@ -5729,7 +5729,7 @@ int ast_pre_call(struct ast_channel *chan, const char *sub_args) return res; } ast_channel_unlock(chan); - return ast_app_exec_sub(NULL, chan, sub_args); + return ast_app_exec_sub(NULL, chan, sub_args, 0); } int ast_call(struct ast_channel *chan, const char *addr, int timeout) @@ -9801,7 +9801,7 @@ int ast_channel_connected_line_sub(struct ast_channel *autoservice_chan, struct } ast_channel_unlock(sub_chan); - retval = ast_app_run_sub(autoservice_chan, sub_chan, sub, sub_args); + retval = ast_app_run_sub(autoservice_chan, sub_chan, sub, sub_args, 0); if (!retval) { struct ast_party_connected_line saved_connected; @@ -9844,7 +9844,7 @@ int ast_channel_redirecting_sub(struct ast_channel *autoservice_chan, struct ast } ast_channel_unlock(sub_chan); - retval = ast_app_run_sub(autoservice_chan, sub_chan, sub, sub_args); + retval = ast_app_run_sub(autoservice_chan, sub_chan, sub, sub_args, 0); if (!retval) { struct ast_party_redirecting saved_redirecting;