diff options
-rw-r--r-- | apps/app_stack.c | 153 | ||||
-rw-r--r-- | funcs/func_dialplan.c | 6 | ||||
-rw-r--r-- | res/ael/pval.c | 79 | ||||
-rw-r--r-- | tests/test_gosub.c | 12 | ||||
-rw-r--r-- | utils/ael_main.c | 30 | ||||
-rw-r--r-- | utils/conf2ael.c | 30 |
6 files changed, 295 insertions, 15 deletions
diff --git a/apps/app_stack.c b/apps/app_stack.c index 827bb508a..28bd3cfb4 100644 --- a/apps/app_stack.c +++ b/apps/app_stack.c @@ -165,6 +165,24 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <ref type="application">Return</ref> </see-also> </function> + <function name="STACK_PEEK" language="en_US"> + <synopsis> + View info about the location which called Gosub + </synopsis> + <syntax> + <parameter name="n" required="true" /> + <parameter name="which" required="true" /> + <parameter name="suppress" required="false" /> + </syntax> + <description> + <para>Read the calling <literal>c</literal>ontext, <literal>e</literal>xtension, + <literal>p</literal>riority, or <literal>l</literal>abel, as specified by + <replaceable>which</replaceable>, by going up <replaceable>n</replaceable> frames + in the Gosub stack. If <replaceable>suppress</replaceable> is true, then if the + number of available stack frames is exceeded, then no error message will be + printed.</para> + </description> + </function> <agi name="gosub" language="en_US"> <synopsis> Cause the channel to execute the specified dialplan subroutine. @@ -285,12 +303,14 @@ static void gosub_free(void *data) static int pop_exec(struct ast_channel *chan, const char *data) { - struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL); + struct ast_datastore *stack_store; struct gosub_stack_frame *oldframe; AST_LIST_HEAD(, gosub_stack_frame) *oldlist; - if (!stack_store) { + ast_channel_lock(chan); + if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) { ast_log(LOG_WARNING, "%s called with no gosub stack allocated.\n", app_pop); + ast_channel_unlock(chan); return 0; } @@ -304,19 +324,22 @@ static int pop_exec(struct ast_channel *chan, const char *data) } else { ast_debug(1, "%s called with an empty gosub stack\n", app_pop); } + ast_channel_unlock(chan); return 0; } static int return_exec(struct ast_channel *chan, const char *data) { - struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL); + struct ast_datastore *stack_store; struct gosub_stack_frame *oldframe; AST_LIST_HEAD(, gosub_stack_frame) *oldlist; const char *retval = data; int res = 0; - if (!stack_store) { + ast_channel_lock(chan); + if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) { ast_log(LOG_ERROR, "Return without Gosub: stack is unallocated\n"); + ast_channel_unlock(chan); return -1; } @@ -327,6 +350,7 @@ static int return_exec(struct ast_channel *chan, const char *data) if (!oldframe) { 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 */ @@ -338,12 +362,13 @@ static int return_exec(struct ast_channel *chan, const char *data) /* Set a return value, if any */ pbx_builtin_setvar_helper(chan, "GOSUB_RETVAL", S_OR(retval, "")); + ast_channel_unlock(chan); return res; } static int gosub_exec(struct ast_channel *chan, const char *data) { - struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL); + struct ast_datastore *stack_store; AST_LIST_HEAD(,gosub_stack_frame) *oldlist; struct gosub_stack_frame *newframe, *lastframe; char argname[15], *tmp = ast_strdupa(data), *label, *endparen; @@ -357,11 +382,13 @@ static int gosub_exec(struct ast_channel *chan, const char *data) return -1; } - if (!stack_store) { + ast_channel_lock(chan); + if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) { ast_debug(1, "Channel %s has no datastore, so we're allocating one.\n", ast_channel_name(chan)); stack_store = ast_datastore_alloc(&stack_info, NULL); if (!stack_store) { ast_log(LOG_ERROR, "Unable to allocate new datastore. Gosub will fail.\n"); + ast_channel_unlock(chan); return -1; } @@ -369,6 +396,7 @@ static int gosub_exec(struct ast_channel *chan, const char *data) if (!oldlist) { ast_log(LOG_ERROR, "Unable to allocate datastore list head. Gosub will fail.\n"); ast_datastore_free(stack_store); + ast_channel_unlock(chan); return -1; } @@ -405,12 +433,14 @@ static int gosub_exec(struct ast_channel *chan, const char *data) newframe = gosub_allocate_frame(ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan) + 1, max_argc); if (!newframe) { + ast_channel_unlock(chan); return -1; } if (ast_parseable_goto(chan, label)) { ast_log(LOG_ERROR, "Gosub address is invalid: '%s'\n", (char *)data); ast_free(newframe); + ast_channel_unlock(chan); return -1; } @@ -423,6 +453,7 @@ static int gosub_exec(struct ast_channel *chan, const char *data) ast_channel_exten_set(chan, newframe->extension); ast_channel_priority_set(chan, newframe->priority - 1); ast_free(newframe); + ast_channel_unlock(chan); return -1; } @@ -440,6 +471,7 @@ static int gosub_exec(struct ast_channel *chan, const char *data) AST_LIST_LOCK(oldlist); AST_LIST_INSERT_HEAD(oldlist, newframe, entries); AST_LIST_UNLOCK(oldlist); + ast_channel_unlock(chan); return 0; } @@ -483,44 +515,49 @@ 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_channel_datastore_find(chan, &stack_info, NULL); + struct ast_datastore *stack_store; AST_LIST_HEAD(, gosub_stack_frame) *oldlist; struct gosub_stack_frame *frame; struct ast_var_t *variables; - if (!stack_store) + ast_channel_lock(chan); + if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) { + ast_channel_unlock(chan); return -1; + } oldlist = stack_store->data; AST_LIST_LOCK(oldlist); if (!(frame = AST_LIST_FIRST(oldlist))) { /* Not within a Gosub routine */ AST_LIST_UNLOCK(oldlist); + ast_channel_unlock(chan); return -1; } AST_LIST_TRAVERSE(&frame->varshead, variables, entries) { if (!strcmp(data, ast_var_name(variables))) { const char *tmp; - ast_channel_lock(chan); tmp = pbx_builtin_getvar_helper(chan, data); ast_copy_string(buf, S_OR(tmp, ""), len); - ast_channel_unlock(chan); break; } } AST_LIST_UNLOCK(oldlist); + ast_channel_unlock(chan); return 0; } static int local_write(struct ast_channel *chan, const char *cmd, char *var, const char *value) { - struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL); + struct ast_datastore *stack_store; AST_LIST_HEAD(, gosub_stack_frame) *oldlist; struct gosub_stack_frame *frame; - if (!stack_store) { + ast_channel_lock(chan); + if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) { ast_log(LOG_ERROR, "Tried to set LOCAL(%s), but we aren't within a Gosub routine\n", var); + ast_channel_unlock(chan); return -1; } @@ -528,10 +565,12 @@ static int local_write(struct ast_channel *chan, const char *cmd, char *var, con AST_LIST_LOCK(oldlist); frame = AST_LIST_FIRST(oldlist); - if (frame) + if (frame) { frame_set_var(chan, frame, var, value); + } AST_LIST_UNLOCK(oldlist); + ast_channel_unlock(chan); return 0; } @@ -576,6 +615,89 @@ static struct ast_custom_function peek_function = { .read = peek_read, }; +static int stackpeek_read(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **str, ssize_t len) +{ + struct ast_datastore *stack_store; + AST_LIST_HEAD(, gosub_stack_frame) *oldlist; + struct gosub_stack_frame *frame; + int n; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(n); + AST_APP_ARG(which); + AST_APP_ARG(suppress); + ); + + if (!chan) { + ast_log(LOG_ERROR, "STACK_PEEK must be called on an active channel\n"); + return -1; + } + + data = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, data); + + n = atoi(args.n); + if (n <= 0) { + ast_log(LOG_ERROR, "STACK_PEEK must be called with a positive peek value\n"); + return -1; + } + + ast_channel_lock(chan); + if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) { + if (!ast_true(args.suppress)) { + ast_log(LOG_ERROR, "STACK_PEEK called on a channel without a gosub stack\n"); + } + ast_channel_unlock(chan); + return -1; + } + + oldlist = stack_store->data; + + AST_LIST_LOCK(oldlist); + AST_LIST_TRAVERSE(oldlist, frame, entries) { + if (--n == 0) { + break; + } + } + + if (!frame) { + /* Too deep */ + if (!ast_true(args.suppress)) { + ast_log(LOG_ERROR, "Stack peek of '%s' is more stack frames than I have\n", args.n); + } + ast_channel_unlock(chan); + return -1; + } + + args.which = ast_skip_blanks(args.which); + + switch (args.which[0]) { + case 'l': /* label */ + ast_str_set(str, len, "%s,%s,%d", frame->context, frame->extension, frame->priority - 1); + break; + case 'c': /* context */ + ast_str_set(str, len, "%s", frame->context); + break; + case 'e': /* extension */ + ast_str_set(str, len, "%s", frame->extension); + break; + case 'p': /* priority */ + ast_str_set(str, len, "%d", frame->priority - 1); + break; + default: + ast_log(LOG_ERROR, "Unknown argument '%s' to STACK_PEEK\n", args.which); + } + + AST_LIST_UNLOCK(oldlist); + ast_channel_unlock(chan); + + return 0; +} + +static struct ast_custom_function stackpeek_function = { + .name = "STACK_PEEK", + .read2 = stackpeek_read, +}; + static int handle_gosub(struct ast_channel *chan, AGI *agi, int argc, const char * const *argv) { int old_priority, priority; @@ -687,6 +809,7 @@ static int unload_module(void) ast_unregister_application(app_gosub); ast_custom_function_unregister(&local_function); ast_custom_function_unregister(&peek_function); + ast_custom_function_unregister(&stackpeek_function); return 0; } @@ -701,12 +824,14 @@ static int load_module(void) ast_register_application_xml(app_gosub, gosub_exec); ast_custom_function_register(&local_function); ast_custom_function_register(&peek_function); + ast_custom_function_register(&stackpeek_function); return 0; } -AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Dialplan subroutines (Gosub, Return, etc)", +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT | AST_MODFLAG_LOAD_ORDER, "Dialplan subroutines (Gosub, Return, etc)", .load = load_module, .unload = unload_module, + .load_pri = AST_MODPRI_APP_DEPEND, .nonoptreq = "res_agi", ); diff --git a/funcs/func_dialplan.c b/funcs/func_dialplan.c index 0c2e8c707..d06ddba9a 100644 --- a/funcs/func_dialplan.c +++ b/funcs/func_dialplan.c @@ -185,4 +185,8 @@ static int load_module(void) return res; } -AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan Context/Extension/Priority Checking Functions"); +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Dialplan Context/Extension/Priority Checking Functions", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_APP_DEPEND, + ); diff --git a/res/ael/pval.c b/res/ael/pval.c index 6fe21b5ca..8dd1f7cf6 100644 --- a/res/ael/pval.c +++ b/res/ael/pval.c @@ -4423,6 +4423,20 @@ static void fix_gotos_in_extensions(struct ael_extension *exten) } } +static int context_used(struct ael_extension *exten_list, struct ast_context *context) +{ + struct ael_extension *exten; + /* Check the simple elements first */ + if (ast_walk_context_extensions(context, NULL) || ast_walk_context_includes(context, NULL) || ast_walk_context_ignorepats(context, NULL) || ast_walk_context_switches(context, NULL)) { + return 1; + } + for (exten = exten_list; exten; exten = exten->next_exten) { + if (exten->context == context) { + return 1; + } + } + return 0; +} int ast_compile_ael2(struct ast_context **local_contexts, struct ast_hashtab *local_table, struct pval *root) { @@ -4604,6 +4618,71 @@ int ast_compile_ael2(struct ast_context **local_contexts, struct ast_hashtab *lo } } + + /* Create default "h" bubble context */ + if (ast_custom_function_find("DIALPLAN_EXISTS") && ast_custom_function_find("STACK_PEEK")) { + int i; + const char *h_context = "ael-builtin-h-bubble"; + struct ael_priority *np; + struct { + int priority; + const char *app; + const char *arg; + } steps[] = { + /* Start high, to avoid conflict with existing h extensions */ + { 1, "Goto", "9991" }, + /* Save the context, because after the StackPop, it disappears */ + { 9991, "Set", "~~parentcxt~~=${STACK_PEEK(1,c,1)}" }, + /* If we're not in a Gosub frame, exit */ + { 9992, "GotoIf", "$[\"${~~parentcxt~~}\"=\"\"]?9996" }, + /* Check for an "h" extension in that context */ + { 9993, "GotoIf", "${DIALPLAN_EXISTS(${~~parentcxt~~},h,1)}?9994:9996" }, + /* Pop off the stack frame to prevent an infinite loop */ + { 9994, "StackPop", "" }, + /* Finally, go there. */ + { 9995, "Goto", "${~~parentcxt~~},h,1" }, + /* Just an empty priority for jumping out early */ + { 9996, "NoOp", "" } + }; + context = ast_context_find_or_create(local_contexts, local_table, h_context, registrar); + if (context_used(exten_list, context)) { + int found = 0; + while (!found) { + /* Pick a new context name that is not used. */ + char h_context_template[] = "/tmp/ael-builtin-h-bubble-XXXXXX"; + int fd = mkstemp(h_context_template); + unlink(h_context_template); + close(fd); + context = ast_context_find_or_create(local_contexts, local_table, h_context_template + 5, registrar); + found = !context_used(exten_list, context); + } + h_context = ast_get_context_name(context); + } + exten = new_exten(); + exten->context = context; + exten->name = strdup("h"); + + for (i = 0; i < ARRAY_LEN(steps); i++) { + np = new_prio(); + np->type = AEL_APPCALL; + np->priority_num = steps[i].priority; + np->app = strdup(steps[i].app); + np->appargs = strdup(steps[i].arg); + linkprio(exten, np, NULL); + } + attach_exten(&exten_list, exten); + + /* Include the default "h" bubble context in each macro context */ + for (exten = exten_list; exten; exten = exten->next_exten) { + /* All macros contain a "~~s~~" extension, and it's the first created. If + * we perchance get a non-macro context, it's no big deal; the logic is + * designed to exit out smoothly if not called from within a Gosub. */ + if (!strcmp(exten->name, "~~s~~")) { + ast_context_add_include2(exten->context, h_context, registrar); + } + } + } + /* moved these from being done after a macro or extension were processed, to after all processing is done, for the sake of fixing gotos to labels inside cases... */ /* I guess this would be considered 2nd pass of compiler now... */ diff --git a/tests/test_gosub.c b/tests/test_gosub.c index e65ebbcbb..36573faf2 100644 --- a/tests/test_gosub.c +++ b/tests/test_gosub.c @@ -51,12 +51,24 @@ AST_TEST_DEFINE(test_gosub) const char *args; const char *expected_value; } testplan[] = { + { NULL, "${STACK_PEEK(1,e,1)}", "" }, /* Stack is empty */ { "Gosub", "tests_test_gosub_virtual_context,s,1" }, + { NULL, "${PRIORITY}", "1" }, { NULL, "${EXTEN}", "s" }, + { NULL, "${STACK_PEEK(1,e,1)}", "" }, /* No extension originally */ { "Gosub", "test,dne,1", (const char *) -1 }, /* This is the only invocation that should fail. */ + { NULL, "${PRIORITY}", "1" }, { NULL, "${EXTEN}", "s" }, { "Gosub", "tests_test_gosub_virtual_context,s,1(5,5,5,5,5)" }, + { NULL, "${PRIORITY}", "1" }, { NULL, "$[0${ARG1} + 0${ARG5}]", "10" }, + { NULL, "${STACK_PEEK(1,e)}", "s" }, + { NULL, "${STACK_PEEK(1,c)}", "tests_test_gosub_virtual_context" }, + { NULL, "${STACK_PEEK(1,p)}", "1" }, + { NULL, "${STACK_PEEK(1,l)}", "tests_test_gosub_virtual_context,s,1" }, + { "StackPop", "" }, + { NULL, "${STACK_PEEK(1,e,1)}", "" }, /* Only 1 frame deep, my caller is top-level */ + { "Gosub", "tests_test_gosub_virtual_context,s,1(5,5,5,5,5)" }, { "Gosub", "tests_test_gosub_virtual_context,s,1(4,4,4,4)" }, { NULL, "$[0${ARG1} + 0${ARG5}]", "4" }, { NULL, "$[0${ARG1} + 0${ARG4}]", "8" }, diff --git a/utils/ael_main.c b/utils/ael_main.c index d3a23382a..fff28b738 100644 --- a/utils/ael_main.c +++ b/utils/ael_main.c @@ -425,6 +425,36 @@ void ast_context_destroy(void) printf("Executed ast_context_destroy();\n"); } +const char *ast_get_context_name(struct ast_context *con); +const char *ast_get_context_name(struct ast_context *con) +{ + return con ? con->name : NULL; +} + +struct ast_exten *ast_walk_context_extensions(struct ast_context *con, struct ast_exten *exten); +struct ast_exten *ast_walk_context_extensions(struct ast_context *con, struct ast_exten *exten) +{ + return NULL; +} + +struct ast_include *ast_walk_context_includes(struct ast_context *con, struct ast_include *inc); +struct ast_include *ast_walk_context_includes(struct ast_context *con, struct ast_include *inc) +{ + return NULL; +} + +struct ast_ignorepat *ast_walk_context_ignorepats(struct ast_context *con, struct ast_ignorepat *ip); +struct ast_ignorepat *ast_walk_context_ignorepats(struct ast_context *con, struct ast_ignorepat *ip) +{ + return NULL; +} + +struct ast_sw *ast_walk_context_switches(struct ast_context *con, struct ast_sw *sw); +struct ast_sw *ast_walk_context_switches(struct ast_context *con, struct ast_sw *sw) +{ + return NULL; +} + void filter_leading_space_from_exprs(char *str) { /* Mainly for aesthetics */ diff --git a/utils/conf2ael.c b/utils/conf2ael.c index 020dbe3af..f0ac38fad 100644 --- a/utils/conf2ael.c +++ b/utils/conf2ael.c @@ -668,6 +668,36 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_ localized_merge_contexts_and_delete(extcontexts, exttable, registrar); } +const char *ast_get_context_name(struct ast_context *con); +const char *ast_get_context_name(struct ast_context *con) +{ + return con ? con->name : NULL; +} + +struct ast_exten *ast_walk_context_extensions(struct ast_context *con, struct ast_exten *exten); +struct ast_exten *ast_walk_context_extensions(struct ast_context *con, struct ast_exten *exten) +{ + return NULL; +} + +struct ast_include *ast_walk_context_includes(struct ast_context *con, struct ast_include *inc); +struct ast_include *ast_walk_context_includes(struct ast_context *con, struct ast_include *inc) +{ + return NULL; +} + +struct ast_ignorepat *ast_walk_context_ignorepats(struct ast_context *con, struct ast_ignorepat *ip); +struct ast_ignorepat *ast_walk_context_ignorepats(struct ast_context *con, struct ast_ignorepat *ip) +{ + return NULL; +} + +struct ast_sw *ast_walk_context_switches(struct ast_context *con, struct ast_sw *sw); +struct ast_sw *ast_walk_context_switches(struct ast_context *con, struct ast_sw *sw) +{ + return NULL; +} + struct ast_exten *pbx_find_extension(struct ast_channel *chan, struct ast_context *bypass, struct pbx_find_info *q, |