</description>
</configOption>
<configOption name="unanswered">
- <synopsis>Log calls that are never answered.</synopsis>
- <description><para>Define whether or not to log unanswered calls. Setting this to "yes" will
- report every attempt to ring a phone in dialing attempts, when it was not
- answered. For example, if you try to dial 3 extensions, and this option is "yes",
- you will get 3 CDR's, one for each phone that was rung. Some find this information horribly
- useless. Others find it very valuable. Note, in "yes" mode, you will see one CDR, with one of
- the call targets on one side, and the originating channel on the other, and then one CDR for
- each channel attempted. This may seem redundant, but cannot be helped.</para>
- <para>In brief, this option controls the reporting of unanswered calls which only have an A
- party. Calls which get offered to an outgoing line, but are unanswered, are still
- logged, and that is the intended behavior. (It also results in some B side CDRs being
- output, as they have the B side channel as their source channel, and no destination
- channel.)</para>
+ <synopsis>Log calls that are never answered and don't set an outgoing party.</synopsis>
+ <description><para>
+ Define whether or not to log unanswered calls that don't involve an outgoing party. Setting
+ this to "yes" will make calls to extensions that don't answer and don't set a side B channel
+ (such as by using the Dial application) receive CDR log entries. If this option is set to
+ "no", then those log entries will not be created. Unanswered calls which get offered to an
+ outgoing line will always receive log entries regardless of this option, and that is the
+ intended behavior.
+ </para>
</description>
</configOption>
<configOption name="congestion">
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
+ subroutines. The default value is <literal>yes</literal>, indicating
that a CDR will be generated during hangup logic.</para>
</description>
</configOption>
***/
+/* The prime here should be similar in size to the channel container. */
+#ifdef LOW_MEMORY
+#define NUM_CDR_BUCKETS 61
+#else
+#define NUM_CDR_BUCKETS 769
+#endif
+
#define DEFAULT_ENABLED "1"
#define DEFAULT_BATCHMODE "0"
#define DEFAULT_UNANSWERED "0"
#define DEFAULT_CONGESTION "0"
-#define DEFAULT_END_BEFORE_H_EXTEN "0"
+#define DEFAULT_END_BEFORE_H_EXTEN "1"
#define DEFAULT_INITIATED_SECONDS "0"
#define DEFAULT_BATCH_SIZE "100"
#define CDR_DEBUG(mod_cfg, fmt, ...) \
do { \
- if (ast_test_flag(&(mod_cfg)->general->settings, CDR_DEBUG)) { \
- ast_verb(1, (fmt), ##__VA_ARGS__); \
- } } while (0)
+ if (ast_test_flag(&(mod_cfg)->general->settings, CDR_DEBUG)) { \
+ ast_verbose((fmt), ##__VA_ARGS__); \
+ } \
+ } while (0)
static void cdr_detach(struct ast_cdr *cdr);
static void cdr_submit_batch(int shutdown);
+static int cdr_toggle_runtime_options(void);
/*! \brief The configuration settings for this module */
struct module_config {
char desc[80];
ast_cdrbe be;
AST_RWLIST_ENTRY(cdr_beitem) list;
+ int suspended:1;
};
/*! \brief List of registered backends */
AST_MUTEX_DEFINE_STATIC(cdr_pending_lock);
static ast_cond_t cdr_pending_cond;
-/*! \brief A container of the active CDRs indexed by Party A channel name */
+/*! \brief A container of the active CDRs indexed by Party A channel id */
static struct ao2_container *active_cdrs_by_channel;
/*! \brief Message router for stasis messages regarding channel state */
static struct stasis_message_router *stasis_router;
/*! \brief Our subscription for bridges */
-static struct stasis_subscription *bridge_subscription;
+static struct stasis_forward *bridge_subscription;
/*! \brief Our subscription for channels */
-static struct stasis_subscription *channel_subscription;
+static struct stasis_forward *channel_subscription;
/*! \brief Our subscription for parking */
-static struct stasis_subscription *parking_subscription;
+static struct stasis_forward *parking_subscription;
/*! \brief The parent topic for all topics we want to aggregate for CDRs */
static struct stasis_topic *cdr_topic;
+/*! \brief A message type used to synchronize with the CDR topic */
+STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_sync_message_type);
+
struct cdr_object;
/*! \brief Return types for \ref process_bridge_enter functions */
struct ast_flags flags; /*!< Flags on the CDR */
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(linkedid); /*!< Linked ID. Cached here as it may change out from party A, which must be immutable */
+ AST_STRING_FIELD(uniqueid); /*!< Unique id of party A. Cached here as it is the primary key of this CDR */
AST_STRING_FIELD(name); /*!< Channel name of party A. Cached here as the party A address may change */
AST_STRING_FIELD(bridge); /*!< The bridge the party A happens to be in. */
AST_STRING_FIELD(appl); /*!< The last accepted application party A was in */
AST_STRING_FIELD(data); /*!< The data for the last accepted application party A was in */
+ AST_STRING_FIELD(context); /*!< The accepted context for Party A */
+ AST_STRING_FIELD(exten); /*!< The accepted extension for Party A */
);
struct cdr_object *next; /*!< The next CDR object in the chain */
struct cdr_object *last; /*!< The last CDR object in the chain */
}
}
/*! \internal
- * \brief Hash function for containers of CDRs indexing by Party A name */
+ * \brief Hash function for containers of CDRs indexing by Party A uniqueid */
static int cdr_object_channel_hash_fn(const void *obj, const int flags)
{
- const struct cdr_object *cdr = obj;
- const char *name = (flags & OBJ_KEY) ? obj : cdr->name;
- return ast_str_case_hash(name);
+ const struct cdr_object *cdr;
+ const char *key;
+
+ switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+ case OBJ_KEY:
+ key = obj;
+ break;
+ case OBJ_POINTER:
+ cdr = obj;
+ key = cdr->uniqueid;
+ break;
+ default:
+ ast_assert(0);
+ return 0;
+ }
+ return ast_str_case_hash(key);
}
/*! \internal
- * \brief Comparison function for containers of CDRs indexing by Party A name
+ * \brief Comparison function for containers of CDRs indexing by Party A uniqueid
*/
static int cdr_object_channel_cmp_fn(void *obj, void *arg, int flags)
{
- struct cdr_object *left = obj;
- struct cdr_object *right = arg;
- const char *match = (flags & OBJ_KEY) ? arg : right->name;
- return strcasecmp(left->name, match) ? 0 : (CMP_MATCH | CMP_STOP);
-}
-
-/*! \internal
- * \brief Comparison function for containers of CDRs indexing by bridge. Note
- * that we expect there to be collisions, as a single bridge may have multiple
- * CDRs active at one point in time
- */
-static int cdr_object_bridge_cmp_fn(void *obj, void *arg, int flags)
-{
- struct cdr_object *left = obj;
- struct cdr_object *it_cdr;
- const char *match = arg;
-
- for (it_cdr = left; it_cdr; it_cdr = it_cdr->next) {
- if (!strcasecmp(it_cdr->bridge, match)) {
- return CMP_MATCH;
- }
- }
- return 0;
+ struct cdr_object *left = obj;
+ struct cdr_object *right = arg;
+ const char *right_key = arg;
+ int cmp;
+
+ switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+ case OBJ_POINTER:
+ right_key = right->uniqueid;
+ /* Fall through */
+ case OBJ_KEY:
+ cmp = strcmp(left->uniqueid, right_key);
+ break;
+ case OBJ_PARTIAL_KEY:
+ /*
+ * We could also use a partial key struct containing a length
+ * so strlen() does not get called for every comparison instead.
+ */
+ cmp = strncmp(left->uniqueid, right_key, strlen(right_key));
+ break;
+ default:
+ /* Sort can only work on something with a full or partial key. */
+ ast_assert(0);
+ cmp = 0;
+ break;
+ }
+ return cmp ? 0 : CMP_MATCH;
}
/*!
struct cdr_object *cdr = obj;
struct ast_var_t *it_var;
- if (!cdr) {
- return;
- }
-
ao2_cleanup(cdr->party_a.snapshot);
ao2_cleanup(cdr->party_b.snapshot);
while ((it_var = AST_LIST_REMOVE_HEAD(&cdr->party_a.variables, entries))) {
}
ast_string_field_free_memory(cdr);
- if (cdr->next) {
- ao2_cleanup(cdr->next);
- }
+ ao2_cleanup(cdr->next);
}
/*!
}
cdr->last = cdr;
if (ast_string_field_init(cdr, 64)) {
+ ao2_cleanup(cdr);
return NULL;
}
+ ast_string_field_set(cdr, uniqueid, chan->uniqueid);
ast_string_field_set(cdr, name, chan->name);
ast_string_field_set(cdr, linkedid, chan->linkedid);
cdr->disposition = AST_CDR_NULL;
} else if (left->snapshot->creationtime.tv_sec > right->snapshot->creationtime.tv_sec) {
return right;
} else if (left->snapshot->creationtime.tv_usec > right->snapshot->creationtime.tv_usec) {
- return right;
+ return right;
} else {
/* Okay, fine, take the left one */
return left;
struct ast_cdr *cdr_copy;
/* Don't create records for CDRs where the party A was a dialed channel */
- if (snapshot_is_dialed(it_cdr->party_a.snapshot)) {
+ if (snapshot_is_dialed(it_cdr->party_a.snapshot) && !it_cdr->party_b.snapshot) {
+ ast_debug(1, "CDR for %s is dialed and has no Party B; discarding\n",
+ it_cdr->party_a.snapshot->name);
continue;
}
ast_copy_string(cdr_copy->uniqueid, party_a->uniqueid, sizeof(cdr_copy->uniqueid));
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));
+ ast_copy_string(cdr_copy->dst, it_cdr->exten, sizeof(cdr_copy->dst));
+ ast_copy_string(cdr_copy->dcontext, it_cdr->context, sizeof(cdr_copy->dcontext));
/* Party B */
if (party_b) {
*/
static void cdr_object_finalize(struct cdr_object *cdr)
{
- RAII_VAR(struct module_config *, mod_cfg,
- ao2_global_obj_ref(module_configs), ao2_cleanup);
-
if (!ast_tvzero(cdr->end)) {
return;
}
/* tv_usec is suseconds_t, which could be int or long */
ast_debug(1, "Finalized CDR for %s - start %ld.%06ld answer %ld.%06ld end %ld.%06ld dispo %s\n",
cdr->party_a.snapshot->name,
- cdr->start.tv_sec,
+ (long)cdr->start.tv_sec,
(long)cdr->start.tv_usec,
- cdr->answer.tv_sec,
+ (long)cdr->answer.tv_sec,
(long)cdr->answer.tv_usec,
- cdr->end.tv_sec,
+ (long)cdr->end.tv_sec,
(long)cdr->end.tv_usec,
ast_cdr_disp2str(cdr->disposition));
}
cdr->answer = ast_tvnow();
/* tv_usec is suseconds_t, which could be int or long */
CDR_DEBUG(mod_cfg, "%p - Set answered time to %ld.%06ld\n", cdr,
- cdr->answer.tv_sec,
+ (long)cdr->answer.tv_sec,
(long)cdr->answer.tv_usec);
}
}
RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
ast_assert(strcasecmp(snapshot->name, cdr->party_a.snapshot->name) == 0);
+
+ /* Ignore any snapshots from a dead or dying channel */
+ if (ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
+ && ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)) {
+ cdr_object_check_party_a_hangup(cdr);
+ return 0;
+ }
+
+ /*
+ * Only record the context and extension if we aren't in a subroutine, or if
+ * we are executing hangup logic.
+ */
+ if (!ast_test_flag(&snapshot->flags, AST_FLAG_SUBROUTINE_EXEC)
+ || ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)) {
+ ast_string_field_set(cdr, context, snapshot->context);
+ ast_string_field_set(cdr, exten, snapshot->exten);
+ }
+
cdr_object_swap_snapshot(&cdr->party_a, snapshot);
/* When Party A is originated to an application and the application exits, the stack
&& !ast_test_flag(&cdr->flags, AST_CDR_LOCK_APP)) {
ast_string_field_set(cdr, appl, snapshot->appl);
ast_string_field_set(cdr, data, snapshot->data);
+
+ /* Dial (app_dial) is a special case. Because pre-dial handlers, which
+ * execute before the dial begins, will alter the application/data to
+ * something people typically don't want to see, if we see a channel enter
+ * into Dial here, we set the appl/data accordingly and lock it.
+ */
+ if (!strcmp(snapshot->appl, "Dial")) {
+ ast_set_flag(&cdr->flags, AST_CDR_LOCK_APP);
+ }
}
ast_string_field_set(cdr, linkedid, snapshot->linkedid);
RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
if (caller && !strcasecmp(cdr->party_a.snapshot->name, caller->name)) {
- cdr_object_swap_snapshot(&cdr->party_a, caller);
+ base_process_party_a(cdr, caller);
CDR_DEBUG(mod_cfg, "%p - Updated Party A %s snapshot\n", cdr,
cdr->party_a.snapshot->name);
cdr_object_swap_snapshot(&cdr->party_b, peer);
CDR_DEBUG(mod_cfg, "%p - Updated Party B %s snapshot\n", cdr,
cdr->party_b.snapshot->name);
+
+ /* If we have two parties, lock the application that caused the
+ * two parties to be associated. This prevents mid-call event
+ * macros/gosubs from perturbing the CDR application/data
+ */
+ ast_set_flag(&cdr->flags, AST_CDR_LOCK_APP);
} else if (!strcasecmp(cdr->party_a.snapshot->name, peer->name)) {
/* We're the entity being dialed, i.e., outbound origination */
- cdr_object_swap_snapshot(&cdr->party_a, peer);
+ base_process_party_a(cdr, peer);
CDR_DEBUG(mod_cfg, "%p - Updated Party A %s snapshot\n", cdr,
cdr->party_a.snapshot->name);
}
static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
struct cdr_object *cand_cdr)
{
+ RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
struct cdr_object_snapshot *party_a;
/* Don't match on ourselves */
/* Try the candidate CDR's Party A first */
party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_a);
if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+ CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+ cdr, cdr->party_a.snapshot->name, cand_cdr->party_a.snapshot->name);
cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a);
if (!cand_cdr->party_b.snapshot) {
/* We just stole them - finalize their CDR. Note that this won't
}
party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_b);
if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+ CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+ cdr, cdr->party_a.snapshot->name, cand_cdr->party_b.snapshot->name);
cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_b);
return 0;
}
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);
+ struct ao2_iterator it_cdrs;
+ char *channel_id;
int success = 0;
ast_string_field_set(cdr, bridge, bridge->uniqueid);
- /* Get parties in the bridge */
- it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE,
- cdr_object_bridge_cmp_fn, bridge_id);
- if (!it_cdrs) {
- /* No one in the bridge yet! */
+ if (ao2_container_count(bridge->channels) == 1) {
+ /* No one in the bridge yet but us! */
cdr_object_transition_state(cdr, &bridge_state_fn_table);
return BRIDGE_ENTER_ONLY_PARTY;
}
- while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+ for (it_cdrs = ao2_iterator_init(bridge->channels, 0);
+ !success && (channel_id = ao2_iterator_next(&it_cdrs));
+ ao2_ref(channel_id, -1)) {
+ RAII_VAR(struct cdr_object *, cand_cdr_master,
+ ao2_find(active_cdrs_by_channel, channel_id, OBJ_KEY),
+ ao2_cleanup);
struct cdr_object *cand_cdr;
- RAII_VAR(struct cdr_object *, cdr_cleanup, cand_cdr_master, ao2_cleanup);
- SCOPED_AO2LOCK(lock, cand_cdr_master);
+ if (!cand_cdr_master) {
+ continue;
+ }
+
+ ao2_lock(cand_cdr_master);
for (cand_cdr = cand_cdr_master; cand_cdr; cand_cdr = cand_cdr->next) {
/* Skip any records that are not in a bridge or in this bridge.
* I'm not sure how that would happen, but it pays to be careful. */
success = 1;
break;
}
+ ao2_unlock(cand_cdr_master);
}
- ao2_iterator_destroy(it_cdrs);
+ ao2_iterator_destroy(&it_cdrs);
/* We always transition state, even if we didn't get a peer */
cdr_object_transition_state(cdr, &bridge_state_fn_table);
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)
{
- RAII_VAR(struct module_config *, mod_cfg,
- ao2_global_obj_ref(module_configs), ao2_cleanup);
struct ast_channel_snapshot *party_a;
if (caller) {
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;
+ struct ao2_iterator it_cdrs;
+ char *channel_id;
int success = 0;
ast_string_field_set(cdr, bridge, bridge->uniqueid);
/* Get parties in the bridge */
- it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE,
- cdr_object_bridge_cmp_fn, bridge_id);
- if (!it_cdrs) {
- /* No one in the bridge yet! */
+ if (ao2_container_count(bridge->channels) == 1) {
+ /* No one in the bridge yet but us! */
cdr_object_transition_state(cdr, &bridge_state_fn_table);
return BRIDGE_ENTER_ONLY_PARTY;
}
- while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+ for (it_cdrs = ao2_iterator_init(bridge->channels, 0);
+ !success && (channel_id = ao2_iterator_next(&it_cdrs));
+ ao2_ref(channel_id, -1)) {
+ RAII_VAR(struct cdr_object *, cand_cdr_master,
+ ao2_find(active_cdrs_by_channel, channel_id, OBJ_KEY),
+ ao2_cleanup);
struct cdr_object *cand_cdr;
- RAII_VAR(struct cdr_object *, cdr_cleanup, cand_cdr_master, ao2_cleanup);
- SCOPED_AO2LOCK(lock, cand_cdr_master);
+ if (!cand_cdr_master) {
+ continue;
+ }
+
+ ao2_lock(cand_cdr_master);
for (cand_cdr = cand_cdr_master; cand_cdr; cand_cdr = cand_cdr->next) {
/* Skip any records that are not in a bridge or in this bridge.
* I'm not sure how that would happen, but it pays to be careful. */
success = 1;
break;
}
+ ao2_unlock(cand_cdr_master);
}
- ao2_iterator_destroy(it_cdrs);
+ ao2_iterator_destroy(&it_cdrs);
/* We always transition state, even if we didn't get a peer */
cdr_object_transition_state(cdr, &bridge_state_fn_table);
static int dialed_pending_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
{
- /* We can't handle this as we have a Party B - ask for a new one */
- return 1;
+ if (cdr->party_b.snapshot) {
+ /* We can't handle this as we have a Party B - ask for a new one */
+ return 1;
+ }
+ cdr_object_transition_state(cdr, &parked_state_fn_table);
+ return 0;
}
static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
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)) {
+ if (ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
+ && ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)) {
return 0;
}
return 1;
}
+/*!
+ * \internal
+ * \brief Filter channel snapshots by technology
+ */
+static int filter_channel_snapshot(struct ast_channel_snapshot *snapshot)
+{
+ return snapshot->tech_properties & AST_CHAN_TP_INTERNAL;
+}
+
+/*!
+ * \internal
+ * \brief Filter a channel cache update
+ */
+static int filter_channel_cache_message(struct ast_channel_snapshot *old_snapshot,
+ struct ast_channel_snapshot *new_snapshot)
+{
+ int ret = 0;
+
+ /* Drop cache updates from certain channel technologies */
+ if (old_snapshot) {
+ ret |= filter_channel_snapshot(old_snapshot);
+ }
+ if (new_snapshot) {
+ ret |= filter_channel_snapshot(new_snapshot);
+ }
+
+ return ret;
+}
+
/* TOPIC ROUTER CALLBACKS */
/*!
* \param topic The topic this message was published for
* \param message The message
*/
-static void handle_dial_message(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+static void handle_dial_message(void *data, struct stasis_subscription *sub, struct stasis_message *message)
{
RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
- struct cdr_object *cdr;
+ RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
struct ast_multi_channel_blob *payload = stasis_message_data(message);
struct ast_channel_snapshot *caller;
struct ast_channel_snapshot *peer;
(unsigned int)stasis_message_timestamp(message)->tv_sec,
(unsigned int)stasis_message_timestamp(message)->tv_usec);
+ if (filter_channel_snapshot(peer) || (caller && filter_channel_snapshot(caller))) {
+ return;
+ }
+
/* Figure out who is running this show */
if (caller) {
- cdr = ao2_find(active_cdrs_by_channel, caller->name, OBJ_KEY);
+ cdr = ao2_find(active_cdrs_by_channel, caller->uniqueid, OBJ_KEY);
} else {
- cdr = ao2_find(active_cdrs_by_channel, peer->name, OBJ_KEY);
+ cdr = ao2_find(active_cdrs_by_channel, peer->uniqueid, OBJ_KEY);
}
if (!cdr) {
ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", caller ? caller->name : peer->name);
+ ast_assert(0);
return;
}
continue;
}
CDR_DEBUG(mod_cfg, "%p - Processing Dial Begin message for channel %s, peer %s\n",
- cdr,
+ it_cdr,
caller ? caller->name : "(none)",
peer ? peer->name : "(none)");
res &= it_cdr->fn_table->process_dial_begin(it_cdr,
continue;
}
CDR_DEBUG(mod_cfg, "%p - Processing Dial End message for channel %s, peer %s\n",
- cdr,
+ it_cdr,
caller ? caller->name : "(none)",
peer ? peer->name : "(none)");
it_cdr->fn_table->process_dial_end(it_cdr,
new_cdr = cdr_object_create_and_append(cdr);
if (!new_cdr) {
+ ao2_unlock(cdr);
return;
}
new_cdr->fn_table->process_dial_begin(new_cdr,
return 0;
}
-/*!
- * \internal
- * \brief Filter channel snapshots by technology
- */
-static int filter_channel_snapshot(struct ast_channel_snapshot *snapshot)
-{
- return snapshot->tech_properties & AST_CHAN_TP_INTERNAL;
-}
-
-/*!
- * \internal
- * \brief Filter a channel cache update
- */
-static int filter_channel_cache_message(struct ast_channel_snapshot *old_snapshot,
- struct ast_channel_snapshot *new_snapshot)
-{
- int ret = 0;
-
- /* Drop cache updates from certain channel technologies */
- if (old_snapshot) {
- ret |= filter_channel_snapshot(old_snapshot);
- }
- if (new_snapshot) {
- ret |= filter_channel_snapshot(new_snapshot);
- }
-
- return ret;
-}
-
/*! \brief Determine if we need to add a new CDR based on snapshots */
static int check_new_cdr_needed(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 (!new_snapshot) {
- return 0;
- }
-
- if (ast_test_flag(&new_snapshot->flags, AST_FLAG_DEAD)) {
+ /* If we're dead, we don't need a new CDR */
+ if (!new_snapshot
+ || (ast_test_flag(&new_snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
+ && ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN))) {
return 0;
}
* \param topic The topic this message was published for
* \param message The message
*/
-static void handle_channel_cache_message(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+static void handle_channel_cache_message(void *data, struct stasis_subscription *sub, struct stasis_message *message)
{
RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
struct stasis_cache_update *update = stasis_message_data(message);
struct ast_channel_snapshot *old_snapshot;
struct ast_channel_snapshot *new_snapshot;
+ const char *uniqueid;
const char *name;
struct cdr_object *it_cdr;
old_snapshot = stasis_message_data(update->old_snapshot);
new_snapshot = stasis_message_data(update->new_snapshot);
+ uniqueid = new_snapshot ? new_snapshot->uniqueid : old_snapshot->uniqueid;
name = new_snapshot ? new_snapshot->name : old_snapshot->name;
if (filter_channel_cache_message(old_snapshot, new_snapshot)) {
return;
}
- CDR_DEBUG(mod_cfg, "Channel Update message for %s: %u.%08u\n",
- name,
- (unsigned int)stasis_message_timestamp(message)->tv_sec,
- (unsigned int)stasis_message_timestamp(message)->tv_usec);
-
if (new_snapshot && !old_snapshot) {
cdr = cdr_object_alloc(new_snapshot);
if (!cdr) {
/* Handle Party A */
if (!cdr) {
- cdr = ao2_find(active_cdrs_by_channel, name, OBJ_KEY);
+ cdr = ao2_find(active_cdrs_by_channel, uniqueid, OBJ_KEY);
}
if (!cdr) {
ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", name);
+ ast_assert(0);
} else {
ao2_lock(cdr);
if (new_snapshot) {
if (!it_cdr->fn_table->process_party_a) {
continue;
}
- CDR_DEBUG(mod_cfg, "%p - Processing new channel snapshot %s\n", it_cdr, new_snapshot->name);
all_reject &= it_cdr->fn_table->process_party_a(it_cdr, new_snapshot);
}
if (all_reject && check_new_cdr_needed(old_snapshot, new_snapshot)) {
* \param message The message - hopefully a bridge one!
*/
static void handle_bridge_leave_message(void *data, struct stasis_subscription *sub,
- struct stasis_topic *topic, struct stasis_message *message)
+ struct stasis_message *message)
{
struct ast_bridge_blob *update = stasis_message_data(message);
struct ast_bridge_snapshot *bridge = update->bridge;
RAII_VAR(struct module_config *, mod_cfg,
ao2_global_obj_ref(module_configs), ao2_cleanup);
RAII_VAR(struct cdr_object *, cdr,
- ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY),
+ ao2_find(active_cdrs_by_channel, channel->uniqueid, OBJ_KEY),
ao2_cleanup);
struct cdr_object *it_cdr;
struct bridge_leave_data leave_data = {
return;
}
+ if (filter_channel_snapshot(channel)) {
+ return;
+ }
+
CDR_DEBUG(mod_cfg, "Bridge Leave message for %s: %u.%08u\n",
channel->name,
(unsigned int)stasis_message_timestamp(message)->tv_sec,
if (!cdr) {
ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
+ ast_assert(0);
return;
}
left_bridge = 1;
}
}
+ ao2_unlock(cdr);
if (!left_bridge) {
- ao2_unlock(cdr);
return;
}
- ao2_unlock(cdr);
if (strcmp(bridge->subclass, "parking")) {
/* Party B */
}
}
-struct bridge_candidate {
- struct cdr_object *cdr; /*!< The actual CDR this candidate belongs to, either as A or B */
- struct cdr_object_snapshot candidate; /*!< The candidate for a new pairing */
-};
-
-/*! \internal
- * \brief Comparison function for \ref bridge_candidate objects
- */
-static int bridge_candidate_cmp_fn(void *obj, void *arg, int flags)
-{
- struct bridge_candidate *left = obj;
- struct bridge_candidate *right = arg;
- const char *match = (flags & OBJ_KEY) ? arg : right->candidate.snapshot->name;
- return strcasecmp(left->candidate.snapshot->name, match) ? 0 : (CMP_MATCH | CMP_STOP);
-}
-
-/*! \internal
- * \brief Hash function for \ref bridge_candidate objects
- */
-static int bridge_candidate_hash_fn(const void *obj, const int flags)
-{
- const struct bridge_candidate *bc = obj;
- const char *id = (flags & OBJ_KEY) ? obj : bc->candidate.snapshot->name;
- return ast_str_case_hash(id);
-}
-
-/*! \brief \ref bridge_candidate Destructor */
-static void bridge_candidate_dtor(void *obj)
-{
- struct bridge_candidate *bcand = obj;
- ao2_cleanup(bcand->cdr);
- ao2_cleanup(bcand->candidate.snapshot);
- free_variables(&bcand->candidate.variables);
-}
-
-/*!
- * \brief \ref bridge_candidate Constructor
- * \param cdr The \ref cdr_object that is a candidate for being compared to in
- * a bridge operation
- * \param candidate The \ref cdr_object_snapshot candidate snapshot in the CDR
- * that should be used during the operaton
- */
-static struct bridge_candidate *bridge_candidate_alloc(struct cdr_object *cdr, struct cdr_object_snapshot *candidate)
-{
- struct bridge_candidate *bcand;
-
- bcand = ao2_alloc(sizeof(*bcand), bridge_candidate_dtor);
- if (!bcand) {
- return NULL;
- }
- bcand->cdr = cdr;
- ao2_ref(bcand->cdr, +1);
- bcand->candidate.flags = candidate->flags;
- strcpy(bcand->candidate.userfield, candidate->userfield);
- bcand->candidate.snapshot = candidate->snapshot;
- ao2_ref(bcand->candidate.snapshot, +1);
- copy_variables(&bcand->candidate.variables, &candidate->variables);
-
- return bcand;
-}
-
-/*!
- * \internal
- * \brief Build and add bridge candidates based on a CDR
- *
- * \param bridge_id The ID of the bridge we need candidates for
- * \param candidates The container of \ref bridge_candidate objects
- * \param cdr The \ref cdr_object that is our candidate
- * \param party_a Non-zero if we should look at the Party A channel; 0 if Party B
- */
-static void add_candidate_for_bridge(const char *bridge_id,
- struct ao2_container *candidates,
- struct cdr_object *cdr,
- int party_a)
-{
- struct cdr_object *it_cdr;
-
- for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
- struct cdr_object_snapshot *party_snapshot;
- RAII_VAR(struct bridge_candidate *, bcand, NULL, ao2_cleanup);
-
- party_snapshot = party_a ? &it_cdr->party_a : &it_cdr->party_b;
-
- if (it_cdr->fn_table != &bridge_state_fn_table || strcmp(bridge_id, it_cdr->bridge)) {
- continue;
- }
-
- if (!party_snapshot->snapshot) {
- continue;
- }
-
- /* Don't add a party twice */
- bcand = ao2_find(candidates, party_snapshot->snapshot->name, OBJ_KEY);
- if (bcand) {
- continue;
- }
-
- bcand = bridge_candidate_alloc(it_cdr, party_snapshot);
- if (bcand) {
- ao2_link(candidates, bcand);
- }
- }
-}
-
-/*!
- * \brief Create new \ref bridge_candidate objects for each party currently
- * in a bridge
- * \param bridge The \param ast_bridge_snapshot for the bridge we're processing
- *
- * Note that we use two passes here instead of one so that we only create a
- * candidate for a party B if they are never a party A in the bridge. Otherwise,
- * we don't care about them.
- */
-static struct ao2_container *create_candidates_for_bridge(struct ast_bridge_snapshot *bridge)
-{
- struct ao2_container *candidates = ao2_container_alloc(51, bridge_candidate_hash_fn, bridge_candidate_cmp_fn);
- char *bridge_id = ast_strdupa(bridge->uniqueid);
- struct ao2_iterator *it_cdrs;
- struct cdr_object *cand_cdr_master;
-
- if (!candidates) {
- return NULL;
- }
-
- /* For each CDR that has a record in the bridge, get their Party A and
- * make them a candidate. Note that we do this in two passes as opposed to one so
- * that we give preference CDRs where the channel is Party A */
- it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE,
- cdr_object_bridge_cmp_fn, bridge_id);
- if (!it_cdrs) {
- /* No one in the bridge yet! */
- ao2_cleanup(candidates);
- return NULL;
- }
- for (; (cand_cdr_master = ao2_iterator_next(it_cdrs)); ao2_cleanup(cand_cdr_master)) {
- SCOPED_AO2LOCK(lock, cand_cdr_master);
- add_candidate_for_bridge(bridge->uniqueid, candidates, cand_cdr_master, 1);
- }
- ao2_iterator_destroy(it_cdrs);
- /* For each CDR that has a record in the bridge, get their Party B and
- * make them a candidate. */
- it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE,
- cdr_object_bridge_cmp_fn, bridge_id);
- if (!it_cdrs) {
- /* Now it's just an error. */
- ao2_cleanup(candidates);
- return NULL;
- }
- for (; (cand_cdr_master = ao2_iterator_next(it_cdrs)); ao2_cleanup(cand_cdr_master)) {
- SCOPED_AO2LOCK(lock, cand_cdr_master);
- add_candidate_for_bridge(bridge->uniqueid, candidates, cand_cdr_master, 0);
- }
- ao2_iterator_destroy(it_cdrs);
-
- return candidates;
-}
-
/*!
* \internal
* \brief Create a new CDR, append it to an existing CDR, and update its snapshots
* \note The new CDR will be automatically transitioned to the bridge state
*/
static void bridge_candidate_add_to_cdr(struct cdr_object *cdr,
- const char *bridge_id,
struct cdr_object_snapshot *party_b)
{
+ RAII_VAR(struct module_config *, mod_cfg,
+ ao2_global_obj_ref(module_configs), ao2_cleanup);
struct cdr_object *new_cdr;
new_cdr = cdr_object_create_and_append(cdr);
cdr_object_check_party_a_answer(new_cdr);
ast_string_field_set(new_cdr, bridge, cdr->bridge);
cdr_object_transition_state(new_cdr, &bridge_state_fn_table);
+ CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+ new_cdr, new_cdr->party_a.snapshot->name,
+ party_b->snapshot->name);
}
/*!
- * \brief Process a single \ref bridge_candidate. Note that this is called as
- * part of an \ref ao2_callback on an \ref ao2_container of \ref bridge_candidate
- * objects previously created by \ref create_candidates_for_bridge.
+ * \brief Process a single \ref bridge_candidate
*
- * \param obj The \ref bridge_candidate being processed
- * \param arg The \ref cdr_object that is being compared against the candidates
+ * When a CDR enters a bridge, it needs to make pairings with everyone else
+ * that it is not currently paired with. This function determines, for the
+ * CDR for the channel that entered the bridge and the CDR for every other
+ * channel currently in the bridge, who is Party A and makes new CDRs.
+ *
+ * \param cdr The \ref cdr_obj being processed
+ * \param cand_cdr The \ref cdr_object that is a candidate
*
- * The purpose of this function is to create the necessary CDR entries as a
- * result of \ref cdr_object having entered the same bridge as the CDR
- * represented by \ref bridge_candidate.
*/
-static int bridge_candidate_process(void *obj, void *arg, int flags)
+static int bridge_candidate_process(struct cdr_object *cdr, struct cdr_object *base_cand_cdr)
{
- struct bridge_candidate *bcand = obj;
- struct cdr_object *cdr = arg;
+ RAII_VAR(struct module_config *, mod_cfg,
+ ao2_global_obj_ref(module_configs), ao2_cleanup);
struct cdr_object_snapshot *party_a;
+ struct cdr_object *cand_cdr;
- /* If the candidate is us or someone we've taken on, pass on by */
- if (!strcasecmp(cdr->party_a.snapshot->name, bcand->candidate.snapshot->name)
- || (cdr->party_b.snapshot
- && !strcasecmp(cdr->party_b.snapshot->name, bcand->candidate.snapshot->name))) {
- return 0;
- }
- party_a = cdr_object_pick_party_a(&cdr->party_a, &bcand->candidate);
- /* We're party A - make a new CDR, append it to us, and set the candidate as
- * Party B */
- if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
- bridge_candidate_add_to_cdr(cdr, cdr->bridge, &bcand->candidate);
- return 0;
- }
+ SCOPED_AO2LOCK(lock, base_cand_cdr);
+
+ for (cand_cdr = base_cand_cdr; cand_cdr; cand_cdr = cand_cdr->next) {
+ /* Skip any records that are not in this bridge */
+ if (strcmp(cand_cdr->bridge, cdr->bridge)) {
+ continue;
+ }
+
+ /* If the candidate is us or someone we've taken on, pass on by */
+ if (!strcasecmp(cdr->party_a.snapshot->name, cand_cdr->party_a.snapshot->name)
+ || (cdr->party_b.snapshot
+ && !strcasecmp(cdr->party_b.snapshot->name, cand_cdr->party_a.snapshot->name))) {
+ return 0;
+ }
+
+ party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_a);
+ /* We're party A - make a new CDR, append it to us, and set the candidate as
+ * Party B */
+ if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+ bridge_candidate_add_to_cdr(cdr, &cand_cdr->party_a);
+ return 0;
+ }
- /* We're Party B. Check if the candidate is the CDR's Party A. If so, find out if we
- * can add ourselves directly as the Party B, or if we need a new CDR. */
- if (!strcasecmp(bcand->cdr->party_a.snapshot->name, bcand->candidate.snapshot->name)) {
- if (bcand->cdr->party_b.snapshot
- && strcasecmp(bcand->cdr->party_b.snapshot->name, cdr->party_a.snapshot->name)) {
- bridge_candidate_add_to_cdr(bcand->cdr, cdr->bridge, &cdr->party_a);
+ /* We're Party B. Check if we can add ourselves immediately or if we need
+ * a new CDR for them (they already have a Party B) */
+ if (cand_cdr->party_b.snapshot
+ && strcasecmp(cand_cdr->party_b.snapshot->name, cdr->party_a.snapshot->name)) {
+ bridge_candidate_add_to_cdr(cand_cdr, &cdr->party_a);
} else {
- cdr_object_snapshot_copy(&bcand->cdr->party_b, &cdr->party_a);
+ CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+ cand_cdr, cand_cdr->party_a.snapshot->name,
+ cdr->party_a.snapshot->name);
+ cdr_object_snapshot_copy(&cand_cdr->party_b, &cdr->party_a);
/* It's possible that this joined at one point and was never chosen
* as party A. Clear their end time, as it would be set in such a
* case.
*/
- memset(&bcand->cdr->end, 0, sizeof(bcand->cdr->end));
- }
- } else {
- /* We are Party B to a candidate CDR's Party B. Since a candidate
- * CDR will only have a Party B represented here if that channel
- * was never a Party A in the bridge, we have to go looking for
- * that channel's primary CDR record.
- */
- struct cdr_object *b_party = ao2_find(active_cdrs_by_channel, bcand->candidate.snapshot->name, OBJ_KEY);
- if (!b_party) {
- /* Holy cow - no CDR? */
- b_party = cdr_object_alloc(bcand->candidate.snapshot);
- cdr_object_snapshot_copy(&b_party->party_a, &bcand->candidate);
- cdr_object_snapshot_copy(&b_party->party_b, &cdr->party_a);
- cdr_object_check_party_a_answer(b_party);
- ast_string_field_set(b_party, bridge, cdr->bridge);
- cdr_object_transition_state(b_party, &bridge_state_fn_table);
- ao2_link(active_cdrs_by_channel, b_party);
- } else {
- bridge_candidate_add_to_cdr(b_party, cdr->bridge, &cdr->party_a);
+ memset(&cand_cdr->end, 0, sizeof(cand_cdr->end));
}
- ao2_ref(b_party, -1);
+ return 0;
}
-
return 0;
}
*/
static void handle_bridge_pairings(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge)
{
- RAII_VAR(struct ao2_container *, candidates,
- create_candidates_for_bridge(bridge),
+ struct ao2_iterator it_channels;
+ char *channel_id;
+
+ it_channels = ao2_iterator_init(bridge->channels, 0);
+ while ((channel_id = ao2_iterator_next(&it_channels))) {
+ RAII_VAR(struct cdr_object *, cand_cdr,
+ ao2_find(active_cdrs_by_channel, channel_id, OBJ_KEY),
ao2_cleanup);
- if (!candidates) {
- return;
- }
- ao2_callback(candidates, OBJ_NODATA,
- bridge_candidate_process,
- cdr);
+ if (!cand_cdr) {
+ ao2_ref(channel_id, -1);
+ continue;
+ }
- return;
+ bridge_candidate_process(cdr, cand_cdr);
+
+ ao2_ref(channel_id, -1);
+ }
+ ao2_iterator_destroy(&it_channels);
}
/*! \brief Handle entering into a parking bridge
if (!handled_cdr) {
handled_cdr = it_cdr;
}
- break;
+ break;
case BRIDGE_ENTER_NEED_CDR:
/* Pass */
- break;
+ 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.
handled_cdr = it_cdr;
}
cdr_object_finalize(cdr);
- break;
+ break;
}
}
}
}
/*!
+ * \internal
* \brief Handler for Stasis-Core bridge enter messages
* \param data Passed on
* \param sub The stasis subscription for this message callback
* \param message The message - hopefully a bridge one!
*/
static void handle_bridge_enter_message(void *data, struct stasis_subscription *sub,
- struct stasis_topic *topic, struct stasis_message *message)
+ struct stasis_message *message)
{
struct ast_bridge_blob *update = stasis_message_data(message);
struct ast_bridge_snapshot *bridge = update->bridge;
struct ast_channel_snapshot *channel = update->channel;
RAII_VAR(struct cdr_object *, cdr,
- ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY),
+ ao2_find(active_cdrs_by_channel, channel->uniqueid, OBJ_KEY),
ao2_cleanup);
RAII_VAR(struct module_config *, mod_cfg,
ao2_global_obj_ref(module_configs), ao2_cleanup);
return;
}
+ if (filter_channel_snapshot(channel)) {
+ return;
+ }
+
CDR_DEBUG(mod_cfg, "Bridge Enter message for channel %s: %u.%08u\n",
channel->name,
(unsigned int)stasis_message_timestamp(message)->tv_sec,
if (!cdr) {
ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
+ ast_assert(0);
return;
}
* \param message The message about who got parked
* */
static void handle_parked_call_message(void *data, struct stasis_subscription *sub,
- struct stasis_topic *topic, struct stasis_message *message)
+ struct stasis_message *message)
{
struct ast_parked_call_payload *payload = stasis_message_data(message);
struct ast_channel_snapshot *channel = payload->parkee;
RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
RAII_VAR(struct module_config *, mod_cfg,
ao2_global_obj_ref(module_configs), ao2_cleanup);
+ int unhandled = 1;
struct cdr_object *it_cdr;
/* Anything other than getting parked will be handled by other updates */
return;
}
+ if (filter_channel_snapshot(channel)) {
+ return;
+ }
+
CDR_DEBUG(mod_cfg, "Parked Call message for channel %s: %u.%08u\n",
channel->name,
(unsigned int)stasis_message_timestamp(message)->tv_sec,
(unsigned int)stasis_message_timestamp(message)->tv_usec);
- cdr = ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY);
+ cdr = ao2_find(active_cdrs_by_channel, channel->uniqueid, OBJ_KEY);
if (!cdr) {
ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
+ ast_assert(0);
return;
}
for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
if (it_cdr->fn_table->process_parked_channel) {
- it_cdr->fn_table->process_parked_channel(it_cdr, payload);
+ unhandled &= it_cdr->fn_table->process_parked_channel(it_cdr, payload);
+ }
+ }
+
+ if (unhandled) {
+ /* Nothing handled the messgae - we need a new one! */
+ struct cdr_object *new_cdr = cdr_object_create_and_append(cdr);
+ if (new_cdr) {
+ /* As the new CDR is created in the single state, it is guaranteed
+ * to have a function for the parked call message and will handle
+ * the message */
+ new_cdr->fn_table->process_parked_channel(new_cdr, payload);
}
}
}
+/*!
+ * \brief Handler for a synchronization message
+ * \param data Passed on
+ * \param sub The stasis subscription for this message callback
+ * \param topic The topic this message was published for
+ * \param message A blank ao2 object
+ * */
+static void handle_cdr_sync_message(void *data, struct stasis_subscription *sub,
+ struct stasis_message *message)
+{
+ return;
+}
+
struct ast_cdr_config *ast_cdr_get_config(void)
{
RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
void ast_cdr_set_config(struct ast_cdr_config *config)
{
RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
ao2_cleanup(mod_cfg->general);
mod_cfg->general = config;
ao2_ref(mod_cfg->general, +1);
+
+ cdr_toggle_runtime_options();
}
int ast_cdr_is_enabled(void)
return ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED);
}
+int ast_cdr_backend_suspend(const char *name)
+{
+ int success = -1;
+ struct cdr_beitem *i = NULL;
+
+ AST_RWLIST_WRLOCK(&be_list);
+ AST_RWLIST_TRAVERSE(&be_list, i, list) {
+ if (!strcasecmp(name, i->name)) {
+ ast_debug(3, "Suspending CDR backend %s\n", i->name);
+ i->suspended = 1;
+ success = 0;
+ }
+ }
+ AST_RWLIST_UNLOCK(&be_list);
+
+ return success;
+}
+
+int ast_cdr_backend_unsuspend(const char *name)
+{
+ int success = -1;
+ struct cdr_beitem *i = NULL;
+
+ AST_RWLIST_WRLOCK(&be_list);
+ AST_RWLIST_TRAVERSE(&be_list, i, list) {
+ if (!strcasecmp(name, i->name)) {
+ ast_debug(3, "Unsuspending CDR backend %s\n", i->name);
+ i->suspended = 0;
+ success = 0;
+ }
+ }
+ AST_RWLIST_UNLOCK(&be_list);
+
+ return success;
+}
+
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
{
struct cdr_beitem *i = NULL;
return 0;
}
-void ast_cdr_unregister(const char *name)
+int ast_cdr_unregister(const char *name)
{
- struct cdr_beitem *i = NULL;
+ struct cdr_beitem *match = NULL;
+ int active_count;
AST_RWLIST_WRLOCK(&be_list);
- AST_RWLIST_TRAVERSE_SAFE_BEGIN(&be_list, i, list) {
- if (!strcasecmp(name, i->name)) {
- AST_RWLIST_REMOVE_CURRENT(list);
+ AST_RWLIST_TRAVERSE(&be_list, match, list) {
+ if (!strcasecmp(name, match->name)) {
break;
}
}
- AST_RWLIST_TRAVERSE_SAFE_END;
- AST_RWLIST_UNLOCK(&be_list);
- if (i) {
- ast_verb(2, "Unregistered '%s' CDR backend\n", name);
- ast_free(i);
+ if (!match) {
+ AST_RWLIST_UNLOCK(&be_list);
+ return 0;
+ }
+
+ active_count = ao2_container_count(active_cdrs_by_channel);
+
+ if (!match->suspended && active_count != 0) {
+ AST_RWLIST_UNLOCK(&be_list);
+ ast_log(AST_LOG_WARNING, "Unable to unregister CDR backend %s; %d CDRs are still active\n",
+ name, active_count);
+ return -1;
}
+
+ AST_RWLIST_REMOVE(&be_list, match, list);
+ AST_RWLIST_UNLOCK(&be_list);
+
+ ast_verb(2, "Unregistered '%s' CDR backend\n", name);
+ ast_free(match);
+
+ return 0;
}
struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr)
if (fmt == NULL) { /* raw mode */
snprintf(buf, bufsize, "%ld.%06ld", (long)when.tv_sec, (long)when.tv_usec);
} else {
+ buf[0] = '\0';/* Ensure the buffer is initialized. */
if (when.tv_sec) {
struct ast_tm tm;
/*
* \internal
- * \brief Callback that finds all CDRs that reference a particular channel
+ * \brief Callback that finds all CDRs that reference a particular channel by name
*/
-static int cdr_object_select_all_by_channel_cb(void *obj, void *arg, int flags)
+static int cdr_object_select_all_by_name_cb(void *obj, void *arg, int flags)
{
struct cdr_object *cdr = obj;
const char *name = arg;
return 0;
}
+/*
+ * \internal
+ * \brief Callback that finds a CDR by channel name
+ */
+static int cdr_object_get_by_name_cb(void *obj, void *arg, int flags)
+{
+ struct cdr_object *cdr = obj;
+ const char *name = arg;
+
+ if (!strcasecmp(cdr->party_a.snapshot->name, name)) {
+ return CMP_MATCH;
+ }
+ return 0;
+}
+
/* 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", "total_duration", "total_billsec", "first_start",
- "first_answer", 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",
+ NULL
+};
int ast_cdr_setvar(const char *channel_name, const char *name, const char *value)
{
}
}
- it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE, cdr_object_select_all_by_channel_cb, arg);
+ it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE, cdr_object_select_all_by_name_cb, arg);
if (!it_cdrs) {
ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
return -1;
} else if (!strcasecmp(name, "billsec")) {
snprintf(value, length, "%ld", cdr_object_get_billsec(cdr_obj));
} else if (!strcasecmp(name, "disposition")) {
- snprintf(value, length, "%d", cdr_obj->disposition);
+ snprintf(value, length, "%u", cdr_obj->disposition);
} else if (!strcasecmp(name, "amaflags")) {
snprintf(value, length, "%d", party_a->amaflags);
} else if (!strcasecmp(name, "accountcode")) {
} else if (!strcasecmp(name, "userfield")) {
ast_copy_string(value, cdr_obj->party_a.userfield, length);
} else if (!strcasecmp(name, "sequence")) {
- snprintf(value, length, "%d", cdr_obj->sequence);
+ snprintf(value, length, "%u", cdr_obj->sequence);
} else {
return 1;
}
return 0;
}
+/*! \internal
+ * \brief Look up and retrieve a CDR object by channel name
+ * \param name The name of the channel
+ * \retval NULL on error
+ * \retval The \ref cdr_object for the channel on success, with the reference
+ * count bumped by one.
+ */
+static struct cdr_object *cdr_object_get_by_name(const char *name)
+{
+ char *param;
+
+ if (ast_strlen_zero(name)) {
+ return NULL;
+ }
+
+ param = ast_strdupa(name);
+ return ao2_callback(active_cdrs_by_channel, 0, cdr_object_get_by_name_cb, param);
+}
+
int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length)
{
- RAII_VAR(struct cdr_object *, cdr,
- ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
- ao2_cleanup);
+ RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
struct cdr_object *cdr_obj;
if (!cdr) {
int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf, char delim, char sep)
{
- RAII_VAR(struct cdr_object *, cdr,
- ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
- ao2_cleanup);
+ RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
struct cdr_object *it_cdr;
struct ast_var_t *variable;
const char *var;
int total = 0, x = 0, i;
if (!workspace) {
- return 1;
+ return 0;
}
if (!cdr) {
- ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
- return 1;
+ RAII_VAR(struct module_config *, mod_cfg,
+ ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+ if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+ ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
+ }
+
+ return 0;
}
ast_str_reset(*buf);
}
for (i = 0; cdr_readonly_vars[i]; i++) {
- /* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */
- workspace[0] = 0;
- cdr_object_format_property(it_cdr, cdr_readonly_vars[i], workspace, sizeof(workspace));
+ if (cdr_object_format_property(it_cdr, cdr_readonly_vars[i], workspace, sizeof(workspace))) {
+ /* Unhandled read-only CDR variable. */
+ ast_assert(0);
+ continue;
+ }
if (!ast_strlen_zero(workspace)
&& ast_str_append(buf, 0, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, workspace, sep) < 0) {
void ast_cdr_setuserfield(const char *channel_name, const char *userfield)
{
- RAII_VAR(struct cdr_object *, cdr,
- ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
- ao2_cleanup);
+ RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
struct party_b_userfield_update party_b_info = {
.channel_name = channel_name,
.userfield = userfield,
}
AST_RWLIST_RDLOCK(&be_list);
AST_RWLIST_TRAVERSE(&be_list, i, list) {
- i->be(cdr);
+ if (!i->suspended) {
+ i->be(cdr);
+ }
}
AST_RWLIST_UNLOCK(&be_list);
}
int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option)
{
- RAII_VAR(struct cdr_object *, cdr,
- ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
- ao2_cleanup);
+ RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
struct cdr_object *it_cdr;
if (!cdr) {
int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option)
{
- RAII_VAR(struct cdr_object *, cdr,
- ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
- ao2_cleanup);
+ RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
struct cdr_object *it_cdr;
if (!cdr) {
return 0;
}
-int ast_cdr_reset(const char *channel_name, struct ast_flags *options)
+int ast_cdr_reset(const char *channel_name, int keep_variables)
{
- RAII_VAR(struct cdr_object *, cdr,
- ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
- ao2_cleanup);
+ RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
struct ast_var_t *vardata;
struct cdr_object *it_cdr;
ao2_lock(cdr);
for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
/* clear variables */
- if (!ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) {
+ if (!keep_variables) {
while ((vardata = AST_LIST_REMOVE_HEAD(&it_cdr->party_a.variables, entries))) {
ast_var_delete(vardata);
}
int ast_cdr_fork(const char *channel_name, struct ast_flags *options)
{
- RAII_VAR(struct cdr_object *, cdr,
- ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
- ao2_cleanup);
+ RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
struct cdr_object *new_cdr;
struct cdr_object *it_cdr;
struct cdr_object *cdr_obj;
{
SCOPED_AO2LOCK(lock, cdr);
+
cdr_obj = cdr->last;
if (cdr_obj->fn_table == &finalized_state_fn_table) {
/* If the last CDR in the chain is finalized, don't allow a fork -
}
new_cdr->fn_table = cdr_obj->fn_table;
ast_string_field_set(new_cdr, bridge, cdr->bridge);
+ ast_string_field_set(new_cdr, appl, cdr->appl);
+ ast_string_field_set(new_cdr, data, cdr->data);
+ ast_string_field_set(new_cdr, context, cdr->context);
+ ast_string_field_set(new_cdr, exten, cdr->exten);
new_cdr->flags = cdr->flags;
+ /* Explicitly clear the AST_CDR_LOCK_APP flag - we want
+ * the application to be changed on the new CDR if the
+ * dialplan demands it
+ */
+ ast_clear_flag(&new_cdr->flags, AST_CDR_LOCK_APP);
/* If there's a Party B, copy it over as well */
if (cdr_obj->party_b.snapshot) {
/* manually reschedule from this point in time */
ast_mutex_lock(&cdr_sched_lock);
- cdr_sched = ast_sched_add(sched, mod_cfg->general->batch_settings.size * 1000, submit_scheduled_batch, NULL);
+ cdr_sched = ast_sched_add(sched, mod_cfg->general->batch_settings.time * 1000, submit_scheduled_batch, NULL);
ast_mutex_unlock(&cdr_sched_lock);
/* returning zero so the scheduler does not automatically reschedule */
return 0;
struct ao2_iterator it_cdrs;
struct cdr_object *cdr;
char start_time_buffer[64];
- char answer_time_buffer[64] = "\0";
+ char answer_time_buffer[64];
char end_time_buffer[64];
#define TITLE_STRING "%-25.25s %-25.25s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8s %-8.8s\n"
struct cdr_object *it_cdr;
char clid[64];
char start_time_buffer[64];
- char answer_time_buffer[64] = "\0";
- char end_time_buffer[64] = "\0";
+ char answer_time_buffer[64];
+ char end_time_buffer[64];
const char *channel_name = a->argv[3];
RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
#define TITLE_STRING "%-10.10s %-20.20s %-25.25s %-15.15s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8s %-8.8s\n"
#define FORMAT_STRING "%-10.10s %-20.20s %-25.25s %-15.15s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8ld %-8.8ld\n"
- cdr = ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY);
+ cdr = cdr_object_get_by_name(channel_name);
if (!cdr) {
ast_cli(a->fd, "Unknown channel: %s\n", channel_name);
return;
ast_cli(a->fd, " Safe shutdown: %s\n", ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN) ? "Enabled" : "Disabled");
ast_cli(a->fd, " Threading model: %s\n", ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SCHEDULER_ONLY) ? "Scheduler only" : "Scheduler plus separate threads");
ast_cli(a->fd, " Current batch size: %d record%s\n", cnt, ESS(cnt));
- ast_cli(a->fd, " Maximum batch size: %d record%s\n", mod_cfg->general->batch_settings.size, ESS(mod_cfg->general->batch_settings.size));
- ast_cli(a->fd, " Maximum batch time: %d second%s\n", mod_cfg->general->batch_settings.time, ESS(mod_cfg->general->batch_settings.time));
+ ast_cli(a->fd, " Maximum batch size: %u record%s\n", mod_cfg->general->batch_settings.size, ESS(mod_cfg->general->batch_settings.size));
+ ast_cli(a->fd, " Maximum batch time: %u second%s\n", mod_cfg->general->batch_settings.time, ESS(mod_cfg->general->batch_settings.time));
ast_cli(a->fd, " Next batch processing time: %ld second%s\n\n", nextbatchtime, ESS(nextbatchtime));
}
ast_cli(a->fd, "* Registered Backends\n");
ast_cli(a->fd, " (none)\n");
} else {
AST_RWLIST_TRAVERSE(&be_list, beitem, list) {
- ast_cli(a->fd, " %s\n", beitem->name);
+ ast_cli(a->fd, " %s%s\n", beitem->name, beitem->suspended ? " (suspended) " : "");
}
}
AST_RWLIST_UNLOCK(&be_list);
ast_cdr_engine_term();
}
+struct stasis_message_router *ast_cdr_message_router(void)
+{
+ if (!stasis_router) {
+ return NULL;
+ }
+
+ ao2_bump(stasis_router);
+ return stasis_router;
+}
+
+/*!
+ * \brief Destroy the active Stasis subscriptions
+ */
+static void destroy_subscriptions(void)
+{
+ channel_subscription = stasis_forward_cancel(channel_subscription);
+ bridge_subscription = stasis_forward_cancel(bridge_subscription);
+ parking_subscription = stasis_forward_cancel(parking_subscription);
+}
+
+/*!
+ * \brief Create the Stasis subcriptions for CDRs
+ */
+static int create_subscriptions(void)
+{
+ if (!cdr_topic) {
+ return -1;
+ }
+
+ if (channel_subscription || bridge_subscription || parking_subscription) {
+ return 0;
+ }
+
+ channel_subscription = stasis_forward_all(ast_channel_topic_all_cached(), cdr_topic);
+ if (!channel_subscription) {
+ return -1;
+ }
+ bridge_subscription = stasis_forward_all(ast_bridge_topic_all_cached(), cdr_topic);
+ if (!bridge_subscription) {
+ return -1;
+ }
+ parking_subscription = stasis_forward_all(ast_parking_topic(), cdr_topic);
+ if (!parking_subscription) {
+ return -1;
+ }
+
+ return 0;
+}
+
static int process_config(int reload)
{
RAII_VAR(struct module_config *, mod_cfg, module_config_alloc(), ao2_cleanup);
*/
if (!reload && !(aco_set_defaults(&general_option, "general", mod_cfg->general))) {
ast_log(LOG_NOTICE, "Failed to process CDR configuration; using defaults\n");
- ao2_global_obj_replace(module_configs, mod_cfg);
+ ao2_global_obj_replace_unref(module_configs, mod_cfg);
return 0;
}
return 1;
}
- if (reload) {
- manager_event(EVENT_FLAG_SYSTEM, "Reload", "Module: CDR\r\nMessage: CDR subsystem reload requested\r\n");
- }
return 0;
}
static void cdr_engine_cleanup(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);
+ destroy_subscriptions();
}
static void cdr_engine_shutdown(void)
{
+ stasis_message_router_unsubscribe_and_join(stasis_router);
+ stasis_router = NULL;
+
+ ao2_cleanup(cdr_topic);
+ cdr_topic = NULL;
+
+ STASIS_MESSAGE_TYPE_CLEANUP(cdr_sync_message_type);
+
ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_dispatch_all_cb,
NULL);
finalize_batch_mode();
aco_info_destroy(&cfg_info);
ao2_global_obj_release(module_configs);
+ ao2_container_unregister("cdrs_by_channel");
ao2_ref(active_cdrs_by_channel, -1);
+ active_cdrs_by_channel = NULL;
}
static void cdr_enable_batch_mode(struct ast_cdr_config *config)
/* Kill the currently scheduled item */
AST_SCHED_DEL(sched, cdr_sched);
cdr_sched = ast_sched_add(sched, config->batch_settings.time * 1000, submit_scheduled_batch, NULL);
- ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n",
+ ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %u or time %u seconds.\n",
config->batch_settings.size, config->batch_settings.time);
}
}
}
-
-int ast_cdr_engine_init(void)
+/*!
+ * \brief Checks if CDRs are enabled and enables/disables the necessary options
+ */
+static int cdr_toggle_runtime_options(void)
{
- RAII_VAR(struct module_config *, mod_cfg, NULL, ao2_cleanup);
+ RAII_VAR(struct module_config *, mod_cfg,
+ ao2_global_obj_ref(module_configs), ao2_cleanup);
- if (process_config(0)) {
- return -1;
+ if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+ if (create_subscriptions()) {
+ destroy_subscriptions();
+ ast_log(AST_LOG_ERROR, "Failed to create Stasis subscriptions\n");
+ return -1;
+ }
+ if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+ cdr_enable_batch_mode(mod_cfg->general);
+ } else {
+ ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
+ }
+ } else {
+ destroy_subscriptions();
+ ast_log(LOG_NOTICE, "CDR logging disabled.\n");
}
- /* The prime here should be the same as the channel container */
- active_cdrs_by_channel = ao2_container_alloc(51, cdr_object_channel_hash_fn, cdr_object_channel_cmp_fn);
- if (!active_cdrs_by_channel) {
+ return 0;
+}
+
+int ast_cdr_engine_init(void)
+{
+ if (process_config(0)) {
return -1;
}
- ao2_container_register("cdrs_by_channel", active_cdrs_by_channel, cdr_container_print_fn);
cdr_topic = stasis_topic_create("cdr_engine");
if (!cdr_topic) {
return -1;
}
- channel_subscription = stasis_forward_all(ast_channel_topic_all_cached(), cdr_topic);
- if (!channel_subscription) {
- return -1;
- }
- bridge_subscription = stasis_forward_all(ast_bridge_topic_all_cached(), cdr_topic);
- if (!bridge_subscription) {
- return -1;
- }
- parking_subscription = stasis_forward_all(ast_parking_topic(), cdr_topic);
- if (!parking_subscription) {
+ stasis_router = stasis_message_router_create(cdr_topic);
+ if (!stasis_router) {
return -1;
}
- stasis_router = stasis_message_router_create(cdr_topic);
- if (!stasis_router) {
+ if (STASIS_MESSAGE_TYPE_INIT(cdr_sync_message_type)) {
return -1;
}
+
stasis_message_router_add_cache_update(stasis_router, ast_channel_snapshot_type(), handle_channel_cache_message, NULL);
stasis_message_router_add(stasis_router, ast_channel_dial_type(), handle_dial_message, NULL);
stasis_message_router_add(stasis_router, ast_channel_entered_bridge_type(), handle_bridge_enter_message, NULL);
stasis_message_router_add(stasis_router, ast_channel_left_bridge_type(), handle_bridge_leave_message, NULL);
stasis_message_router_add(stasis_router, ast_parked_call_type(), handle_parked_call_message, NULL);
+ stasis_message_router_add(stasis_router, cdr_sync_message_type(), handle_cdr_sync_message, NULL);
+
+ active_cdrs_by_channel = ao2_container_alloc(NUM_CDR_BUCKETS,
+ cdr_object_channel_hash_fn, cdr_object_channel_cmp_fn);
+ if (!active_cdrs_by_channel) {
+ return -1;
+ }
+ ao2_container_register("cdrs_by_channel", active_cdrs_by_channel, cdr_container_print_fn);
sched = ast_sched_context_create();
if (!sched) {
ast_register_cleanup(cdr_engine_cleanup);
ast_register_atexit(cdr_engine_shutdown);
- mod_cfg = ao2_global_obj_ref(module_configs);
-
- if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
- if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
- cdr_enable_batch_mode(mod_cfg->general);
- } else {
- ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
- }
- } else {
- ast_log(LOG_NOTICE, "CDR logging disabled.\n");
- }
-
- return 0;
+ return cdr_toggle_runtime_options();
}
void ast_cdr_engine_term(void)
{
RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+ RAII_VAR(void *, payload, NULL, ao2_cleanup);
+ RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
/* Since this is called explicitly during process shutdown, we might not have ever
* been initialized. If so, the config object will be NULL.
if (!mod_cfg) {
return;
}
- if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
- return;
+
+ if (cdr_sync_message_type()) {
+ /* Make sure we have the needed items */
+ payload = ao2_alloc(sizeof(*payload), NULL);
+ if (!stasis_router || !payload) {
+ return;
+ }
+
+ ast_debug(1, "CDR Engine termination request received; waiting on messages...\n");
+
+ message = stasis_message_create(cdr_sync_message_type(), payload);
+ if (message) {
+ stasis_message_router_publish_sync(stasis_router, message);
+ }
+ }
+
+ if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+ cdr_submit_batch(ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN));
}
- cdr_submit_batch(ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN));
}
int ast_cdr_engine_reload(void)
}
}
- if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
- if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
- ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
- } else {
- cdr_enable_batch_mode(mod_cfg->general);
- }
- } else {
- ast_log(LOG_NOTICE, "CDR logging disabled, data will be lost.\n");
- }
-
- return 0;
+ return cdr_toggle_runtime_options();
}