Merge "AST-2018-005: Fix tdata leaks when calling pjsip_endpt_send_response(2)"
[asterisk/asterisk.git] / apps / app_stack.c
index beeea30..4560865 100644 (file)
  * \brief Stack applications Gosub, Return, etc.
  *
  * \author Tilghman Lesher <app_stack_v003@the-tilghman.com>
- * 
+ *
  * \ingroup applications
  */
 
 /*** MODULEINFO
-       <use>res_agi</use>
+       <use type="module">res_agi</use>
+       <support_level>core</support_level>
  ***/
 
 #include "asterisk.h"
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #include "asterisk/pbx.h"
 #include "asterisk/module.h"
 #include "asterisk/app.h"
 #include "asterisk/manager.h"
 #include "asterisk/channel.h"
-
-/* usage of AGI is optional, so indicate that to the header file */
-#define ASTERISK_AGI_OPTIONAL
 #include "asterisk/agi.h"
+#include "asterisk/stasis_channels.h"
 
 /*** DOCUMENTATION
        <application name="Gosub" language="en_US">
@@ -59,6 +56,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>Jumps to the label specified, saving the return address.</para>
                </description>
+               <see-also>
+                       <ref type="application">GosubIf</ref>
+                       <ref type="application">Macro</ref>
+                       <ref type="application">Goto</ref>
+                       <ref type="application">Return</ref>
+                       <ref type="application">StackPop</ref>
+               </see-also>
        </application>
        <application name="GosubIf" language="en_US">
                <synopsis>
@@ -68,10 +72,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <parameter name="condition" required="true" />
                        <parameter name="destination" required="true" argsep=":">
                                <argument name="labeliftrue" hasparams="optional">
+                                       <para>Continue at <replaceable>labeliftrue</replaceable> if the condition is true.
+                                       Takes the form similar to Goto() of [[context,]extension,]priority.</para>
                                        <argument name="arg1" required="true" multiple="true" />
                                        <argument name="argN" />
                                </argument>
                                <argument name="labeliffalse" hasparams="optional">
+                                       <para>Continue at <replaceable>labeliffalse</replaceable> if the condition is false.
+                                       Takes the form similar to Goto() of [[context,]extension,]priority.</para>
                                        <argument name="arg1" required="true" multiple="true" />
                                        <argument name="argN" />
                                </argument>
@@ -82,6 +90,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        labeliffalse, if specified.  In either case, a jump saves the return point
                        in the dialplan, to be returned to with a Return.</para>
                </description>
+               <see-also>
+                       <ref type="application">Gosub</ref>
+                       <ref type="application">Return</ref>
+                       <ref type="application">MacroIf</ref>
+                       <ref type="function">IF</ref>
+                       <ref type="application">GotoIf</ref>
+                       <ref type="application">Goto</ref>
+               </see-also>
        </application>
        <application name="Return" language="en_US">
                <synopsis>
@@ -96,6 +112,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>Jumps to the last label on the stack, removing it. The return <replaceable>value</replaceable>, if
                        any, is saved in the channel variable <variable>GOSUB_RETVAL</variable>.</para>
                </description>
+               <see-also>
+                       <ref type="application">Gosub</ref>
+                       <ref type="application">StackPop</ref>
+               </see-also>
        </application>
        <application name="StackPop" language="en_US">
                <synopsis>
@@ -105,6 +125,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>Removes last label on the stack, discarding it.</para>
                </description>
+               <see-also>
+                       <ref type="application">Return</ref>
+                       <ref type="application">Gosub</ref>
+               </see-also>
        </application>
        <function name="LOCAL" language="en_US">
                <synopsis>
@@ -123,16 +147,96 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <ref type="application">Return</ref>
                </see-also>
        </function>
+       <function name="LOCAL_PEEK" language="en_US">
+               <synopsis>
+                       Retrieve variables hidden by the local gosub stack frame.
+               </synopsis>
+               <syntax>
+                       <parameter name="n" required="true" />
+                       <parameter name="varname" required="true" />
+               </syntax>
+               <description>
+                       <para>Read a variable <replaceable>varname</replaceable> hidden by
+                       <replaceable>n</replaceable> levels of gosub stack frames.  Note that ${LOCAL_PEEK(0,foo)}
+                       is the same as <variable>foo</variable>, since the value of <replaceable>n</replaceable>
+                       peeks under 0 levels of stack frames; in other words, 0 is the current level.  If
+                       <replaceable>n</replaceable> exceeds the available number of stack frames, then an empty
+                       string is returned.</para>
+               </description>
+               <see-also>
+                       <ref type="application">Gosub</ref>
+                       <ref type="application">GosubIf</ref>
+                       <ref type="application">Return</ref>
+               </see-also>
+       </function>
+       <function name="STACK_PEEK" language="en_US">
+               <synopsis>
+                       View info about the location which called Gosub
+               </synopsis>
+               <syntax>
+                       <parameter name="n" required="true" />
+                       <parameter name="which" required="true" />
+                       <parameter name="suppress" required="false" />
+               </syntax>
+               <description>
+                       <para>Read the calling <literal>c</literal>ontext, <literal>e</literal>xtension,
+                       <literal>p</literal>riority, or <literal>l</literal>abel, as specified by
+                       <replaceable>which</replaceable>, by going up <replaceable>n</replaceable> frames
+                       in the Gosub stack.  If <replaceable>suppress</replaceable> is true, then if the
+                       number of available stack frames is exceeded, then no error message will be
+                       printed.</para>
+               </description>
+       </function>
+       <agi name="gosub" language="en_US">
+               <synopsis>
+                       Cause the channel to execute the specified dialplan subroutine.
+               </synopsis>
+               <syntax>
+                       <parameter name="context" required="true" />
+                       <parameter name="extension" required="true" />
+                       <parameter name="priority" required="true" />
+                       <parameter name="optional-argument" />
+               </syntax>
+               <description>
+                       <para>Cause the channel to execute the specified dialplan subroutine,
+                       returning to the dialplan with execution of a Return().</para>
+               </description>
+               <see-also>
+                       <ref type="application">GoSub</ref>
+               </see-also>
+       </agi>
+       <managerEvent language="en_US" name="VarSet">
+               <managerEventInstance class="EVENT_FLAG_DIALPLAN">
+                       <synopsis>Raised when a variable local to the gosub stack frame is set due to a subroutine call.</synopsis>
+                       <syntax>
+                               <channel_snapshot/>
+                               <parameter name="Variable">
+                                       <para>The LOCAL variable being set.</para>
+                                       <note><para>The variable name will always be enclosed with
+                                       <literal>LOCAL()</literal></para></note>
+                               </parameter>
+                               <parameter name="Value">
+                                       <para>The new value of the variable.</para>
+                               </parameter>
+                       </syntax>
+                       <see-also>
+                               <ref type="application">GoSub</ref>
+                               <ref type="agi">gosub</ref>
+                               <ref type="function">LOCAL</ref>
+                               <ref type="function">LOCAL_PEEK</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
  ***/
 
