Skip to content
Snippets Groups Projects
cdr.c 140 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	if (!strcasecmp(name, "clid")) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		ast_copy_string(workspace, cdr->clid, workspacelen);
    
    	} else if (!strcasecmp(name, "src")) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		ast_copy_string(workspace, cdr->src, workspacelen);
    
    	} else if (!strcasecmp(name, "dst")) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		ast_copy_string(workspace, cdr->dst, workspacelen);
    
    	} else if (!strcasecmp(name, "dcontext")) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		ast_copy_string(workspace, cdr->dcontext, workspacelen);
    
    	} else if (!strcasecmp(name, "channel")) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		ast_copy_string(workspace, cdr->channel, workspacelen);
    
    	} else if (!strcasecmp(name, "dstchannel")) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		ast_copy_string(workspace, cdr->dstchannel, workspacelen);
    
    	} else if (!strcasecmp(name, "lastapp")) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		ast_copy_string(workspace, cdr->lastapp, workspacelen);
    
    	} else if (!strcasecmp(name, "lastdata")) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		ast_copy_string(workspace, cdr->lastdata, workspacelen);
    
    	} else if (!strcasecmp(name, "start")) {
    
    		cdr_get_tv(cdr->start, raw ? NULL : fmt, workspace, workspacelen);
    
    	} else if (!strcasecmp(name, "answer")) {
    
    		cdr_get_tv(cdr->answer, raw ? NULL : fmt, workspace, workspacelen);
    
    	} else if (!strcasecmp(name, "end")) {
    
    		cdr_get_tv(cdr->end, raw ? NULL : fmt, workspace, workspacelen);
    
    	} else if (!strcasecmp(name, "duration")) {
    
    		snprintf(workspace, workspacelen, "%ld", cdr->end.tv_sec != 0 ? cdr->duration : (long)ast_tvdiff_ms(ast_tvnow(), cdr->start) / 1000);
    
    	} else if (!strcasecmp(name, "billsec")) {
    
    		snprintf(workspace, workspacelen, "%ld", (cdr->billsec || !ast_tvzero(cdr->end) || ast_tvzero(cdr->answer)) ? cdr->billsec : (long)ast_tvdiff_ms(ast_tvnow(), cdr->answer) / 1000);
    
    	} else if (!strcasecmp(name, "disposition")) {
    
    			snprintf(workspace, workspacelen, "%ld", cdr->disposition);
    
    		} else {
    			ast_copy_string(workspace, ast_cdr_disp2str(cdr->disposition), workspacelen);
    		}
    	} else if (!strcasecmp(name, "amaflags")) {
    		if (raw) {
    
    			snprintf(workspace, workspacelen, "%ld", cdr->amaflags);
    
    			ast_copy_string(workspace, ast_channel_amaflags2string(cdr->amaflags), workspacelen);
    
    	} else if (!strcasecmp(name, "accountcode")) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		ast_copy_string(workspace, cdr->accountcode, workspacelen);
    
    	} else if (!strcasecmp(name, "peeraccount")) {
    
    		ast_copy_string(workspace, cdr->peeraccount, workspacelen);
    
    	} else if (!strcasecmp(name, "uniqueid")) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		ast_copy_string(workspace, cdr->uniqueid, workspacelen);
    
    	} else if (!strcasecmp(name, "linkedid")) {
    
    		ast_copy_string(workspace, cdr->linkedid, workspacelen);
    
    	} else if (!strcasecmp(name, "userfield")) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		ast_copy_string(workspace, cdr->userfield, workspacelen);
    
    	} else if (!strcasecmp(name, "sequence")) {
    
    		snprintf(workspace, workspacelen, "%d", cdr->sequence);
    
    	} else if ((varbuf = cdr_format_var_internal(cdr, name))) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		ast_copy_string(workspace, varbuf, workspacelen);
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    
    
    	if (!ast_strlen_zero(workspace)) {
    
    		*ret = workspace;
    
     * \brief Callback that finds all CDRs that reference a particular channel by name
    
    static int cdr_object_select_all_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) ||
    			(cdr->party_b.snapshot && !strcasecmp(cdr->party_b.snapshot->name, name))) {
    		return CMP_MATCH;
    	}
    	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",
    	NULL
    };
    
    
    int ast_cdr_setvar(const char *channel_name, const char *name, const char *value)
    
    	struct cdr_object *cdr;
    	struct cdr_object *it_cdr;
    	struct ao2_iterator *it_cdrs;
    	char *arg = ast_strdupa(channel_name);
    
    	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);
    
    	it_cdrs = ao2_callback(active_cdrs_master, 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);
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    
    
    	for (; (cdr = ao2_iterator_next(it_cdrs)); ao2_unlock(cdr), ao2_cleanup(cdr)) {
    
    		ao2_lock(cdr);
    		for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
    			struct varshead *headp = NULL;
    
    			if (it_cdr->fn_table == &finalized_state_fn_table && it_cdr->next != NULL) {
    
    			if (!strcasecmp(channel_name, it_cdr->party_a.snapshot->name)) {
    
    				headp = &it_cdr->party_a.variables;
    
    			} else if (it_cdr->party_b.snapshot
    				&& !strcasecmp(channel_name, it_cdr->party_b.snapshot->name)) {
    
    				headp = &it_cdr->party_b.variables;
    
    			if (headp) {
    				set_variable(headp, name, value);
    
    	ao2_iterator_destroy(it_cdrs);
    
    	return 0;
    
    /*!
     * \brief Format a variable on a \ref cdr_object
     */
    static void cdr_object_format_var_internal(struct cdr_object *cdr, const char *name, char *value, size_t length)
    
    	struct ast_var_t *variable;
    
    	AST_LIST_TRAVERSE(&cdr->party_a.variables, variable, entries) {
    		if (!strcasecmp(name, ast_var_name(variable))) {
    			ast_copy_string(value, ast_var_value(variable), length);
    			return;
    
    /*!
     * \brief Format one of the standard properties on a \ref cdr_object
     */
    static int cdr_object_format_property(struct cdr_object *cdr_obj, const char *name, char *value, size_t length)
    {
    	struct ast_channel_snapshot *party_a = cdr_obj->party_a.snapshot;
    	struct ast_channel_snapshot *party_b = cdr_obj->party_b.snapshot;
    
    	if (!strcasecmp(name, "clid")) {
    		ast_callerid_merge(value, length, party_a->caller_name, party_a->caller_number, "");
    	} else if (!strcasecmp(name, "src")) {
    		ast_copy_string(value, party_a->caller_number, length);
    	} else if (!strcasecmp(name, "dst")) {
    		ast_copy_string(value, party_a->exten, length);
    	} else if (!strcasecmp(name, "dcontext")) {
    		ast_copy_string(value, party_a->context, length);
    	} else if (!strcasecmp(name, "channel")) {
    		ast_copy_string(value, party_a->name, length);
    	} else if (!strcasecmp(name, "dstchannel")) {
    		if (party_b) {
    			ast_copy_string(value, party_b->name, length);
    
    			ast_copy_string(value, "", length);
    
    	} else if (!strcasecmp(name, "lastapp")) {
    		ast_copy_string(value, party_a->appl, length);
    	} else if (!strcasecmp(name, "lastdata")) {
    		ast_copy_string(value, party_a->data, length);
    	} else if (!strcasecmp(name, "start")) {
    		cdr_get_tv(cdr_obj->start, NULL, value, length);
    	} else if (!strcasecmp(name, "answer")) {
    		cdr_get_tv(cdr_obj->answer, NULL, value, length);
    	} else if (!strcasecmp(name, "end")) {
    		cdr_get_tv(cdr_obj->end, NULL, value, length);
    	} else if (!strcasecmp(name, "duration")) {
    		snprintf(value, length, "%ld", cdr_object_get_duration(cdr_obj));
    	} else if (!strcasecmp(name, "billsec")) {
    		snprintf(value, length, "%ld", cdr_object_get_billsec(cdr_obj));
    	} else if (!strcasecmp(name, "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")) {
    		ast_copy_string(value, party_a->accountcode, length);
    	} else if (!strcasecmp(name, "peeraccount")) {
    		if (party_b) {
    			ast_copy_string(value, party_b->accountcode, length);
    
    			ast_copy_string(value, "", length);
    
    	} else if (!strcasecmp(name, "uniqueid")) {
    		ast_copy_string(value, party_a->uniqueid, length);
    	} else if (!strcasecmp(name, "linkedid")) {
    		ast_copy_string(value, cdr_obj->linkedid, length);
    	} else if (!strcasecmp(name, "userfield")) {
    		ast_copy_string(value, cdr_obj->party_a.userfield, length);
    	} else if (!strcasecmp(name, "sequence")) {
    
    		snprintf(value, length, "%u", cdr_obj->sequence);
    
    Martin Pycko's avatar
     
    Martin Pycko committed
    	}
    
    
    /*! \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_master, 0, cdr_object_get_by_name_cb, param);
    
    int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length)
    
    	struct cdr_object *cdr;
    
    	struct cdr_object *cdr_obj;
    
    	if (ast_strlen_zero(name)) {
    
    	cdr = cdr_object_get_by_name(channel_name);
    	if (!cdr) {
    		ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
    
    	cdr_obj = cdr->last;
    	if (cdr_object_format_property(cdr_obj, name, value, length)) {
    		/* Property failed; attempt variable */
    		cdr_object_format_var_internal(cdr_obj, name, value, length);
    
    Martin Pycko's avatar
     
    Martin Pycko committed
    	}
    
    Martin Pycko's avatar
     
    Martin Pycko committed
    
    
    	ao2_cleanup(cdr);
    
    int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf, char delim, char sep)
    
    	struct cdr_object *cdr;
    
    	struct cdr_object *it_cdr;
    	struct ast_var_t *variable;
    	const char *var;
    
    	char workspace[256];
    
    	int total = 0, x = 0, i;
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    
    
    	cdr = cdr_object_get_by_name(channel_name);
    
    		if (is_cdr_flag_set(CDR_ENABLED)) {
    
    			ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
    		}
    
    	ao2_lock(cdr);
    	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
    
    		if (++x > 1) {
    
    			ast_str_append(buf, 0, "\n");
    
    		AST_LIST_TRAVERSE(&it_cdr->party_a.variables, variable, entries) {
    			if (!(var = ast_var_name(variable))) {
    				continue;
    			}
    
    			if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, var, delim, S_OR(ast_var_value(variable), ""), sep) < 0) {
    				ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
    				break;
    			}
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    
    
    		for (i = 0; cdr_readonly_vars[i]; i++) {
    
    			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) {
    				ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
    				break;
    			}
    			total++;
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	ao2_cleanup(cdr);
    
    void ast_cdr_free(struct ast_cdr *cdr)
    
    	while (cdr) {
    		struct ast_cdr *next = cdr->next;
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    
    
    		free_variables(&cdr->varshead);
    		ast_free(cdr);
    		cdr = next;
    
    struct ast_cdr *ast_cdr_alloc(void)
    
    	struct ast_cdr *x;
    
    	x = ast_calloc(1, sizeof(*x));
    	return x;
    
    const char *ast_cdr_disp2str(int disposition)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	switch (disposition) {
    
    	case AST_CDR_NULL:
    		return "NO ANSWER"; /* by default, for backward compatibility */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	case AST_CDR_NOANSWER:
    		return "NO ANSWER";
    
    Martin Pycko's avatar
     
    Martin Pycko committed
    	case AST_CDR_FAILED:
    
    Mark Spencer's avatar
    Mark Spencer committed
    	case AST_CDR_BUSY:
    
    Mark Spencer's avatar
    Mark Spencer committed
    	case AST_CDR_ANSWERED:
    		return "ANSWERED";
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    	return "UNKNOWN";
    
    struct party_b_userfield_update {
    	const char *channel_name;
    	const char *userfield;
    };
    
    /*! \brief Callback used to update the userfield on Party B on all CDRs */
    
    static int cdr_object_update_party_b_userfield_cb(void *obj, void *arg, void *data, int flags)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct cdr_object *cdr = obj;
    
    	if ((cdr->fn_table != &finalized_state_fn_table || !cdr->next)
    		&& !strcasecmp(cdr->party_b_name, arg)) {
    		struct party_b_userfield_update *info = data;
    
    		/*
    		 * For sanity's sake we also assert the party_b snapshot
    		 * is consistent with the key.
    		 */
    		ast_assert(cdr->party_b.snapshot
    			&& !strcasecmp(cdr->party_b.snapshot->name, info->channel_name));
    
    
    		ast_copy_string(cdr->party_b.userfield, info->userfield,
    			sizeof(cdr->party_b.userfield));
    
    void ast_cdr_setuserfield(const char *channel_name, const char *userfield)
    
    	struct cdr_object *cdr;
    
    	struct party_b_userfield_update party_b_info = {
    
    		.channel_name = channel_name,
    		.userfield = userfield,
    
    	};
    	struct cdr_object *it_cdr;
    
    	cdr = cdr_object_get_by_name(channel_name);
    
    	if (cdr) {
    		ao2_lock(cdr);
    		for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
    
    			if (it_cdr->fn_table == &finalized_state_fn_table && it_cdr->next != NULL) {
    
    			ast_copy_string(it_cdr->party_a.userfield, userfield,
    				sizeof(it_cdr->party_a.userfield));
    
    	ao2_callback_data(active_cdrs_all, OBJ_NODATA | OBJ_MULTIPLE | OBJ_SEARCH_KEY,
    		cdr_object_update_party_b_userfield_cb, (char *) party_b_info.channel_name,
    		&party_b_info);
    
    	ao2_cleanup(cdr);
    
    static void post_cdr(struct ast_cdr *cdr)
    
    	struct module_config *mod_cfg;
    
    	mod_cfg = ao2_global_obj_ref(module_configs);
    	if (!mod_cfg) {
    		return;
    	}
    
    
    	for (; cdr ; cdr = cdr->next) {
    		/* For people, who don't want to see unanswered single-channel events */
    		if (!ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED) &&
    				cdr->disposition < AST_CDR_ANSWERED &&
    				(ast_strlen_zero(cdr->channel) || ast_strlen_zero(cdr->dstchannel))) {
    
    			ast_debug(1, "Skipping CDR for %s since we weren't answered\n", cdr->channel);
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    
    
    		if (ast_test_flag(cdr, AST_CDR_FLAG_DISABLE)) {
    			continue;
    		}
    		AST_RWLIST_RDLOCK(&be_list);
    		AST_RWLIST_TRAVERSE(&be_list, i, list) {
    
    int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option)
    
    	struct cdr_object *cdr;
    
    	cdr = cdr_object_get_by_name(channel_name);
    
    	ao2_lock(cdr);
    	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
    		if (it_cdr->fn_table == &finalized_state_fn_table) {
    			continue;
    		}
    
    		/* Note: in general, set the flags on both the CDR record as well as the
    		 * Party A. Sometimes all we have is the Party A to look at.
    		 */
    
    		ast_set_flag(&it_cdr->flags, option);
    
    		ast_set_flag(&it_cdr->party_a, option);
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    
    
    	ao2_cleanup(cdr);
    
    int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option)
    
    	struct cdr_object *cdr;
    
    	struct cdr_object *it_cdr;
    
    	cdr = cdr_object_get_by_name(channel_name);
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    
    
    	ao2_lock(cdr);
    	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
    		if (it_cdr->fn_table == &finalized_state_fn_table) {
    			continue;
    		}
    		ast_clear_flag(&it_cdr->flags, option);
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    
    
    	ao2_cleanup(cdr);
    
    int ast_cdr_reset(const char *channel_name, int keep_variables)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct cdr_object *cdr;
    
    	struct ast_var_t *vardata;
    	struct cdr_object *it_cdr;
    
    	cdr = cdr_object_get_by_name(channel_name);
    
    	ao2_lock(cdr);
    	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
    		/* clear variables */
    
    			while ((vardata = AST_LIST_REMOVE_HEAD(&it_cdr->party_a.variables, entries))) {
    				ast_var_delete(vardata);
    			}
    			if (cdr->party_b.snapshot) {
    				while ((vardata = AST_LIST_REMOVE_HEAD(&it_cdr->party_b.variables, entries))) {
    					ast_var_delete(vardata);
    				}
    			}
    
    
    		/* Reset to initial state */
    		memset(&it_cdr->start, 0, sizeof(it_cdr->start));
    		memset(&it_cdr->end, 0, sizeof(it_cdr->end));
    		memset(&it_cdr->answer, 0, sizeof(it_cdr->answer));
    		it_cdr->start = ast_tvnow();
    		cdr_object_check_party_a_answer(it_cdr);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	ao2_cleanup(cdr);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    int ast_cdr_fork(const char *channel_name, struct ast_flags *options)
    
    	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;
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    
    
    	{
    		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 -
    			 * things are already dying at this point
    			 */
    			return -1;
    
    		/* Copy over the basic CDR information. The Party A information is
    		 * copied over automatically as part of the append
    		 */
    		ast_debug(1, "Forking CDR for channel %s\n", cdr->party_a.snapshot->name);
    		new_cdr = cdr_object_create_and_append(cdr);
    		if (!new_cdr) {
    			return -1;
    
    		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) {
    			new_cdr->party_b.snapshot = cdr_obj->party_b.snapshot;
    			ao2_ref(new_cdr->party_b.snapshot, +1);
    
    			strcpy(new_cdr->party_b.userfield, cdr_obj->party_b.userfield);
    			new_cdr->party_b.flags = cdr_obj->party_b.flags;
    			if (ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) {
    				copy_variables(&new_cdr->party_b.variables, &cdr_obj->party_b.variables);
    			}
    
    		new_cdr->start = cdr_obj->start;
    		new_cdr->answer = cdr_obj->answer;
    
    		/* Modify the times based on the flags passed in */
    		if (ast_test_flag(options, AST_CDR_FLAG_SET_ANSWER)
    				&& new_cdr->party_a.snapshot->state == AST_STATE_UP) {
    			new_cdr->answer = ast_tvnow();
    		}
    		if (ast_test_flag(options, AST_CDR_FLAG_RESET)) {
    			new_cdr->answer = ast_tvnow();
    			new_cdr->start = ast_tvnow();
    		}
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    
    
    		/* Create and append, by default, copies over the variables */
    		if (!ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) {
    			free_variables(&new_cdr->party_a.variables);
    		}
    
    		/* Finalize any current CDRs */
    		if (ast_test_flag(options, AST_CDR_FLAG_FINALIZE)) {
    			for (it_cdr = cdr; it_cdr != new_cdr; it_cdr = it_cdr->next) {
    				if (it_cdr->fn_table == &finalized_state_fn_table) {
    					continue;
    
    				/* Force finalization on the CDR. This will bypass any checks for
    				 * end before 'h' extension.
    				 */
    				cdr_object_finalize(it_cdr);
    				cdr_object_transition_state(it_cdr, &finalized_state_fn_table);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    
    
    /*! \note Don't call without cdr_batch_lock */
    
    static void reset_batch(void)
    {
    	batch->size = 0;
    	batch->head = NULL;
    	batch->tail = NULL;
    }
    
    
    /*! \note Don't call without cdr_batch_lock */
    
    static int init_batch(void)
    {
    	/* This is the single meta-batch used to keep track of all CDRs during the entire life of the program */
    
    	if (!(batch = ast_malloc(sizeof(*batch))))
    
    		return -1;
    
    	reset_batch();
    
    	return 0;
    }
    
    static void *do_batch_backend_process(void *data)
    {
    
    	struct cdr_batch_item *processeditem;
    	struct cdr_batch_item *batchitem = data;
    
    
    	/* Push each CDR into storage mechanism(s) and free all the memory */
    	while (batchitem) {
    		post_cdr(batchitem->cdr);
    		ast_cdr_free(batchitem->cdr);
    		processeditem = batchitem;
    		batchitem = batchitem->next;
    
    static void cdr_submit_batch(int do_shutdown)
    
    	struct module_config *mod_cfg;
    
    	struct cdr_batch_item *oldbatchitems = NULL;
    
    	pthread_t batch_post_thread = AST_PTHREADT_NULL;
    
    	/* if there's no batch, or no CDRs in the batch, then there's nothing to do */
    
    	if (!batch || !batch->head) {
    
    
    	/* move the old CDRs aside, and prepare a new CDR batch */
    	ast_mutex_lock(&cdr_batch_lock);
    	oldbatchitems = batch->head;
    	reset_batch();
    	ast_mutex_unlock(&cdr_batch_lock);
    
    
    	mod_cfg = ao2_global_obj_ref(module_configs);
    
    
    	/* if configured, spawn a new thread to post these CDRs,
    	   also try to save as much as possible if we are shutting down safely */
    
    	if (!mod_cfg
    		|| ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SCHEDULER_ONLY)
    		|| do_shutdown) {
    
    		ast_debug(1, "CDR single-threaded batch processing begins now\n");
    
    		do_batch_backend_process(oldbatchitems);
    	} else {
    
    		if (ast_pthread_create_detached_background(&batch_post_thread, NULL, do_batch_backend_process, oldbatchitems)) {
    
    			ast_log(LOG_WARNING, "CDR processing thread could not detach, now trying in this thread\n");
    			do_batch_backend_process(oldbatchitems);
    		} else {
    
    			ast_debug(1, "CDR multi-threaded batch processing begins now\n");
    
    static int submit_scheduled_batch(const void *data)
    
    	struct module_config *mod_cfg;
    
    
    	mod_cfg = ao2_global_obj_ref(module_configs);
    	if (!mod_cfg) {
    		return 0;
    	}
    
    	/* manually reschedule from this point in time */
    
    	ast_mutex_lock(&cdr_sched_lock);
    
    	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;
    }
    
    
    /*! Do not hold the batch lock while calling this function */
    
    static void submit_unscheduled_batch(void)
    {
    
    	/* Prevent two deletes from happening at the same time */
    	ast_mutex_lock(&cdr_sched_lock);
    
    	/* this is okay since we are not being called from within the scheduler */
    
    	AST_SCHED_DEL(sched, cdr_sched);
    
    	/* schedule the submission to occur ASAP (1 ms) */
    	cdr_sched = ast_sched_add(sched, 1, submit_scheduled_batch, NULL);
    
    	ast_mutex_unlock(&cdr_sched_lock);
    
    
    	/* signal the do_cdr thread to wakeup early and do some work (that lazy thread ;) */
    
    	ast_mutex_lock(&cdr_pending_lock);
    
    	ast_mutex_unlock(&cdr_pending_lock);
    
    static void cdr_detach(struct ast_cdr *cdr)
    
    	struct cdr_batch_item *newtail;
    
    	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
    
    	/* maybe they disabled CDR stuff completely, so just drop it */
    
    	if (!mod_cfg || !ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
    
    		ast_cdr_free(cdr);
    		return;
    	}
    
    	/* post stuff immediately if we are not in batch mode, this is legacy behaviour */
    
    	if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
    
    		post_cdr(cdr);
    		ast_cdr_free(cdr);
    		return;
    	}
    
    	/* otherwise, each CDR gets put into a batch list (at the end) */
    
    	ast_debug(1, "CDR detaching from this thread\n");
    
    	/* we'll need a new tail for every CDR */
    
    	if (!(newtail = ast_calloc(1, sizeof(*newtail)))) {
    
    		post_cdr(cdr);
    		ast_cdr_free(cdr);
    		return;
    	}
    
    	/* don't traverse a whole list (just keep track of the tail) */
    	ast_mutex_lock(&cdr_batch_lock);
    	if (!batch)
    		init_batch();
    	if (!batch->head) {
    		/* new batch is empty, so point the head at the new tail */
    		batch->head = newtail;
    	} else {
    		/* already got a batch with something in it, so just append a new tail */
    		batch->tail->next = newtail;
    	}
    	newtail->cdr = cdr;
    	batch->tail = newtail;
    	curr = batch->size++;
    
    	/* if we have enough stuff to post, then do it */
    
    	if (curr >= (mod_cfg->general->batch_settings.size - 1)) {
    
    
    	/* Don't call submit_unscheduled_batch with the cdr_batch_lock held */
    	if (submit_batch) {
    		submit_unscheduled_batch();
    	}
    
    }
    
    static void *do_cdr(void *data)
    {
    	struct timespec timeout;
    	int schedms;
    	int numevents = 0;
    
    
    		struct timeval now;
    
    		schedms = ast_sched_wait(sched);
    		/* this shouldn't happen, but provide a 1 second default just in case */
    		if (schedms <= 0)
    			schedms = 1000;
    
    		now = ast_tvadd(ast_tvnow(), ast_samp2tv(schedms, 1000));
    		timeout.tv_sec = now.tv_sec;
    		timeout.tv_nsec = now.tv_usec * 1000;
    
    		/* prevent stuff from clobbering cdr_pending_cond, then wait on signals sent to it until the timeout expires */
    
    		ast_mutex_lock(&cdr_pending_lock);
    
    		ast_cond_timedwait(&cdr_pending_cond, &cdr_pending_lock, &timeout);
    
    		ast_mutex_unlock(&cdr_pending_lock);
    
    		ast_debug(2, "Processed %d scheduled CDR batches from the run queue\n", numevents);
    
    static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    
    	struct module_config *mod_cfg;
    
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "cdr set debug [on|off]";
    
    		e->usage = "Enable or disable extra debugging in the CDR Engine. Note\n"
    				"that this will dump debug information to the VERBOSE setting\n"
    				"and should only be used when debugging information from the\n"
    				"CDR engine is needed.\n";
    
    		return NULL;
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    	if (a->argc != 4) {
    		return CLI_SHOWUSAGE;
    	}
    
    
    	mod_cfg = ao2_global_obj_ref(module_configs);
    	if (!mod_cfg) {
    		ast_cli(a->fd, "Could not set CDR debugging mode\n");
    		return CLI_SUCCESS;
    	}
    
    	if (!strcasecmp(a->argv[3], "on")
    		&& !ast_test_flag(&mod_cfg->general->settings, CDR_DEBUG)) {
    
    		ast_set_flag(&mod_cfg->general->settings, CDR_DEBUG);
    		ast_cli(a->fd, "CDR debugging enabled\n");
    
    	} else if (!strcasecmp(a->argv[3], "off")
    		&& ast_test_flag(&mod_cfg->general->settings, CDR_DEBUG)) {
    
    		ast_clear_flag(&mod_cfg->general->settings, CDR_DEBUG);
    		ast_cli(a->fd, "CDR debugging disabled\n");
    	}
    
    	cdr_set_debug_mode(mod_cfg);
    	ao2_cleanup(mod_cfg);
    
    /*! \brief Complete user input for 'cdr show' */
    static char *cli_complete_show(struct ast_cli_args *a)
    {
    	char *result = NULL;
    	int wordlen = strlen(a->word);
    	int which = 0;
    	struct ao2_iterator it_cdrs;
    	struct cdr_object *cdr;
    
    
    	it_cdrs = ao2_iterator_init(active_cdrs_master, 0);
    
    	while ((cdr = ao2_iterator_next(&it_cdrs))) {
    		if (!strncasecmp(a->word, cdr->party_a.snapshot->name, wordlen) &&
    			(++which > a->n)) {
    			result = ast_strdup(cdr->party_a.snapshot->name);
    			if (result) {
    				ao2_ref(cdr, -1);
    				break;
    			}
    		}
    		ao2_ref(cdr, -1);
    	}
    	ao2_iterator_destroy(&it_cdrs);
    	return result;
    }
    
    static void cli_show_channels(struct ast_cli_args *a)
    {
    	struct ao2_iterator it_cdrs;
    	struct cdr_object *cdr;
    	char start_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"
    #define FORMAT_STRING "%-25.25s %-25.25s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8ld %-8.8ld\n"
    
    	ast_cli(a->fd, "\n");
    	ast_cli(a->fd, "Channels with Call Detail Record (CDR) Information\n");
    	ast_cli(a->fd, "--------------------------------------------------\n");
    	ast_cli(a->fd, TITLE_STRING, "Channel", "Dst. Channel", "LastApp", "Start", "Answer", "End", "Billsec", "Duration");
    
    
    	it_cdrs = ao2_iterator_init(active_cdrs_master, 0);
    
    	for (; (cdr = ao2_iterator_next(&it_cdrs)); ao2_cleanup(cdr)) {
    
    		struct cdr_object *it_cdr;
    		struct timeval start_time = { 0, };
    		struct timeval answer_time = { 0, };
    		struct timeval end_time = { 0, };
    
    		SCOPED_AO2LOCK(lock, cdr);
    
    		/* Calculate the start, end, answer, billsec, and duration over the
    		 * life of all of the CDR entries
    		 */
    		for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
    			if (snapshot_is_dialed(it_cdr->party_a.snapshot)) {
    				continue;
    			}
    			if (ast_tvzero(start_time)) {
    				start_time = it_cdr->start;
    			}
    			if (!ast_tvzero(it_cdr->answer) && ast_tvzero(answer_time)) {
    				answer_time = it_cdr->answer;
    			}
    		}
    
    
    		/* If there was no start time, then all CDRs were for a dialed channel; skip */
    
    		if (ast_tvzero(start_time)) {
    			continue;
    		}
    		it_cdr = cdr->last;
    
    
    		end_time = ast_tvzero(it_cdr->end) ? ast_tvnow() : it_cdr->end;
    
    		cdr_get_tv(start_time, "%T", start_time_buffer, sizeof(start_time_buffer));
    		cdr_get_tv(answer_time, "%T", answer_time_buffer, sizeof(answer_time_buffer));
    		cdr_get_tv(end_time, "%T", end_time_buffer, sizeof(end_time_buffer));
    		ast_cli(a->fd, FORMAT_STRING, it_cdr->party_a.snapshot->name,
    				it_cdr->party_b.snapshot ? it_cdr->party_b.snapshot->name : "<none>",
    				it_cdr->appl,
    				start_time_buffer,
    				answer_time_buffer,
    				end_time_buffer,
    
    				ast_tvzero(answer_time) ? 0 : (long)ast_tvdiff_ms(end_time, answer_time) / 1000,
    
    				(long)ast_tvdiff_ms(end_time, start_time) / 1000);
    	}
    	ao2_iterator_destroy(&it_cdrs);
    #undef FORMAT_STRING
    #undef TITLE_STRING
    }
    
    static void cli_show_channel(struct ast_cli_args *a)
    {
    	struct cdr_object *it_cdr;
    	char clid[64];
    	char start_time_buffer[64];
    
    	char answer_time_buffer[64];
    	char end_time_buffer[64];
    
    	const char *channel_name = a->argv[3];
    
    	struct cdr_object *cdr;
    
    
    #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"