Handle hangup logic in the Stasis message bus and consumers of Stasis messages
authorMatthew Jordan <mjordan@digium.com>
Sun, 7 Jul 2013 20:34:38 +0000 (20:34 +0000)
committerMatthew Jordan <mjordan@digium.com>
Sun, 7 Jul 2013 20:34:38 +0000 (20:34 +0000)
This patch does the following:
* It adds a new soft hangup flag AST_SOFTHANGUP_HANGUP_EXEC that is set when a
  channel is executing dialplan hangup logic, i.e., the 'h' extension or a
  hangup handler. Stasis messages now also convey the soft hangup flag so
  consumers of the messages can know when a channel is executing said
  hangup logic.
* It adds a new channel flag, AST_FLAG_DEAD, which is set when a channel is
  well and truly dead. Not just a zombie, but dead, Jim. Manager, CEL, CDRs,
  and other consumers of Stasis have been updated to look for this flag to
  know when the channel should by lying six feet under.
* The CDR engine has been updated to better handle a channel entering and
  leaving a bridge. Previously, a new CDR was automatically created when a
  channel left a bridge and put into the 'Pending' state; however, this
  way of handling CDRs made it difficult for the 'endbeforehexten' logic to
  work correctly - there was always a new CDR waiting in the hangup logic
  and, even if 'ended', wouldn't be the CDR people wanted to inspect in the
  hangup routine. This patch completely removes the Pending state and instead
  defers creation of the new CDR until it gets a new message that requires
  a new CDR.

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

CHANGES
include/asterisk/cdr.h
include/asterisk/channel.h
include/asterisk/stasis_channels.h
main/cdr.c
main/cel.c
main/channel.c
main/channel_internal_api.c
main/manager_channels.c
main/pbx.c
main/stasis_channels.c

diff --git a/CHANGES b/CHANGES
index 3223822..037001d 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -286,8 +286,11 @@ CDR (Call Detail Records)
  * CDRs will now be created between all participants in a bridge. For each
    pair of channels in a bridge, a CDR is created to represent the path of
    communication between those two endpoints. This lets an end user choose who
-   to bill for what during multi-party bridges or bridge operations during
-   transfer scenarios.
+   to bill for what during bridge operations with multiple parties.
+
+ * The duration, billsec, start, answer, and end times now reflect the times
+   associated with the current CDR for the channel, as opposed to a cumulative
+   measurement of all CDRs for that channel.
 
  * When a CDR is dispatched, user defined CDR variables from both parties are
    included in the resulting CDR. If both parties have the same variable, only
index cd0501f..49acc61 100644 (file)
  *
  * The following transitions can occur while in the Bridge state:
  * \li If a \ref ast_bridge_blob_type message indicating a leave is received,
- * the state transitions to the Pending state
+ * the state transitions to the Finalized state.
  *
  * \par Parked
  *
  *
  * The following transitions can occur while in the Parked state:
  * \li If a \ref ast_bridge_blob_type message indicating a leave is received,
- * the state transitions to the Pending state
- *
- * \par Pending
- *
- * After a channel leaves a bridge, we often don't know what's going to happen
- * to it. It can enter another bridge; it can be hung up; it can continue on
- * in the dialplan. It can even enter into limbo! Pending holds the state of the
- * CDR until we get a subsequent Stasis message telling us what should happen.
- *
- * The following transitions can occur while in the Pending state:
- * \li If a \ref ast_bridge_blob_type message is received, a new CDR is created
- * and it is transitioned to the Bridge state
- * \li If a \ref ast_channel_dial_type indicating a Dial Begin is received, a
- * new CDR is created and it is transitioned to the Dial state
- * \li If a \ref ast_channel_cache_update is received indicating a change in
- * Context/Extension/Priority, a new CDR is created and transitioned to the
- * Single state. If the update indicates that the party has been hung up, the
- * CDR is transitioned to the Finalized state.
- * \li If a \ref ast_bridge_blob_type message indicating an entrance to a
- * holding bridge with a subclass type of "parking" is received, the CDR is
- * transitioned to the Parked state.
+ * the state transitions to the Finalized state
  *
  * \par Finalized
  *
index 0f55491..a5f5210 100644 (file)
@@ -927,6 +927,12 @@ enum {
         * This flag indicates that the channel was originated.
         */
        AST_FLAG_ORIGINATED = (1 << 23),
+       /*!
+        * The channel is well and truly dead. Once this is set and published, no further
+        * actions should be taken upon the channel, and no further publications should
+        * occur.
+        */
+       AST_FLAG_DEAD = (1 << 24),
 };
 
 /*! \brief ast_bridge_config flags */
@@ -1018,8 +1024,12 @@ enum {
         * instead of actually hanging up.
         */
        AST_SOFTHANGUP_UNBRIDGE =  (1 << 6),
-
-
+       /*!
+        * Used to indicate that the channel is currently executing hangup
+        * logic in the dialplan. The channel has been hungup when this is
+        * set.
+        */
+       AST_SOFTHANGUP_HANGUP_EXEC = (1 << 7),
        /*!
         * \brief All softhangup flags.
         *
index 90df75e..e8fc486 100644 (file)
@@ -68,6 +68,7 @@ struct ast_channel_snapshot {
        int hangupcause;                        /*!< Why is the channel hanged up. See causes.h */
        int caller_pres;                        /*!< Caller ID presentation. */
        struct ast_flags flags;                 /*!< channel flags of AST_FLAG_ type */
+       struct ast_flags softhangup_flags;      /*!< softhangup channel flags */
        struct varshead *manager_vars;          /*!< Variables to be appended to manager events */
 };
 
index 80887d1..5fdd60c 100644 (file)
@@ -120,12 +120,17 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        </description>
                                </configOption>
                                <configOption name="endbeforehexten">
