*/
/*** MODULEINFO
- <use>res_agi</use>
+ <use type="module">res_agi</use>
<support_level>core</support_level>
***/
</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,
};
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;
}
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);
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);
{
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))) {
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;
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 */
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];
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 */
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);
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);
}
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';
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,
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");
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;
}
break;
default:
ast_log(LOG_ERROR, "Unknown argument '%s' to STACK_PEEK\n", args.which);
+ break;
}
AST_LIST_UNLOCK(oldlist);
.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) {
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;
}
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);
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);
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;
}
/*! \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,