-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 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,
 };
@@ -143,14 +247,22 @@ struct gosub_stack_frame {
        unsigned char arguments;
        struct varshead varshead;
        int priority;
+       /*! TRUE if the return location marks the end of a special routine. */
+       unsigned int is_special:1;
+       /*! Whether or not we were in a subroutine when this one was created */
+       unsigned int in_subroutine: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;
        int found = 0;
+       int len;
+       RAII_VAR(char *, local_buffer, NULL, ast_free);
 
        /* Does this variable already exist? */
        AST_LIST_TRAVERSE(&frame->varshead, variables, entries) {
@@ -160,21 +272,22 @@ static int frame_set_var(struct ast_channel *chan, struct gosub_stack_frame *fra
                }
        }
 
-       if (!ast_strlen_zero(value)) {
-               if (!found) {
-                       variables = ast_var_assign(var, "");
+       if (!found) {
+               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);
+               }
+               pbx_builtin_pushvar_helper(chan, var, value);
+       } else {
+               pbx_builtin_setvar_helper(chan, var, value);
+       }
 
-               manager_event(EVENT_FLAG_DIALPLAN, "VarSet", 
-                       "Channel: %s\r\n"
-                       "Variable: LOCAL(%s)\r\n"
-                       "Value: %s\r\n"
-                       "Uniqueid: %s\r\n", 
-                       chan->name, var, value, chan->uniqueid);
+       len = 8 + strlen(var); /* LOCAL() + var */
+       local_buffer = ast_malloc(len);
+       if (!local_buffer) {
+               return 0;
        }
+       sprintf(local_buffer, "LOCAL(%s)", var);
+       ast_channel_publish_varset(chan, local_buffer, value);
        return 0;
 }
 