-                                       <synopsis>End the CDR before executing the "h" extension</synopsis>
-                                       <description><para>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 <literal>h</literal> extension and hangup handlers so that CDR values such as <literal>end</literal> and
-                                       <literal>"billsec"</literal> may be retrieved inside of this extension.
-                                       The default value is "no".</para>
+                                       <synopsis>Don't produce CDRs while executing hangup logic</synopsis>
+                                       <description>
+                                               <para>As each CDR for a channel is finished, its end time is updated
+                                               and the CDR is finalized. When a channel is hung up and hangup
+                                               logic is present (in the form of a hangup handler or the
+                                               <literal>h</literal> extension), a new CDR is generated for the
+                                               channel. Any statistics are gathered from this new CDR. By enabling
+                                               this option, no new CDR is created for the dialplan logic that is
+                                               executed in <literal>h</literal> extensions or attached hangup handler
+                                               subroutines. The default value is <literal>no</literal>, indicating
+                                               that a CDR will be generated during hangup logic.</para>
                                        </description>
                                </configOption>
                                <configOption name="initiatedseconds">
@@ -335,6 +340,26 @@ static struct stasis_topic *cdr_topic;
 
 struct cdr_object;
 
+/*! \brief Return types for \ref process_bridge_enter functions */
+enum process_bridge_enter_results {
+       /*!
+        * The CDR was the only party in the bridge.
+        */
+       BRIDGE_ENTER_ONLY_PARTY,
+       /*!
+        * The CDR was able to obtain a Party B from some other party already in the bridge
+        */
+       BRIDGE_ENTER_OBTAINED_PARTY_B,
+       /*!
+        * The CDR was not able to obtain a Party B
+        */
+       BRIDGE_ENTER_NO_PARTY_B,
+       /*!
+        * This CDR can't handle a bridge enter message and a new CDR needs to be created
+        */
+       BRIDGE_ENTER_NEED_CDR,
+};
+
 /*!
  * \brief A virtual table used for \ref cdr_object.
  *
@@ -425,11 +450,11 @@ struct cdr_object_fn_table {
         * \param bridge The bridge that the Party A just entered into
         * \param channel The \ref ast_channel_snapshot for this CDR's Party A
         *
-        * \retval 0 This CDR found a Party B for itself and updated it, or there
-        * was no Party B to find (we're all alone)
-        * \retval 1 This CDR couldn't find a Party B and channels were in the bridge
+        * \retval process_bridge_enter_results Defines whether or not this CDR was able
+        * to fully handle the bridge enter message.
         */
-       int (* const process_bridge_enter)(struct cdr_object *cdr,
+       enum process_bridge_enter_results (* const process_bridge_enter)(
+                       struct cdr_object *cdr,
                        struct ast_bridge_snapshot *bridge,
                        struct ast_channel_snapshot *channel);
 
@@ -476,6 +501,7 @@ struct cdr_object_fn_table {
 };
 
 static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static enum process_bridge_enter_results base_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 static int base_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 static int base_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status);
 static int base_process_parked_channel(struct cdr_object *cdr, struct ast_parked_call_payload *parking_info);
