summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorRussell Bryant <russell@russellbryant.com>2009-06-26 15:28:53 +0000
committerRussell Bryant <russell@russellbryant.com>2009-06-26 15:28:53 +0000
commit0264eef1156b8ef7369884dd5c663646f1b2b429 (patch)
treea28e9113cf1daf97e45a8fc6d41a52c76ac69836 /main
parente06c6f97c4c222b4c802ac2b85f76a331991dffb (diff)
Merge the new Channel Event Logging (CEL) subsystem.
CEL is the new system for logging channel events. This was inspired after facing many problems trying to represent what is possible to happen to a call in Asterisk using CDR records. For more information on CEL, see the built in HTML or PDF documentation generated from the files in doc/tex/. Many thanks to Steve Murphy (murf) and Brian Degenhardt (bmd) for their hard work developing this code. Also, thanks to Matt Nicholson (mnicholson) and Sean Bright (seanbright) for their assistance in the final push to get this code ready for Asterisk trunk. Review: https://reviewboard.asterisk.org/r/239/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@203638 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'main')
-rw-r--r--main/asterisk.c6
-rw-r--r--main/cdr.c62
-rw-r--r--main/cel.c652
-rw-r--r--main/channel.c333
-rw-r--r--main/cli.c13
-rw-r--r--main/devicestate.c2
-rw-r--r--main/dial.c2
-rw-r--r--main/event.c64
-rw-r--r--main/features.c70
-rw-r--r--main/loader.c1
-rw-r--r--main/logger.c2
-rw-r--r--main/manager.c2
-rw-r--r--main/pbx.c44
13 files changed, 1168 insertions, 85 deletions
diff --git a/main/asterisk.c b/main/asterisk.c
index e83534787..b497a64a5 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -118,6 +118,7 @@ int daemon(int, int); /* defined in libresolv of all places */
#include "asterisk/term.h"
#include "asterisk/manager.h"
#include "asterisk/cdr.h"
+#include "asterisk/cel.h"
#include "asterisk/pbx.h"
#include "asterisk/enum.h"
#include "asterisk/http.h"
@@ -3599,6 +3600,11 @@ int main(int argc, char *argv[])
exit(1);
}
+ if (ast_cel_engine_init()) {
+ printf("%s", term_quit());
+ exit(1);
+ }
+
if (ast_device_state_engine_init()) {
printf("%s", term_quit());
exit(1);
diff --git a/main/cdr.c b/main/cdr.c
index 610c2ab31..0658ead75 100644
--- a/main/cdr.c
+++ b/main/cdr.c
@@ -271,8 +271,12 @@ void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *wor
}
} else if (!strcasecmp(name, "accountcode"))
ast_copy_string(workspace, cdr->accountcode, workspacelen);
+ else if (!strcasecmp(name, "peeraccount"))
+ ast_copy_string(workspace, cdr->peeraccount, workspacelen);
else if (!strcasecmp(name, "uniqueid"))
ast_copy_string(workspace, cdr->uniqueid, workspacelen);
+ else if (!strcasecmp(name, "linkedid"))
+ ast_copy_string(workspace, cdr->linkedid, workspacelen);
else if (!strcasecmp(name, "userfield"))
ast_copy_string(workspace, cdr->userfield, workspacelen);
else if ((varbuf = ast_cdr_getvar_internal(cdr, name, recur)))
@@ -287,7 +291,7 @@ void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *wor
/* readonly 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",
+ "billsec", "disposition", "amaflags", "accountcode", "uniqueid", "linkedid",
"userfield", NULL };
/*! Set a CDR channel variable
\note You can't set the CDR variables that belong to the actual CDR record, like "billsec".
@@ -298,9 +302,6 @@ int ast_cdr_setvar(struct ast_cdr *cdr, const char *name, const char *value, int
struct varshead *headp;
int x;
- if (!cdr) /* don't die if the cdr is null */
- return -1;
-
for (x = 0; cdr_readonly_vars[x]; x++) {
if (!strcasecmp(name, cdr_readonly_vars[x])) {
ast_log(LOG_ERROR, "Attempt to set the '%s' read-only variable!.\n", name);
@@ -644,6 +645,9 @@ void ast_cdr_merge(struct ast_cdr *to, struct ast_cdr *from)
if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->accountcode) && !ast_strlen_zero(from->accountcode))) {
ast_copy_string(to->accountcode, from->accountcode, sizeof(to->accountcode));
}
+ if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->peeraccount) && !ast_strlen_zero(from->peeraccount))) {
+ ast_copy_string(to->peeraccount, from->peeraccount, sizeof(to->peeraccount));
+ }
if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->userfield) && !ast_strlen_zero(from->userfield))) {
ast_copy_string(to->userfield, from->userfield, sizeof(to->userfield));
}
@@ -856,11 +860,14 @@ int ast_cdr_init(struct ast_cdr *cdr, struct ast_channel *c)
cdr->disposition = (c->_state == AST_STATE_UP) ? AST_CDR_ANSWERED : AST_CDR_NOANSWER;
cdr->amaflags = c->amaflags ? c->amaflags : ast_default_amaflags;
ast_copy_string(cdr->accountcode, c->accountcode, sizeof(cdr->accountcode));
+ ast_copy_string(cdr->peeraccount, c->peeraccount, sizeof(cdr->peeraccount));
/* Destination information */
ast_copy_string(cdr->dst, S_OR(c->macroexten,c->exten), sizeof(cdr->dst));
ast_copy_string(cdr->dcontext, S_OR(c->macrocontext,c->context), sizeof(cdr->dcontext));
/* Unique call identifier */
ast_copy_string(cdr->uniqueid, c->uniqueid, sizeof(cdr->uniqueid));
+ /* Linked call identifier */
+ ast_copy_string(cdr->linkedid, c->linkedid, sizeof(cdr->linkedid));
}
}
return 0;
@@ -938,9 +945,11 @@ char *ast_cdr_flags2str(int flag)
int ast_cdr_setaccount(struct ast_channel *chan, const char *account)
{
struct ast_cdr *cdr = chan->cdr;
- char buf[BUFSIZ/2] = "";
- if (!ast_strlen_zero(chan->accountcode))
- ast_copy_string(buf, chan->accountcode, sizeof(buf));
+ const char *old_acct = "";
+
+ if (!ast_strlen_zero(chan->accountcode)) {
+ old_acct = ast_strdupa(chan->accountcode);
+ }
ast_string_field_set(chan, accountcode, account);
for ( ; cdr ; cdr = cdr->next) {
@@ -949,8 +958,39 @@ int ast_cdr_setaccount(struct ast_channel *chan, const char *account)
}
}
- /* Signal change of account code to manager */
- manager_event(EVENT_FLAG_CALL, "NewAccountCode", "Channel: %s\r\nUniqueid: %s\r\nAccountCode: %s\r\nOldAccountCode: %s\r\n", chan->name, chan->uniqueid, chan->accountcode, buf);
+ manager_event(EVENT_FLAG_CALL, "NewAccountCode",
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "AccountCode: %s\r\n"
+ "OldAccountCode: %s\r\n",
+ chan->name, chan->uniqueid, chan->accountcode, old_acct);
+
+ return 0;
+}
+
+int ast_cdr_setpeeraccount(struct ast_channel *chan, const char *account)
+{
+ struct ast_cdr *cdr = chan->cdr;
+ const char *old_acct = "";
+
+ if (!ast_strlen_zero(chan->peeraccount)) {
+ old_acct = ast_strdupa(chan->peeraccount);
+ }
+
+ ast_string_field_set(chan, peeraccount, account);
+ for ( ; cdr ; cdr = cdr->next) {
+ if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
+ ast_copy_string(cdr->peeraccount, chan->peeraccount, sizeof(cdr->peeraccount));
+ }
+ }
+
+ manager_event(EVENT_FLAG_CALL, "NewPeerAccount",
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "PeerAccount: %s\r\n"
+ "OldPeerAccount: %s\r\n",
+ chan->name, chan->uniqueid, chan->peeraccount, old_acct);
+
return 0;
}
@@ -1005,7 +1045,9 @@ int ast_cdr_update(struct ast_channel *c)
/* Copy account code et-al */
ast_copy_string(cdr->accountcode, c->accountcode, sizeof(cdr->accountcode));
-
+ ast_copy_string(cdr->peeraccount, c->peeraccount, sizeof(cdr->peeraccount));
+ ast_copy_string(cdr->linkedid, c->linkedid, sizeof(cdr->linkedid));
+
/* Destination information */ /* XXX privilege macro* ? */
ast_copy_string(cdr->dst, S_OR(c->macroexten, c->exten), sizeof(cdr->dst));
ast_copy_string(cdr->dcontext, S_OR(c->macrocontext, c->context), sizeof(cdr->dcontext));
diff --git a/main/cel.c b/main/cel.c
new file mode 100644
index 000000000..26b0fd9fa
--- /dev/null
+++ b/main/cel.c
@@ -0,0 +1,652 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2007 - 2009, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ *
+ * \brief Channel Event Logging API
+ *
+ * \author Steve Murphy <murf@digium.com>
+ * \author Russell Bryant <russell@digium.com>
+ *
+ * \todo Do thorough testing of all transfer methods to ensure that BLINDTRANSFER,
+ * ATTENDEDTRANSFER, BRIDGE_START, and BRIDGE_END events are all reported
+ * as expected.
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/cel.h"
+#include "asterisk/logger.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/utils.h"
+#include "asterisk/config.h"
+#include "asterisk/cli.h"
+#include "asterisk/astobj2.h"
+
+/*! Is the CEL subsystem enabled ? */
+static unsigned char cel_enabled;
+
+/*! \brief CEL is off by default */
+static const unsigned char CEL_ENALBED_DEFAULT = 0;
+
+/*!
+ * \brief which events we want to track
+ *
+ * \note bit field, up to 64 events
+ */
+static int64_t eventset;
+
+/*!
+ * \brief Maximum possible CEL event IDs
+ * \note This limit is currently imposed by the eventset definition
+ */
+#define CEL_MAX_EVENT_IDS 64
+
+/*!
+ * \brief Track no events by default.
+ */
+static const int64_t CEL_DEFAULT_EVENTS = 0;
+
+/*!
+ * \brief Number of buckets for the appset container
+ */
+static const int NUM_APP_BUCKETS = 97;
+
+/*!
+ * \brief Container of Asterisk application names
+ *
+ * The apps in this container are the applications that were specified
+ * in the configuration as applications that CEL events should be generated
+ * for when they start and end on a channel.
+ */
+static struct ao2_container *appset;
+
+/*!
+ * \brief Configured date format for event timestamps
+ */
+static char cel_dateformat[256];
+
+/*!
+ * \brief Map of ast_cel_event_type to strings
+ */
+static const char const *cel_event_types[CEL_MAX_EVENT_IDS] = {
+ [0] = "ALL",
+ [AST_CEL_CHANNEL_START] = "CHAN_START",
+ [AST_CEL_CHANNEL_END] = "CHAN_END",
+ [AST_CEL_ANSWER] = "ANSWER",
+ [AST_CEL_HANGUP] = "HANGUP",
+ [AST_CEL_APP_START] = "APP_START",
+ [AST_CEL_APP_END] = "APP_END",
+ [AST_CEL_BRIDGE_START] = "BRIDGE_START",
+ [AST_CEL_BRIDGE_END] = "BRIDGE_END",
+ [AST_CEL_BRIDGE_UPDATE] = "BRIDGE_UPDATE",
+ [AST_CEL_CONF_START] = "CONF_START",
+ [AST_CEL_CONF_END] = "CONF_END",
+ [AST_CEL_PARK_START] = "PARK_START",
+ [AST_CEL_PARK_END] = "PARK_END",
+ [AST_CEL_TRANSFER] = "TRANSFER",
+ [AST_CEL_USER_DEFINED] = "USER_DEFINED",
+ [AST_CEL_CONF_ENTER] = "CONF_ENTER",
+ [AST_CEL_CONF_EXIT] = "CONF_EXIT",
+ [AST_CEL_BLINDTRANSFER] = "BLINDTRANSFER",
+ [AST_CEL_ATTENDEDTRANSFER] = "ATTENDEDTRANSFER",
+ [AST_CEL_PICKUP] = "PICKUP",
+ [AST_CEL_FORWARD] = "FORWARD",
+ [AST_CEL_3WAY_START] = "3WAY_START",
+ [AST_CEL_3WAY_END] = "3WAY_END",
+ [AST_CEL_HOOKFLASH] = "HOOKFLASH",
+ [AST_CEL_LINKEDID_END] = "LINKEDID_END",
+};
+
+/*!
+ * \brief Map of ast_cel_ama_flags to strings
+ */
+static const char const *cel_ama_flags[AST_CEL_AMA_FLAG_TOTAL] = {
+ [AST_CEL_AMA_FLAG_OMIT] = "OMIT",
+ [AST_CEL_AMA_FLAG_BILLING] = "BILLING",
+ [AST_CEL_AMA_FLAG_DOCUMENTATION] = "DOCUMENTATION",
+};
+
+unsigned int ast_cel_check_enabled(void)
+{
+ return cel_enabled;
+}
+
+static int print_app(void *obj, void *arg, int flags)
+{
+ struct ast_cli_args *a = arg;
+
+ ast_cli(a->fd, "CEL Tracking Application: %s\n", (const char *) obj);
+
+ return 0;
+}
+
+static void print_cel_sub(const struct ast_event *event, void *data)
+{
+ struct ast_cli_args *a = data;
+
+ ast_cli(a->fd, "CEL Event Subscriber: %s\n",
+ ast_event_get_ie_str(event, AST_EVENT_IE_DESCRIPTION));
+}
+
+static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ unsigned int i;
+ struct ast_event_sub *sub;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "cel show status";
+ e->usage =
+ "Usage: cel show status\n"
+ " Displays the Channel Event Logging system status.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ case CLI_HANDLER:
+ break;
+ }
+
+ if (a->argc > 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ ast_cli(a->fd, "CEL Logging: %s\n", cel_enabled ? "Enabled" : "Disabled");
+
+ if (!cel_enabled) {
+ return CLI_SUCCESS;
+ }
+
+ for (i = 0; i < (sizeof(eventset) * 8); i++) {
+ const char *name;
+
+ if (!(eventset & ((int64_t) 1 << i))) {
+ continue;
+ }
+
+ name = ast_cel_get_type_name(i);
+ if (strcasecmp(name, "Unknown")) {
+ ast_cli(a->fd, "CEL Tracking Event: %s\n", name);
+ }
+ }
+
+ ao2_callback(appset, OBJ_NODATA, print_app, a);
+
+ if (!(sub = ast_event_subscribe_new(AST_EVENT_SUB, print_cel_sub, a))) {
+ return CLI_FAILURE;
+ }
+ ast_event_sub_append_ie_uint(sub, AST_EVENT_IE_EVENTTYPE, AST_EVENT_CEL);
+ ast_event_report_subs(sub);
+ ast_event_sub_destroy(sub);
+ sub = NULL;
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_status = AST_CLI_DEFINE(handle_cli_status, "Display the CEL status");
+
+enum ast_cel_event_type ast_cel_str_to_event_type(const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_LEN(cel_event_types); i++) {
+ if (!cel_event_types[i]) {
+ continue;
+ }
+
+ if (!strcasecmp(name, cel_event_types[i])) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static int ast_cel_track_event(enum ast_cel_event_type et)
+{
+ return (eventset & ((int64_t) 1 << et));
+}
+
+static void parse_events(const char *val)
+{
+ char *events = ast_strdupa(val);
+ char *cur_event;
+
+ while ((cur_event = strsep(&events, ","))) {
+ enum ast_cel_event_type event_type;
+
+ cur_event = ast_strip(cur_event);
+ if (ast_strlen_zero(cur_event)) {
+ continue;
+ }
+
+ event_type = ast_cel_str_to_event_type(cur_event);
+
+ if (event_type == 0) {
+ /* All events */
+ eventset = (int64_t) -1;
+ } else if (event_type == -1) {
+ ast_log(LOG_WARNING, "Unknown event name '%s'\n",
+ cur_event);
+ } else {
+ eventset |= ((int64_t) 1 << event_type);
+ }
+ }
+}
+
+static void parse_apps(const char *val)
+{
+ char *apps = ast_strdupa(val);
+ char *cur_app;
+
+ if (!ast_cel_track_event(AST_CEL_APP_START) && !ast_cel_track_event(AST_CEL_APP_END)) {
+ ast_log(LOG_WARNING, "An apps= config line, but not tracking APP events\n");
+ return;
+ }
+
+ while ((cur_app = strsep(&apps, ","))) {
+ char *app;
+
+ cur_app = ast_strip(cur_app);
+ if (ast_strlen_zero(cur_app)) {
+ continue;
+ }
+
+ if (!(app = ao2_alloc(strlen(cur_app) + 1, NULL))) {
+ continue;
+ }
+ strcpy(app, cur_app);
+
+ ao2_link(appset, app);
+ ao2_ref(app, -1);
+ app = NULL;
+ }
+}
+
+AST_MUTEX_DEFINE_STATIC(reload_lock);
+
+static int do_reload(void)
+{
+ struct ast_config *config;
+ const char *enabled_value;
+ const char *val;
+ int res = 0;
+ struct ast_flags config_flags = { 0, };
+ const char *s;
+
+ ast_mutex_lock(&reload_lock);
+
+ /* Reset all settings before reloading configuration */
+ cel_enabled = CEL_ENALBED_DEFAULT;
+ eventset = CEL_DEFAULT_EVENTS;
+ *cel_dateformat = '\0';
+ ao2_callback(appset, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
+
+ config = ast_config_load2("cel.conf", "cel", config_flags);
+
+ if (config == CONFIG_STATUS_FILEMISSING) {
+ config = NULL;
+ goto return_cleanup;
+ }
+
+ if ((enabled_value = ast_variable_retrieve(config, "general", "enable"))) {
+ cel_enabled = ast_true(enabled_value);
+ }
+
+ if (!cel_enabled) {
+ goto return_cleanup;
+ }
+
+ /* get the date format for logging */
+ if ((s = ast_variable_retrieve(config, "general", "dateformat"))) {
+ ast_copy_string(cel_dateformat, s, sizeof(cel_dateformat));
+ }
+
+ if ((val = ast_variable_retrieve(config, "general", "events"))) {
+ parse_events(val);
+ }
+
+ if ((val = ast_variable_retrieve(config, "general", "apps"))) {
+ parse_apps(val);
+ }
+
+return_cleanup:
+ ast_verb(3, "CEL logging %sabled.\n", cel_enabled ? "en" : "dis");
+
+ ast_mutex_unlock(&reload_lock);
+
+ if (config) {
+ ast_config_destroy(config);
+ }
+
+ return res;
+}
+
+const char *ast_cel_get_type_name(enum ast_cel_event_type type)
+{
+ return S_OR(cel_event_types[type], "Unknown");
+}
+
+const char *ast_cel_get_ama_flag_name(enum ast_cel_ama_flag flag)
+{
+ return S_OR(cel_ama_flags[flag], "Unknown");
+}
+
+/* called whenever a channel is destroyed or a linkedid is changed to
+ * potentially emit a CEL_LINKEDID_END event */
+
+struct channel_find_data {
+ const struct ast_channel *chan;
+ const char *linkedid;
+};
+
+static int linkedid_match(void *obj, void *arg, void *data, int flags)
+{
+ struct ast_channel *c = obj;
+ struct channel_find_data *find_dat = data;
+ int res;
+
+ ast_channel_lock(c);
+ res = (c != find_dat->chan && c->linkedid && !strcmp(find_dat->linkedid, c->linkedid));
+ ast_channel_unlock(c);
+
+ return res ? CMP_MATCH | CMP_STOP : 0;
+}
+
+void ast_cel_check_retire_linkedid(struct ast_channel *chan)
+{
+ const char *linkedid = chan->linkedid;
+ struct channel_find_data find_dat;
+
+ /* make sure we need to do all this work */
+
+ if (!ast_strlen_zero(linkedid) && ast_cel_track_event(AST_CEL_LINKEDID_END)) {
+ struct ast_channel *tmp = NULL;
+ find_dat.chan = chan;
+ find_dat.linkedid = linkedid;
+ if ((tmp = ast_channel_callback(linkedid_match, NULL, &find_dat, 0))) {
+ tmp = ast_channel_unref(tmp);
+ } else {
+ ast_cel_report_event(chan, AST_CEL_LINKEDID_END, NULL, NULL, NULL);
+ }
+ }
+}
+
+struct ast_channel *ast_cel_fabricate_channel_from_event(const struct ast_event *event)
+{
+ struct varshead *headp;
+ struct ast_var_t *newvariable;
+ char timebuf[30];
+ struct ast_channel *tchan;
+ struct ast_cel_event_record record = {
+ .version = AST_CEL_EVENT_RECORD_VERSION,
+ };
+
+ /* do not call ast_channel_alloc because this is not really a real channel */
+ if (!(tchan = ast_dummy_channel_alloc())) {
+ return NULL;
+ }
+
+ headp = &tchan->varshead;
+
+ /* first, get the variables from the event */
+ if (ast_cel_fill_record(event, &record)) {
+ ast_channel_release(tchan);
+ return NULL;
+ }
+
+ /* next, fill the channel with their data */
+ if ((newvariable = ast_var_assign("eventtype", record.event_name))) {
+ AST_LIST_INSERT_HEAD(headp, newvariable, entries);
+ }
+
+ if (ast_strlen_zero(cel_dateformat)) {
+ snprintf(timebuf, sizeof(timebuf), "%ld.%06ld", record.event_time.tv_sec, record.event_time.tv_usec);
+ } else {
+ struct ast_tm tm;
+ ast_localtime(&record.event_time, &tm, NULL);
+ ast_strftime(timebuf, sizeof(timebuf), cel_dateformat, &tm);
+ }
+
+ if ((newvariable = ast_var_assign("eventtime", timebuf))) {
+ AST_LIST_INSERT_HEAD(headp, newvariable, entries);
+ }
+
+ if ((newvariable = ast_var_assign("eventextra", record.extra))) {
+ AST_LIST_INSERT_HEAD(headp, newvariable, entries);
+ }
+
+ tchan->cid.cid_name = ast_strdup(record.caller_id_name);
+ tchan->cid.cid_num = ast_strdup(record.caller_id_num);
+ tchan->cid.cid_ani = ast_strdup(record.caller_id_ani);
+ tchan->cid.cid_rdnis = ast_strdup(record.caller_id_rdnis);
+ tchan->cid.cid_dnid = ast_strdup(record.caller_id_dnid);
+
+ ast_copy_string(tchan->exten, record.extension, sizeof(tchan->exten));
+ ast_copy_string(tchan->context, record.context, sizeof(tchan->context));
+ ast_string_field_set(tchan, name, record.channel_name);
+ ast_string_field_set(tchan, uniqueid, record.unique_id);
+ ast_string_field_set(tchan, linkedid, record.linked_id);
+ ast_string_field_set(tchan, accountcode, record.account_code);
+ ast_string_field_set(tchan, peeraccount, record.peer_account);
+ ast_string_field_set(tchan, userfield, record.user_field);
+
+ pbx_builtin_setvar_helper(tchan, "BRIDGEPEER", record.peer);
+
+ tchan->appl = ast_strdup(record.application_name);
+ tchan->data = ast_strdup(record.application_data);
+ tchan->amaflags = record.amaflag;
+
+ return tchan;
+}
+
+int ast_cel_report_event(struct ast_channel *chan, enum ast_cel_event_type event_type,
+ const char *userdefevname, const char *extra, struct ast_channel *peer2)
+{
+ struct timeval eventtime;
+ struct ast_event *ev;
+ const char *peername = "";
+ struct ast_channel *peer;
+
+ ast_channel_lock(chan);
+ peer = ast_bridged_channel(chan);
+ if (peer) {
+ ast_channel_ref(peer);
+ }
+ ast_channel_unlock(chan);
+
+ /* Make sure a reload is not occurring while we're checking to see if this
+ * is an event that we care about. We could lose an important event in this
+ * process otherwise. */
+ ast_mutex_lock(&reload_lock);
+
+ if (!cel_enabled || !ast_cel_track_event(event_type)) {
+ ast_mutex_unlock(&reload_lock);
+ return 0;
+ }
+
+ if (event_type == AST_CEL_APP_START || event_type == AST_CEL_APP_END) {
+ char *app;
+ if (!(app = ao2_find(appset, (char *) chan->appl, OBJ_POINTER))) {
+ ast_mutex_unlock(&reload_lock);
+ return 0;
+ }
+ ao2_ref(app, -1);
+ }
+
+ ast_mutex_unlock(&reload_lock);
+
+ if (peer) {
+ ast_channel_lock(peer);
+ peername = ast_strdupa(peer->name);
+ ast_channel_unlock(peer);
+ } else if (peer2) {
+ ast_channel_lock(peer2);
+ peername = ast_strdupa(peer2->name);
+ ast_channel_unlock(peer2);
+ }
+
+ if (!userdefevname) {
+ userdefevname = "";
+ }
+
+ if (!extra) {
+ extra = "";
+ }
+
+ eventtime = ast_tvnow();
+
+ ast_channel_lock(chan);
+
+ ev = ast_event_new(AST_EVENT_CEL,
+ AST_EVENT_IE_CEL_EVENT_TYPE, AST_EVENT_IE_PLTYPE_UINT, event_type,
+ AST_EVENT_IE_CEL_EVENT_TIME, AST_EVENT_IE_PLTYPE_UINT, eventtime.tv_sec,
+ AST_EVENT_IE_CEL_EVENT_TIME_USEC, AST_EVENT_IE_PLTYPE_UINT, eventtime.tv_usec,
+ AST_EVENT_IE_CEL_USEREVENT_NAME, AST_EVENT_IE_PLTYPE_STR, userdefevname,
+ AST_EVENT_IE_CEL_CIDNAME, AST_EVENT_IE_PLTYPE_STR, S_OR(chan->cid.cid_name, ""),
+ AST_EVENT_IE_CEL_CIDNUM, AST_EVENT_IE_PLTYPE_STR, S_OR(chan->cid.cid_num, ""),
+ AST_EVENT_IE_CEL_CIDANI, AST_EVENT_IE_PLTYPE_STR, S_OR(chan->cid.cid_ani, ""),
+ AST_EVENT_IE_CEL_CIDRDNIS, AST_EVENT_IE_PLTYPE_STR, S_OR(chan->cid.cid_rdnis, ""),
+ AST_EVENT_IE_CEL_CIDDNID, AST_EVENT_IE_PLTYPE_STR, S_OR(chan->cid.cid_dnid, ""),
+ AST_EVENT_IE_CEL_EXTEN, AST_EVENT_IE_PLTYPE_STR, chan->exten,
+ AST_EVENT_IE_CEL_CONTEXT, AST_EVENT_IE_PLTYPE_STR, chan->context,
+ AST_EVENT_IE_CEL_CHANNAME, AST_EVENT_IE_PLTYPE_STR, chan->name,
+ AST_EVENT_IE_CEL_APPNAME, AST_EVENT_IE_PLTYPE_STR, S_OR(chan->appl, ""),
+ AST_EVENT_IE_CEL_APPDATA, AST_EVENT_IE_PLTYPE_STR, S_OR(chan->data, ""),
+ AST_EVENT_IE_CEL_AMAFLAGS, AST_EVENT_IE_PLTYPE_UINT, chan->amaflags,
+ AST_EVENT_IE_CEL_ACCTCODE, AST_EVENT_IE_PLTYPE_STR, chan->accountcode,
+ AST_EVENT_IE_CEL_PEERACCT, AST_EVENT_IE_PLTYPE_STR, chan->peeraccount,
+ AST_EVENT_IE_CEL_UNIQUEID, AST_EVENT_IE_PLTYPE_STR, chan->uniqueid,
+ AST_EVENT_IE_CEL_LINKEDID, AST_EVENT_IE_PLTYPE_STR, chan->linkedid,
+ AST_EVENT_IE_CEL_USERFIELD, AST_EVENT_IE_PLTYPE_STR, chan->userfield,
+ AST_EVENT_IE_CEL_EXTRA, AST_EVENT_IE_PLTYPE_STR, extra,
+ AST_EVENT_IE_CEL_PEER, AST_EVENT_IE_PLTYPE_STR, peername,
+ AST_EVENT_IE_END);
+
+ ast_channel_unlock(chan);
+
+ if (peer) {
+ peer = ast_channel_unref(peer);
+ }
+
+ if (ev && ast_event_queue(ev)) {
+ ast_event_destroy(ev);
+ return -1;
+ }
+
+ return 0;
+}
+
+int ast_cel_fill_record(const struct ast_event *e, struct ast_cel_event_record *r)
+{
+ if (r->version != AST_CEL_EVENT_RECORD_VERSION) {
+ ast_log(LOG_ERROR, "Module ABI mismatch for ast_cel_event_record. "
+ "Please ensure all modules were compiled for "
+ "this version of Asterisk.\n");
+ return -1;
+ }
+
+ r->event_type = ast_event_get_ie_uint(e, AST_EVENT_IE_CEL_EVENT_TYPE);
+
+ r->event_time.tv_sec = ast_event_get_ie_uint(e, AST_EVENT_IE_CEL_EVENT_TIME);
+ r->event_time.tv_usec = ast_event_get_ie_uint(e, AST_EVENT_IE_CEL_EVENT_TIME_USEC);
+
+ r->user_defined_name = "";
+
+ if (r->event_type == AST_CEL_USER_DEFINED) {
+ r->user_defined_name = ast_event_get_ie_str(e, AST_EVENT_IE_CEL_USEREVENT_NAME);
+ r->event_name = r->user_defined_name;
+ } else {
+ r->event_name = ast_cel_get_type_name(r->event_type);
+ }
+
+ r->caller_id_name = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_CIDNAME), "");
+ r->caller_id_num = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_CIDNUM), "");
+ r->caller_id_ani = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_CIDANI), "");
+ r->caller_id_rdnis = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_CIDRDNIS), "");
+ r->caller_id_dnid = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_CIDDNID), "");
+ r->extension = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_EXTEN), "");
+ r->context = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_CONTEXT), "");
+ r->channel_name = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_CHANNAME), "");
+ r->application_name = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_APPNAME), "");
+ r->application_data = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_APPDATA), "");
+ r->account_code = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_ACCTCODE), "");
+ r->peer_account = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_ACCTCODE), "");
+ r->unique_id = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_UNIQUEID), "");
+ r->linked_id = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_LINKEDID), "");
+ r->amaflag = ast_event_get_ie_uint(e, AST_EVENT_IE_CEL_AMAFLAGS);
+ r->user_field = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_USERFIELD), "");
+ r->peer = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_PEER), "");
+ r->extra = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_EXTRA), "");
+
+ return 0;
+}
+
+static int app_hash(const void *obj, const int flags)
+{
+ return ast_str_case_hash((const char *) obj);
+}
+
+static int app_cmp(void *obj, void *arg, int flags)
+{
+ const char *app1 = obj, *app2 = arg;
+
+ return !strcasecmp(app1, app2) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static void ast_cel_engine_term(void)
+{
+ if (appset) {
+ ao2_ref(appset, -1);
+ appset = NULL;
+ }
+}
+
+int ast_cel_engine_init(void)
+{
+ if (!(appset = ao2_container_alloc(NUM_APP_BUCKETS, app_hash, app_cmp))) {
+ return -1;
+ }
+
+ if (do_reload()) {
+ ao2_ref(appset, -1);
+ appset = NULL;
+ return -1;
+ }
+
+ if (ast_cli_register(&cli_status)) {
+ ao2_ref(appset, -1);
+ appset = NULL;
+ return -1;
+ }
+
+ ast_register_atexit(ast_cel_engine_term);
+
+ return 0;
+}
+
+int ast_cel_engine_reload(void)
+{
+ return do_reload();
+}
+
diff --git a/main/channel.c b/main/channel.c
index fdfe9e941..62377f897 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -45,6 +45,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/cli.h"
#include "asterisk/translate.h"
#include "asterisk/manager.h"
+#include "asterisk/cel.h"
#include "asterisk/chanvars.h"
#include "asterisk/linkedlists.h"
#include "asterisk/indications.h"
@@ -62,6 +63,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/audiohook.h"
#include "asterisk/timing.h"
#include "asterisk/autochan.h"
+#include "asterisk/stringfields.h"
#ifdef HAVE_EPOLL
#include <sys/epoll.h>
@@ -777,13 +779,14 @@ static const struct ast_channel_tech null_tech = {
};
static void ast_channel_destructor(void *obj);
+static void ast_dummy_channel_destructor(void *obj);
/*! \brief Create a new channel structure */
-static struct ast_channel * attribute_malloc __attribute__((format(printf, 12, 0)))
+static struct ast_channel * attribute_malloc __attribute__((format(printf, 13, 0)))
__ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char *cid_name,
const char *acctcode, const char *exten, const char *context,
- const int amaflag, const char *file, int line, const char *function,
- const char *name_fmt, va_list ap1, va_list ap2)
+ const char *linkedid, const int amaflag, const char *file, int line,
+ const char *function, const char *name_fmt, va_list ap1, va_list ap2)
{
struct ast_channel *tmp;
int x;
@@ -902,7 +905,14 @@ alertpipe_failed:
ast_string_field_build(tmp, uniqueid, "%s-%li.%d", ast_config_AST_SYSTEM_NAME,
(long) time(NULL), ast_atomic_fetchadd_int(&uniqueint, 1));
}
-
+
+ if (!ast_strlen_zero(linkedid)) {
+ ast_string_field_set(tmp, linkedid, linkedid);
+ }
+ else {
+ ast_string_field_set(tmp, linkedid, tmp->uniqueid);
+ }
+
if (!ast_strlen_zero(name_fmt)) {
/* Almost every channel is calling this function, and setting the name via the ast_string_field_build() call.
* And they all use slightly different formats for their name string.
@@ -938,11 +948,13 @@ alertpipe_failed:
strcpy(tmp->exten, "s");
tmp->priority = 1;
-
+
tmp->cdr = ast_cdr_alloc();
ast_cdr_init(tmp->cdr, tmp);
ast_cdr_start(tmp->cdr);
-
+
+ ast_cel_report_event(tmp, AST_CEL_CHANNEL_START, NULL, NULL, NULL);
+
headp = &tmp->varshead;
AST_LIST_HEAD_INIT_NOLOCK(headp);
@@ -990,8 +1002,9 @@ alertpipe_failed:
struct ast_channel *__ast_channel_alloc(int needqueue, int state, const char *cid_num,
const char *cid_name, const char *acctcode,
const char *exten, const char *context,
- const int amaflag, const char *file, int line,
- const char *function, const char *name_fmt, ...)
+ const char *linkedid, const int amaflag,
+ const char *file, int line, const char *function,
+ const char *name_fmt, ...)
{
va_list ap1, ap2;
struct ast_channel *result;
@@ -999,13 +1012,45 @@ struct ast_channel *__ast_channel_alloc(int needqueue, int state, const char *ci
va_start(ap1, name_fmt);
va_start(ap2, name_fmt);
result = __ast_channel_alloc_ap(needqueue, state, cid_num, cid_name, acctcode, exten, context,
- amaflag, file, line, function, name_fmt, ap1, ap2);
+ linkedid, amaflag, file, line, function, name_fmt, ap1, ap2);
va_end(ap1);
va_end(ap2);
return result;
}
+/* only do the minimum amount of work needed here to make a channel
+ * structure that can be used to expand channel vars */
+struct ast_channel *ast_dummy_channel_alloc(void)
+{
+ struct ast_channel *tmp;
+ struct varshead *headp;
+
+#if defined(REF_DEBUG)
+ if (!(tmp = __ao2_alloc_debug(sizeof(*tmp), ast_dummy_channel_destructor, "", file, line, function, 1))) {
+ return NULL;
+ }
+#elif defined(__AST_DEBUG_MALLOC)
+ if (!(tmp = __ao2_alloc_debug(sizeof(*tmp), ast_dummy_channel_destructor, "", file, line, function, 0))) {
+ return NULL;
+ }
+#else
+ if (!(tmp = ao2_alloc(sizeof(*tmp), ast_dummy_channel_destructor))) {
+ return NULL;
+ }
+#endif
+
+ if ((ast_string_field_init(tmp, 128))) {
+ ast_channel_unref(tmp);
+ return NULL;
+ }
+
+ headp = &tmp->varshead;
+ AST_LIST_HEAD_INIT_NOLOCK(headp);
+
+ return tmp;
+}
+
static int __ast_queue_frame(struct ast_channel *chan, struct ast_frame *fin, int head, struct ast_frame *after)
{
struct ast_frame *f;
@@ -1693,6 +1738,9 @@ static void ast_channel_destructor(void *obj)
headp = &chan->varshead;
+ ast_cel_report_event(chan, AST_CEL_CHANNEL_END, NULL, NULL, NULL);
+ ast_cel_check_retire_linkedid(chan);
+
/* Get rid of each of the data stores on the channel */
while ((datastore = AST_LIST_REMOVE_HEAD(&chan->datastores, entry)))
/* Free the data store */
@@ -1782,6 +1830,30 @@ static void ast_channel_destructor(void *obj)
ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, name);
}
+/*! \brief Free a dummy channel structure */
+static void ast_dummy_channel_destructor(void *obj)
+{
+ struct ast_channel *chan = obj;
+ struct ast_var_t *vardata;
+ struct varshead *headp;
+
+ headp = &chan->varshead;
+
+ free_cid(&chan->cid);
+
+ /* loop over the variables list, freeing all data and deleting list items */
+ /* no need to lock the list, as the channel is already locked */
+ while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries)))
+ ast_var_delete(vardata);
+
+ if (chan->cdr) {
+ ast_cdr_discard(chan->cdr);
+ chan->cdr = NULL;
+ }
+
+ ast_string_field_free_memory(chan);
+}
+
struct ast_datastore *ast_channel_datastore_alloc(const struct ast_datastore_info *info, const char *uid)
{
return ast_datastore_alloc(info, uid);
@@ -1964,10 +2036,30 @@ static void free_translation(struct ast_channel *clonechan)
clonechan->rawreadformat = clonechan->nativeformats;
}
+void ast_set_hangupsource(struct ast_channel *chan, const char *source, int force)
+{
+ struct ast_channel *bridge;
+
+ ast_channel_lock(chan);
+ if (force || ast_strlen_zero(chan->hangupsource)) {
+ ast_string_field_set(chan, hangupsource, source);
+ }
+ bridge = ast_bridged_channel(chan);
+ ast_channel_unlock(chan);
+
+ if (bridge && (force || ast_strlen_zero(bridge->hangupsource))) {
+ ast_channel_lock(bridge);
+ ast_string_field_set(chan, hangupsource, source);
+ ast_channel_unlock(bridge);
+ }
+}
+
/*! \brief Hangup a channel */
int ast_hangup(struct ast_channel *chan)
{
int res = 0;
+ struct ast_cdr *cdr = NULL;
+ char extra_str[64]; /* used for cel logging below */
/* Don't actually hang up a channel that will masquerade as someone else, or
if someone is going to masquerade as us */
@@ -2012,12 +2104,21 @@ int ast_hangup(struct ast_channel *chan)
sched_context_destroy(chan->sched);
chan->sched = NULL;
}
-
+
if (chan->generatordata) /* Clear any tone stuff remaining */
if (chan->generator && chan->generator->release)
chan->generator->release(chan, chan->generatordata);
chan->generatordata = NULL;
chan->generator = NULL;
+
+ snprintf(extra_str, sizeof(extra_str), "%d,%s,%s", chan->hangupcause, chan->hangupsource, S_OR(pbx_builtin_getvar_helper(chan, "DIALSTATUS"), ""));
+ ast_cel_report_event(chan, AST_CEL_HANGUP, NULL, extra_str, NULL);
+
+ if (chan->cdr) { /* End the CDR if it hasn't already */
+ ast_cdr_end(chan->cdr);
+ cdr = chan->cdr;
+ chan->cdr = NULL;
+ }
if (ast_test_flag(chan, AST_FLAG_BLOCKING)) {
ast_log(LOG_WARNING, "Hard hangup called by thread %ld on %s, while fd "
"is blocked by thread %ld in procedure %s! Expect a failure\n",
@@ -2093,9 +2194,11 @@ int ast_raw_answer(struct ast_channel *chan, int cdr_answer)
if (cdr_answer) {
ast_cdr_answer(chan->cdr);
}
+ ast_cel_report_event(chan, AST_CEL_ANSWER, NULL, NULL, NULL);
ast_channel_unlock(chan);
break;
case AST_STATE_UP:
+ ast_cel_report_event(chan, AST_CEL_ANSWER, NULL, NULL, NULL);
/* Calling ast_cdr_answer when it it has previously been called
* is essentially a no-op, so it is safe.
*/
@@ -3081,6 +3184,7 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio)
/* Answer the CDR */
ast_setstate(chan, AST_STATE_UP);
/* removed a call to ast_cdr_answer(chan->cdr) from here. */
+ ast_cel_report_event(chan, AST_CEL_ANSWER, NULL, NULL, NULL);
}
}
break;
@@ -4049,7 +4153,7 @@ struct ast_channel *ast_call_forward(struct ast_channel *caller, struct ast_chan
data = tmpchan;
type = "Local";
}
- if (!(new = ast_request(type, format, data, &cause))) {
+ if (!(new = ast_request(type, format, orig, data, &cause))) {
ast_log(LOG_NOTICE, "Unable to create channel for call forward to '%s/%s' (cause = %d)\n", type, data, cause);
handle_cause(cause, outstate);
ast_hangup(orig);
@@ -4103,7 +4207,7 @@ struct ast_channel *ast_call_forward(struct ast_channel *caller, struct ast_chan
return new;
}
-struct ast_channel *__ast_request_and_dial(const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, struct outgoing_helper *oh)
+struct ast_channel *__ast_request_and_dial(const char *type, int format, const struct ast_channel *requestor, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, struct outgoing_helper *oh)
{
int dummy_outstate;
int cause = 0;
@@ -4117,7 +4221,7 @@ struct ast_channel *__ast_request_and_dial(const char *type, int format, void *d
else
outstate = &dummy_outstate; /* make outstate always a valid pointer */
- chan = ast_request(type, format, data, &cause);
+ chan = ast_request(type, format, requestor, data, &cause);
if (!chan) {
ast_log(LOG_NOTICE, "Unable to request channel %s/%s\n", type, (char *)data);
handle_cause(cause, outstate);
@@ -4238,12 +4342,12 @@ struct ast_channel *__ast_request_and_dial(const char *type, int format, void *d
return chan;
}
-struct ast_channel *ast_request_and_dial(const char *type, int format, void *data, int timeout, int *outstate, const char *cidnum, const char *cidname)
+struct ast_channel *ast_request_and_dial(const char *type, int format, const struct ast_channel *requestor, void *data, int timeout, int *outstate, const char *cidnum, const char *cidname)
{
- return __ast_request_and_dial(type, format, data, timeout, outstate, cidnum, cidname, NULL);
+ return __ast_request_and_dial(type, format, requestor, data, timeout, outstate, cidnum, cidname, NULL);
}
-struct ast_channel *ast_request(const char *type, int format, void *data, int *cause)
+struct ast_channel *ast_request(const char *type, int format, const struct ast_channel *requestor, void *data, int *cause)
{
struct chanlist *chan;
struct ast_channel *c;
@@ -4284,10 +4388,10 @@ struct ast_channel *ast_request(const char *type, int format, void *data, int *c
AST_RWLIST_UNLOCK(&backends);
if (!chan->tech->requester)
return NULL;
-
- if (!(c = chan->tech->requester(type, capabilities | videoformat | textformat, data, cause)))
+
+ if (!(c = chan->tech->requester(type, capabilities | videoformat | textformat, requestor, data, cause)))
return NULL;
-
+
/* no need to generate a Newchannel event here; it is done in the channel_alloc call */
return c;
}
@@ -4666,6 +4770,162 @@ static void clone_variables(struct ast_channel *original, struct ast_channel *cl
}
}
+
+
+/* return the oldest of two linkedids. linkedid is derived from
+ uniqueid which is formed like this: [systemname-]ctime.seq
+
+ The systemname, and the dash are optional, followed by the epoch
+ time followed by an integer sequence. Note that this is not a
+ decimal number, since 1.2 is less than 1.11 in uniqueid land.
+
+ To compare two uniqueids, we parse out the integer values of the
+ time and the sequence numbers and compare them, with time trumping
+ sequence.
+*/
+static const char *oldest_linkedid(const char *a, const char *b)
+{
+ const char *satime, *saseq;
+ const char *sbtime, *sbseq;
+ const char *dash;
+
+ unsigned int atime, aseq, btime, bseq;
+
+ if (ast_strlen_zero(a))
+ return b;
+
+ if (ast_strlen_zero(b))
+ return a;
+
+ satime = a;
+ sbtime = b;
+
+ /* jump over the system name */
+ if ((dash = strrchr(satime, '-'))) {
+ satime = dash+1;
+ }
+ if ((dash = strrchr(sbtime, '-'))) {
+ sbtime = dash+1;
+ }
+
+ /* the sequence comes after the '.' */
+ saseq = strchr(satime, '.');
+ sbseq = strchr(sbtime, '.');
+ if (!saseq || !sbseq)
+ return NULL;
+ saseq++;
+ sbseq++;
+
+ /* convert it all to integers */
+ atime = atoi(satime); /* note that atoi is ignoring the '.' after the time string */
+ btime = atoi(sbtime); /* note that atoi is ignoring the '.' after the time string */
+ aseq = atoi(saseq);
+ bseq = atoi(sbseq);
+
+ /* and finally compare */
+ if (atime == btime) {
+ return (aseq < bseq) ? a : b;
+ }
+ else {
+ return (atime < btime) ? a : b;
+ }
+}
+
+/*! Set the channel's linkedid to the given string, and also check to
+ * see if the channel's old linkedid is now being retired */
+static void ast_channel_change_linkedid(struct ast_channel *chan, const char *linkedid)
+{
+ /* if the linkedid for this channel is being changed from something, check... */
+ if (!ast_strlen_zero(chan->linkedid) && 0 != strcmp(chan->linkedid, linkedid)) {
+ ast_cel_check_retire_linkedid(chan);
+ }
+
+ ast_string_field_set(chan, linkedid, linkedid);
+}
+
+
+/*!
+ \brief Propagate the oldest linkedid between associated channels
+
+*/
+void ast_channel_set_linkgroup(struct ast_channel *chan, struct ast_channel *peer)
+{
+ const char* linkedid=NULL;
+ struct ast_channel *bridged;
+
+ linkedid = oldest_linkedid(chan->linkedid, peer->linkedid);
+ linkedid = oldest_linkedid(linkedid, chan->uniqueid);
+ linkedid = oldest_linkedid(linkedid, peer->uniqueid);
+ if (chan->_bridge) {
+ bridged = ast_bridged_channel(chan);
+ if (bridged != peer) {
+ linkedid = oldest_linkedid(linkedid, bridged->linkedid);
+ linkedid = oldest_linkedid(linkedid, bridged->uniqueid);
+ }
+ }
+ if (peer->_bridge) {
+ bridged = ast_bridged_channel(peer);
+ if (bridged != chan) {
+ linkedid = oldest_linkedid(linkedid, bridged->linkedid);
+ linkedid = oldest_linkedid(linkedid, bridged->uniqueid);
+ }
+ }
+
+ /* just in case setting a stringfield to itself causes problems */
+ linkedid = ast_strdupa(linkedid);
+
+ ast_channel_change_linkedid(chan, linkedid);
+ ast_channel_change_linkedid(peer, linkedid);
+ if (chan->_bridge) {
+ bridged = ast_bridged_channel(chan);
+ if (bridged != peer) {
+ ast_channel_change_linkedid(bridged, linkedid);
+ }
+ }
+ if (peer->_bridge) {
+ bridged = ast_bridged_channel(peer);
+ if (bridged != chan) {
+ ast_channel_change_linkedid(bridged, linkedid);
+ }
+ }
+}
+
+/* copy accountcode and peeraccount across during a link */
+static void ast_set_owners_and_peers(struct ast_channel *chan1,
+ struct ast_channel *chan2)
+{
+ if (!ast_strlen_zero(chan1->accountcode) && ast_strlen_zero(chan2->peeraccount)) {
+ ast_log(LOG_DEBUG, "setting peeraccount to %s for %s from data on channel %s\n",
+ chan1->accountcode, chan2->name, chan1->name);
+ ast_string_field_set(chan2, peeraccount, chan1->accountcode);
+ }
+ if (!ast_strlen_zero(chan2->accountcode) && ast_strlen_zero(chan1->peeraccount)) {
+ ast_log(LOG_DEBUG, "setting peeraccount to %s for %s from data on channel %s\n",
+ chan2->accountcode, chan1->name, chan2->name);
+ ast_string_field_set(chan1, peeraccount, chan2->accountcode);
+ }
+ if (!ast_strlen_zero(chan1->peeraccount) && ast_strlen_zero(chan2->accountcode)) {
+ ast_log(LOG_DEBUG, "setting accountcode to %s for %s from data on channel %s\n",
+ chan1->peeraccount, chan2->name, chan1->name);
+ ast_string_field_set(chan2, accountcode, chan1->peeraccount);
+ }
+ if (!ast_strlen_zero(chan2->peeraccount) && ast_strlen_zero(chan1->accountcode)) {
+ ast_log(LOG_DEBUG, "setting accountcode to %s for %s from data on channel %s\n",
+ chan2->peeraccount, chan1->name, chan2->name);
+ ast_string_field_set(chan1, accountcode, chan2->peeraccount);
+ }
+ if (0 != strcmp(chan1->accountcode, chan2->peeraccount)) {
+ ast_log(LOG_DEBUG, "changing peeraccount from %s to %s on %s to match channel %s\n",
+ chan2->peeraccount, chan1->peeraccount, chan2->name, chan1->name);
+ ast_string_field_set(chan2, peeraccount, chan1->accountcode);
+ }
+ if (0 != strcmp(chan2->accountcode, chan1->peeraccount)) {
+ ast_log(LOG_DEBUG, "changing peeraccount from %s to %s on %s to match channel %s\n",
+ chan1->peeraccount, chan2->peeraccount, chan1->name, chan2->name);
+ ast_string_field_set(chan1, peeraccount, chan2->accountcode);
+ }
+}
+
/*!
* \pre chan is locked
*/
@@ -4749,7 +5009,14 @@ int ast_do_masquerade(struct ast_channel *original)
/* Mangle the name of the clone channel */
ast_change_name(clonechan, masqn);
- /* Swap the technologies */
+ /* share linked id's */
+ ast_channel_set_linkgroup(original, clonechan);
+
+ /* Notify any managers of the change, first the masq then the other */
+ manager_event(EVENT_FLAG_CALL, "Rename", "Channel: %s\r\nNewname: %s\r\nUniqueid: %s\r\n", newn, masqn, clonechan->uniqueid);
+ manager_event(EVENT_FLAG_CALL, "Rename", "Channel: %s\r\nNewname: %s\r\nUniqueid: %s\r\n", orig, newn, original->uniqueid);
+
+ /* Swap the technologies */
t = original->tech;
original->tech = clonechan->tech;
clonechan->tech = t;
@@ -4876,7 +5143,7 @@ int ast_do_masquerade(struct ast_channel *original)
/* XXX What about blocking, softhangup, blocker, and lock and blockproc? XXX */
/* Application and data remain the same */
/* Clone exception becomes real one, as with fdno */
- ast_set_flag(original, ast_test_flag(clonechan, AST_FLAG_OUTGOING | AST_FLAG_EXCEPTION));
+ ast_copy_flags(original, clonechan, AST_FLAG_EXCEPTION | AST_FLAG_OUTGOING);
original->fdno = clonechan->fdno;
/* Schedule context remains the same */
/* Stream stuff stays the same */
@@ -4916,6 +5183,14 @@ int ast_do_masquerade(struct ast_channel *original)
/* Copy the music class */
ast_string_field_set(original, musicclass, clonechan->musicclass);
+ /* copy over accuntcode and set peeraccount across the bridge */
+ ast_string_field_set(original, accountcode, S_OR(clonechan->accountcode, ""));
+ if (original->_bridge) {
+ /* XXX - should we try to lock original->_bridge here? */
+ ast_string_field_set(original->_bridge, peeraccount, S_OR(clonechan->accountcode, ""));
+ ast_cel_report_event(original, AST_CEL_BRIDGE_UPDATE, NULL, NULL, NULL);
+ }
+
ast_debug(1, "Putting channel %s in %d/%d formats\n", original->name, wformat, rformat);
/* Okay. Last thing is to let the channel driver know about all this mess, so he
@@ -5417,6 +5692,7 @@ enum ast_bridge_result ast_channel_bridge(struct ast_channel *c0, struct ast_cha
c0->_bridge = c1;
c1->_bridge = c0;
+ ast_set_owners_and_peers(c0, c1);
o0nativeformats = c0->nativeformats;
o1nativeformats = c1->nativeformats;
@@ -5555,6 +5831,8 @@ enum ast_bridge_result ast_channel_bridge(struct ast_channel *c0, struct ast_cha
"CallerID1: %s\r\n"
"CallerID2: %s\r\n",
c0->name, c1->name, c0->uniqueid, c1->uniqueid, S_OR(c0->cid.cid_num, "<unknown>"), S_OR(c1->cid.cid_num, "<unknown>"));
+ ast_cel_report_event(c0, AST_CEL_BRIDGE_END, NULL, NULL, NULL);
+
ast_debug(1, "Returning from native bridge, channels: %s, %s\n", c0->name, c1->name);
ast_clear_flag(c0, AST_FLAG_NBRIDGE);
@@ -5565,7 +5843,6 @@ enum ast_bridge_result ast_channel_bridge(struct ast_channel *c0, struct ast_cha
c0->_bridge = NULL;
c1->_bridge = NULL;
-
return res;
} else {
ast_clear_flag(c0, AST_FLAG_NBRIDGE);
@@ -5592,6 +5869,7 @@ enum ast_bridge_result ast_channel_bridge(struct ast_channel *c0, struct ast_cha
if (ast_channel_make_compatible(c0, c1)) {
ast_log(LOG_WARNING, "Can't make %s and %s compatible\n", c0->name, c1->name);
manager_bridge_event(0, 1, c0, c1);
+ /* ast_cel_report_event(c0, AST_CEL_BRIDGE_END, NULL, NULL, NULL); */
return AST_BRIDGE_FAILED;
}
o0nativeformats = c0->nativeformats;
@@ -5619,6 +5897,7 @@ enum ast_bridge_result ast_channel_bridge(struct ast_channel *c0, struct ast_cha
c0->_bridge = NULL;
c1->_bridge = NULL;
+ ast_cel_report_event(c0, AST_CEL_BRIDGE_END, NULL, NULL, NULL);
manager_event(EVENT_FLAG_CALL, "Unlink",
"Channel1: %s\r\n"
"Channel2: %s\r\n"
@@ -6747,15 +7026,17 @@ int ast_channel_connected_line_macro(struct ast_channel *autoservice_chan, struc
* newly compiled modules will call __ast_channel_alloc() via the macros in channel.h
*/
#undef ast_channel_alloc
-struct ast_channel __attribute__((format(printf, 9, 10)))
+struct ast_channel __attribute__((format(printf, 10, 11)))
*ast_channel_alloc(int needqueue, int state, const char *cid_num,
const char *cid_name, const char *acctcode,
const char *exten, const char *context,
- const int amaflag, const char *name_fmt, ...);
+ const char *linkedid, const int amaflag,
+ const char *name_fmt, ...);
struct ast_channel *ast_channel_alloc(int needqueue, int state, const char *cid_num,
const char *cid_name, const char *acctcode,
const char *exten, const char *context,
- const int amaflag, const char *name_fmt, ...)
+ const char *linkedid, const int amaflag,
+ const char *name_fmt, ...)
{
va_list ap1, ap2;
struct ast_channel *result;
@@ -6764,7 +7045,7 @@ struct ast_channel *ast_channel_alloc(int needqueue, int state, const char *cid_
va_start(ap1, name_fmt);
va_start(ap2, name_fmt);
result = __ast_channel_alloc_ap(needqueue, state, cid_num, cid_name, acctcode, exten, context,
- amaflag, __FILE__, __LINE__, __FUNCTION__, name_fmt, ap1, ap2);
+ linkedid, amaflag, __FILE__, __LINE__, __FUNCTION__, name_fmt, ap1, ap2);
va_end(ap1);
va_end(ap2);
diff --git a/main/cli.c b/main/cli.c
index a822d1eb8..bdf8c1129 100644
--- a/main/cli.c
+++ b/main/cli.c
@@ -781,9 +781,9 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
{
#define FORMAT_STRING "%-20.20s %-20.20s %-7.7s %-30.30s\n"
#define FORMAT_STRING2 "%-20.20s %-20.20s %-7.7s %-30.30s\n"
-#define CONCISE_FORMAT_STRING "%s!%s!%s!%d!%s!%s!%s!%s!%s!%d!%s!%s!%s\n"
-#define VERBOSE_FORMAT_STRING "%-20.20s %-20.20s %-16.16s %4d %-7.7s %-12.12s %-25.25s %-15.15s %8.8s %-11.11s %-20.20s\n"
-#define VERBOSE_FORMAT_STRING2 "%-20.20s %-20.20s %-16.16s %-4.4s %-7.7s %-12.12s %-25.25s %-15.15s %8.8s %-11.11s %-20.20s\n"
+#define CONCISE_FORMAT_STRING "%s!%s!%s!%d!%s!%s!%s!%s!%s!%s!%d!%s!%s!%s\n"
+#define VERBOSE_FORMAT_STRING "%-20.20s %-20.20s %-16.16s %4d %-7.7s %-12.12s %-25.25s %-15.15s %8.8s %-11.11s %-11.11s %-20.20s\n"
+#define VERBOSE_FORMAT_STRING2 "%-20.20s %-20.20s %-16.16s %-4.4s %-7.7s %-12.12s %-25.25s %-15.15s %8.8s %-11.11s %-11.11s %-20.20s\n"
struct ast_channel *c = NULL;
int numchans = 0, concise = 0, verbose = 0, count = 0;
@@ -824,7 +824,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
ast_cli(a->fd, FORMAT_STRING2, "Channel", "Location", "State", "Application(Data)");
else if (verbose)
ast_cli(a->fd, VERBOSE_FORMAT_STRING2, "Channel", "Context", "Extension", "Priority", "State", "Application", "Data",
- "CallerID", "Duration", "Accountcode", "BridgedTo");
+ "CallerID", "Duration", "Accountcode", "PeerAccount", "BridgedTo");
}
if (!count && !(iter = ast_channel_iterator_all_new(0))) {
@@ -857,6 +857,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
S_OR(c->data, ""), /* XXX different from verbose ? */
S_OR(c->cid.cid_num, ""),
S_OR(c->accountcode, ""),
+ S_OR(c->peeraccount, ""),
c->amaflags,
durbuf,
bc ? bc->name : "(None)",
@@ -868,6 +869,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
S_OR(c->cid.cid_num, ""),
durbuf,
S_OR(c->accountcode, ""),
+ S_OR(c->peeraccount, ""),
bc ? bc->name : "(None)");
} else {
char locbuf[40] = "(None)";
@@ -1355,6 +1357,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
" Name: %s\n"
" Type: %s\n"
" UniqueID: %s\n"
+ " LinkedID: %s\n"
" Caller ID: %s\n"
" Caller ID Name: %s\n"
" DNID Digits: %s\n"
@@ -1382,7 +1385,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
" Application: %s\n"
" Data: %s\n"
" Blocking in: %s\n",
- c->name, c->tech->type, c->uniqueid,
+ c->name, c->tech->type, c->uniqueid, c->linkedid,
S_OR(c->cid.cid_num, "(N/A)"),
S_OR(c->cid.cid_name, "(N/A)"),
S_OR(c->cid.cid_dnid, "(N/A)"),
diff --git a/main/devicestate.c b/main/devicestate.c
index 17178faf2..564aecfc9 100644
--- a/main/devicestate.c
+++ b/main/devicestate.c
@@ -818,7 +818,7 @@ int ast_enable_distributed_devstate(void)
}
devstate_collector.event_sub = ast_event_subscribe(AST_EVENT_DEVICE_STATE_CHANGE,
- devstate_change_collector_cb, NULL, AST_EVENT_IE_END);
+ devstate_change_collector_cb, "devicestate_engine_enable_distributed", NULL, AST_EVENT_IE_END);
if (!devstate_collector.event_sub) {
ast_log(LOG_ERROR, "Failed to create subscription for the device state change collector\n");
diff --git a/main/dial.c b/main/dial.c
index 1f65f50c7..4c57cb12d 100644
--- a/main/dial.c
+++ b/main/dial.c
@@ -262,7 +262,7 @@ static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_chann
ast_copy_string(numsubst, channel->device, sizeof(numsubst));
/* If we fail to create our owner channel bail out */
- if (!(channel->owner = ast_request(channel->tech, chan ? chan->nativeformats : AST_FORMAT_AUDIO_MASK, numsubst, &channel->cause)))
+ if (!(channel->owner = ast_request(channel->tech, chan ? chan->nativeformats : AST_FORMAT_AUDIO_MASK, chan, numsubst, &channel->cause)))
return -1;
channel->owner->appl = "AppDial2";
diff --git a/main/event.c b/main/event.c
index 3370cf8b3..6a70a6741 100644
--- a/main/event.c
+++ b/main/event.c
@@ -119,6 +119,7 @@ struct ast_event_ie_val {
struct ast_event_sub {
enum ast_event_type type;
ast_event_cb_t cb;
+ char description[64];
void *userdata;
uint32_t uniqueid;
AST_LIST_HEAD_NOLOCK(, ast_event_ie_val) ie_vals;
@@ -195,6 +196,7 @@ static struct event_name {
{ AST_EVENT_UNSUB, "Unsubscription" },
{ AST_EVENT_DEVICE_STATE, "DeviceState" },
{ AST_EVENT_DEVICE_STATE_CHANGE, "DeviceStateChange" },
+ { AST_EVENT_CEL, "CEL" },
};
/*!
@@ -206,16 +208,38 @@ static struct ie_map {
const char *name;
} ie_maps[] = {
{ 0, 0, "" },
- { AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_UINT, "NewMessages" },
- { AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_UINT, "OldMessages" },
- { AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, "Mailbox" },
- { AST_EVENT_IE_UNIQUEID, AST_EVENT_IE_PLTYPE_UINT, "UniqueID" },
- { AST_EVENT_IE_EVENTTYPE, AST_EVENT_IE_PLTYPE_UINT, "EventType" },
- { AST_EVENT_IE_EXISTS, AST_EVENT_IE_PLTYPE_UINT, "Exists" },
- { AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, "Device" },
- { AST_EVENT_IE_STATE, AST_EVENT_IE_PLTYPE_UINT, "State" },
- { AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, "Context" },
- { AST_EVENT_IE_EID, AST_EVENT_IE_PLTYPE_RAW, "EntityID" },
+ { AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_UINT, "NewMessages" },
+ { AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_UINT, "OldMessages" },
+ { AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, "Mailbox" },
+ { AST_EVENT_IE_UNIQUEID, AST_EVENT_IE_PLTYPE_UINT, "UniqueID" },
+ { AST_EVENT_IE_EVENTTYPE, AST_EVENT_IE_PLTYPE_UINT, "EventType" },
+ { AST_EVENT_IE_EXISTS, AST_EVENT_IE_PLTYPE_UINT, "Exists" },
+ { AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, "Device" },
+ { AST_EVENT_IE_STATE, AST_EVENT_IE_PLTYPE_UINT, "State" },
+ { AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, "Context" },
+ { AST_EVENT_IE_EID, AST_EVENT_IE_PLTYPE_RAW, "EntityID" },
+ { AST_EVENT_IE_CEL_EVENT_TYPE, AST_EVENT_IE_PLTYPE_UINT, "CELEventType" },
+ { AST_EVENT_IE_CEL_EVENT_TIME, AST_EVENT_IE_PLTYPE_UINT, "CELEventTime" },
+ { AST_EVENT_IE_CEL_EVENT_TIME_USEC, AST_EVENT_IE_PLTYPE_UINT, "CELEventTimeUSec" },
+ { AST_EVENT_IE_CEL_USEREVENT_NAME, AST_EVENT_IE_PLTYPE_UINT, "CELUserEventName" },
+ { AST_EVENT_IE_CEL_CIDNAME, AST_EVENT_IE_PLTYPE_STR, "CELCIDName" },
+ { AST_EVENT_IE_CEL_CIDNUM, AST_EVENT_IE_PLTYPE_STR, "CELCIDNum" },
+ { AST_EVENT_IE_CEL_EXTEN, AST_EVENT_IE_PLTYPE_STR, "CELExten" },
+ { AST_EVENT_IE_CEL_CONTEXT, AST_EVENT_IE_PLTYPE_STR, "CELContext" },
+ { AST_EVENT_IE_CEL_CHANNAME, AST_EVENT_IE_PLTYPE_STR, "CELChanName" },
+ { AST_EVENT_IE_CEL_APPNAME, AST_EVENT_IE_PLTYPE_STR, "CELAppName" },
+ { AST_EVENT_IE_CEL_APPDATA, AST_EVENT_IE_PLTYPE_STR, "CELAppData" },
+ { AST_EVENT_IE_CEL_AMAFLAGS, AST_EVENT_IE_PLTYPE_STR, "CELAMAFlags" },
+ { AST_EVENT_IE_CEL_ACCTCODE, AST_EVENT_IE_PLTYPE_UINT, "CELAcctCode" },
+ { AST_EVENT_IE_CEL_UNIQUEID, AST_EVENT_IE_PLTYPE_STR, "CELUniqueID" },
+ { AST_EVENT_IE_CEL_USERFIELD, AST_EVENT_IE_PLTYPE_STR, "CELUserField" },
+ { AST_EVENT_IE_CEL_CIDANI, AST_EVENT_IE_PLTYPE_STR, "CELCIDani" },
+ { AST_EVENT_IE_CEL_CIDRDNIS, AST_EVENT_IE_PLTYPE_STR, "CELCIDrdnis" },
+ { AST_EVENT_IE_CEL_CIDDNID, AST_EVENT_IE_PLTYPE_STR, "CELCIDdnid" },
+ { AST_EVENT_IE_CEL_PEER, AST_EVENT_IE_PLTYPE_STR, "CELPeer" },
+ { AST_EVENT_IE_CEL_LINKEDID, AST_EVENT_IE_PLTYPE_STR, "CELLinkedID" },
+ { AST_EVENT_IE_CEL_PEERACCT, AST_EVENT_IE_PLTYPE_STR, "CELPeerAcct" },
+ { AST_EVENT_IE_CEL_EXTRA, AST_EVENT_IE_PLTYPE_STR, "CELExtra" },
};
const char *ast_event_get_type_name(const struct ast_event *event)
@@ -535,8 +559,9 @@ static struct ast_event *gen_sub_event(struct ast_event_sub *sub)
struct ast_event *event;
event = ast_event_new(AST_EVENT_SUB,
- AST_EVENT_IE_UNIQUEID, AST_EVENT_IE_PLTYPE_UINT, sub->uniqueid,
- AST_EVENT_IE_EVENTTYPE, AST_EVENT_IE_PLTYPE_UINT, sub->type,
+ AST_EVENT_IE_UNIQUEID, AST_EVENT_IE_PLTYPE_UINT, sub->uniqueid,
+ AST_EVENT_IE_EVENTTYPE, AST_EVENT_IE_PLTYPE_UINT, sub->type,
+ AST_EVENT_IE_DESCRIPTION, AST_EVENT_IE_PLTYPE_STR, sub->description,
AST_EVENT_IE_END);
if (!event)
@@ -773,7 +798,7 @@ int ast_event_sub_activate(struct ast_event_sub *sub)
}
struct ast_event_sub *ast_event_subscribe(enum ast_event_type type, ast_event_cb_t cb,
- void *userdata, ...)
+ char *description, void *userdata, ...)
{
va_list ap;
enum ast_event_ie_type ie_type;
@@ -783,6 +808,8 @@ struct ast_event_sub *ast_event_subscribe(enum ast_event_type type, ast_event_cb
return NULL;
}
+ ast_copy_string(sub->description, description, sizeof(sub->description));
+
va_start(ap, userdata);
for (ie_type = va_arg(ap, enum ast_event_type);
ie_type != AST_EVENT_IE_END;
@@ -843,6 +870,11 @@ void ast_event_sub_destroy(struct ast_event_sub *sub)
ast_free(sub);
}
+const char *ast_event_subscriber_get_description(struct ast_event_sub *sub)
+{
+ return sub ? sub->description : NULL;
+}
+
struct ast_event_sub *ast_event_unsubscribe(struct ast_event_sub *sub)
{
struct ast_event *event;
@@ -856,8 +888,9 @@ struct ast_event_sub *ast_event_unsubscribe(struct ast_event_sub *sub)
AST_EVENT_IE_END) != AST_EVENT_SUB_NONE) {
event = ast_event_new(AST_EVENT_UNSUB,
- AST_EVENT_IE_UNIQUEID, AST_EVENT_IE_PLTYPE_UINT, sub->uniqueid,
- AST_EVENT_IE_EVENTTYPE, AST_EVENT_IE_PLTYPE_UINT, sub->type,
+ AST_EVENT_IE_UNIQUEID, AST_EVENT_IE_PLTYPE_UINT, sub->uniqueid,
+ AST_EVENT_IE_EVENTTYPE, AST_EVENT_IE_PLTYPE_UINT, sub->type,
+ AST_EVENT_IE_DESCRIPTION, AST_EVENT_IE_PLTYPE_STR, sub->description,
AST_EVENT_IE_END);
if (event) {
@@ -1330,6 +1363,7 @@ int ast_event_queue(struct ast_event *event)
if (ast_event_check_subscriber(host_event_type, AST_EVENT_IE_END)
== AST_EVENT_SUB_NONE) {
ast_event_destroy(event);
+ ast_log(LOG_NOTICE, "Event destroyed, no subscriber\n");
return 0;
}
diff --git a/main/features.c b/main/features.c
index 22e3a2b65..bb716fa80 100644
--- a/main/features.c
+++ b/main/features.c
@@ -55,6 +55,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/audiohook.h"
#include "asterisk/global_datastores.h"
#include "asterisk/astobj2.h"
+#include "asterisk/cel.h"
/*** DOCUMENTATION
<application name="Bridge" language="en_US">
@@ -426,7 +427,7 @@ static void check_goto_on_transfer(struct ast_channel *chan)
goto_on_transfer = ast_strdupa(val);
- if (!(xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "%s", chan->name)))
+ if (!(xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", chan->linkedid, 0, "%s", chan->name)))
return;
for (x = goto_on_transfer; x && *x; x++) {
@@ -808,6 +809,8 @@ static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, st
pthread_kill(parking_thread, SIGURG);
ast_verb(2, "Parked %s on %d (lot %s). Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, pu->parkinglot->name, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000));
+ ast_cel_report_event(pu->chan, AST_CEL_PARK_START, NULL, pu->parkinglot->name, peer);
+
if (peer) {
event_from = peer->name;
} else {
@@ -895,7 +898,7 @@ static int masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, i
}
/* Make a new, fake channel that we'll use to masquerade in the real one */
- if (!(chan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, rchan->accountcode, rchan->exten, rchan->context, rchan->amaflags, "Parked/%s",rchan->name))) {
+ if (!(chan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, rchan->accountcode, rchan->exten, rchan->context, rchan->linkedid, rchan->amaflags, "Parked/%s",rchan->name))) {
ast_log(LOG_WARNING, "Unable to create parked channel\n");
return -1;
}
@@ -1345,6 +1348,7 @@ static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *p
}
/*! \todo XXX Maybe we should have another message here instead of invalid extension XXX */
} else if (ast_exists_extension(transferee, transferer_real_context, xferto, 1, transferer->cid.cid_num)) {
+ ast_cel_report_event(transferer, AST_CEL_BLINDTRANSFER, NULL, xferto, transferee);
pbx_builtin_setvar_helper(transferer, "BLINDTRANSFER", transferee->name);
pbx_builtin_setvar_helper(transferee, "BLINDTRANSFER", transferer->name);
res=finishup(transferee);
@@ -1561,6 +1565,9 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
ast_party_connected_line_free(&connected_line);
return AST_FEATURE_RETURN_SUCCESS;
}
+
+ ast_cel_report_event(transferee, AST_CEL_ATTENDEDTRANSFER, NULL, NULL, newchan);
+
if (check_compat(transferee, newchan)) {
finishup(transferee);
ast_party_connected_line_free(&connected_line);
@@ -1577,7 +1584,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
ast_party_connected_line_free(&connected_line);
return -1;
}
- xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name);
+ xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", transferee->linkedid, 0, "Transfered/%s", transferee->name);
if (!xferchan) {
ast_hangup(newchan);
ast_party_connected_line_free(&connected_line);
@@ -1731,6 +1738,8 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
if (!newchan)
return -1;
+ ast_cel_report_event(transferee, AST_CEL_ATTENDEDTRANSFER, NULL, NULL, newchan);
+
/* newchan is up, we should prepare transferee and bridge them */
if (check_compat(transferee, newchan)) {
finishup(transferee);
@@ -1746,7 +1755,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
return -1;
}
- xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name);
+ xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", transferee->linkedid, 0, "Transfered/%s", transferee->name);
if (!xferchan) {
ast_hangup(newchan);
return -1;
@@ -2315,7 +2324,7 @@ static struct ast_channel *feature_request_and_dial(struct ast_channel *caller,
int x, len = 0;
char *disconnect_code = NULL, *dialed_code = NULL;
- if (!(chan = ast_request(type, format, data, &cause))) {
+ if (!(chan = ast_request(type, format, caller, data, &cause))) {
ast_log(LOG_NOTICE, "Unable to request channel %s/%s\n", type, (char *)data);
switch(cause) {
case AST_CAUSE_BUSY:
@@ -2482,6 +2491,27 @@ done:
return chan;
}
+void ast_channel_log(char *title, struct ast_channel *chan);
+
+void ast_channel_log(char *title, struct ast_channel *chan) /* for debug, this is handy enough to justify keeping it in the source */
+{
+ ast_log(LOG_NOTICE, "______ %s (%lx)______\n", title, (unsigned long)chan);
+ ast_log(LOG_NOTICE, "CHAN: name: %s; appl: %s; data: %s; contxt: %s; exten: %s; pri: %d;\n",
+ chan->name, chan->appl, chan->data, chan->context, chan->exten, chan->priority);
+ ast_log(LOG_NOTICE, "CHAN: acctcode: %s; dialcontext: %s; amaflags: %x; maccontxt: %s; macexten: %s; macpri: %d;\n",
+ chan->accountcode, chan->dialcontext, chan->amaflags, chan->macrocontext, chan->macroexten, chan->macropriority);
+ ast_log(LOG_NOTICE, "CHAN: masq: %p; masqr: %p; _bridge: %p; uniqueID: %s; linkedID:%s\n",
+ chan->masq, chan->masqr,
+ chan->_bridge, chan->uniqueid, chan->linkedid);
+ if (chan->masqr)
+ ast_log(LOG_NOTICE, "CHAN: masquerading as: %s; cdr: %p;\n",
+ chan->masqr->name, chan->masqr->cdr);
+ if (chan->_bridge)
+ ast_log(LOG_NOTICE, "CHAN: Bridged to %s\n", chan->_bridge->name);
+
+ ast_log(LOG_NOTICE, "===== done ====\n");
+}
+
/*!
* \brief return the first unlocked cdr in a possible chain
*/
@@ -2661,6 +2691,26 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
}
}
+#ifdef FOR_DEBUG
+ /* show the two channels and cdrs involved in the bridge for debug & devel purposes */
+ ast_channel_log("Pre-bridge CHAN Channel info", chan);
+ ast_channel_log("Pre-bridge PEER Channel info", peer);
+#endif
+ /* two channels are being marked as linked here */
+ ast_channel_set_linkgroup(chan,peer);
+
+ /* copy the userfield from the B-leg to A-leg if applicable */
+ if (chan->cdr && peer->cdr && !ast_strlen_zero(peer->cdr->userfield)) {
+ char tmp[256];
+ if (!ast_strlen_zero(chan->cdr->userfield)) {
+ snprintf(tmp, sizeof(tmp), "%s;%s", chan->cdr->userfield, peer->cdr->userfield);
+ ast_cdr_appenduserfield(chan, tmp);
+ } else
+ ast_cdr_setuserfield(chan, peer->cdr->userfield);
+ /* free the peer's cdr without ast_cdr_free complaining */
+ ast_free(peer->cdr);
+ peer->cdr = NULL;
+ }
ast_copy_string(orig_channame,chan->name,sizeof(orig_channame));
ast_copy_string(orig_peername,peer->name,sizeof(orig_peername));
orig_peer_cdr = peer_cdr;
@@ -2733,6 +2783,7 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
}
}
}
+ ast_cel_report_event(chan, AST_CEL_BRIDGE_START, NULL, NULL, NULL);
for (;;) {
struct ast_channel *other; /* used later */
@@ -3213,6 +3264,7 @@ int manage_parkinglot(struct ast_parkinglot *curlot, fd_set *rfds, fd_set *efds,
set_c_e_p(chan, pu->context, pu->exten, pu->priority);
}
post_manager_event("ParkedCallTimeOut", pu);
+ ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "ParkedCallTimeOut", NULL);
ast_verb(2, "Timeout for %s parked on %d (%s). Returning to %s,%s,%d\n", pu->chan->name, pu->parkingnum, pu->parkinglot->name, pu->chan->context, pu->chan->exten, pu->chan->priority);
/* Start up the PBX, or hang them up */
@@ -3251,6 +3303,7 @@ int manage_parkinglot(struct ast_parkinglot *curlot, fd_set *rfds, fd_set *efds,
if (f)
ast_frfree(f);
post_manager_event("ParkedCallGiveUp", pu);
+ ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "ParkedCallGiveUp", NULL);
/* There's a problem, hang them up*/
ast_verb(2, "%s got tired of being parked\n", chan->name);
@@ -3495,6 +3548,7 @@ static int park_exec_full(struct ast_channel *chan, const char *data, struct ast
} else
ast_log(LOG_WARNING, "Whoa, no parking context?\n");
+ ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "UnParkedCall", chan);
manager_event(EVENT_FLAG_CALL, "UnParkedCall",
"Exten: %s\r\n"
"Channel: %s\r\n"
@@ -4294,7 +4348,7 @@ static int action_bridge(struct mansession *s, const struct message *m)
/* create the placeholder channels and grab the other channels */
if (!(tmpchana = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL,
- NULL, NULL, 0, "Bridge/%s", chana->name))) {
+ NULL, NULL, chana->linkedid, 0, "Bridge/%s", chana->name))) {
astman_send_error(s, m, "Unable to create temporary channel!");
chana = ast_channel_unref(chana);
return 1;
@@ -4321,7 +4375,7 @@ static int action_bridge(struct mansession *s, const struct message *m)
/* create the placeholder channels and grab the other channels */
if (!(tmpchanb = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL,
- NULL, NULL, 0, "Bridge/%s", chanb->name))) {
+ NULL, NULL, chanb->linkedid, 0, "Bridge/%s", chanb->name))) {
astman_send_error(s, m, "Unable to create temporary channels!");
ast_hangup(tmpchana);
chanb = ast_channel_unref(chanb);
@@ -4715,7 +4769,7 @@ static int bridge_exec(struct ast_channel *chan, const char *data)
/* try to allocate a place holder where current_dest_chan will be placed */
if (!(final_dest_chan = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL,
- NULL, NULL, 0, "Bridge/%s", current_dest_chan->name))) {
+ NULL, NULL, current_dest_chan->linkedid, 0, "Bridge/%s", current_dest_chan->name))) {
ast_log(LOG_WARNING, "Cannot create placeholder channel for chan %s\n", args.dest_chan);
manager_event(EVENT_FLAG_CALL, "BridgeExec",
"Response: Failed\r\n"
diff --git a/main/loader.c b/main/loader.c
index a8e986409..451d33194 100644
--- a/main/loader.c
+++ b/main/loader.c
@@ -258,6 +258,7 @@ static struct reload_classes {
{ "dsp", ast_dsp_reload},
{ "udptl", ast_udptl_reload },
{ "indications", ast_indications_reload },
+ { "cel", ast_cel_engine_reload },
{ NULL, NULL }
};
diff --git a/main/logger.c b/main/logger.c
index 34009053a..02cf4f4fc 100644
--- a/main/logger.c
+++ b/main/logger.c
@@ -535,7 +535,7 @@ static int rotate_file(const char *filename)
}
if (!ast_strlen_zero(exec_after_rotate)) {
- struct ast_channel *c = ast_channel_alloc(0, 0, "", "", "", "", "", 0, "Logger/rotate");
+ struct ast_channel *c = ast_dummy_channel_alloc();
char buf[512];
pbx_builtin_setvar_helper(c, "filename", filename);
pbx_substitute_variables_helper(c, exec_after_rotate, buf, sizeof(buf));
diff --git a/main/manager.c b/main/manager.c
index a2e85f351..76e24d170 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -2488,7 +2488,7 @@ static int action_getvar(struct mansession *s, const struct message *m)
if (varname[strlen(varname) - 1] == ')') {
if (!c) {
- c = ast_channel_alloc(0, 0, "", "", "", "", "", 0, "Bogus/manager");
+ c = ast_dummy_channel_alloc();
if (c) {
ast_func_read(c, (char *) varname, workspace, sizeof(workspace));
c = ast_channel_release(c);
diff --git a/main/pbx.c b/main/pbx.c
index 32838a0e6..c7de70f87 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -46,6 +46,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/file.h"
#include "asterisk/callerid.h"
#include "asterisk/cdr.h"
+#include "asterisk/cel.h"
#include "asterisk/config.h"
#include "asterisk/term.h"
#include "asterisk/time.h"
@@ -1356,6 +1357,8 @@ int pbx_exec(struct ast_channel *c, /*!< Channel */
c->appl = app->name;
c->data = data;
+ ast_cel_report_event(c, AST_CEL_APP_START, NULL, NULL, NULL);
+
if (app->module)
u = __ast_module_user_add(app->module, c);
if (strcasecmp(app->name, "system") && !ast_strlen_zero(data) &&
@@ -1367,6 +1370,7 @@ int pbx_exec(struct ast_channel *c, /*!< Channel */
res = app->execute(c, S_OR(data, ""));
if (app->module && u)
__ast_module_user_remove(app->module, u);
+ ast_cel_report_event(c, AST_CEL_APP_END, NULL, NULL, NULL);
/* restore channel values */
c->appl = saved_c_appl;
c->data = saved_c_data;
@@ -3622,7 +3626,7 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str
cp4 = ast_func_read2(c, finalvars, &substr3, 0) ? NULL : ast_str_buffer(substr3);
} else {
struct varshead old;
- struct ast_channel *bogus = ast_channel_alloc(0, 0, "", "", "", "", "", 0, "Bogus/%p", vars);
+ struct ast_channel *bogus = ast_dummy_channel_alloc();
if (bogus) {
memcpy(&old, &bogus->varshead, sizeof(old));
memcpy(&bogus->varshead, headp, sizeof(bogus->varshead));
@@ -3821,14 +3825,14 @@ void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead
cp4 = ast_func_read(c, vars, workspace, VAR_BUF_SIZE) ? NULL : workspace;
else {
struct varshead old;
- struct ast_channel *bogus = ast_channel_alloc(0, 0, "", "", "", "", "", 0, "Bogus/%p", vars);
- if (bogus) {
- memcpy(&old, &bogus->varshead, sizeof(old));
- memcpy(&bogus->varshead, headp, sizeof(bogus->varshead));
- cp4 = ast_func_read(bogus, vars, workspace, VAR_BUF_SIZE) ? NULL : workspace;
+ struct ast_channel *c = ast_dummy_channel_alloc();
+ if (c) {
+ memcpy(&old, &c->varshead, sizeof(old));
+ memcpy(&c->varshead, headp, sizeof(c->varshead));
+ cp4 = ast_func_read(c, vars, workspace, VAR_BUF_SIZE) ? NULL : workspace;
/* Don't deallocate the varshead that was passed in */
- memcpy(&bogus->varshead, &old, sizeof(bogus->varshead));
- bogus = ast_channel_release(bogus);
+ memcpy(&c->varshead, &old, sizeof(c->varshead));
+ c = ast_channel_release(c);
} else {
ast_log(LOG_ERROR, "Unable to allocate bogus channel for variable substitution. Function results may be blank.\n");
}
@@ -7598,7 +7602,7 @@ int ast_async_goto(struct ast_channel *chan, const char *context, const char *ex
/* In order to do it when the channel doesn't really exist within
the PBX, we have to make a new channel, masquerade, and start the PBX
at the new location */
- struct ast_channel *tmpchan = ast_channel_alloc(0, chan->_state, 0, 0, chan->accountcode, chan->exten, chan->context, chan->amaflags, "AsyncGoto/%s", chan->name);
+ struct ast_channel *tmpchan = ast_channel_alloc(0, chan->_state, 0, 0, chan->accountcode, chan->exten, chan->context, chan->linkedid, chan->amaflags, "AsyncGoto/%s", chan->name);
if (!tmpchan) {
res = -1;
} else {
@@ -7905,7 +7909,10 @@ static int ast_add_extension2_lockopt(struct ast_context *con,
/* If we are adding a hint evalulate in variables and global variables */
if (priority == PRIORITY_HINT && strstr(application, "${") && !strstr(extension, "_")) {
- struct ast_channel *c = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", extension, con->name, 0, "Bogus/%s", __PRETTY_FUNCTION__);
+ struct ast_channel *c = ast_dummy_channel_alloc();
+ ast_copy_string(c->exten, extension, sizeof(c->exten));
+ ast_copy_string(c->context, con->name, sizeof(c->context));
+
pbx_substitute_variables_helper(c, application, expand_buf, sizeof(expand_buf));
application = expand_buf;
ast_channel_release(c);
@@ -8154,11 +8161,12 @@ static void *async_wait(void *data)
static int ast_pbx_outgoing_cdr_failed(void)
{
/* allocate a channel */
- struct ast_channel *chan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "%s", "");
+ struct ast_channel *chan = ast_dummy_channel_alloc();
if (!chan)
return -1; /* failure */
+ chan->cdr = ast_cdr_alloc();
if (!chan->cdr) {
/* allocation of the cdr failed */
chan = ast_channel_release(chan); /* free the channel */
@@ -8194,7 +8202,7 @@ int ast_pbx_outgoing_exten(const char *type, int format, void *data, int timeout
oh.vars = vars;
oh.parent_channel = NULL;
- chan = __ast_request_and_dial(type, format, data, timeout, reason, cid_num, cid_name, &oh);
+ chan = __ast_request_and_dial(type, format, NULL, data, timeout, reason, cid_num, cid_name, &oh);
if (channel) {
*channel = chan;
if (chan)
@@ -8260,7 +8268,7 @@ int ast_pbx_outgoing_exten(const char *type, int format, void *data, int timeout
/* create a fake channel and execute the "failed" extension (if it exists) within the requested context */
/* check if "failed" exists */
if (ast_exists_extension(chan, context, "failed", 1, NULL)) {
- chan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "OutgoingSpoolFailed");
+ chan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", NULL, 0, "OutgoingSpoolFailed");
if (chan) {
char failed_reason[4] = "";
if (!ast_strlen_zero(context))
@@ -8284,7 +8292,7 @@ int ast_pbx_outgoing_exten(const char *type, int format, void *data, int timeout
res = -1;
goto outgoing_exten_cleanup;
}
- chan = ast_request_and_dial(type, format, data, timeout, reason, cid_num, cid_name);
+ chan = ast_request_and_dial(type, format, NULL, data, timeout, reason, cid_num, cid_name);
if (channel) {
*channel = chan;
if (chan)
@@ -8361,7 +8369,7 @@ int ast_pbx_outgoing_app(const char *type, int format, void *data, int timeout,
goto outgoing_app_cleanup;
}
if (synchronous) {
- chan = __ast_request_and_dial(type, format, data, timeout, reason, cid_num, cid_name, &oh);
+ chan = __ast_request_and_dial(type, format, NULL, data, timeout, reason, cid_num, cid_name, &oh);
if (chan) {
ast_set_variables(chan, vars);
if (account)
@@ -8426,7 +8434,7 @@ int ast_pbx_outgoing_app(const char *type, int format, void *data, int timeout,
res = -1;
goto outgoing_app_cleanup;
}
- chan = __ast_request_and_dial(type, format, data, timeout, reason, cid_num, cid_name, &oh);
+ chan = __ast_request_and_dial(type, format, NULL, data, timeout, reason, cid_num, cid_name, &oh);
if (!chan) {
ast_free(as);
res = -1;
@@ -8833,6 +8841,8 @@ static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data)
*/
static int pbx_builtin_hangup(struct ast_channel *chan, const char *data)
{
+ ast_set_hangupsource(chan, "dialplan/builtin", 0);
+
if (!ast_strlen_zero(data)) {
int cause;
char *endptr;
@@ -9549,7 +9559,7 @@ int load_pbx(void)
/* Register manager application */
ast_manager_register_xml("ShowDialPlan", EVENT_FLAG_CONFIG | EVENT_FLAG_REPORTING, manager_show_dialplan);
- if (!(device_state_sub = ast_event_subscribe(AST_EVENT_DEVICE_STATE, device_state_cb, NULL,
+ if (!(device_state_sub = ast_event_subscribe(AST_EVENT_DEVICE_STATE_CHANGE, device_state_cb, "pbx Device State Change", NULL,
AST_EVENT_IE_END))) {
return -1;
}