@@ -190,14 +303,14 @@ static void gosub_release_frame(struct ast_channel *chan, struct gosub_stack_fra
         */
        while ((vardata = AST_LIST_REMOVE_HEAD(&frame->varshead, entries))) {
                if (chan)
-                       pbx_builtin_setvar_helper(chan, ast_var_name(vardata), NULL);   
+                       pbx_builtin_setvar_helper(chan, ast_var_name(vardata), NULL);
                ast_var_delete(vardata);
        }
 
        ast_free(frame);
 }
 
-static struct gosub_stack_frame *gosub_allocate_frame(const char *context, const char *extension, int priority, unsigned char arguments)
+static struct gosub_stack_frame *gosub_allocate_frame(const char *context, const char *extension, int priority, int in_subroutine, unsigned char arguments)
 {
        struct gosub_stack_frame *new = NULL;
        int len_extension = strlen(extension), len_context = strlen(context);
@@ -208,6 +321,7 @@ static struct gosub_stack_frame *gosub_allocate_frame(const char *context, const
                new->context = new->extension + len_extension + 1;
                strcpy(new->context, context);
                new->priority = priority;
+               new->in_subroutine = in_subroutine ? 1 : 0;
                new->arguments = arguments;
        }
        return new;
@@ -215,8 +329,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);
@@ -226,39 +341,53 @@ static void gosub_free(void *data)
        ast_free(oldlist);
 }
 
-static int pop_exec(struct ast_channel *chan, void *data)
+static int pop_exec(struct ast_channel *chan, const char *data)
 {
-       struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
+       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;
 
-       if (!stack_store) {
+       ast_channel_lock(chan);
+       if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) {
                ast_log(LOG_WARNING, "%s called with no gosub stack allocated.\n", app_pop);
+               ast_channel_unlock(chan);
                return 0;
        }
 
        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);
        }
-       return 0;
+       AST_LIST_UNLOCK(oldlist);
+       ast_channel_unlock(chan);
+       return res;
 }
 
-static int return_exec(struct ast_channel *chan, void *data)
+static int return_exec(struct ast_channel *chan, const char *data)
 {
-       struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
+       struct ast_datastore *stack_store;
        struct gosub_stack_frame *oldframe;
-       AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
-       char *retval = data;
+       struct gosub_stack_list *oldlist;
+       const char *retval = data;
+       int res = 0;
 
-       if (!stack_store) {
+       ast_channel_lock(chan);
+       if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) {
                ast_log(LOG_ERROR, "Return without Gosub: stack is unallocated\n");
+               ast_channel_unlock(chan);
                return -1;
        }
 
@@ -269,24 +398,139 @@ static int return_exec(struct ast_channel *chan, void *data)
 
        if (!oldframe) {
                ast_log(LOG_ERROR, "Return without Gosub: stack is empty\n");
+               ast_channel_unlock(chan);
                return -1;
        }
+       if (oldframe->is_special) {
+               /* Exit from special routine. */
+               res = -1;
+       }
+
+       /*
+        * 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);
+       ast_set2_flag(ast_channel_flags(chan), oldframe->in_subroutine, AST_FLAG_SUBROUTINE_EXEC);
 
-       ast_explicit_goto(chan, oldframe->context, oldframe->extension, oldframe->priority);
        gosub_release_frame(chan, oldframe);
 
        /* Set a return value, if any */
        pbx_builtin_setvar_helper(chan, "GOSUB_RETVAL", S_OR(retval, ""));
-       return 0;
+       ast_channel_unlock(chan);
+       return res;
 }
 