@@ -483,7 +509,7 @@ static int base_process_parked_channel(struct cdr_object *cdr, struct ast_parked
 static void single_state_init_function(struct cdr_object *cdr);
 static void single_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
 static int single_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
-static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+static enum process_bridge_enter_results single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 static int single_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 
 /*!
@@ -513,7 +539,7 @@ struct cdr_object_fn_table single_state_fn_table = {
 static void dial_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
 static int dial_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
 static int dial_state_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status);
-static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+static enum process_bridge_enter_results dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 
 /*!
  * \brief The virtual table for the Dial state.
@@ -540,7 +566,7 @@ struct cdr_object_fn_table dial_state_fn_table = {
 
 static int dialed_pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
 static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
-static int dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+static enum process_bridge_enter_results dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 static int dialed_pending_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 
 /*!
@@ -582,7 +608,6 @@ static int bridge_state_process_bridge_leave(struct cdr_object *cdr, struct ast_
  *
  * A \ref cdr_object from this state can go to:
  * * \ref finalized_state_fn_table
- * * \ref pending_state_fn_table
  */
 struct cdr_object_fn_table bridge_state_fn_table = {
        .name = "Bridged",
@@ -592,36 +617,6 @@ struct cdr_object_fn_table bridge_state_fn_table = {
        .process_parked_channel = base_process_parked_channel,
 };
 
-static void pending_state_init_function(struct cdr_object *cdr);
-static int pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
-static int pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
-static int pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
-static int pending_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
-
-/*!
- * \brief The virtual table for the Pending state
- *
- * At certain times, we don't know where to go with the CDR. A good example is
- * when a channel leaves a bridge - we don't know if the channel is about to
- * be hung up; if it is about to go execute dialplan; dial someone; go into
- * another bridge, etc. At these times, the CDR goes into pending and observes
- * the messages that come in next to infer where the next logical place to go
- * is.
- *
- * In this state, a CDR can go anywhere!
- */
-struct cdr_object_fn_table bridged_pending_state_fn_table = {
-       .name = "Pending",
-       .init_function = pending_state_init_function,
-       .process_party_a = pending_state_process_party_a,
-       .process_dial_begin = pending_state_process_dial_begin,
-       .process_dial_end = base_process_dial_end,
-       .process_bridge_enter = pending_state_process_bridge_enter,
-       .process_parking_bridge_enter = pending_state_process_parking_bridge_enter,
-       .process_bridge_leave = base_process_bridge_leave,
-       .process_parked_channel = base_process_parked_channel,
-};
-
 static int parked_state_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 
 /*!
@@ -653,6 +648,7 @@ struct cdr_object_fn_table finalized_state_fn_table = {
        .name = "Finalized",
        .init_function = finalized_state_init_function,
        .process_party_a = finalized_state_process_party_a,
+       .process_bridge_enter = base_process_bridge_enter,
 };
 
 /*! \brief A wrapper object around a snapshot.
@@ -899,6 +895,38 @@ static struct cdr_object *cdr_object_create_and_append(struct cdr_object *cdr)
 }
 
 /*!
+ * \brief Return whether or not a channel has changed its state in the dialplan, subject
+ * to endbeforehexten logic
+ *
+ * \param old_snapshot The previous state
+ * \param new_snapshot The new state
+ *
+ * \retval 0 if the state has not changed
+ * \retval 1 if the state changed
+ */
+static int snapshot_cep_changed(struct ast_channel_snapshot *old_snapshot,
+       struct ast_channel_snapshot *new_snapshot)
+{
+       RAII_VAR(struct module_config *, mod_cfg,
+               ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+       /* If we ignore hangup logic, don't indicate that we're executing anything new */
+       if (ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)
+               && ast_test_flag(&new_snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)) {
+               return 0;
+       }
+
+       if (strcmp(new_snapshot->context, old_snapshot->context)
+               || strcmp(new_snapshot->exten, old_snapshot->exten)
+               || new_snapshot->priority != old_snapshot->priority
+               || strcmp(new_snapshot->appl, old_snapshot->appl)) {
+               return 1;
+       }
+
+       return 0;
+}
+
+/*!
  * \brief Return whether or not a \ref ast_channel_snapshot is for a channel
  * that was created as the result of a dial operation
  *
@@ -955,11 +983,7 @@ static struct cdr_object_snapshot *cdr_object_pick_party_a(struct cdr_object_sna
  */
 static long cdr_object_get_duration(struct cdr_object *cdr)
 {
-       if (ast_tvzero(cdr->end)) {
-               return (long)(ast_tvdiff_ms(ast_tvnow(), cdr->start) / 1000);
-       } else {
-               return (long)(ast_tvdiff_ms(cdr->end, cdr->start) / 1000);
-       }
+       return (long)(ast_tvdiff_ms(ast_tvzero(cdr->end) ? ast_tvnow() : cdr->end, cdr->start) / 1000);
 }
 
 /*!
@@ -973,7 +997,7 @@ static long cdr_object_get_billsec(struct cdr_object *cdr)
        if (ast_tvzero(cdr->answer)) {
                return 0;
        }
-       ms = ast_tvdiff_ms(cdr->end, cdr->answer);
+       ms = ast_tvdiff_ms(ast_tvzero(cdr->end) ? ast_tvnow() : cdr->end, cdr->answer);
        if (ast_test_flag(&mod_cfg->general->settings, CDR_INITIATED_SECONDS)
                && (ms % 1000 >= 500)) {
                ms = (ms / 1000) + 1;
@@ -985,6 +1009,33 @@ static long cdr_object_get_billsec(struct cdr_object *cdr)
 }
 
 /*!
+ * \internal
+ * \brief Set a variable on a CDR object
+ *
+ * \param headp The header pointer to the variable to set
+ * \param name The name of the variable
+ * \param value The value of the variable
+ */
+static void set_variable(struct varshead *headp, const char *name, const char *value)
+{
+       struct ast_var_t *newvariable;
+
+       AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) {
+               if (!strcasecmp(ast_var_name(newvariable), name)) {
+                       AST_LIST_REMOVE_CURRENT(entries);
+                       ast_var_delete(newvariable);
+                       break;
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END;
+
+       if (value) {
+               newvariable = ast_var_assign(name, value);
+               AST_LIST_INSERT_HEAD(headp, newvariable, entries);
+       }
+}
+
+/*!
  * \brief Create a chain of \ref ast_cdr objects from a chain of \ref cdr_object
  * suitable for consumption by the registered CDR backends
  * \param cdr The \ref cdr_object to convert to a public record
@@ -994,16 +1045,16 @@ static long cdr_object_get_billsec(struct cdr_object *cdr)
 static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
 {
        struct ast_cdr *pub_cdr = NULL, *cdr_prev = NULL;
+       struct cdr_object *it_cdr;
        struct ast_var_t *it_var, *it_copy_var;
        struct ast_channel_snapshot *party_a;
        struct ast_channel_snapshot *party_b;
 
-       while (cdr) {
+       for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
                struct ast_cdr *cdr_copy;
 
                /* Don't create records for CDRs where the party A was a dialed channel */
-               if (snapshot_is_dialed(cdr->party_a.snapshot)) {
-                       cdr = cdr->next;
+               if (snapshot_is_dialed(it_cdr->party_a.snapshot)) {
                        continue;
                }
 
@@ -1013,8 +1064,8 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
                        return NULL;
                }
 
-               party_a = cdr->party_a.snapshot;
-               party_b = cdr->party_b.snapshot;
+               party_a = it_cdr->party_a.snapshot;
+               party_b = it_cdr->party_b.snapshot;
 
                /* Party A */
                ast_assert(party_a != NULL);
@@ -1024,8 +1075,8 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
                ast_callerid_merge(cdr_copy->clid, sizeof(cdr_copy->clid), party_a->caller_name, party_a->caller_number, "");
                ast_copy_string(cdr_copy->src, party_a->caller_number, sizeof(cdr_copy->src));
                ast_copy_string(cdr_copy->uniqueid, party_a->uniqueid, sizeof(cdr_copy->uniqueid));
-               ast_copy_string(cdr_copy->lastapp, cdr->appl, sizeof(cdr_copy->lastapp));
-               ast_copy_string(cdr_copy->lastdata, cdr->data, sizeof(cdr_copy->lastdata));
+               ast_copy_string(cdr_copy->lastapp, it_cdr->appl, sizeof(cdr_copy->lastapp));
+               ast_copy_string(cdr_copy->lastdata, it_cdr->data, sizeof(cdr_copy->lastdata));
                ast_copy_string(cdr_copy->dst, party_a->exten, sizeof(cdr_copy->dst));
                ast_copy_string(cdr_copy->dcontext, party_a->context, sizeof(cdr_copy->dcontext));
 
@@ -1033,30 +1084,30 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
                if (party_b) {
                        ast_copy_string(cdr_copy->dstchannel, party_b->name, sizeof(cdr_copy->dstchannel));
                        ast_copy_string(cdr_copy->peeraccount, party_b->accountcode, sizeof(cdr_copy->peeraccount));
-                       if (!ast_strlen_zero(cdr->party_b.userfield)) {
-                               snprintf(cdr_copy->userfield, sizeof(cdr_copy->userfield), "%s;%s", cdr->party_a.userfield, cdr->party_b.userfield);
+                       if (!ast_strlen_zero(it_cdr->party_b.userfield)) {
+                               snprintf(cdr_copy->userfield, sizeof(cdr_copy->userfield), "%s;%s", it_cdr->party_a.userfield, it_cdr->party_b.userfield);
                        }
                }
-               if (ast_strlen_zero(cdr_copy->userfield) && !ast_strlen_zero(cdr->party_a.userfield)) {
-                       ast_copy_string(cdr_copy->userfield, cdr->party_a.userfield, sizeof(cdr_copy->userfield));
+               if (ast_strlen_zero(cdr_copy->userfield) && !ast_strlen_zero(it_cdr->party_a.userfield)) {
+                       ast_copy_string(cdr_copy->userfield, it_cdr->party_a.userfield, sizeof(cdr_copy->userfield));
                }
 
                /* Timestamps/durations */
-               cdr_copy->start = cdr->start;
-               cdr_copy->answer = cdr->answer;
-               cdr_copy->end = cdr->end;
-               cdr_copy->billsec = cdr_object_get_billsec(cdr);
-               cdr_copy->duration = cdr_object_get_duration(cdr);
+               cdr_copy->start = it_cdr->start;
+               cdr_copy->answer = it_cdr->answer;
+               cdr_copy->end = it_cdr->end;
+               cdr_copy->billsec = cdr_object_get_billsec(it_cdr);
+               cdr_copy->duration = cdr_object_get_duration(it_cdr);
 
                /* Flags and IDs */
-               ast_copy_flags(cdr_copy, &cdr->flags, AST_FLAGS_ALL);
-               ast_copy_string(cdr_copy->linkedid, cdr->linkedid, sizeof(cdr_copy->linkedid));
-               cdr_copy->disposition = cdr->disposition;
-               cdr_copy->sequence = cdr->sequence;
+               ast_copy_flags(cdr_copy, &it_cdr->flags, AST_FLAGS_ALL);
+               ast_copy_string(cdr_copy->linkedid, it_cdr->linkedid, sizeof(cdr_copy->linkedid));
+               cdr_copy->disposition = it_cdr->disposition;
+               cdr_copy->sequence = it_cdr->sequence;
 
                /* Variables */
-               copy_variables(&cdr_copy->varshead, &cdr->party_a.variables);
-               AST_LIST_TRAVERSE(&cdr->party_b.variables, it_var, entries) {
+               copy_variables(&cdr_copy->varshead, &it_cdr->party_a.variables);
+               AST_LIST_TRAVERSE(&it_cdr->party_b.variables, it_var, entries) {
                        int found = 0;
                        AST_LIST_TRAVERSE(&cdr_copy->varshead, it_copy_var, entries) {
                                if (!strcmp(ast_var_name(it_var), ast_var_name(it_copy_var))) {
@@ -1077,7 +1128,6 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
                        cdr_prev->next = cdr_copy;
                        cdr_prev = cdr_copy;
                }
-               cdr = cdr->next;
        }
 
        return pub_cdr;
@@ -1188,7 +1238,14 @@ static void cdr_object_finalize(struct cdr_object *cdr)
  */
 static void cdr_object_check_party_a_hangup(struct cdr_object *cdr)
 {
-       if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_ZOMBIE)
+       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+       if (ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)
+               && ast_test_flag(&cdr->party_a.snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)) {
+               cdr_object_finalize(cdr);
+       }
+
+       if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_DEAD)
                && cdr->fn_table != &finalized_state_fn_table) {
                cdr_object_transition_state(cdr, &finalized_state_fn_table);
        }
@@ -1210,35 +1267,6 @@ static void cdr_object_check_party_a_answer(struct cdr_object *cdr) {
        }
 }
 
-/*!
- * \internal
- * \brief Set a variable on a CDR object
- *
- * \param headp The header pointer to the variable to set
- * \param name The name of the variable
- * \param value The value of the variable
- *
- * CDRs that are in a hungup state cannot have their variables set.
- */
-static void set_variable(struct varshead *headp, const char *name, const char *value)
-{
-       struct ast_var_t *newvariable;
-
-       AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) {
-               if (!strcasecmp(ast_var_name(newvariable), name)) {
-                       AST_LIST_REMOVE_CURRENT(entries);
-                       ast_var_delete(newvariable);
-                       break;
-               }
-       }
-       AST_LIST_TRAVERSE_SAFE_END;
-
-       if (value) {
-               newvariable = ast_var_assign(name, value);
-               AST_LIST_INSERT_HEAD(headp, newvariable, entries);
-       }
-}
-
 /* \brief Set Caller ID information on a CDR */
 static void cdr_object_update_cid(struct cdr_object_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot)
 {
@@ -1318,6 +1346,12 @@ static int base_process_dial_end(struct cdr_object *cdr, struct ast_channel_snap
        return 0;
 }
 
+static enum process_bridge_enter_results base_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+       /* Base process bridge enter simply indicates that we can't handle it */
+       return BRIDGE_ENTER_NEED_CDR;
+}
+
 static int base_process_parked_channel(struct cdr_object *cdr, struct ast_parked_call_payload *parking_info)
 {
        char park_info[128];
@@ -1424,12 +1458,12 @@ static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
        return 1;
 }
 
-static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+static enum process_bridge_enter_results single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
 {
        struct ao2_iterator *it_cdrs;
        struct cdr_object *cand_cdr_master;
        char *bridge_id = ast_strdupa(bridge->uniqueid);
-       int success = 1;
+       int success = 0;
 
        ast_string_field_set(cdr, bridge, bridge->uniqueid);
 
@@ -1439,7 +1473,7 @@ static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_
        if (!it_cdrs) {
                /* No one in the bridge yet! */
                cdr_object_transition_state(cdr, &bridge_state_fn_table);
-               return 0;
+               return BRIDGE_ENTER_ONLY_PARTY;
        }
 
        while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
@@ -1458,7 +1492,7 @@ static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_
                                continue;
                        }
                        /* We successfully got a party B - break out */
-                       success = 0;
+                       success = 1;
                        break;
                }
                ao2_unlock(cand_cdr_master);
@@ -1470,7 +1504,11 @@ static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_
        cdr_object_transition_state(cdr, &bridge_state_fn_table);
 
        /* Success implies that we have a Party B */
-       return success;
+       if (success) {
+               return BRIDGE_ENTER_OBTAINED_PARTY_B;
+       }
+
+       return BRIDGE_ENTER_NO_PARTY_B;
 }
 
 static int single_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
