Allow non-normal execution routines to be able to run on hungup channels.
authorRichard Mudgett <rmudgett@digium.com>
Thu, 14 Jun 2012 23:22:53 +0000 (23:22 +0000)
committerRichard Mudgett <rmudgett@digium.com>
Thu, 14 Jun 2012 23:22:53 +0000 (23:22 +0000)
* Make non-normal dialplan execution routines be able to run on a hung up
channel.  This is preparation work for hangup handler routines.

* Fixed ability to support relative non-normal dialplan execution
routines.  (i.e., The context and exten are optional for the specified
dialplan location.) Predial routines are the only non-normal routines that
it makes sense to optionally omit the context and exten.  Setting a hangup
handler also needs this ability.

* Fix Return application being able to restore a dialplan location
exactly.  Channels without a PBX may not have context or exten set.

* Fixes non-normal execution routines like connected line interception and
predial leaving the dialplan execution stack unbalanced.  Errors like
missing Return statements, popping too many stack frames using StackPop,
or an application returning non-zero could leave the dialplan stack
unbalanced.

* Fixed the AGI gosub application so it cleans up the dialplan execution
stack and handles the autoloop priority increments correctly.

* Eliminated the need for the gosub_virtual_context return location.

Review: https://reviewboard.asterisk.org/r/1984/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@368985 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/app_dial.c
apps/app_followme.c
apps/app_queue.c
apps/app_stack.c
include/asterisk/app.h
main/app.c
main/ccss.c
main/channel.c

index 1ebad34..002d2be 100644 (file)
@@ -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");
index f44d453..275ed43 100644 (file)
@@ -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);
index 245b451..c7cd40a 100644 (file)
@@ -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");
index 508a43c..70fb728 100644 (file)
@@ -200,10 +200,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
        </agi>
  ***/
 
-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) {
+               ast_agi_send(agi->fd, chan, "503 result=-2 Memory allocation failure\n");
+               return RESULT_FAILURE;
+       }
+
+       ast_channel_lock(chan);
 
-       if (gosub_args) {
-               int res;
+       ast_verb(3, "%s AGI %s(%s) start\n", ast_channel_name(chan), app_gosub, gosub_args);
 
-               ast_debug(1, "Trying gosub with arguments '%s'\n", 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);
 
-               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;
+       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 */
-                       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);
+                       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_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",
+                               ast_channel_name(chan), 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", 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;
 }
 
index 50f6674..d10a0a6 100644 (file)
@@ -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.
@@ -185,6 +183,68 @@ 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 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
+ * 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 11
  * \brief Run a subroutine on a channel, placing an optional second channel into autoservice.
  *
@@ -194,17 +254,17 @@ int ast_app_run_macro(struct ast_channel *autoservice_chan,
  * 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 ast_app_exec_sub(struct ast_channel *autoservice_chan, struct ast_channel *sub_chan, const char *sub_args, int ignore_hangup);
 
 /*!
  * \since 11
@@ -216,19 +276,19 @@ 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_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,
index 8cc22c6..210ef11 100644 (file)
@@ -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;
-       int res;
+       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;
 
-       sub_app = pbx_findapp("Gosub");
-       if (!sub_app) {
+       funcs = app_stack_callbacks;
+       if (!funcs || !funcs->expand_sub_args) {
                ast_log(LOG_WARNING,
-                       "Cannot run 'Gosub(%s)'.  The application is not available.\n", sub_args);
-               return -1;
-       }
-       if (autoservice_chan) {
-               ast_autoservice_start(autoservice_chan);
+                       "Cannot expand 'Gosub(%s)' arguments.  The app_stack module is not available.\n",
+                       args);
+               return NULL;
        }
+       ast_module_ref(funcs->module);
 
-       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);
+       new_args = funcs->expand_sub_args(chan, args);
+       ast_module_unref(funcs->module);
+       return new_args;
+}
 
-               ast_channel_unlock(sub_chan);
-               gosub_args.no_hangup_chan = 1;
-               ast_pbx_run_args(sub_chan, &gosub_args);
-               ast_channel_lock(sub_chan);
+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;
 
-               /* Restore pbx. */
-               ast_free(ast_channel_pbx(sub_chan));
-               ast_channel_pbx_set(sub_chan, saved_pbx);
+       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);
 
-       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);
+       if (autoservice_chan) {
+               ast_autoservice_start(autoservice_chan);
+       }
 
-       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;
 }
index fe748e9..b27823a 100644 (file)
@@ -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;
index 6b3621d..b9fc0b5 100644 (file)
@@ -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;