-static int gosub_exec(struct ast_channel *chan, void *data)
+/*!
+ * \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)
 {
-       struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
-       AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
+       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;
+       struct gosub_stack_list *oldlist;
        struct gosub_stack_frame *newframe;
-       char argname[15], *tmp = ast_strdupa(data), *label, *endparen;
+       struct gosub_stack_frame *lastframe;
+       char argname[15];
+       char *parse;
+       char *label;
+       char *caller_id;
+       char *orig_context;
+       char *orig_exten;
+       char *dest_context;
+       char *dest_exten;
+       int orig_in_subroutine;
+       int orig_priority;
+       int dest_priority;
        int i;
+       int max_argc = 0;
        AST_DECLARE_APP_ARGS(args2,
                AST_APP_ARG(argval)[100];
        );
@@ -296,70 +540,137 @@ static int gosub_exec(struct ast_channel *chan, void *data)
                return -1;
        }
 
-       if (!stack_store) {
-               ast_debug(1, "Channel %s has no datastore, so we're allocating one.\n", chan->name);
+       /*
+        * Separate the arguments from the label
+        *
+        * NOTE:  You cannot use ast_app_separate_args for this, because
+        * '(' cannot be used as a delimiter.
+        */
+       parse = ast_strdupa(data);
+       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", data);
+               }
+               AST_STANDARD_RAW_ARGS(args2, parse);
+       } else {
+               args2.argc = 0;
+       }
+
+       ast_channel_lock(chan);
+       orig_context = ast_strdupa(ast_channel_context(chan));
+       orig_exten = ast_strdupa(ast_channel_exten(chan));
+       orig_priority = ast_channel_priority(chan);
+       orig_in_subroutine = ast_test_flag(ast_channel_flags(chan), AST_FLAG_SUBROUTINE_EXEC);
+       ast_channel_unlock(chan);
+
+       if (ast_parseable_goto(chan, label)) {
+               ast_log(LOG_ERROR, "%s address is invalid: '%s'\n", app_gosub, data);
+               goto error_exit;
+       }
+
+       ast_channel_lock(chan);
+       dest_context = ast_strdupa(ast_channel_context(chan));
+       dest_exten = ast_strdupa(ast_channel_exten(chan));
+       dest_priority = ast_channel_priority(chan);
+       if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP)) {
+               ++dest_priority;
+       }
+       caller_id = S_COR(ast_channel_caller(chan)->id.number.valid,
+               ast_channel_caller(chan)->id.number.str, NULL);
+       if (caller_id) {
+               caller_id = ast_strdupa(caller_id);
+       }
+       ast_channel_unlock(chan);
+
+       if (!ast_exists_extension(chan, dest_context, dest_exten, dest_priority, caller_id)) {
+               ast_log(LOG_ERROR, "Attempt to reach a non-existent destination for %s: (Context:%s, Extension:%s, Priority:%d)\n",
+                       app_gosub, dest_context, dest_exten, dest_priority);
+               goto error_exit;
+       }
+
+       /* Now we know that we're going to a new location */
+
+       ast_channel_lock(chan);
+
+       /* Find stack datastore return list. */
+       if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) {
+               ast_debug(1, "Channel %s has no datastore, so we're allocating one.\n",
+                       ast_channel_name(chan));
                stack_store = ast_datastore_alloc(&stack_info, NULL);
                if (!stack_store) {
-                       ast_log(LOG_ERROR, "Unable to allocate new datastore.  Gosub will fail.\n");
-                       return -1;
+                       ast_log(LOG_ERROR, "Unable to allocate new datastore.  %s failed.\n",
+                               app_gosub);
+                       goto error_exit_locked;
                }
 
                oldlist = ast_calloc(1, sizeof(*oldlist));
                if (!oldlist) {
-                       ast_log(LOG_ERROR, "Unable to allocate datastore list head.  Gosub will fail.\n");
+                       ast_log(LOG_ERROR, "Unable to allocate datastore list head.  %s failed.\n",
+                               app_gosub);
                        ast_datastore_free(stack_store);
-                       return -1;
+                       goto error_exit_locked;
                }
+               AST_LIST_HEAD_INIT(oldlist);
 
                stack_store->data = oldlist;
-               AST_LIST_HEAD_INIT(oldlist);
                ast_channel_datastore_add(chan, stack_store);
+       } else {
+               oldlist = stack_store->data;
        }
 