@@ -1492,7 +1530,7 @@ static void dial_state_process_party_b(struct cdr_object *cdr, struct ast_channe
        cdr_object_swap_snapshot(&cdr->party_b, snapshot);
 
        /* If party B hangs up, finalize this CDR */
-       if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_ZOMBIE)) {
+       if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_DEAD)) {
                cdr_object_transition_state(cdr, &finalized_state_fn_table);
        }
 }
@@ -1563,12 +1601,12 @@ static int dial_state_process_dial_end(struct cdr_object *cdr, struct ast_channe
        return 0;
 }
 
-static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+static enum process_bridge_enter_results dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
 {
        struct ao2_iterator *it_cdrs;
        char *bridge_id = ast_strdupa(bridge->uniqueid);
        struct cdr_object *cand_cdr_master;
-       int success = 1;
+       int success = 0;
 
        ast_string_field_set(cdr, bridge, bridge->uniqueid);
 
@@ -1578,7 +1616,7 @@ static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_br
        if (!it_cdrs) {
                /* No one in the bridge yet! */
                cdr_object_transition_state(cdr, &bridge_state_fn_table);
-               return 0;
+               return BRIDGE_ENTER_ONLY_PARTY;
        }
 
        while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
@@ -1610,7 +1648,7 @@ static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_br
                        if (!cand_cdr->party_b.snapshot) {
                                cdr_object_finalize(cand_cdr);
                        }
-                       success = 0;
+                       success = 1;
                        break;
                }
                ao2_unlock(cand_cdr_master);
