]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
features.c: Allow appliationmap to use Gosub. certified/1.8.15
authorRichard Mudgett <rmudgett@digium.com>
Sat, 26 Jul 2014 00:40:18 +0000 (00:40 +0000)
committerRichard Mudgett <rmudgett@digium.com>
Sat, 26 Jul 2014 00:40:18 +0000 (00:40 +0000)
Using DYNAMIC_FEATURES with a Gosub application as the mapped application
does not work.  It does not work because Gosub just pushes the current
dialplan context, exten, and priority onto a stack and sets the specified
Gosub location.  Gosub does not have a dialplan execution loop to run
dialplan like Macro.

* Made the DYNAMIC_FEATURES application mapping feature call
ast_app_exec_macro() and ast_app_exec_sub() for the Macro and Gosub
applications respectively.

* Backported ast_app_exec_macro() and ast_app_exec_sub() from v11 to
execute dialplan routines from the DYNAMIC_FEATURES application mapping
feature.

NOTE: This issue does not affect v12+ because it already does what this
patch implements.

AST-1391 #close
Reported by: Guenther Kelleter

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

Merged revisions 419630 from http://svn.asterisk.org/svn/asterisk/branches/1.8

git-svn-id: https://origsvn.digium.com/svn/asterisk/certified/branches/1.8.15@419679 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/app_stack.c
include/asterisk/app.h
main/app.c
main/features.c

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