-       /* Separate the arguments from the label */
-       /* NOTE:  you cannot use ast_app_separate_args for this, because '(' cannot be used as a delimiter. */
-       label = strsep(&tmp, "(");
-       if (tmp) {
-               endparen = strrchr(tmp, ')');
-               if (endparen)
-                       *endparen = '\0';
-               else
-                       ast_log(LOG_WARNING, "Ouch.  No closing paren: '%s'?\n", (char *)data);
-               AST_STANDARD_APP_ARGS(args2, tmp);
-       } else
-               args2.argc = 0;
-
-       /* Create the return address, but don't save it until we know that the Gosub destination exists */
-       newframe = gosub_allocate_frame(chan->context, chan->exten, chan->priority + 1, args2.argc);
+       if ((lastframe = AST_LIST_FIRST(oldlist))) {
+               max_argc = lastframe->arguments;
+       }
 
-       if (!newframe)
-               return -1;
+       /* Mask out previous Gosub arguments in this invocation */
+       if (args2.argc > max_argc) {
+               max_argc = args2.argc;
+       }
 
-       if (ast_parseable_goto(chan, label)) {
-               ast_log(LOG_ERROR, "Gosub address is invalid: '%s'\n", (char *)data);
-               ast_free(newframe);
-               return -1;
+       /* Create the return address */
+       newframe = gosub_allocate_frame(orig_context, orig_exten, orig_priority + 1, orig_in_subroutine, max_argc);
+       if (!newframe) {
+               goto error_exit_locked;
        }
 
-       /* Now that we know for certain that we're going to a new location, set our arguments */
-       for (i = 0; i < args2.argc; i++) {
+       /* Set our arguments */
+       for (i = 0; i < max_argc; i++) {
                snprintf(argname, sizeof(argname), "ARG%d", i + 1);
-               frame_set_var(chan, newframe, argname, args2.argval[i]);
-               ast_debug(1, "Setting '%s' to '%s'\n", argname, args2.argval[i]);
+               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);
 
+       ast_set_flag(ast_channel_flags(chan), AST_FLAG_SUBROUTINE_EXEC);
+
        /* And finally, save our return address */
-       oldlist = stack_store->data;
        AST_LIST_LOCK(oldlist);
        AST_LIST_INSERT_HEAD(oldlist, newframe, entries);
        AST_LIST_UNLOCK(oldlist);
+       ast_channel_unlock(chan);
 
        return 0;
+
+error_exit:
+       ast_channel_lock(chan);
+
+error_exit_locked:
+       /* Restore the original dialplan location. */
+       ast_channel_context_set(chan, orig_context);
+       ast_channel_exten_set(chan, orig_exten);
+       ast_channel_priority_set(chan, orig_priority);
+       ast_channel_unlock(chan);
+       return -1;
 }
 
-static int gosubif_exec(struct ast_channel *chan, void *data)
+static int gosubif_exec(struct ast_channel *chan, const char *data)
 {
        char *args;
        int res=0;
@@ -378,13 +689,13 @@ static int gosubif_exec(struct ast_channel *chan, void *data)
        }
 
        args = ast_strdupa(data);
-       AST_NONSTANDARD_APP_ARGS(cond, args, '?');
+       AST_NONSTANDARD_RAW_ARGS(cond, args, '?');
        if (cond.argc != 2) {
                ast_log(LOG_WARNING, "GosubIf requires an argument: GosubIf(cond?label1(args):label2(args)\n");
                return 0;
        }
 
-       AST_NONSTANDARD_APP_ARGS(label, cond.labels, ':');
+       AST_NONSTANDARD_RAW_ARGS(label, cond.labels, ':');
 
        if (pbx_checkcondition(cond.ition)) {
                if (!ast_strlen_zero(label.iftrue))
@@ -398,39 +709,59 @@ static int gosubif_exec(struct ast_channel *chan, void *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_channel_datastore_find(chan, &stack_info, NULL);
-       AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
+       struct ast_datastore *stack_store;
+       struct gosub_stack_list *oldlist;
        struct gosub_stack_frame *frame;
        struct ast_var_t *variables;
 
-       if (!stack_store)
+       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);
                return -1;
+       }
 
        oldlist = stack_store->data;
        AST_LIST_LOCK(oldlist);
-       frame = AST_LIST_FIRST(oldlist);
+       if (!(frame = AST_LIST_FIRST(oldlist))) {
+               /* Not within a Gosub routine */
+               AST_LIST_UNLOCK(oldlist);
+               ast_channel_unlock(chan);
+               return -1;
+       }
+
        AST_LIST_TRAVERSE(&frame->varshead, variables, entries) {
                if (!strcmp(data, ast_var_name(variables))) {
                        const char *tmp;
-                       ast_channel_lock(chan);
                        tmp = pbx_builtin_getvar_helper(chan, data);
                        ast_copy_string(buf, S_OR(tmp, ""), len);
-                       ast_channel_unlock(chan);
                        break;
                }
        }
        AST_LIST_UNLOCK(oldlist);
+       ast_channel_unlock(chan);
        return 0;
 }
 
 static int local_write(struct ast_channel *chan, const char *cmd, char *var, const char *value)
 {
-       struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
-       AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
+       struct ast_datastore *stack_store;
+       struct gosub_stack_list *oldlist;
        struct gosub_stack_frame *frame;
 
-       if (!stack_store) {
+       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);
+               ast_channel_unlock(chan);
                return -1;
        }
 