@@ -1622,7 +1660,10 @@ static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_br
        cdr_object_transition_state(cdr, &bridge_state_fn_table);
 
        /* Success implies that we have a Party B */
-       return success;
+       if (success) {
+               return BRIDGE_ENTER_OBTAINED_PARTY_B;
+       }
+       return BRIDGE_ENTER_NO_PARTY_B;
 }
 
 /* DIALED PENDING STATE */
@@ -1632,10 +1673,7 @@ static int dialed_pending_state_process_party_a(struct cdr_object *cdr, struct a
        /* If we get a CEP change, we're executing dialplan. If we have a Party B
         * that means we need a new CDR; otherwise, switch us over to single.
         */
-       if (strcmp(snapshot->context, cdr->party_a.snapshot->context)
-               || strcmp(snapshot->exten, cdr->party_a.snapshot->exten)
-               || snapshot->priority != cdr->party_a.snapshot->priority
-               || strcmp(snapshot->appl, cdr->party_a.snapshot->appl)) {
+       if (snapshot_cep_changed(cdr->party_a.snapshot, snapshot)) {
                if (cdr->party_b.snapshot) {
                        cdr_object_transition_state(cdr, &finalized_state_fn_table);
                        cdr->fn_table->process_party_a(cdr, snapshot);
@@ -1650,7 +1688,7 @@ static int dialed_pending_state_process_party_a(struct cdr_object *cdr, struct a
        return 0;
 }
 
-static int dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+static enum process_bridge_enter_results dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
 {
        cdr_object_transition_state(cdr, &dial_state_fn_table);
        return cdr->fn_table->process_bridge_enter(cdr, bridge, channel);
@@ -1680,7 +1718,7 @@ static void bridge_state_process_party_b(struct cdr_object *cdr, struct ast_chan
        cdr_object_swap_snapshot(&cdr->party_b, snapshot);
 
        /* If party B hangs up, finalize this CDR */
-       if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_ZOMBIE)) {
+       if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_DEAD)) {
                cdr_object_transition_state(cdr, &finalized_state_fn_table);
        }
 }
@@ -1700,53 +1738,6 @@ static int bridge_state_process_bridge_leave(struct cdr_object *cdr, struct ast_
        return 0;
 }
 
-/* PENDING STATE */
-
-static void pending_state_init_function(struct cdr_object *cdr)
-{
-       ast_cdr_set_property(cdr->name, AST_CDR_FLAG_DISABLE);
-}
-
-static int pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
-{
-       if (ast_test_flag(&snapshot->flags, AST_FLAG_ZOMBIE)) {
-               return 0;
-       }
-
-       /* Ignore if we don't get a CEP change */
-       if (!strcmp(snapshot->context, cdr->party_a.snapshot->context)
-               && !strcmp(snapshot->exten, cdr->party_a.snapshot->exten)
-               && snapshot->priority == cdr->party_a.snapshot->priority) {
-               return 0;
-       }
-
-       cdr_object_transition_state(cdr, &single_state_fn_table);
-       ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE);
-       cdr->fn_table->process_party_a(cdr, snapshot);
-       return 0;
-}
-
-static int pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
-{
-       cdr_object_transition_state(cdr, &single_state_fn_table);
-       ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE);
-       return cdr->fn_table->process_dial_begin(cdr, caller, peer);
-}
-
-static int pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
-{
-       cdr_object_transition_state(cdr, &single_state_fn_table);
-       ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE);
-       return cdr->fn_table->process_bridge_enter(cdr, bridge, channel);
-}
-
-static int pending_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
-{
-       cdr_object_transition_state(cdr, &single_state_fn_table);
-       ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE);
-       return cdr->fn_table->process_parking_bridge_enter(cdr, bridge, channel);
-}
-
 /* PARKED STATE */
 
 static int parked_state_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
@@ -1763,19 +1754,18 @@ static int parked_state_process_bridge_leave(struct cdr_object *cdr, struct ast_
 
 static void finalized_state_init_function(struct cdr_object *cdr)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
-
-       if (!ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)) {
-               return;
-       }
-
        cdr_object_finalize(cdr);
 }
 
 static int finalized_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
 {
-       if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_ZOMBIE)) {
-               cdr_object_finalize(cdr);
+       RAII_VAR(struct module_config *, mod_cfg,
+               ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+       /* If we ignore hangup logic, indicate that we don't need a new CDR */
+       if (ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)
+               && ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)) {
+               return 0;
        }
 
        /* Indicate that, if possible, we should get a new CDR */
