features.c: Allow appliationmap to use Gosub.

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/


git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/1.8@419630 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Richard Mudgett
2014-07-25 23:04:09 +00:00
parent 0789fefda6
commit 9e3411a762
4 changed files with 721 additions and 99 deletions

View File

@@ -26,7 +26,7 @@
*/
/*** MODULEINFO
<use>res_agi</use>
<use type="module">res_agi</use>
<support_level>core</support_level>
***/
@@ -205,10 +205,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
</agi>
***/
static const char * const app_gosub = "Gosub";
static const char * const app_gosubif = "GosubIf";
static const char * const app_return = "Return";
static const char * const app_pop = "StackPop";
static const char app_gosub[] = "Gosub";
static const char app_gosubif[] = "GosubIf";
static const char app_return[] = "Return";
static const char app_pop[] = "StackPop";
static void gosub_free(void *data);
@@ -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;
@@ -296,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);
@@ -311,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))) {
@@ -322,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;
@@ -358,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 */
@@ -372,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];
@@ -565,7 +675,7 @@ static int gosubif_exec(struct ast_channel *chan, const char *data)
static int local_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
{
struct ast_datastore *stack_store;
AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
struct gosub_stack_list *oldlist;
struct gosub_stack_frame *frame;
struct ast_var_t *variables;
@@ -605,7 +715,7 @@ static int local_read(struct ast_channel *chan, const char *cmd, char *data, cha
static int local_write(struct ast_channel *chan, const char *cmd, char *var, const char *value)
{
struct ast_datastore *stack_store;
AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
struct gosub_stack_list *oldlist;
struct gosub_stack_frame *frame;
if (!chan) {
@@ -683,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,
@@ -756,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);
@@ -769,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) {
@@ -797,78 +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 (ast_asprintf(&gosub_args, "%s,%s,%d(%s)", argv[1], argv[2], priority + (chan->pbx ? 1 : 0), argv[4]) < 0) {
if (ast_asprintf(&gosub_args, "%s,%s,%d(%s)", argv[1], argv[2], priority, argv[4]) < 0) {
gosub_args = NULL;
}
} else {
if (ast_asprintf(&gosub_args, "%s,%s,%d", argv[1], argv[2], priority + (chan->pbx ? 1 : 0)) < 0) {
if (ast_asprintf(&gosub_args, "%s,%s,%d", argv[1], argv[2], priority) < 0) {
gosub_args = NULL;
}
}
if (gosub_args) {
int res;
ast_debug(1, "Trying gosub with arguments '%s'\n", gosub_args);
if ((res = pbx_exec(chan, theapp, gosub_args)) == 0) {
struct ast_pbx *pbx = 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;
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;
} else {
ast_agi_send(agi->fd, chan, "200 result=%d Gosub failed\n", res);
}
ast_free(gosub_args);
} else {
if (!gosub_args) {
ast_agi_send(agi->fd, chan, "503 result=-2 Memory allocation failure\n");
return RESULT_FAILURE;
}
ast_channel_lock(chan);
ast_verb(3, "%s AGI %s(%s) start\n", chan->name, app_gosub, gosub_args);
/* 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;
oldlist = stack_store->data;
cur = AST_LIST_FIRST(oldlist);
cur->is_special = 1;
}
ast_channel_unlock(chan);
}
if (!res) {
struct ast_pbx *pbx;
struct ast_pbx_args args;
int abnormal_exit;
memset(&args, 0, sizeof(args));
args.no_hangup_chan = 1;
ast_channel_lock(chan);
/* Next dialplan priority. */
++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_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_channel_unlock(chan);
ast_agi_send(agi->fd, chan, "200 result=0 Gosub complete%s\n",
abnormal_exit ? " (abnormal exit)" : "");
} else {
ast_agi_send(agi->fd, chan, "200 result=%d Gosub failed\n", res);
}
/* Must use free because the memory was allocated by asprintf(). */
free(gosub_args);
ast_channel_lock(chan);
ast_debug(4, "%s Ending location: %s,%s,%d\n", 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;
}
@@ -877,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);
@@ -892,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);
@@ -902,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;
}