Hangup handlers - Dialplan subroutines that run when the channel hangs up.
authorRichard Mudgett <rmudgett@digium.com>
Fri, 29 Jun 2012 17:02:32 +0000 (17:02 +0000)
committerRichard Mudgett <rmudgett@digium.com>
Fri, 29 Jun 2012 17:02:32 +0000 (17:02 +0000)
Hangup handlers are an alternative to the h extension.  They can be used
in addition to the h extension.  The idea is to attach a Gosub routine to
a channel that will execute when the call hangs up.  Whereas which h
extension gets executed depends on the location of dialplan execution when
the call hangs up, hangup handlers are attached to the call channel.  You
can attach multiple handlers that will execute in the order of most
recently added first.

(closes issue ASTERISK-19549)
Reported by: Mark Murawski
Tested by: rmudgett

Review: https://reviewboard.asterisk.org/r/2002/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@369493 65c4cc65-6c06-0410-ace0-fbb531ad65f3

14 files changed:
CHANGES
apps/app_dial.c
apps/app_followme.c
apps/app_queue.c
channels/chan_local.c
configs/cdr.conf.sample
funcs/func_channel.c
include/asterisk/channel.h
include/asterisk/pbx.h
main/autoservice.c
main/channel.c
main/channel_internal_api.c
main/features.c
main/pbx.c

diff --git a/CHANGES b/CHANGES
index 5fd87b7..82654e5 100644 (file)
--- 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
 -------------------
index 2812048..ccb7b01 100644 (file)
@@ -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) {
index 275ed43..9b4a925 100644 (file)
@@ -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:
index 4acd799..cee5d44 100644 (file)
@@ -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;
index 695949f..5e72a61 100644 (file)
@@ -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;
index 882f487..458e19a 100644 (file)
@@ -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)
index 14ece0c..699f3b6 100644 (file)
@@ -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;
index 06be07b..4f2cc88 100644 (file)
@@ -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);
index 2305f39..bea7e5e 100644 (file)
@@ -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
index 37bbee6..afbcbd3 100644 (file)
@@ -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;
index 59d40dc..0f4c3bc 100644 (file)
@@ -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);
index 1922db9..b91e2e2 100644 (file)
@@ -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;
index 79d1036..b602dbb 100644 (file)
@@ -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);
index 006549c..db92951 100644 (file)
@@ -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"),