@@ -1968,7 +1958,7 @@ static int check_new_cdr_needed(struct ast_channel_snapshot *old_snapshot,
                return 0;
        }
 
-       if (ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE)) {
+       if (ast_test_flag(&new_snapshot->flags, AST_FLAG_DEAD)) {
                return 0;
        }
 
@@ -1977,10 +1967,7 @@ static int check_new_cdr_needed(struct ast_channel_snapshot *old_snapshot,
                return 0;
        }
 
-       if (old_snapshot && !strcmp(old_snapshot->context, new_snapshot->context)
-                       && !strcmp(old_snapshot->exten, new_snapshot->exten)
-                       && old_snapshot->priority == new_snapshot->priority
-                       && !(strcmp(old_snapshot->appl, new_snapshot->appl))) {
+       if (old_snapshot && !snapshot_cep_changed(old_snapshot, new_snapshot)) {
                return 0;
        }
 
@@ -2102,13 +2089,10 @@ static int cdr_object_party_b_left_bridge_cb(void *obj, void *arg, int flags)
                if (strcmp(it_cdr->party_b.snapshot->name, leave_data->channel->name)) {
                        continue;
                }
-               if (!it_cdr->fn_table->process_bridge_leave(it_cdr, leave_data->bridge, leave_data->channel)) {
-                       /* Update the end times for this CDR. We don't want to actually
-                        * finalize it, as the Party A will eventually need to leave, which
-                        * will switch the records to pending bridged.
-                        */
-                       cdr_object_finalize(it_cdr);
-               }
+               /* It is our Party B, in our bridge. Set the end time and let the handler
+                * transition our CDR appropriately when we leave the bridge.
+                */
+               cdr_object_finalize(it_cdr);
        }
        return 0;
 }
@@ -2144,7 +2128,6 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
                        ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY),
                        ao2_cleanup);
        struct cdr_object *it_cdr;
-       struct cdr_object *pending_cdr;
        struct bridge_leave_data leave_data = {
                .bridge = bridge,
                .channel = channel,
@@ -2182,14 +2165,6 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
                ao2_unlock(cdr);
                return;
        }
-
-       /* Create a new pending record. If the channel decides to do something else,
-        * the pending record will handle it - otherwise, if gets dropped.
-        */
-       pending_cdr = cdr_object_create_and_append(cdr);
-       if (pending_cdr) {
-               cdr_object_transition_state(pending_cdr, &bridged_pending_state_fn_table);
-       }
        ao2_unlock(cdr);
 
        if (strcmp(bridge->subclass, "parking")) {
@@ -2518,8 +2493,9 @@ static void handle_standard_bridge_enter_message(struct cdr_object *cdr,
 {
        RAII_VAR(struct module_config *, mod_cfg,
                        ao2_global_obj_ref(module_configs), ao2_cleanup);
-       int res = 1;
+       enum process_bridge_enter_results result;
        struct cdr_object *it_cdr;
+       struct cdr_object *new_cdr;
        struct cdr_object *handled_cdr = NULL;
 
        ao2_lock(cdr);
@@ -2535,20 +2511,31 @@ static void handle_standard_bridge_enter_message(struct cdr_object *cdr,
                if (it_cdr->fn_table->process_bridge_enter) {
                        CDR_DEBUG(mod_cfg, "%p - Processing bridge enter for %s\n", it_cdr,
                                        channel->name);
-                       res &= it_cdr->fn_table->process_bridge_enter(it_cdr, bridge, channel);
-                       if (!res && !handled_cdr) {
-                               handled_cdr = it_cdr;
+                       result = it_cdr->fn_table->process_bridge_enter(it_cdr, bridge, channel);
+                       switch (result) {
+                       case BRIDGE_ENTER_ONLY_PARTY:
+                               /* Fall through */
+                       case BRIDGE_ENTER_OBTAINED_PARTY_B:
+                               if (!handled_cdr) {
+                                       handled_cdr = it_cdr;
+                               }
+                       break;
+                       case BRIDGE_ENTER_NEED_CDR:
+                               /* Pass */
+                       break;
+                       case BRIDGE_ENTER_NO_PARTY_B:
+                               /* We didn't win on any - end this CDR. If someone else comes in later
+                                * that is Party B to this CDR, it can re-activate this CDR.
+                                */
+                               if (!handled_cdr) {
+                                       handled_cdr = it_cdr;
+                               }
+                               cdr_object_finalize(cdr);
+                       break;
                        }
                }
        }
 
-       if (res) {
-               /* We didn't win on any - end this CDR. If someone else comes in later
-                * that is Party B to this CDR, it can re-activate this CDR.
-                */
-               cdr_object_finalize(cdr);
-       }
-
        /* Create the new matchings, but only for either:
         *  * The first CDR in the chain that handled it. This avoids issues with
         *    forked CDRs.
@@ -2556,10 +2543,18 @@ static void handle_standard_bridge_enter_message(struct cdr_object *cdr,
         *    a CDR joined a bridge and it wasn't Party A for anyone. We still need
         *    to make pairings with everyone in the bridge.
         */
-       if (!handled_cdr) {
-               handled_cdr = cdr->last;
+       if (handled_cdr) {
+               handle_bridge_pairings(handled_cdr, bridge);
+       } else {
+               /* Nothing handled it - we need a new one! */
+               new_cdr = cdr_object_create_and_append(cdr);
+               if (new_cdr) {
+                       /* This is guaranteed to succeed: the new CDR is created in the single state
+                        * and will be able to handle the bridge enter message
+                        */
+                       handle_standard_bridge_enter_message(cdr, bridge, channel);
+               }
        }
-       handle_bridge_pairings(handled_cdr, bridge);
        ao2_unlock(cdr);
 }
 
@@ -2869,10 +2864,11 @@ static int cdr_object_select_all_by_channel_cb(void *obj, void *arg, int flags)
 }
 
 /* Read Only CDR variables */
-static const char * const cdr_readonly_vars[] = { "clid", "src", "dst", "dcontext", "channel", "dstchannel",
-                                                 "lastapp", "lastdata", "start", "answer", "end", "duration",
-                                                 "billsec", "disposition", "amaflags", "accountcode", "uniqueid", "linkedid",
-                                                 "userfield", "sequence", NULL };
+static const char * const cdr_readonly_vars[] = { "clid", "src", "dst", "dcontext",
+       "channel", "dstchannel", "lastapp", "lastdata", "start", "answer", "end", "duration",
+       "billsec", "disposition", "amaflags", "accountcode", "uniqueid", "linkedid",
+       "userfield", "sequence", "total_duration", "total_billsec", "first_start",
+       "first_answer", NULL };
 
 int ast_cdr_setvar(const char *channel_name, const char *name, const char *value)
 {
@@ -3020,11 +3016,11 @@ int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size
        ao2_lock(cdr);
 
        cdr_obj = cdr->last;
-
        if (cdr_object_format_property(cdr_obj, name, value, length)) {
                /* Property failed; attempt variable */
                cdr_object_format_var_internal(cdr_obj, name, value, length);
        }
+
        ao2_unlock(cdr);
 
        return 0;
@@ -3188,7 +3184,7 @@ static void post_cdr(struct ast_cdr *cdr)
                if (!ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED) &&
                                cdr->disposition < AST_CDR_ANSWERED &&
                                (ast_strlen_zero(cdr->channel) || ast_strlen_zero(cdr->dstchannel))) {
-                       ast_log(AST_LOG_WARNING, "Skipping CDR since we weren't answered\n");
+                       ast_debug(1, "Skipping CDR  for %s since we weren't answered\n", cdr->channel);
                        continue;
                }
 
@@ -3311,7 +3307,6 @@ int ast_cdr_fork(const char *channel_name, struct ast_flags *options)
                        /* If the last CDR in the chain is finalized, don't allow a fork -
                         * things are already dying at this point
                         */
-                       ast_log(AST_LOG_ERROR, "FARK\n");
                        return -1;
                }
 
@@ -3655,13 +3650,15 @@ static void cli_show_channels(struct ast_cli_args *a)
                                answer_time = it_cdr->answer;
                        }
                }
