diff options
author | Richard Mudgett <rmudgett@digium.com> | 2012-06-01 23:53:59 +0000 |
---|---|---|
committer | Richard Mudgett <rmudgett@digium.com> | 2012-06-01 23:53:59 +0000 |
commit | 91a20ee2f95e2eb8f080bb90568459659960d726 (patch) | |
tree | 46894d256ffc39b60fadd84260e6d67cffead8e4 /apps/app_stack.c | |
parent | dd02d976f59bd85844ea9a4d35d716843aec3923 (diff) |
Fix deadlock when Gosub used with alternate dialplan switches.
Attempting to remove a channel from autoservice with the channel lock held
will result in deadlock.
* Restructured gosub_exec() to not call ast_parseable_goto() and
ast_exists_extension() with the channel lock held.
(closes issue ASTERISK-19764)
Reported by: rmudgett
Tested by: rmudgett
........
Merged revisions 368308 from http://svn.asterisk.org/svn/asterisk/branches/1.8
........
Merged revisions 368310 from http://svn.asterisk.org/svn/asterisk/branches/10
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@368311 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'apps/app_stack.c')
-rw-r--r-- | apps/app_stack.c | 150 |
1 files changed, 97 insertions, 53 deletions
diff --git a/apps/app_stack.c b/apps/app_stack.c index eca3e8398..508a43c4e 100644 --- a/apps/app_stack.c +++ b/apps/app_stack.c @@ -369,10 +369,21 @@ static int return_exec(struct ast_channel *chan, const char *data) 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_frame *newframe, *lastframe; - char argname[15], *tmp = ast_strdupa(data), *label, *endparen; - int i, max_argc = 0; + AST_LIST_HEAD(, gosub_stack_frame) *oldlist; + struct gosub_stack_frame *newframe; + struct gosub_stack_frame *lastframe; + char argname[15]; + char *parse; + char *label; + char *caller_id; + char *orig_context; + char *orig_exten; + char *dest_context; + char *dest_exten; + int orig_priority; + int dest_priority; + int i; + int max_argc = 0; AST_DECLARE_APP_ARGS(args2, AST_APP_ARG(argval)[100]; ); @@ -382,26 +393,84 @@ static int gosub_exec(struct ast_channel *chan, const char *data) return -1; } + /* + * Separate the arguments from the label + * + * NOTE: You cannot use ast_app_separate_args for this, because + * '(' cannot be used as a delimiter. + */ + parse = ast_strdupa(data); + label = strsep(&parse, "("); + if (parse) { + char *endparen; + + endparen = strrchr(parse, ')'); + if (endparen) { + *endparen = '\0'; + } else { + ast_log(LOG_WARNING, "Ouch. No closing paren: '%s'?\n", data); + } + AST_STANDARD_RAW_ARGS(args2, parse); + } else { + args2.argc = 0; + } + + ast_channel_lock(chan); + orig_context = ast_strdupa(ast_channel_context(chan)); + orig_exten = ast_strdupa(ast_channel_exten(chan)); + orig_priority = ast_channel_priority(chan); + ast_channel_unlock(chan); + + if (ast_parseable_goto(chan, label)) { + ast_log(LOG_ERROR, "%s address is invalid: '%s'\n", app_gosub, data); + goto error_exit; + } + + ast_channel_lock(chan); + dest_context = ast_strdupa(ast_channel_context(chan)); + dest_exten = ast_strdupa(ast_channel_exten(chan)); + dest_priority = ast_channel_priority(chan); + if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP)) { + ++dest_priority; + } + caller_id = S_COR(ast_channel_caller(chan)->id.number.valid, + ast_channel_caller(chan)->id.number.str, NULL); + if (caller_id) { + caller_id = ast_strdupa(caller_id); + } + ast_channel_unlock(chan); + + if (!ast_exists_extension(chan, dest_context, dest_exten, dest_priority, caller_id)) { + ast_log(LOG_ERROR, "Attempt to reach a non-existent destination for %s: (Context:%s, Extension:%s, Priority:%d)\n", + app_gosub, dest_context, dest_exten, dest_priority); + goto error_exit; + } + + /* Now we know that we're going to a new location */ + ast_channel_lock(chan); + + /* Find stack datastore return list. */ if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) { - ast_debug(1, "Channel %s has no datastore, so we're allocating one.\n", ast_channel_name(chan)); + 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; + ast_log(LOG_ERROR, "Unable to allocate new datastore. %s failed.\n", + app_gosub); + goto error_exit_locked; } oldlist = ast_calloc(1, sizeof(*oldlist)); if (!oldlist) { - ast_log(LOG_ERROR, "Unable to allocate datastore list head. Gosub will fail.\n"); + ast_log(LOG_ERROR, "Unable to allocate datastore list head. %s failed.\n", + app_gosub); ast_datastore_free(stack_store); - ast_channel_unlock(chan); - return -1; + goto error_exit_locked; } + AST_LIST_HEAD_INIT(oldlist); stack_store->data = oldlist; - AST_LIST_HEAD_INIT(oldlist); ast_channel_datastore_add(chan, stack_store); } else { oldlist = stack_store->data; @@ -411,53 +480,18 @@ static int gosub_exec(struct ast_channel *chan, const char *data) max_argc = lastframe->arguments; } - /* Separate the arguments from the label */ - /* NOTE: you cannot use ast_app_separate_args for this, because '(' cannot be used as a delimiter. */ - label = strsep(&tmp, "("); - if (tmp) { - endparen = strrchr(tmp, ')'); - if (endparen) - *endparen = '\0'; - else - ast_log(LOG_WARNING, "Ouch. No closing paren: '%s'?\n", (char *)data); - AST_STANDARD_RAW_ARGS(args2, tmp); - } else - args2.argc = 0; - - /* Mask out previous arguments in this invocation */ + /* Mask out previous Gosub arguments in this invocation */ if (args2.argc > max_argc) { max_argc = args2.argc; } - /* Create the return address, but don't save it until we know that the Gosub destination exists */ - newframe = gosub_allocate_frame(ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan) + 1, max_argc); - + /* Create the return address */ + newframe = gosub_allocate_frame(orig_context, orig_exten, orig_priority + 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; - } - - if (!ast_exists_extension(chan, ast_channel_context(chan), ast_channel_exten(chan), - ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP) ? ast_channel_priority(chan) + 1 : ast_channel_priority(chan), - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { - ast_log(LOG_ERROR, "Attempt to reach a non-existent destination for gosub: (Context:%s, Extension:%s, Priority:%d)\n", - ast_channel_context(chan), ast_channel_exten(chan), ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP) ? ast_channel_priority(chan) + 1 : ast_channel_priority(chan)); - ast_channel_context_set(chan, newframe->context); - ast_channel_exten_set(chan, newframe->extension); - ast_channel_priority_set(chan, newframe->priority - 1); - ast_free(newframe); - ast_channel_unlock(chan); - return -1; + goto error_exit_locked; } - /* Now that we know for certain that we're going to a new location, set our arguments */ + /* Set our arguments */ for (i = 0; i < max_argc; i++) { snprintf(argname, sizeof(argname), "ARG%d", i + 1); frame_set_var(chan, newframe, argname, i < args2.argc ? args2.argval[i] : ""); @@ -467,13 +501,23 @@ static int gosub_exec(struct ast_channel *chan, const char *data) frame_set_var(chan, newframe, "ARGC", argname); /* And finally, save our return address */ - oldlist = stack_store->data; AST_LIST_LOCK(oldlist); AST_LIST_INSERT_HEAD(oldlist, newframe, entries); AST_LIST_UNLOCK(oldlist); ast_channel_unlock(chan); return 0; + +error_exit: + ast_channel_lock(chan); + +error_exit_locked: + /* Restore the original dialplan location. */ + ast_channel_context_set(chan, orig_context); + ast_channel_exten_set(chan, orig_exten); + ast_channel_priority_set(chan, orig_priority); + ast_channel_unlock(chan); + return -1; } static int gosubif_exec(struct ast_channel *chan, const char *data) |