diff options
Diffstat (limited to 'main')
-rw-r--r-- | main/autoservice.c | 10 | ||||
-rw-r--r-- | main/channel.c | 93 | ||||
-rw-r--r-- | main/channel_internal_api.c | 5 | ||||
-rw-r--r-- | main/features.c | 139 | ||||
-rw-r--r-- | main/pbx.c | 382 |
5 files changed, 481 insertions, 148 deletions
diff --git a/main/autoservice.c b/main/autoservice.c index 37bbee682..afbcbd3e0 100644 --- a/main/autoservice.c +++ b/main/autoservice.c @@ -313,6 +313,16 @@ int ast_autoservice_stop(struct ast_channel *chan) return res; } +void ast_autoservice_chan_hangup_peer(struct ast_channel *chan, struct ast_channel *peer) +{ + if (chan && !ast_autoservice_start(chan)) { + ast_hangup(peer); + ast_autoservice_stop(chan); + } else { + ast_hangup(peer); + } +} + int ast_autoservice_ignore(struct ast_channel *chan, enum ast_frame_type ftype) { struct asent *as; diff --git a/main/channel.c b/main/channel.c index 59d40dc6c..0f4c3bc77 100644 --- a/main/channel.c +++ b/main/channel.c @@ -1107,6 +1107,7 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char headp = ast_channel_varshead(tmp); AST_LIST_HEAD_INIT_NOLOCK(headp); + ast_pbx_hangup_handler_init(tmp); AST_LIST_HEAD_INIT_NOLOCK(ast_channel_datastores(tmp)); AST_LIST_HEAD_INIT_NOLOCK(ast_channel_autochans(tmp)); @@ -1183,6 +1184,9 @@ struct ast_channel *ast_dummy_channel_alloc(void) return NULL; } + ast_pbx_hangup_handler_init(tmp); + AST_LIST_HEAD_INIT_NOLOCK(ast_channel_datastores(tmp)); + headp = ast_channel_varshead(tmp); AST_LIST_HEAD_INIT_NOLOCK(headp); @@ -2198,8 +2202,11 @@ static void ast_channel_destructor(void *obj) ast_cel_check_retire_linkedid(chan); } - /* Get rid of each of the data stores on the channel */ + ast_pbx_hangup_handler_destroy(chan); + ast_channel_lock(chan); + + /* Get rid of each of the data stores on the channel */ while ((datastore = AST_LIST_REMOVE_HEAD(ast_channel_datastores(chan), entry))) /* Free the data store */ ast_datastore_free(datastore); @@ -2316,10 +2323,17 @@ static void ast_channel_destructor(void *obj) static void ast_dummy_channel_destructor(void *obj) { struct ast_channel *chan = obj; + struct ast_datastore *datastore; struct ast_var_t *vardata; struct varshead *headp; - headp = ast_channel_varshead(chan); + ast_pbx_hangup_handler_destroy(chan); + + /* Get rid of each of the data stores on the channel */ + while ((datastore = AST_LIST_REMOVE_HEAD(ast_channel_datastores(chan), entry))) { + /* Free the data store */ + ast_datastore_free(datastore); + } ast_party_dialed_free(ast_channel_dialed(chan)); ast_party_caller_free(ast_channel_caller(chan)); @@ -2328,6 +2342,7 @@ static void ast_dummy_channel_destructor(void *obj) /* loop over the variables list, freeing all data and deleting list items */ /* no need to lock the list, as the channel is already locked */ + headp = ast_channel_varshead(chan); while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) ast_var_delete(vardata); @@ -2599,7 +2614,6 @@ static void destroy_hooks(struct ast_channel *chan) int ast_hangup(struct ast_channel *chan) { char extra_str[64]; /* used for cel logging below */ - int was_zombie; ast_autoservice_stop(chan); @@ -2632,11 +2646,20 @@ int ast_hangup(struct ast_channel *chan) } /* Mark as a zombie so a masquerade cannot be setup on this channel. */ - if (!(was_zombie = ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE))) { - ast_set_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE); - } + ast_set_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE); ast_channel_unlock(chan); + + /* + * XXX if running the hangup handlers here causes problems + * because the handlers take too long to execute, we could move + * the meat of this function into another thread. A thread + * where channels go to die. + * + * If this is done, ast_autoservice_chan_hangup_peer() will no + * longer be needed. + */ + ast_pbx_hangup_handler_run(chan); ao2_unlink(channels, chan); ast_channel_lock(chan); @@ -2675,14 +2698,10 @@ int ast_hangup(struct ast_channel *chan) (long) pthread_self(), ast_channel_name(chan), (long)ast_channel_blocker(chan), ast_channel_blockproc(chan)); ast_assert(ast_test_flag(ast_channel_flags(chan), AST_FLAG_BLOCKING) == 0); } - if (!was_zombie) { - ast_debug(1, "Hanging up channel '%s'\n", ast_channel_name(chan)); - if (ast_channel_tech(chan)->hangup) { - ast_channel_tech(chan)->hangup(chan); - } - } else { - ast_debug(1, "Hanging up zombie '%s'\n", ast_channel_name(chan)); + ast_debug(1, "Hanging up channel '%s'\n", ast_channel_name(chan)); + if (ast_channel_tech(chan)->hangup) { + ast_channel_tech(chan)->hangup(chan); } ast_channel_unlock(chan); @@ -6535,6 +6554,7 @@ int ast_do_masquerade(struct ast_channel *original) const struct ast_channel_tech *t; void *t_pvt; union { + struct ast_hangup_handler_list handlers; struct ast_party_dialed dialed; struct ast_party_caller caller; struct ast_party_connected_line connected; @@ -6758,6 +6778,11 @@ int ast_do_masquerade(struct ast_channel *original) ast_app_group_update(clonechan, original); + /* Swap hangup handlers. */ + exchange.handlers = *ast_channel_hangup_handlers(original); + *ast_channel_hangup_handlers(original) = *ast_channel_hangup_handlers(clonechan); + *ast_channel_hangup_handlers(clonechan) = exchange.handlers; + /* Move data stores over */ if (AST_LIST_FIRST(ast_channel_datastores(clonechan))) { struct ast_datastore *ds; @@ -6878,19 +6903,6 @@ int ast_do_masquerade(struct ast_channel *original) * container lock. */ ast_channel_unlock(original); - - /* Disconnect the original original's physical side */ - if (ast_channel_tech(clonechan)->hangup && ast_channel_tech(clonechan)->hangup(clonechan)) { - ast_log(LOG_WARNING, "Hangup failed! Strange things may happen!\n"); - } else { - /* - * We just hung up the original original's physical side of the - * channel. Set the new zombie to use the kill channel driver - * for safety. - */ - ast_channel_tech_set(clonechan, &ast_kill_tech); - } - ast_channel_unlock(clonechan); /* @@ -6938,33 +6950,18 @@ int ast_do_masquerade(struct ast_channel *original) ast_datastore_free(xfer_ds); } - if (clone_was_zombie) { - ast_channel_lock(clonechan); - ast_debug(1, "Destroying channel clone '%s'\n", ast_channel_name(clonechan)); - ast_manager_event(clonechan, EVENT_FLAG_CALL, "Hangup", - "Channel: %s\r\n" - "Uniqueid: %s\r\n" - "Cause: %d\r\n" - "Cause-txt: %s\r\n", - ast_channel_name(clonechan), - ast_channel_uniqueid(clonechan), - ast_channel_hangupcause(clonechan), - ast_cause2str(ast_channel_hangupcause(clonechan)) - ); - ast_channel_unlock(clonechan); - - /* - * Drop the system reference to destroy the channel since it is - * already unlinked. - */ - ast_channel_unref(clonechan); - } else { + if (!clone_was_zombie) { ao2_link(channels, clonechan); } - ao2_link(channels, original); ao2_unlock(channels); + if (clone_was_zombie) { + /* Restart the ast_hangup() that was deferred because of this masquerade. */ + ast_debug(1, "Destroying channel clone '%s'\n", ast_channel_name(clonechan)); + ast_hangup(clonechan); + } + /* Release our held safety references. */ ast_channel_unref(original); ast_channel_unref(clonechan); diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c index 1922db9a9..b91e2e253 100644 --- a/main/channel_internal_api.c +++ b/main/channel_internal_api.c @@ -137,6 +137,7 @@ struct ast_channel { struct ast_readq_list readq; struct ast_jb jb; /*!< The jitterbuffer state */ struct timeval dtmf_tv; /*!< The time that an in process digit began, or the last digit ended */ + struct ast_hangup_handler_list hangup_handlers;/*!< Hangup handlers on the channel. */ struct ast_datastore_list datastores; /*!< Data stores on the channel */ struct ast_autochan_list autochans; /*!< Autochans on the channel */ unsigned long insmpl; /*!< Track the read/written samples for monitor use */ @@ -884,6 +885,10 @@ struct ast_format *ast_channel_writeformat(struct ast_channel *chan) { return &chan->writeformat; } +struct ast_hangup_handler_list *ast_channel_hangup_handlers(struct ast_channel *chan) +{ + return &chan->hangup_handlers; +} struct ast_datastore_list *ast_channel_datastores(struct ast_channel *chan) { return &chan->datastores; diff --git a/main/features.c b/main/features.c index 79d1036f2..b602dbb98 100644 --- a/main/features.c +++ b/main/features.c @@ -1077,10 +1077,10 @@ static void *bridge_call_thread(void *data) ast_log(LOG_VERBOSE, "putting peer %s into PBX again\n", ast_channel_name(tobj->peer)); if (ast_pbx_start(tobj->peer)) { ast_log(LOG_WARNING, "FAILED continuing PBX on peer %s\n", ast_channel_name(tobj->peer)); - ast_hangup(tobj->peer); + ast_autoservice_chan_hangup_peer(tobj->chan, tobj->peer); } } else { - ast_hangup(tobj->peer); + ast_autoservice_chan_hangup_peer(tobj->chan, tobj->peer); } if (!ast_check_hangup(tobj->chan)) { ast_log(LOG_VERBOSE, "putting chan %s into PBX again\n", ast_channel_name(tobj->chan)); @@ -2526,7 +2526,7 @@ static int check_compat(struct ast_channel *c, struct ast_channel *newchan) if (ast_channel_make_compatible(c, newchan) < 0) { ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", ast_channel_name(c), ast_channel_name(newchan)); - ast_hangup(newchan); + ast_autoservice_chan_hangup_peer(c, newchan); return -1; } return 0; @@ -2762,7 +2762,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st } if (ast_check_hangup(newchan) || !ast_check_hangup(transferer)) { - ast_hangup(newchan); + ast_autoservice_chan_hangup_peer(transferer, newchan); if (ast_stream_and_wait(transferer, xfersound, "")) { ast_log(LOG_WARNING, "Failed to play transfer sound!\n"); } @@ -2882,7 +2882,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st /* newchan is up, we should prepare transferee and bridge them */ if (ast_check_hangup(newchan)) { - ast_hangup(newchan); + ast_autoservice_chan_hangup_peer(transferee, newchan); ast_party_connected_line_free(&connected_line); return -1; } @@ -2908,7 +2908,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", ast_channel_linkedid(transferee), 0, "Transfered/%s", ast_channel_name(transferee)); if (!xferchan) { - ast_hangup(newchan); + ast_autoservice_chan_hangup_peer(transferee, newchan); ast_party_connected_line_free(&connected_line); return -1; } @@ -2922,7 +2922,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st if (ast_channel_masquerade(xferchan, transferee)) { ast_hangup(xferchan); - ast_hangup(newchan); + ast_autoservice_chan_hangup_peer(transferee, newchan); ast_party_connected_line_free(&connected_line); return -1; } @@ -4169,7 +4169,6 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a int diff; int hasfeatures=0; int hadfeatures=0; - int autoloopflag; int sendingdtmfdigit = 0; int we_disabled_peer_cdr = 0; struct ast_option_header *aoh; @@ -4179,7 +4178,8 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a struct ast_cdr *new_chan_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */ struct ast_cdr *new_peer_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */ struct ast_silence_generator *silgen = NULL; - const char *h_context; + /*! TRUE if h-exten or hangup handlers run. */ + int hangup_run = 0; pbx_builtin_setvar_helper(chan, "BRIDGEPEER", ast_channel_name(peer)); pbx_builtin_setvar_helper(peer, "BRIDGEPEER", ast_channel_name(chan)); @@ -4611,105 +4611,82 @@ before_you_go: config->end_bridge_callback(config->end_bridge_callback_data); } - /* run the hangup exten on the chan object IFF it was NOT involved in a parking situation - * if it were, then chan belongs to a different thread now, and might have been hung up long - * ago. - */ - if (ast_test_flag(&config->features_caller, AST_FEATURE_NO_H_EXTEN)) { - h_context = NULL; - } else if (ast_exists_extension(chan, ast_channel_context(chan), "h", 1, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { - h_context = ast_channel_context(chan); - } else if (!ast_strlen_zero(ast_channel_macrocontext(chan)) - && ast_exists_extension(chan, ast_channel_macrocontext(chan), "h", 1, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { - h_context = ast_channel_macrocontext(chan); - } else { - h_context = NULL; - } - if (h_context) { + if (!ast_test_flag(&config->features_caller, AST_FEATURE_NO_H_EXTEN)) { struct ast_cdr *swapper = NULL; char savelastapp[AST_MAX_EXTENSION]; char savelastdata[AST_MAX_EXTENSION]; char save_context[AST_MAX_CONTEXT]; char save_exten[AST_MAX_EXTENSION]; int save_prio; - int found = 0; /* set if we find at least one match */ - int spawn_error = 0; - /* - * Make sure that the channel is marked as hungup since we are - * going to run the "h" exten on it. - */ - ast_softhangup(chan, AST_SOFTHANGUP_APPUNLOAD); - - autoloopflag = ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP); - ast_set_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP); - if (bridge_cdr && ast_opt_end_cdr_before_h_exten) { - ast_cdr_end(bridge_cdr); - } - - /* swap the bridge cdr and the chan cdr for a moment, and let the endbridge - dialplan code operate on it */ ast_channel_lock(chan); if (bridge_cdr) { + /* + * Swap the bridge_cdr and the chan cdr for a moment, and let + * the hangup dialplan code operate on it. + */ swapper = ast_channel_cdr(chan); + ast_channel_cdr_set(chan, bridge_cdr); + + /* protect the lastapp/lastdata against the effects of the hangup/dialplan code */ ast_copy_string(savelastapp, bridge_cdr->lastapp, sizeof(bridge_cdr->lastapp)); ast_copy_string(savelastdata, bridge_cdr->lastdata, sizeof(bridge_cdr->lastdata)); - ast_channel_cdr_set(chan, bridge_cdr); } ast_copy_string(save_context, ast_channel_context(chan), sizeof(save_context)); ast_copy_string(save_exten, ast_channel_exten(chan), sizeof(save_exten)); save_prio = ast_channel_priority(chan); - if (h_context != ast_channel_context(chan)) { - ast_channel_context_set(chan, h_context); - } - ast_channel_exten_set(chan, "h"); - ast_channel_priority_set(chan, 1); ast_channel_unlock(chan); - while ((spawn_error = ast_spawn_extension(chan, ast_channel_context(chan), ast_channel_exten(chan), - ast_channel_priority(chan), - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL), - &found, 1)) == 0) { - ast_channel_priority_set(chan, ast_channel_priority(chan) + 1); - } - if (found && spawn_error) { - /* Something bad happened, or a hangup has been requested. */ - ast_debug(1, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan), ast_channel_name(chan)); - ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan), ast_channel_name(chan)); - } + ast_autoservice_start(peer); + if (ast_exists_extension(chan, ast_channel_context(chan), "h", 1, + S_COR(ast_channel_caller(chan)->id.number.valid, + ast_channel_caller(chan)->id.number.str, NULL))) { + ast_pbx_h_exten_run(chan, ast_channel_context(chan)); + hangup_run = 1; + } else if (!ast_strlen_zero(ast_channel_macrocontext(chan)) + && ast_exists_extension(chan, ast_channel_macrocontext(chan), "h", 1, + S_COR(ast_channel_caller(chan)->id.number.valid, + ast_channel_caller(chan)->id.number.str, NULL))) { + ast_pbx_h_exten_run(chan, ast_channel_macrocontext(chan)); + hangup_run = 1; + } + if (ast_pbx_hangup_handler_run(chan)) { + /* Indicate hangup handlers were run. */ + hangup_run = 1; + } + ast_autoservice_stop(peer); - /* swap it back */ ast_channel_lock(chan); + + /* swap it back */ ast_channel_context_set(chan, save_context); ast_channel_exten_set(chan, save_exten); ast_channel_priority_set(chan, save_prio); if (bridge_cdr) { if (ast_channel_cdr(chan) == bridge_cdr) { ast_channel_cdr_set(chan, swapper); + + /* Restore the lastapp/lastdata */ + ast_copy_string(bridge_cdr->lastapp, savelastapp, sizeof(bridge_cdr->lastapp)); + ast_copy_string(bridge_cdr->lastdata, savelastdata, sizeof(bridge_cdr->lastdata)); } else { bridge_cdr = NULL; } } - /* An "h" exten has been run, so indicate that one has been run. */ - ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_RUN); ast_channel_unlock(chan); - - /* protect the lastapp/lastdata against the effects of the hangup/dialplan code */ - if (bridge_cdr) { - ast_copy_string(bridge_cdr->lastapp, savelastapp, sizeof(bridge_cdr->lastapp)); - ast_copy_string(bridge_cdr->lastdata, savelastdata, sizeof(bridge_cdr->lastdata)); - } - ast_set2_flag(ast_channel_flags(chan), autoloopflag, AST_FLAG_IN_AUTOLOOP); } /* obey the NoCDR() wishes. -- move the DISABLED flag to the bridge CDR if it was set on the channel during the bridge... */ new_chan_cdr = pick_unlocked_cdr(ast_channel_cdr(chan)); /* the proper chan cdr, if there are forked cdrs */ - /* If the channel CDR has been modified during the call, record the changes in the bridge cdr, - * BUT, if we've gone through the h extension block above, the CDR got swapped so don't overwrite - * what was done in the h extension. What a mess. This is why you never touch CDR code. */ - if (new_chan_cdr && bridge_cdr && !h_context) { + + /* + * If the channel CDR has been modified during the call, record + * the changes in the bridge cdr, BUT, if hangup_run, the CDR + * got swapped so don't overwrite what was done in the + * h-extension or hangup handlers. What a mess. This is why + * you never touch CDR code. + */ + if (new_chan_cdr && bridge_cdr && !hangup_run) { ast_cdr_copy_vars(bridge_cdr, new_chan_cdr); ast_copy_string(bridge_cdr->userfield, new_chan_cdr->userfield, sizeof(bridge_cdr->userfield)); bridge_cdr->amaflags = new_chan_cdr->amaflags; @@ -5540,7 +5517,7 @@ static int parked_call_exec(struct ast_channel *chan, const char *data) break; } if (res) { - ast_hangup(peer); + ast_autoservice_chan_hangup_peer(chan, peer); parkinglot_unref(parkinglot); return -1; } @@ -5549,7 +5526,7 @@ static int parked_call_exec(struct ast_channel *chan, const char *data) res = ast_channel_make_compatible(chan, peer); if (res < 0) { ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", ast_channel_name(chan), ast_channel_name(peer)); - ast_hangup(peer); + ast_autoservice_chan_hangup_peer(chan, peer); parkinglot_unref(parkinglot); return -1; } @@ -5601,7 +5578,7 @@ static int parked_call_exec(struct ast_channel *chan, const char *data) ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer)); /* Simulate the PBX hanging up */ - ast_hangup(peer); + ast_autoservice_chan_hangup_peer(chan, peer); } else { if (ast_stream_and_wait(chan, "pbx-invalidpark", "")) { ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", "pbx-invalidpark", @@ -8014,7 +7991,7 @@ static int bridge_exec(struct ast_channel *chan, const char *data) "Channel2: %s\r\n", ast_channel_name(chan), ast_channel_name(final_dest_chan)); /* Maybe we should return this channel to the PBX? */ - ast_hangup(final_dest_chan); + ast_autoservice_chan_hangup_peer(chan, final_dest_chan); pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "INCOMPATIBLE"); current_dest_chan = ast_channel_unref(current_dest_chan); @@ -8088,7 +8065,7 @@ static int bridge_exec(struct ast_channel *chan, const char *data) goto_opt = ast_goto_if_exists(final_dest_chan, caller_context, caller_extension, caller_priority + 1); } if (goto_opt || ast_pbx_start(final_dest_chan)) { - ast_hangup(final_dest_chan); + ast_autoservice_chan_hangup_peer(chan, final_dest_chan); } } else if (!ast_test_flag(&opts, OPT_CALLEE_KILL)) { ast_debug(1, "starting new PBX in %s,%s,%d for chan %s\n", @@ -8097,16 +8074,16 @@ static int bridge_exec(struct ast_channel *chan, const char *data) if (ast_pbx_start(final_dest_chan)) { ast_log(LOG_WARNING, "FAILED continuing PBX on dest chan %s\n", ast_channel_name(final_dest_chan)); - ast_hangup(final_dest_chan); + ast_autoservice_chan_hangup_peer(chan, final_dest_chan); } else { ast_debug(1, "SUCCESS continuing PBX on chan %s\n", ast_channel_name(final_dest_chan)); } } else { - ast_hangup(final_dest_chan); + ast_autoservice_chan_hangup_peer(chan, final_dest_chan); } } else { ast_debug(1, "chan %s was hungup\n", ast_channel_name(final_dest_chan)); - ast_hangup(final_dest_chan); + ast_autoservice_chan_hangup_peer(chan, final_dest_chan); } done: ast_free((char *) bconfig.warning_sound); diff --git a/main/pbx.c b/main/pbx.c index 006549c12..db929519e 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -5342,6 +5342,360 @@ int ast_spawn_extension(struct ast_channel *c, const char *context, const char * return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_SPAWN, found, combined_find_spawn); } +void ast_pbx_h_exten_run(struct ast_channel *chan, const char *context) +{ + int autoloopflag; + int found; + int spawn_error; + + ast_channel_lock(chan); + + if (ast_channel_cdr(chan) && ast_opt_end_cdr_before_h_exten) { + ast_cdr_end(ast_channel_cdr(chan)); + } + + /* Set h exten location */ + if (context != ast_channel_context(chan)) { + ast_channel_context_set(chan, context); + } + ast_channel_exten_set(chan, "h"); + ast_channel_priority_set(chan, 1); + + /* + * Make sure that the channel is marked as hungup since we are + * going to run the h exten on it. + */ + ast_softhangup_nolock(chan, AST_SOFTHANGUP_APPUNLOAD); + + /* Save autoloop flag */ + autoloopflag = ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP); + ast_set_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP); + ast_channel_unlock(chan); + + for (;;) { + spawn_error = ast_spawn_extension(chan, ast_channel_context(chan), + ast_channel_exten(chan), ast_channel_priority(chan), + S_COR(ast_channel_caller(chan)->id.number.valid, + ast_channel_caller(chan)->id.number.str, NULL), &found, 1); + + ast_channel_lock(chan); + if (spawn_error) { + /* The code after the loop needs the channel locked. */ + break; + } + ast_channel_priority_set(chan, ast_channel_priority(chan) + 1); + ast_channel_unlock(chan); + } + if (found && spawn_error) { + /* Something bad happened, or a hangup has been requested. */ + ast_debug(1, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", + ast_channel_context(chan), ast_channel_exten(chan), + ast_channel_priority(chan), ast_channel_name(chan)); + ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", + ast_channel_context(chan), ast_channel_exten(chan), + ast_channel_priority(chan), ast_channel_name(chan)); + } + + /* An "h" exten has been run, so indicate that one has been run. */ + ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_RUN); + + /* Restore autoloop flag */ + ast_set2_flag(ast_channel_flags(chan), autoloopflag, AST_FLAG_IN_AUTOLOOP); + ast_channel_unlock(chan); +} + +int ast_pbx_hangup_handler_run(struct ast_channel *chan) +{ + struct ast_hangup_handler_list *handlers; + struct ast_hangup_handler *h_handler; + + ast_channel_lock(chan); + handlers = ast_channel_hangup_handlers(chan); + if (AST_LIST_EMPTY(handlers)) { + ast_channel_unlock(chan); + return 0; + } + + if (ast_channel_cdr(chan) && ast_opt_end_cdr_before_h_exten) { + ast_cdr_end(ast_channel_cdr(chan)); + } + + /* + * Make sure that the channel is marked as hungup since we are + * going to run the hangup handlers on it. + */ + ast_softhangup_nolock(chan, AST_SOFTHANGUP_APPUNLOAD); + + for (;;) { + handlers = ast_channel_hangup_handlers(chan); + h_handler = AST_LIST_REMOVE_HEAD(handlers, node); + if (!h_handler) { + break; + } + + /*** DOCUMENTATION + <managerEventInstance> + <synopsis>Raised when a hangup handler is about to be called.</synopsis> + <syntax> + <parameter name="Handler"> + <para>Hangup handler parameter string passed to the Gosub application.</para> + </parameter> + </syntax> + </managerEventInstance> + ***/ + manager_event(EVENT_FLAG_DIALPLAN, "HangupHandlerRun", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Handler: %s\r\n", + ast_channel_name(chan), + ast_channel_uniqueid(chan), + h_handler->args); + ast_channel_unlock(chan); + + ast_app_exec_sub(NULL, chan, h_handler->args, 1); + ast_free(h_handler); + + ast_channel_lock(chan); + } + ast_channel_unlock(chan); + return 1; +} + +void ast_pbx_hangup_handler_init(struct ast_channel *chan) +{ + struct ast_hangup_handler_list *handlers; + + handlers = ast_channel_hangup_handlers(chan); + AST_LIST_HEAD_INIT_NOLOCK(handlers); +} + +void ast_pbx_hangup_handler_destroy(struct ast_channel *chan) +{ + struct ast_hangup_handler_list *handlers; + struct ast_hangup_handler *h_handler; + + ast_channel_lock(chan); + + /* Get rid of each of the hangup handlers on the channel */ + handlers = ast_channel_hangup_handlers(chan); + while ((h_handler = AST_LIST_REMOVE_HEAD(handlers, node))) { + ast_free(h_handler); + } + + ast_channel_unlock(chan); +} + +int ast_pbx_hangup_handler_pop(struct ast_channel *chan) +{ + struct ast_hangup_handler_list *handlers; + struct ast_hangup_handler *h_handler; + + ast_channel_lock(chan); + handlers = ast_channel_hangup_handlers(chan); + h_handler = AST_LIST_REMOVE_HEAD(handlers, node); + if (h_handler) { + /*** DOCUMENTATION + <managerEventInstance> + <synopsis> + Raised when a hangup handler is removed from the handler + stack by the CHANNEL() function. + </synopsis> + <syntax> + <parameter name="Handler"> + <para>Hangup handler parameter string passed to the Gosub application.</para> + </parameter> + </syntax> + <see-also> + <ref type="managerEvent">HangupHandlerPush</ref> + <ref type="function">CHANNEL</ref> + </see-also> + </managerEventInstance> + ***/ + manager_event(EVENT_FLAG_DIALPLAN, "HangupHandlerPop", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Handler: %s\r\n", + ast_channel_name(chan), + ast_channel_uniqueid(chan), + h_handler->args); + } + ast_channel_unlock(chan); + if (h_handler) { + ast_free(h_handler); + return 1; + } + return 0; +} + +void ast_pbx_hangup_handler_push(struct ast_channel *chan, const char *handler) +{ + struct ast_hangup_handler_list *handlers; + struct ast_hangup_handler *h_handler; + const char *expanded_handler; + + if (ast_strlen_zero(handler)) { + return; + } + + expanded_handler = ast_app_expand_sub_args(chan, handler); + if (!expanded_handler) { + return; + } + h_handler = ast_malloc(sizeof(*h_handler) + 1 + strlen(expanded_handler)); + if (!h_handler) { + ast_free((char *) expanded_handler); + return; + } + strcpy(h_handler->args, expanded_handler);/* Safe */ + ast_free((char *) expanded_handler); + + ast_channel_lock(chan); + + handlers = ast_channel_hangup_handlers(chan); + AST_LIST_INSERT_HEAD(handlers, h_handler, node); + + /*** DOCUMENTATION + <managerEventInstance> + <synopsis> + Raised when a hangup handler is added to the handler + stack by the CHANNEL() function. + </synopsis> + <syntax> + <parameter name="Handler"> + <para>Hangup handler parameter string passed to the Gosub application.</para> + </parameter> + </syntax> + <see-also> + <ref type="managerEvent">HangupHandlerPop</ref> + <ref type="function">CHANNEL</ref> + </see-also> + </managerEventInstance> + ***/ + manager_event(EVENT_FLAG_DIALPLAN, "HangupHandlerPush", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Handler: %s\r\n", + ast_channel_name(chan), + ast_channel_uniqueid(chan), + h_handler->args); + + ast_channel_unlock(chan); +} + +#define HANDLER_FORMAT "%-30s %s\n" + +/*! + * \internal + * \brief CLI output the hangup handler headers. + * \since 11.0 + * + * \param fd CLI file descriptor to use. + * + * \return Nothing + */ +static void ast_pbx_hangup_handler_headers(int fd) +{ + ast_cli(fd, HANDLER_FORMAT, "Channel", "Handler"); +} + +/*! + * \internal + * \brief CLI output the channel hangup handlers. + * \since 11.0 + * + * \param fd CLI file descriptor to use. + * \param chan Channel to show hangup handlers. + * + * \return Nothing + */ +static void ast_pbx_hangup_handler_show(int fd, struct ast_channel *chan) +{ + struct ast_hangup_handler_list *handlers; + struct ast_hangup_handler *h_handler; + int first = 1; + + ast_channel_lock(chan); + handlers = ast_channel_hangup_handlers(chan); + AST_LIST_TRAVERSE(handlers, h_handler, node) { + ast_cli(fd, HANDLER_FORMAT, first ? ast_channel_name(chan) : "", h_handler->args); + first = 0; + } + ast_channel_unlock(chan); +} + +/* + * \brief 'show hanguphandlers <channel>' CLI command implementation function... + */ +static char *handle_show_hangup_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_channel *chan; + + switch (cmd) { + case CLI_INIT: + e->command = "core show hanguphandlers"; + e->usage = + "Usage: core show hanguphandlers <channel>\n" + " Show hangup handlers of a specified channel.\n"; + return NULL; + case CLI_GENERATE: + return ast_complete_channels(a->line, a->word, a->pos, a->n, e->args); + } + + if (a->argc < 4) { + return CLI_SHOWUSAGE; + } + + chan = ast_channel_get_by_name(a->argv[3]); + if (!chan) { + ast_cli(a->fd, "Channel does not exist.\n"); + return CLI_FAILURE; + } + + ast_pbx_hangup_handler_headers(a->fd); + ast_pbx_hangup_handler_show(a->fd, chan); + + ast_channel_unref(chan); + + return CLI_SUCCESS; +} + +/* + * \brief 'show hanguphandlers all' CLI command implementation function... + */ +static char *handle_show_hangup_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_channel_iterator *iter; + struct ast_channel *chan; + + switch (cmd) { + case CLI_INIT: + e->command = "core show hanguphandlers all"; + e->usage = + "Usage: core show hanguphandlers all\n" + " Show hangup handlers for all channels.\n"; + return NULL; + case CLI_GENERATE: + return ast_complete_channels(a->line, a->word, a->pos, a->n, e->args); + } + + if (a->argc < 4) { + return CLI_SHOWUSAGE; + } + + iter = ast_channel_iterator_all_new(); + if (!iter) { + return CLI_FAILURE; + } + + ast_pbx_hangup_handler_headers(a->fd); + for (; (chan = ast_channel_iterator_next(iter)); ast_channel_unref(chan)) { + ast_pbx_hangup_handler_show(a->fd, chan); + } + ast_channel_iterator_destroy(iter); + + return CLI_SUCCESS; +} + /*! helper function to set extension and priority */ static void set_ext_pri(struct ast_channel *c, const char *exten, int pri) { @@ -5680,27 +6034,15 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c, if (!args || !args->no_hangup_chan) { ast_softhangup(c, AST_SOFTHANGUP_APPUNLOAD); - } - - if ((!args || !args->no_hangup_chan) - && !ast_test_flag(ast_channel_flags(c), AST_FLAG_BRIDGE_HANGUP_RUN) - && ast_exists_extension(c, ast_channel_context(c), "h", 1, - S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) { - set_ext_pri(c, "h", 1); - if (ast_channel_cdr(c) && ast_opt_end_cdr_before_h_exten) { - ast_cdr_end(ast_channel_cdr(c)); - } - while ((res = ast_spawn_extension(c, ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), - S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL), - &found, 1)) == 0) { - ast_channel_priority_set(c, ast_channel_priority(c) + 1); - } - if (found && res) { - /* Something bad happened, or a hangup has been requested. */ - ast_debug(1, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c)); - ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c)); + if (!ast_test_flag(ast_channel_flags(c), AST_FLAG_BRIDGE_HANGUP_RUN) + && ast_exists_extension(c, ast_channel_context(c), "h", 1, + S_COR(ast_channel_caller(c)->id.number.valid, + ast_channel_caller(c)->id.number.str, NULL))) { + ast_pbx_h_exten_run(c, ast_channel_context(c)); } + ast_pbx_hangup_handler_run(c); } + ast_set2_flag(ast_channel_flags(c), autoloopflag, AST_FLAG_IN_AUTOLOOP); ast_clear_flag(ast_channel_flags(c), AST_FLAG_BRIDGE_HANGUP_RUN); /* from one round to the next, make sure this gets cleared */ pbx_destroy(ast_channel_pbx(c)); @@ -7590,6 +7932,8 @@ static struct ast_cli_entry pbx_cli[] = { #endif AST_CLI_DEFINE(handle_show_chanvar, "Show channel variables"), AST_CLI_DEFINE(handle_show_function, "Describe a specific dialplan function"), + AST_CLI_DEFINE(handle_show_hangup_all, "Show hangup handlers of all channels"), + AST_CLI_DEFINE(handle_show_hangup_channel, "Show hangup handlers of a specified channel"), AST_CLI_DEFINE(handle_show_application, "Describe a specific dialplan application"), AST_CLI_DEFINE(handle_set_global, "Set global dialplan variable"), AST_CLI_DEFINE(handle_set_chanvar, "Set a channel variable"), |