-               /* Only CDRs when this was dialed are available; skip */
+
+               /* If there was no start time, then all CDRs were for a dialed channel; skip */
                if (ast_tvzero(start_time)) {
                        ao2_ref(cdr, -1);
                        continue;
                }
                it_cdr = cdr->last;
-               end_time = ast_tvzero(cdr->last->end) ? ast_tvnow() : cdr->last->end;
+
+               end_time = ast_tvzero(it_cdr->end) ? ast_tvnow() : it_cdr->end;
                cdr_get_tv(start_time, "%T", start_time_buffer, sizeof(start_time_buffer));
                cdr_get_tv(answer_time, "%T", answer_time_buffer, sizeof(answer_time_buffer));
                cdr_get_tv(end_time, "%T", end_time_buffer, sizeof(end_time_buffer));
@@ -3671,7 +3668,7 @@ static void cli_show_channels(struct ast_cli_args *a)
                                start_time_buffer,
                                answer_time_buffer,
                                end_time_buffer,
-                               (long)ast_tvdiff_ms(end_time, answer_time) / 1000,
+                               ast_tvzero(answer_time) ? 0 : (long)ast_tvdiff_ms(end_time, answer_time) / 1000,
                                (long)ast_tvdiff_ms(end_time, start_time) / 1000);
                ao2_ref(cdr, -1);
        }
@@ -3829,18 +3826,32 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_
 
 static char *handle_cli_submit(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
+       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
        switch (cmd) {
        case CLI_INIT:
                e->command = "cdr submit";
                e->usage =
                        "Usage: cdr submit\n"
-                       "       Posts all pending batched CDR data to the configured CDR backend engine modules.\n";
+                       "Posts all pending batched CDR data to the configured CDR\n"
+                       "backend engine modules.\n";
                return NULL;
        case CLI_GENERATE:
                return NULL;
        }
-       if (a->argc > 2)
+       if (a->argc > 2) {
                return CLI_SHOWUSAGE;
+       }
+
+       if (!ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+               ast_cli(a->fd, "Cannot submit CDR batch: CDR engine disabled.\n");
+               return CLI_SUCCESS;
+       }
+
+       if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+               ast_cli(a->fd, "Cannot submit CDR batch: batch mode not enabled.\n");
+               return CLI_SUCCESS;
+       }
 
        submit_unscheduled_batch();
        ast_cli(a->fd, "Submitted CDRs to backend engines for processing.  This may take a while.\n");
@@ -3848,11 +3859,12 @@ static char *handle_cli_submit(struct ast_cli_entry *e, int cmd, struct ast_cli_
        return CLI_SUCCESS;
 }
 
-static struct ast_cli_entry cli_submit = AST_CLI_DEFINE(handle_cli_submit, "Posts all pending batched CDR data");
-static struct ast_cli_entry cli_status = AST_CLI_DEFINE(handle_cli_status, "Display the CDR status");
-static struct ast_cli_entry cli_show = AST_CLI_DEFINE(handle_cli_show, "Display CDRs");
-static struct ast_cli_entry cli_debug = AST_CLI_DEFINE(handle_cli_debug, "Enable debugging");
-
+static struct ast_cli_entry cli_commands[] = {
+       AST_CLI_DEFINE(handle_cli_submit, "Posts all pending batched CDR data"),
+       AST_CLI_DEFINE(handle_cli_status, "Display the CDR status"),
+       AST_CLI_DEFINE(handle_cli_show, "Display active CDRs for channels"),
+       AST_CLI_DEFINE(handle_cli_debug, "Enable debugging in the CDR engine"),
+};
 
 /*!
  * \brief This dispatches *all* \ref cdr_objects. It should only be used during
@@ -3882,7 +3894,6 @@ static void finalize_batch_mode(void)
        pthread_join(cdr_thread, NULL);
        cdr_thread = AST_PTHREADT_NULL;
        ast_cond_destroy(&cdr_pending_cond);
-       ast_cli_unregister(&cli_submit);
        ast_cdr_engine_term();
 }
 
@@ -3934,9 +3945,7 @@ static void cdr_engine_shutdown(void)
        ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_dispatch_all_cb,
                NULL);
        finalize_batch_mode();
-       ast_cli_unregister(&cli_status);
-       ast_cli_unregister(&cli_debug);
-       ast_cli_unregister(&cli_show);
+       ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
        ast_sched_context_destroy(sched);
        sched = NULL;
        ast_free(batch);
@@ -3944,6 +3953,7 @@ static void cdr_engine_shutdown(void)
 
        channel_subscription = stasis_unsubscribe_and_join(channel_subscription);
        bridge_subscription = stasis_unsubscribe_and_join(bridge_subscription);
+       parking_subscription = stasis_unsubscribe_and_join(parking_subscription);
        stasis_message_router_unsubscribe_and_join(stasis_router);
        aco_info_destroy(&cfg_info);
        ao2_global_obj_release(module_configs);
@@ -3962,7 +3972,6 @@ static void cdr_enable_batch_mode(struct ast_cdr_config *config)
                        ast_log(LOG_ERROR, "Unable to start CDR thread.\n");
                        return;
                }
-               ast_cli_register(&cli_submit);
        }
 
        /* Kill the currently scheduled item */