@@ -438,10 +769,12 @@ static int local_write(struct ast_channel *chan, const char *cmd, char *var, con
        AST_LIST_LOCK(oldlist);
        frame = AST_LIST_FIRST(oldlist);
 
-       if (frame)
+       if (frame) {
                frame_set_var(chan, frame, var, value);
+       }
 
        AST_LIST_UNLOCK(oldlist);
+       ast_channel_unlock(chan);
 
        return 0;
 }
@@ -452,11 +785,344 @@ static struct ast_custom_function local_function = {
        .read = local_read,
 };
 
-static int handle_gosub(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+static int peek_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+       int found = 0, n;
+       struct ast_var_t *variables;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(n);
+               AST_APP_ARG(name);
+       );
+
+       if (!chan) {
+               ast_log(LOG_ERROR, "LOCAL_PEEK must be called on an active channel\n");
+               return -1;
+       }
+
+       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';
+
+       ast_channel_lock(chan);
+       AST_LIST_TRAVERSE(ast_channel_varshead(chan), variables, entries) {
+               if (!strcmp(args.name, ast_var_name(variables)) && ++found > n) {
+                       ast_copy_string(buf, ast_var_value(variables), len);
+                       break;
+               }
+       }
+       ast_channel_unlock(chan);
+       return 0;
+}
+
+static struct ast_custom_function peek_function = {
+       .name = "LOCAL_PEEK",
+       .read = peek_read,
+};
+
+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;
+       struct gosub_stack_list *oldlist;
+       struct gosub_stack_frame *frame;
+       int n;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(n);
+               AST_APP_ARG(which);
+               AST_APP_ARG(suppress);
+       );
+
+       if (!chan) {
+               ast_log(LOG_ERROR, "STACK_PEEK must be called on an active channel\n");
+               return -1;
+       }
+
+       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");
+               return -1;
+       }
+
+       ast_channel_lock(chan);
+       if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) {
+               if (!ast_true(args.suppress)) {
+                       ast_log(LOG_ERROR, "STACK_PEEK called on a channel without a gosub stack\n");
+               }
+               ast_channel_unlock(chan);
+               return -1;
+       }
+
+       oldlist = stack_store->data;
+
+       AST_LIST_LOCK(oldlist);
+       AST_LIST_TRAVERSE(oldlist, frame, entries) {
+               if (--n == 0) {
+                       break;
+               }
+       }
+
+       if (!frame) {
+               /* Too deep */
+               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;
+       }
+
+       args.which = ast_skip_blanks(args.which);
+
+       switch (args.which[0]) {
+       case 'l': /* label */
+               ast_str_set(str, len, "%s,%s,%d", frame->context, frame->extension, frame->priority - 1);
+               break;
+       case 'c': /* context */
+               ast_str_set(str, len, "%s", frame->context);
+               break;
+       case 'e': /* extension */
+               ast_str_set(str, len, "%s", frame->extension);
+               break;
+       case 'p': /* priority */
+               ast_str_set(str, len, "%d", frame->priority - 1);
+               break;
+       default:
+               ast_log(LOG_ERROR, "Unknown argument '%s' to STACK_PEEK\n", args.which);
+               break;
+       }
+
+       AST_LIST_UNLOCK(oldlist);
+       ast_channel_unlock(chan);
+
+       return 0;
+}
+
+static struct ast_custom_function stackpeek_function = {
+       .name = "STACK_PEEK",
+       .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)
 {
-       int old_priority, priority;
-       char old_context[AST_MAX_CONTEXT], old_extension[AST_MAX_EXTENSION];
-       struct ast_app *theapp;
+       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 saved_in_subroutine;
+       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;
+       if (saved_hangup_flags) {
+               ast_channel_clear_softhangup(chan, AST_SOFTHANGUP_ASYNCGOTO);
+       }
+
+       /* 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);
+
+       /* Save whether or not we are in a subroutine */
+       saved_in_subroutine = ast_test_flag(ast_channel_flags(chan), AST_FLAG_SUBROUTINE_EXEC);
+
+       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_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 subroutine flag */
+       ast_set2_flag(ast_channel_flags(chan), saved_in_subroutine, AST_FLAG_SUBROUTINE_EXEC);
+
+       /* 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 res;
+       int priority;
+       int old_autoloopflag;
+       int old_in_subroutine;
+       int old_priority;
+       const char *old_context;
+       const char *old_extension;
        char *gosub_args;
 
        if (argc < 4 || argc > 5) {
@@ -465,139 +1131,194 @@ static int handle_gosub(struct ast_channel *chan, AGI *agi, int argc, char **arg
 
        ast_debug(1, "Gosub called with %d arguments: 0:%s 1:%s 2:%s 3:%s 4:%s\n", argc, argv[0], argv[1], argv[2], argv[3], argc == 5 ? argv[4] : "");
 
-       if (sscanf(argv[3], "%d", &priority) != 1 || priority < 1) {
+       if (sscanf(argv[3], "%30d", &priority) != 1 || priority < 1) {
                /* Lookup the priority label */
-               if ((priority = ast_findlabel_extension(chan, argv[1], argv[2], argv[3], chan->cid.cid_num)) < 0) {
+               priority = ast_findlabel_extension(chan, argv[1], argv[2], argv[3],
+                       S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL));
+               if (priority < 0) {
                        ast_log(LOG_ERROR, "Priority '%s' not found in '%s@%s'\n", argv[3], argv[2], argv[1]);
-                       ast_agi_fdprintf(chan, agi->fd, "200 result=-1 Gosub label not found\n");
+                       ast_agi_send(agi->fd, chan, "200 result=-1 Gosub label not found\n");
                        return RESULT_FAILURE;
                }
-       } else if (!ast_exists_extension(chan, argv[1], argv[2], priority, chan->cid.cid_num)) {
-               ast_agi_fdprintf(chan, agi->fd, "200 result=-1 Gosub label not found\n");
+       } else if (!ast_exists_extension(chan, argv[1], argv[2], priority,
+               S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
+               ast_agi_send(agi->fd, chan, "200 result=-1 Gosub label not found\n");
                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_fdprintf(chan, agi->fd, "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 + 1, 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 + 1) < 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_copy_string(chan->context, "app_stack_gosub_virtual_context", sizeof(chan->context));
-               ast_copy_string(chan->exten, "s", sizeof(chan->exten));
-               chan->priority = 0;
+       ast_verb(3, "%s AGI %s(%s) start\n", ast_channel_name(chan), app_gosub, gosub_args);
 
-               if ((res = pbx_exec(chan, theapp, gosub_args)) == 0) {
-                       struct ast_pbx *pbx = chan->pbx;
-                       /* Suppress warning about PBX already existing */
-                       chan->pbx = NULL;
-                       ast_agi_fdprintf(chan, agi->fd, "100 result=0 Trying...\n");
-                       ast_pbx_run(chan);
-                       ast_agi_fdprintf(chan, agi->fd, "200 result=0 Gosub complete\n");
-                       if (chan->pbx) {
-                               ast_free(chan->pbx);
-                       }
-                       chan->pbx = pbx;
+       /* 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 subroutine flag */
+       old_in_subroutine = ast_test_flag(ast_channel_flags(chan), AST_FLAG_SUBROUTINE_EXEC);
+
+       /* 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 {
-                       ast_agi_fdprintf(chan, agi->fd, "200 result=%d Gosub failed\n", res);
+                       struct gosub_stack_list *oldlist;
+                       struct gosub_stack_frame *cur;
+
+                       oldlist = stack_store->data;
+                       cur = AST_LIST_FIRST(oldlist);
+                       cur->is_special = 1;
                }
-               ast_free(gosub_args);
+               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_fdprintf(chan, agi->fd, "503 result=-2 Memory allocation failure\n");
-               return RESULT_FAILURE;
+               ast_agi_send(agi->fd, chan, "200 result=%d Gosub failed\n", res);
        }
 
+       ast_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_copy_string(chan->context, old_context, sizeof(chan->context));
-       ast_copy_string(chan->exten, old_extension, sizeof(chan->exten));
-       chan->priority = old_priority;
+       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);
+
+       /* Restore subroutine flag */
+       ast_set2_flag(ast_channel_flags(chan), old_in_subroutine, AST_FLAG_SUBROUTINE_EXEC);
+       ast_channel_unlock(chan);
 
        return RESULT_SUCCESS;
 }
 
-static char usage_gosub[] =
-" Usage: GOSUB <context> <extension> <priority> [<optional-argument>]\n"
-"   Cause the channel to execute the specified dialplan subroutine, returning\n"
-" to the dialplan with execution of a Return()\n";
-
-struct agi_command gosub_agi_command =
-       { { "gosub", NULL }, handle_gosub, "Execute a dialplan subroutine", usage_gosub , 0 };
+static struct agi_command gosub_agi_command =
+       { { "gosub", NULL }, handle_gosub, NULL, NULL, 0 };
 
 static int unload_module(void)
 {
-       struct ast_context *con;
-
-       if (ast_agi_unregister) {
-               ast_agi_unregister(ast_module_info->self, &gosub_agi_command);
+       ast_install_stack_functions(NULL);
 
-               if ((con = ast_context_find("app_stack_gosub_virtual_context"))) {
-                       ast_context_remove_extension2(con, "s", 1, NULL, 0);
-                       ast_context_destroy(con, "app_stack"); /* leave nothing behind */
-               }
-       }
+       ast_agi_unregister(&gosub_agi_command);
 
        ast_unregister_application(app_return);
        ast_unregister_application(app_pop);
        ast_unregister_application(app_gosubif);
        ast_unregister_application(app_gosub);
        ast_custom_function_unregister(&local_function);
+       ast_custom_function_unregister(&peek_function);
+       ast_custom_function_unregister(&stackpeek_function);
 
        return 0;
 }
 
 static int load_module(void)
 {
-       struct ast_context *con;
-
-       /* usage of AGI is optional, so check to see if the ast_agi_register()
-          function is available; if so, use it.
-       */
-       if (ast_agi_register) {
-               con = ast_context_find_or_create(NULL, NULL, "app_stack_gosub_virtual_context", "app_stack");
-               if (!con) {
-                       ast_log(LOG_ERROR, "Virtual context 'app_stack_gosub_virtual_context' does not exist and unable to create\n");
-                       return AST_MODULE_LOAD_DECLINE;
-               } else {
-                       ast_add_extension2(con, 1, "s", 1, NULL, NULL, "KeepAlive", ast_strdup(""), 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);
-       }
+       ast_agi_register(ast_module_info->self, &gosub_agi_command);
 
        ast_register_application_xml(app_pop, pop_exec);
        ast_register_application_xml(app_return, return_exec);
        ast_register_application_xml(app_gosubif, gosubif_exec);
        ast_register_application_xml(app_gosub, gosub_exec);
        ast_custom_function_register(&local_function);
+       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;
 }
 
-AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan subroutines (Gosub, Return, etc)");
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT | AST_MODFLAG_LOAD_ORDER, "Dialplan subroutines (Gosub, Return, etc)",
+       .support_level = AST_MODULE_SUPPORT_CORE,
+       .load = load_module,
+       .unload = unload_module,
+       .load_pri = AST_MODPRI_APP_DEPEND,
+       .optional_modules = "res_agi",
+);