summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES3
-rw-r--r--apps/app_dial.c38
-rw-r--r--apps/app_followme.c4
-rw-r--r--apps/app_queue.c10
-rw-r--r--channels/chan_local.c6
-rw-r--r--configs/cdr.conf.sample5
-rw-r--r--funcs/func_channel.c34
-rw-r--r--include/asterisk/channel.h21
-rw-r--r--include/asterisk/pbx.h65
-rw-r--r--main/autoservice.c10
-rw-r--r--main/channel.c93
-rw-r--r--main/channel_internal_api.c5
-rw-r--r--main/features.c139
-rw-r--r--main/pbx.c382
14 files changed, 630 insertions, 185 deletions
diff --git a/CHANGES b/CHANGES
index 5fd87b7a3..82654e51c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -46,6 +46,9 @@ Core
have their own verbosity level. The command 'core set verbose' will now set
a separate level for each remote console without affecting any other
console.
+ * Hangup handlers can be attached to channels using the CHANNEL(hangup_handler_xxx)
+ options. Hangup handlers will run when the channel is hung up similar to the
+ h extension.
CLI Changes
-------------------
diff --git a/apps/app_dial.c b/apps/app_dial.c
index 281204847..ccb7b018c 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -1881,7 +1881,8 @@ static int do_privacy(struct ast_channel *chan, struct ast_channel *peer,
}
return 0; /* the good exit path */
} else {
- ast_hangup(peer); /* hang up on the callee -- he didn't want to talk anyway! */
+ /* hang up on the callee -- he didn't want to talk anyway! */
+ ast_autoservice_chan_hangup_peer(chan, peer);
return -1;
}
}
@@ -2774,7 +2775,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
if (active_chan) {
struct ast_frame *fr = ast_read(active_chan);
if (!fr) {
- ast_hangup(peer);
+ ast_autoservice_chan_hangup_peer(chan, peer);
res = -1;
goto done;
}
@@ -2790,7 +2791,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
switch (fr->subclass.integer) {
case AST_CONTROL_HANGUP:
ast_frfree(fr);
- ast_hangup(peer);
+ ast_autoservice_chan_hangup_peer(chan, peer);
res = -1;
goto done;
default:
@@ -2821,7 +2822,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
ast_channel_exten_set(peer, ast_channel_exten(chan));
ast_channel_priority_set(peer, ast_channel_priority(chan) + 2);
if (ast_pbx_start(peer)) {
- ast_hangup(peer);
+ ast_autoservice_chan_hangup_peer(chan, peer);
}
hanguptree(&out_chans, NULL, ast_test_flag64(&opts, OPT_CANCEL_ELSEWHERE) ? 1 : 0);
if (continue_exec)
@@ -3027,7 +3028,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
res = ast_channel_make_compatible(chan, peer);
if (res < 0) {
ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", ast_channel_name(chan), ast_channel_name(peer));
- ast_hangup(peer);
+ ast_autoservice_chan_hangup_peer(chan, peer);
res = -1;
goto done;
}
@@ -3047,28 +3048,9 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
if (ast_test_flag64(&opts, OPT_PEER_H)
&& ast_exists_extension(peer, ast_channel_context(peer), "h", 1,
S_COR(ast_channel_caller(peer)->id.number.valid, ast_channel_caller(peer)->id.number.str, NULL))) {
- int autoloopflag;
- int found;
- int res9;
-
- ast_channel_exten_set(peer, "h");
- ast_channel_priority_set(peer, 1);
- autoloopflag = ast_test_flag(ast_channel_flags(peer), AST_FLAG_IN_AUTOLOOP); /* save value to restore at the end */
- ast_set_flag(ast_channel_flags(peer), AST_FLAG_IN_AUTOLOOP);
-
- while ((res9 = ast_spawn_extension(peer, ast_channel_context(peer), ast_channel_exten(peer),
- ast_channel_priority(peer),
- S_COR(ast_channel_caller(peer)->id.number.valid, ast_channel_caller(peer)->id.number.str, NULL),
- &found, 1)) == 0) {
- ast_channel_priority_set(peer, ast_channel_priority(peer) + 1);
- }
-
- if (found && res9) {
- /* 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(peer), ast_channel_exten(peer), ast_channel_priority(peer), ast_channel_name(peer));
- ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", ast_channel_context(peer), ast_channel_exten(peer), ast_channel_priority(peer), ast_channel_name(peer));
- }
- ast_set2_flag(ast_channel_flags(peer), autoloopflag, AST_FLAG_IN_AUTOLOOP); /* set it back the way it was */
+ ast_autoservice_start(chan);
+ ast_pbx_h_exten_run(peer, ast_channel_context(peer));
+ ast_autoservice_stop(chan);
}
if (!ast_check_hangup(peer)) {
if (ast_test_flag64(&opts, OPT_CALLEE_GO_ON)) {
@@ -3089,7 +3071,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
ast_channel_hangupcause_set(chan, ast_channel_hangupcause(peer));
}
}
- ast_hangup(peer);
+ ast_autoservice_chan_hangup_peer(chan, peer);
}
out:
if (moh) {
diff --git a/apps/app_followme.c b/apps/app_followme.c
index 275ed43c5..9b4a925da 100644
--- a/apps/app_followme.c
+++ b/apps/app_followme.c
@@ -1490,7 +1490,7 @@ static int app_exec(struct ast_channel *chan, const char *data)
res = ast_channel_make_compatible(caller, outbound);
if (res < 0) {
ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", ast_channel_name(caller), ast_channel_name(outbound));
- ast_hangup(outbound);
+ ast_autoservice_chan_hangup_peer(caller, outbound);
goto outrun;
}
@@ -1513,7 +1513,7 @@ static int app_exec(struct ast_channel *chan, const char *data)
}
res = ast_bridge_call(caller, outbound, &config);
- ast_hangup(outbound);
+ ast_autoservice_chan_hangup_peer(caller, outbound);
}
outrun:
diff --git a/apps/app_queue.c b/apps/app_queue.c
index 4acd799f1..cee5d449f 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -5266,7 +5266,7 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
"%s",
queuename, ast_channel_uniqueid(qe->chan), ast_channel_name(peer), member->interface, member->membername,
qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
- ast_hangup(peer);
+ ast_autoservice_chan_hangup_peer(qe->chan, peer);
ao2_ref(member, -1);
goto out;
} else if (ast_check_hangup(qe->chan)) {
@@ -5274,7 +5274,7 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
ast_log(LOG_NOTICE, "Caller was about to talk to agent on %s but the caller hungup.\n", ast_channel_name(peer));
ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "ABANDON", "%d|%d|%ld", qe->pos, qe->opos, (long) time(NULL) - qe->start);
record_abandoned(qe);
- ast_hangup(peer);
+ ast_autoservice_chan_hangup_peer(qe->chan, peer);
ao2_ref(member, -1);
return -1;
}
@@ -5296,7 +5296,7 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", ast_channel_name(qe->chan), ast_channel_name(peer));
record_abandoned(qe);
ast_cdr_failed(ast_channel_cdr(qe->chan));
- ast_hangup(peer);
+ ast_autoservice_chan_hangup_peer(qe->chan, peer);
ao2_ref(member, -1);
return -1;
}
@@ -5673,10 +5673,10 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
caller_priority + 1);
}
if (goto_res || ast_pbx_start(peer)) {
- ast_hangup(peer);
+ ast_autoservice_chan_hangup_peer(qe->chan, peer);
}
} else {
- ast_hangup(peer);
+ ast_autoservice_chan_hangup_peer(qe->chan, peer);
}
res = bridge ? bridge : 1;
diff --git a/channels/chan_local.c b/channels/chan_local.c
index 695949fdc..5e72a6153 100644
--- a/channels/chan_local.c
+++ b/channels/chan_local.c
@@ -238,6 +238,12 @@ static int local_setoption(struct ast_channel *ast, int option, void * data, int
return -1;
}
+ if (!strcmp(write_info->function, "CHANNEL")
+ && !strncasecmp(write_info->data, "hangup_handler_", 15)) {
+ /* Block CHANNEL(hangup_handler_xxx) writes to the other local channel. */
+ return 0;
+ }
+
/* get the tech pvt */
if (!(p = ast_channel_tech_pvt(ast))) {
return -1;
diff --git a/configs/cdr.conf.sample b/configs/cdr.conf.sample
index 882f48735..458e19ab4 100644
--- a/configs/cdr.conf.sample
+++ b/configs/cdr.conf.sample
@@ -36,8 +36,9 @@
; Normally, CDR's are not closed out until after all extensions are finished
; executing. By enabling this option, the CDR will be ended before executing
-; the "h" extension so that CDR values such as "end" and "billsec" may be
-; retrieved inside of of this extension. The default value is "no".
+; the "h" extension and hangup handlers so that CDR values such as "end" and
+; "billsec" may be retrieved inside of of this extension.
+; The default value is "no".
;endbeforehexten=no
; Normally, the 'billsec' field logged to the backends (text files or databases)
diff --git a/funcs/func_channel.c b/funcs/func_channel.c
index 14ece0cfd..699f3b660 100644
--- a/funcs/func_channel.c
+++ b/funcs/func_channel.c
@@ -98,6 +98,29 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<enum name="checkhangup">
<para>R/O Whether the channel is hanging up (1/0)</para>
</enum>
+ <enum name="hangup_handler_pop">
+ <para>W/O Replace the most recently added hangup handler
+ with a new hangup handler on the channel if supplied. The
+ assigned string is passed to the Gosub application when
+ the channel is hung up. Any optionally omitted context
+ and exten are supplied by the channel pushing the handler
+ before it is pushed.</para>
+ </enum>
+ <enum name="hangup_handler_push">
+ <para>W/O Push a hangup handler onto the channel hangup
+ handler stack. The assigned string is passed to the
+ Gosub application when the channel is hung up. Any
+ optionally omitted context and exten are supplied by the
+ channel pushing the handler before it is pushed.</para>
+ </enum>
+ <enum name="hangup_handler_wipe">
+ <para>W/O Wipe the entire hangup handler stack and replace
+ with a new hangup handler on the channel if supplied. The
+ assigned string is passed to the Gosub application when
+ the channel is hung up. Any optionally omitted context
+ and exten are supplied by the channel pushing the handler
+ before it is pushed.</para>
+ </enum>
<enum name="language">
<para>R/W language for sounds played.</para>
</enum>
@@ -523,6 +546,17 @@ static int func_channel_write_real(struct ast_channel *chan, const char *functio
break;
}
}
+ } else if (!strcasecmp(data, "hangup_handler_pop")) {
+ /* Pop one hangup handler before pushing the new handler. */
+ ast_pbx_hangup_handler_pop(chan);
+ ast_pbx_hangup_handler_push(chan, value);
+ } else if (!strcasecmp(data, "hangup_handler_push")) {
+ ast_pbx_hangup_handler_push(chan, value);
+ } else if (!strcasecmp(data, "hangup_handler_wipe")) {
+ /* Pop all hangup handlers before pushing the new handler. */
+ while (ast_pbx_hangup_handler_pop(chan)) {
+ }
+ ast_pbx_hangup_handler_push(chan, value);
} else if (!strncasecmp(data, "secure_bridge_", 14)) {
struct ast_datastore *ds;
struct ast_secure_call_store *store;
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 06be07b2a..4f2cc8849 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -731,6 +731,15 @@ enum ast_t38_state {
T38_STATE_NEGOTIATED, /*!< T38 established */
};
+/*! Hangup handler instance node. */
+struct ast_hangup_handler {
+ /*! Next hangup handler node. */
+ AST_LIST_ENTRY(ast_hangup_handler) node;
+ /*! Hangup handler arg string passed to the Gosub application */
+ char args[0];
+};
+
+AST_LIST_HEAD_NOLOCK(ast_hangup_handler_list, ast_hangup_handler);
AST_LIST_HEAD_NOLOCK(ast_datastore_list, ast_datastore);
AST_LIST_HEAD_NOLOCK(ast_autochan_list, ast_autochan);
AST_LIST_HEAD_NOLOCK(ast_readq_list, ast_frame);
@@ -2198,6 +2207,17 @@ int ast_autoservice_start(struct ast_channel *chan);
int ast_autoservice_stop(struct ast_channel *chan);
/*!
+ * \brief Put chan into autoservice while hanging up peer.
+ * \since 11.0
+ *
+ * \param chan Chan to put into autoservice.
+ * \param peer Chan to run hangup handlers and hangup.
+ *
+ * \return Nothing
+ */
+void ast_autoservice_chan_hangup_peer(struct ast_channel *chan, struct ast_channel *peer);
+
+/*!
* \brief Ignore certain frame types
* \note Normally, we cache DTMF, IMAGE, HTML, TEXT, and CONTROL frames
* while a channel is in autoservice and queue them up when taken out of
@@ -3748,6 +3768,7 @@ void ast_channel_whentohangup_set(struct ast_channel *chan, struct timeval *valu
void ast_channel_varshead_set(struct ast_channel *chan, struct varshead *value);
/* List getters */
+struct ast_hangup_handler_list *ast_channel_hangup_handlers(struct ast_channel *chan);
struct ast_datastore_list *ast_channel_datastores(struct ast_channel *chan);
struct ast_autochan_list *ast_channel_autochans(struct ast_channel *chan);
struct ast_readq_list *ast_channel_readq(struct ast_channel *chan);
diff --git a/include/asterisk/pbx.h b/include/asterisk/pbx.h
index 2305f3910..bea7e5ebf 100644
--- a/include/asterisk/pbx.h
+++ b/include/asterisk/pbx.h
@@ -368,6 +368,71 @@ struct ast_pbx_args {
enum ast_pbx_result ast_pbx_run_args(struct ast_channel *c, struct ast_pbx_args *args);
/*!
+ * \brief Run the h exten from the given context.
+ * \since 11.0
+ *
+ * \param chan Channel to run the h exten on.
+ * \param context Context the h exten is in.
+ *
+ * \return Nothing
+ */
+void ast_pbx_h_exten_run(struct ast_channel *chan, const char *context);
+
+/*!
+ * \brief Run all hangup handlers on the channel.
+ * \since 11.0
+ *
+ * \param chan Channel to run the hangup handlers on.
+ *
+ * \note Absolutely _NO_ channel locks should be held before calling this function.
+ *
+ * \retval Zero if no hangup handlers run.
+ * \retval non-zero if hangup handlers were run.
+ */
+int ast_pbx_hangup_handler_run(struct ast_channel *chan);
+
+/*!
+ * \brief Init the hangup handler container on a channel.
+ * \since 11.0
+ *
+ * \param chan Channel to init the hangup handler container on.
+ *
+ * \return Nothing
+ */
+void ast_pbx_hangup_handler_init(struct ast_channel *chan);
+
+/*!
+ * \brief Destroy the hangup handler container on a channel.
+ * \since 11.0
+ *
+ * \param chan Channel to destroy the hangup handler container on.
+ *
+ * \return Nothing
+ */
+void ast_pbx_hangup_handler_destroy(struct ast_channel *chan);
+
+/*!
+ * \brief Pop the top of the channel hangup handler stack.
+ * \since 11.0
+ *
+ * \param chan Channel to push the hangup handler onto.
+ *
+ * \retval TRUE if a handler was popped off of the stack.
+ */
+int ast_pbx_hangup_handler_pop(struct ast_channel *chan);
+
+/*!
+ * \brief Push the given hangup handler onto the channel hangup handler stack.
+ * \since 11.0
+ *
+ * \param chan Channel to push the hangup handler onto.
+ * \param handler Gosub application parameter string.
+ *
+ * \return Nothing
+ */
+void ast_pbx_hangup_handler_push(struct ast_channel *chan, const char *handler);
+
+/*!
* \brief Add and extension to an extension context.
*
* \param context context to add the extension to
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"),