@@ -4046,9 +4055,7 @@ int ast_cdr_engine_init(void)
                return -1;
        }
 
-       ast_cli_register(&cli_status);
-       ast_cli_register(&cli_debug);
-       ast_cli_register(&cli_show);
+       ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
        ast_register_atexit(cdr_engine_shutdown);
 
        mod_cfg = ao2_global_obj_ref(module_configs);
index bc1182a..b71afde 100644 (file)
@@ -1084,8 +1084,8 @@ static void cel_channel_state_change(
                return;
        }
 
-       was_hungup = ast_test_flag(&old_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0;
-       is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0;
+       was_hungup = ast_test_flag(&old_snapshot->flags, AST_FLAG_DEAD) ? 1 : 0;
+       is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_DEAD) ? 1 : 0;
 
        if (!was_hungup && is_hungup) {
                RAII_VAR(struct ast_str *, extra_str, ast_str_create(128), ast_free);
index ce11e50..104558b 100644 (file)
@@ -2360,6 +2360,8 @@ static void ast_channel_destructor(void *obj)
        char device_name[AST_CHANNEL_NAME];
        struct ast_callid *callid;
 
+       ast_set_flag(ast_channel_flags(chan), AST_FLAG_DEAD);
+       ast_channel_publish_snapshot(chan);
        publish_cache_clear(chan);
 
        ast_pbx_hangup_handler_destroy(chan);
@@ -2866,8 +2868,6 @@ int ast_hangup(struct ast_channel *chan)
 
        ast_cc_offer(chan);
 
-       ast_channel_publish_snapshot(chan);
-
        ast_channel_unref(chan);
 
        return 0;
index afa4c9f..085052a 100644 (file)
@@ -276,6 +276,9 @@ static void channel_data_add_flags(struct ast_data *tree,
        ast_data_add_bool(tree, "BRIDGE_HANGUP_RUN", ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_RUN));
        ast_data_add_bool(tree, "DISABLE_WORKAROUNDS", ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_WORKAROUNDS));
        ast_data_add_bool(tree, "DISABLE_DEVSTATE_CACHE", ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_DEVSTATE_CACHE));
+       ast_data_add_bool(tree, "BRIDGE_DUAL_REDIRECT_WAIT", ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT));
+       ast_data_add_bool(tree, "ORIGINATED", ast_test_flag(ast_channel_flags(chan), AST_FLAG_ORIGINATED));
+       ast_data_add_bool(tree, "DEAD", ast_test_flag(ast_channel_flags(chan), AST_FLAG_DEAD));
 }
 
 int ast_channel_data_add_structure(struct ast_data *tree,
index c9e38df..7dd91a6 100644 (file)
@@ -603,8 +603,8 @@ static struct ast_manager_event_blob *channel_state_change(
                        EVENT_FLAG_CALL, "Newchannel", NO_EXTRA_FIELDS);
        }
 
-       was_hungup = ast_test_flag(&old_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0;
-       is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0;
+       was_hungup = ast_test_flag(&old_snapshot->flags, AST_FLAG_DEAD) ? 1 : 0;
+       is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_DEAD) ? 1 : 0;
 
        if (!was_hungup && is_hungup) {
                return ast_manager_event_blob_create(
@@ -639,7 +639,7 @@ static struct ast_manager_event_blob *channel_newexten(
        }
 
        /* Ignore any updates if we're hungup */
-       if (ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE)) {
+       if (ast_test_flag(&new_snapshot->flags, AST_FLAG_DEAD)) {
                return NULL;
        }
 
index 8079edc..f3d9184 100644 (file)
@@ -5692,7 +5692,7 @@ void ast_pbx_h_exten_run(struct ast_channel *chan, const char *context)
         * 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);
+       ast_softhangup_nolock(chan, AST_SOFTHANGUP_HANGUP_EXEC);
 
        /* Save autoloop flag */
        autoloopflag = ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP);
@@ -5765,7 +5765,7 @@ int ast_pbx_hangup_handler_run(struct ast_channel *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);
+       ast_softhangup_nolock(chan, AST_SOFTHANGUP_HANGUP_EXEC);
 
        for (;;) {
                handlers = ast_channel_hangup_handlers(chan);
index 8cf2595..9179878 100644 (file)
@@ -185,8 +185,9 @@ struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *cha
        snapshot->priority = ast_channel_priority(chan);
        snapshot->amaflags = ast_channel_amaflags(chan);
        snapshot->hangupcause = ast_channel_hangupcause(chan);
-       snapshot->flags = *ast_channel_flags(chan);
+       ast_copy_flags(&snapshot->flags, ast_channel_flags(chan), 0xFFFFFFFF);
        snapshot->caller_pres = ast_party_id_presentation(&ast_channel_caller(chan)->id);
+       ast_set_flag(&snapshot->softhangup_flags, ast_channel_softhangup_internal_flag(chan));
 
        snapshot->manager_vars = ast_channel_get_manager_vars(chan);