2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief Call Detail Record API
23 * \author Mark Spencer <markster@digium.com>
25 * \note Includes code and algorithms from the Zapata library.
27 * \note We do a lot of checking here in the CDR code to try to be sure we don't ever let a CDR slip
28 * through our fingers somehow. If someone allocates a CDR, it must be completely handled normally
29 * or a WARNING shall be logged, so that we can best keep track of any escape condition where the CDR
30 * isn't properly generated and posted.
36 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
44 #include "asterisk/lock.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/cdr.h"
47 #include "asterisk/logger.h"
48 #include "asterisk/callerid.h"
49 #include "asterisk/causes.h"
50 #include "asterisk/options.h"
51 #include "asterisk/linkedlists.h"
52 #include "asterisk/utils.h"
53 #include "asterisk/sched.h"
54 #include "asterisk/config.h"
55 #include "asterisk/cli.h"
56 #include "asterisk/stringfields.h"
58 /*! Default AMA flag for billing records (CDR's) */
59 int ast_default_amaflags
= AST_CDR_DOCUMENTATION
;
60 char ast_default_accountcode
[AST_MAX_ACCOUNT_CODE
];
62 struct ast_cdr_beitem
{
66 AST_LIST_ENTRY(ast_cdr_beitem
) list
;
69 static AST_LIST_HEAD_STATIC(be_list
, ast_cdr_beitem
);
71 struct ast_cdr_batch_item
{
73 struct ast_cdr_batch_item
*next
;
76 static struct ast_cdr_batch
{
78 struct ast_cdr_batch_item
*head
;
79 struct ast_cdr_batch_item
*tail
;
82 static struct sched_context
*sched
;
83 static int cdr_sched
= -1;
84 static pthread_t cdr_thread
= AST_PTHREADT_NULL
;
86 #define BATCH_SIZE_DEFAULT 100
87 #define BATCH_TIME_DEFAULT 300
88 #define BATCH_SCHEDULER_ONLY_DEFAULT 0
89 #define BATCH_SAFE_SHUTDOWN_DEFAULT 1
91 static int enabled
; /*! Is the CDR subsystem enabled ? */
92 static int unanswered
;
96 static int batchscheduleronly
;
97 static int batchsafeshutdown
;
99 AST_MUTEX_DEFINE_STATIC(cdr_batch_lock
);
101 /* these are used to wake up the CDR thread when there's work to do */
102 AST_MUTEX_DEFINE_STATIC(cdr_pending_lock
);
103 static ast_cond_t cdr_pending_cond
;
106 /*! Register a CDR driver. Each registered CDR driver generates a CDR
107 \return 0 on success, -1 on failure
109 int ast_cdr_register(const char *name
, const char *desc
, ast_cdrbe be
)
111 struct ast_cdr_beitem
*i
;
116 ast_log(LOG_WARNING
, "CDR engine '%s' lacks backend\n", name
);
120 AST_LIST_LOCK(&be_list
);
121 AST_LIST_TRAVERSE(&be_list
, i
, list
) {
122 if (!strcasecmp(name
, i
->name
))
125 AST_LIST_UNLOCK(&be_list
);
128 ast_log(LOG_WARNING
, "Already have a CDR backend called '%s'\n", name
);
132 if (!(i
= ast_calloc(1, sizeof(*i
))))
136 ast_copy_string(i
->name
, name
, sizeof(i
->name
));
137 ast_copy_string(i
->desc
, desc
, sizeof(i
->desc
));
139 AST_LIST_LOCK(&be_list
);
140 AST_LIST_INSERT_HEAD(&be_list
, i
, list
);
141 AST_LIST_UNLOCK(&be_list
);
146 /*! unregister a CDR driver */
147 void ast_cdr_unregister(const char *name
)
149 struct ast_cdr_beitem
*i
= NULL
;
151 AST_LIST_LOCK(&be_list
);
152 AST_LIST_TRAVERSE_SAFE_BEGIN(&be_list
, i
, list
) {
153 if (!strcasecmp(name
, i
->name
)) {
154 AST_LIST_REMOVE_CURRENT(&be_list
, list
);
155 if (option_verbose
> 1)
156 ast_verbose(VERBOSE_PREFIX_2
"Unregistered '%s' CDR backend\n", name
);
161 AST_LIST_TRAVERSE_SAFE_END
;
162 AST_LIST_UNLOCK(&be_list
);
165 int ast_cdr_isset_unanswered(void)
170 /*! Duplicate a CDR record
171 \returns Pointer to new CDR record
173 struct ast_cdr
*ast_cdr_dup(struct ast_cdr
*cdr
)
175 struct ast_cdr
*newcdr
;
177 if (!cdr
) /* don't die if we get a null cdr pointer */
179 newcdr
= ast_cdr_alloc();
183 memcpy(newcdr
, cdr
, sizeof(*newcdr
));
184 /* The varshead is unusable, volatile even, after the memcpy so we take care of that here */
185 memset(&newcdr
->varshead
, 0, sizeof(newcdr
->varshead
));
186 ast_cdr_copy_vars(newcdr
, cdr
);
192 static const char *ast_cdr_getvar_internal(struct ast_cdr
*cdr
, const char *name
, int recur
)
194 if (ast_strlen_zero(name
))
197 for (; cdr
; cdr
= recur
? cdr
->next
: NULL
) {
198 struct ast_var_t
*variables
;
199 struct varshead
*headp
= &cdr
->varshead
;
200 AST_LIST_TRAVERSE(headp
, variables
, entries
) {
201 if (!strcasecmp(name
, ast_var_name(variables
)))
202 return ast_var_value(variables
);
209 static void cdr_get_tv(struct timeval tv
, const char *fmt
, char *buf
, int bufsize
)
211 if (fmt
== NULL
) { /* raw mode */
212 snprintf(buf
, bufsize
, "%ld.%06ld", (long)tv
.tv_sec
, (long)tv
.tv_usec
);
214 time_t t
= tv
.tv_sec
;
218 ast_localtime(&t
, &tm
, NULL
);
219 strftime(buf
, bufsize
, fmt
, &tm
);
224 /*! CDR channel variable retrieval */
225 void ast_cdr_getvar(struct ast_cdr
*cdr
, const char *name
, char **ret
, char *workspace
, int workspacelen
, int recur
, int raw
)
227 const char *fmt
= "%Y-%m-%d %T";
230 if (!cdr
) /* don't die if the cdr is null */
234 /* special vars (the ones from the struct ast_cdr when requested by name)
235 I'd almost say we should convert all the stringed vals to vars */
237 if (!strcasecmp(name
, "clid"))
238 ast_copy_string(workspace
, cdr
->clid
, workspacelen
);
239 else if (!strcasecmp(name
, "src"))
240 ast_copy_string(workspace
, cdr
->src
, workspacelen
);
241 else if (!strcasecmp(name
, "dst"))
242 ast_copy_string(workspace
, cdr
->dst
, workspacelen
);
243 else if (!strcasecmp(name
, "dcontext"))
244 ast_copy_string(workspace
, cdr
->dcontext
, workspacelen
);
245 else if (!strcasecmp(name
, "channel"))
246 ast_copy_string(workspace
, cdr
->channel
, workspacelen
);
247 else if (!strcasecmp(name
, "dstchannel"))
248 ast_copy_string(workspace
, cdr
->dstchannel
, workspacelen
);
249 else if (!strcasecmp(name
, "lastapp"))
250 ast_copy_string(workspace
, cdr
->lastapp
, workspacelen
);
251 else if (!strcasecmp(name
, "lastdata"))
252 ast_copy_string(workspace
, cdr
->lastdata
, workspacelen
);
253 else if (!strcasecmp(name
, "start"))
254 cdr_get_tv(cdr
->start
, raw
? NULL
: fmt
, workspace
, workspacelen
);
255 else if (!strcasecmp(name
, "answer"))
256 cdr_get_tv(cdr
->answer
, raw
? NULL
: fmt
, workspace
, workspacelen
);
257 else if (!strcasecmp(name
, "end"))
258 cdr_get_tv(cdr
->end
, raw
? NULL
: fmt
, workspace
, workspacelen
);
259 else if (!strcasecmp(name
, "duration"))
260 snprintf(workspace
, workspacelen
, "%ld", cdr
->duration
);
261 else if (!strcasecmp(name
, "billsec"))
262 snprintf(workspace
, workspacelen
, "%ld", cdr
->billsec
);
263 else if (!strcasecmp(name
, "disposition")) {
265 snprintf(workspace
, workspacelen
, "%ld", cdr
->disposition
);
267 ast_copy_string(workspace
, ast_cdr_disp2str(cdr
->disposition
), workspacelen
);
269 } else if (!strcasecmp(name
, "amaflags")) {
271 snprintf(workspace
, workspacelen
, "%ld", cdr
->amaflags
);
273 ast_copy_string(workspace
, ast_cdr_flags2str(cdr
->amaflags
), workspacelen
);
275 } else if (!strcasecmp(name
, "accountcode"))
276 ast_copy_string(workspace
, cdr
->accountcode
, workspacelen
);
277 else if (!strcasecmp(name
, "uniqueid"))
278 ast_copy_string(workspace
, cdr
->uniqueid
, workspacelen
);
279 else if (!strcasecmp(name
, "userfield"))
280 ast_copy_string(workspace
, cdr
->userfield
, workspacelen
);
281 else if ((varbuf
= ast_cdr_getvar_internal(cdr
, name
, recur
)))
282 ast_copy_string(workspace
, varbuf
, workspacelen
);
286 if (!ast_strlen_zero(workspace
))
290 /* readonly cdr variables */
291 static const char *cdr_readonly_vars
[] = { "clid", "src", "dst", "dcontext", "channel", "dstchannel",
292 "lastapp", "lastdata", "start", "answer", "end", "duration",
293 "billsec", "disposition", "amaflags", "accountcode", "uniqueid",
295 /*! Set a CDR channel variable
296 \note You can't set the CDR variables that belong to the actual CDR record, like "billsec".
298 int ast_cdr_setvar(struct ast_cdr
*cdr
, const char *name
, const char *value
, int recur
)
300 struct ast_var_t
*newvariable
;
301 struct varshead
*headp
;
304 if (!cdr
) /* don't die if the cdr is null */
307 for(x
= 0; cdr_readonly_vars
[x
]; x
++) {
308 if (!strcasecmp(name
, cdr_readonly_vars
[x
])) {
309 ast_log(LOG_ERROR
, "Attempt to set the '%s' read-only variable!.\n", name
);
315 ast_log(LOG_ERROR
, "Attempt to set a variable on a nonexistent CDR record.\n");
319 for (; cdr
; cdr
= recur
? cdr
->next
: NULL
) {
320 if (ast_test_flag(cdr
, AST_CDR_FLAG_DONT_TOUCH
) && ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
))
322 headp
= &cdr
->varshead
;
323 AST_LIST_TRAVERSE_SAFE_BEGIN(headp
, newvariable
, entries
) {
324 if (!strcasecmp(ast_var_name(newvariable
), name
)) {
325 /* there is already such a variable, delete it */
326 AST_LIST_REMOVE_CURRENT(headp
, entries
);
327 ast_var_delete(newvariable
);
331 AST_LIST_TRAVERSE_SAFE_END
;
334 newvariable
= ast_var_assign(name
, value
);
335 AST_LIST_INSERT_HEAD(headp
, newvariable
, entries
);
342 int ast_cdr_copy_vars(struct ast_cdr
*to_cdr
, struct ast_cdr
*from_cdr
)
344 struct ast_var_t
*variables
, *newvariable
= NULL
;
345 struct varshead
*headpa
, *headpb
;
346 const char *var
, *val
;
349 if (!to_cdr
|| !from_cdr
) /* don't die if one of the pointers is null */
352 headpa
= &from_cdr
->varshead
;
353 headpb
= &to_cdr
->varshead
;
355 AST_LIST_TRAVERSE(headpa
,variables
,entries
) {
357 (var
= ast_var_name(variables
)) && (val
= ast_var_value(variables
)) &&
358 !ast_strlen_zero(var
) && !ast_strlen_zero(val
)) {
359 newvariable
= ast_var_assign(var
, val
);
360 AST_LIST_INSERT_HEAD(headpb
, newvariable
, entries
);
368 int ast_cdr_serialize_variables(struct ast_cdr
*cdr
, char *buf
, size_t size
, char delim
, char sep
, int recur
)
370 struct ast_var_t
*variables
;
371 const char *var
, *val
;
374 int total
= 0, x
= 0, i
;
376 memset(buf
, 0, size
);
378 for (; cdr
; cdr
= recur
? cdr
->next
: NULL
) {
380 ast_build_string(&buf
, &size
, "\n");
382 AST_LIST_TRAVERSE(&cdr
->varshead
, variables
, entries
) {
384 (var
= ast_var_name(variables
)) && (val
= ast_var_value(variables
)) &&
385 !ast_strlen_zero(var
) && !ast_strlen_zero(val
)) {
386 if (ast_build_string(&buf
, &size
, "level %d: %s%c%s%c", x
, var
, delim
, val
, sep
)) {
387 ast_log(LOG_ERROR
, "Data Buffer Size Exceeded!\n");
395 for (i
= 0; cdr_readonly_vars
[i
]; i
++) {
396 workspace
[0] = 0; /* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */
397 ast_cdr_getvar(cdr
, cdr_readonly_vars
[i
], &tmp
, workspace
, sizeof(workspace
), 0, 0);
401 if (ast_build_string(&buf
, &size
, "level %d: %s%c%s%c", x
, cdr_readonly_vars
[i
], delim
, tmp
, sep
)) {
402 ast_log(LOG_ERROR
, "Data Buffer Size Exceeded!\n");
413 void ast_cdr_free_vars(struct ast_cdr
*cdr
, int recur
)
416 /* clear variables */
417 for (; cdr
; cdr
= recur
? cdr
->next
: NULL
) {
418 struct ast_var_t
*vardata
;
419 struct varshead
*headp
= &cdr
->varshead
;
420 while ((vardata
= AST_LIST_REMOVE_HEAD(headp
, entries
)))
421 ast_var_delete(vardata
);
425 /*! \brief print a warning if cdr already posted */
426 static void check_post(struct ast_cdr
*cdr
)
430 if (ast_test_flag(cdr
, AST_CDR_FLAG_POSTED
))
431 ast_log(LOG_NOTICE
, "CDR on channel '%s' already posted\n", S_OR(cdr
->channel
, "<unknown>"));
434 void ast_cdr_free(struct ast_cdr
*cdr
)
438 struct ast_cdr
*next
= cdr
->next
;
439 char *chan
= S_OR(cdr
->channel
, "<unknown>");
440 if (option_verbose
> 1 && !ast_test_flag(cdr
, AST_CDR_FLAG_POSTED
) && !ast_test_flag(cdr
, AST_CDR_FLAG_POST_DISABLED
))
441 ast_verbose(VERBOSE_PREFIX_2
"CDR on channel '%s' not posted\n", chan
);
442 if (option_verbose
> 1 && ast_tvzero(cdr
->end
))
443 ast_verbose(VERBOSE_PREFIX_2
"CDR on channel '%s' lacks end\n", chan
);
444 if (option_verbose
> 1 && ast_tvzero(cdr
->start
))
445 ast_verbose(VERBOSE_PREFIX_2
"CDR on channel '%s' lacks start\n", chan
);
447 ast_cdr_free_vars(cdr
, 0);
453 /*! \brief the same as a cdr_free call, only with no checks; just get rid of it */
454 void ast_cdr_discard(struct ast_cdr
*cdr
)
457 struct ast_cdr
*next
= cdr
->next
;
459 ast_cdr_free_vars(cdr
, 0);
465 struct ast_cdr
*ast_cdr_alloc(void)
467 struct ast_cdr
*x
= ast_calloc(1, sizeof(struct ast_cdr
));
469 ast_log(LOG_ERROR
,"Allocation Failure for a CDR!\n");
473 static void cdr_merge_vars(struct ast_cdr
*to
, struct ast_cdr
*from
)
475 struct ast_var_t
*variablesfrom
,*variablesto
;
476 struct varshead
*headpfrom
= &to
->varshead
;
477 struct varshead
*headpto
= &from
->varshead
;
478 AST_LIST_TRAVERSE_SAFE_BEGIN(headpfrom
, variablesfrom
, entries
) {
479 /* for every var in from, stick it in to */
480 const char *fromvarname
= NULL
, *fromvarval
= NULL
;
481 const char *tovarname
= NULL
, *tovarval
= NULL
;
482 fromvarname
= ast_var_name(variablesfrom
);
483 fromvarval
= ast_var_value(variablesfrom
);
486 /* now, quick see if that var is in the 'to' cdr already */
487 AST_LIST_TRAVERSE(headpto
, variablesto
, entries
) {
489 /* now, quick see if that var is in the 'to' cdr already */
490 if ( strcasecmp(fromvarname
, ast_var_name(variablesto
)) == 0 ) {
491 tovarname
= ast_var_name(variablesto
);
492 tovarval
= ast_var_value(variablesto
);
496 if (tovarname
&& strcasecmp(fromvarval
,tovarval
) != 0) { /* this message here to see how irritating the userbase finds it */
497 ast_log(LOG_NOTICE
, "Merging CDR's: variable %s value %s dropped in favor of value %s\n", tovarname
, fromvarval
, tovarval
);
499 } else if (tovarname
&& strcasecmp(fromvarval
,tovarval
) == 0) /* if they are the same, the job is done */
502 /*rip this var out of the from cdr, and stick it in the to cdr */
503 AST_LIST_REMOVE_CURRENT(headpfrom
, entries
);
504 AST_LIST_INSERT_HEAD(headpto
, variablesfrom
, entries
);
506 AST_LIST_TRAVERSE_SAFE_END
;
509 void ast_cdr_merge(struct ast_cdr
*to
, struct ast_cdr
*from
)
511 struct ast_cdr
*zcdr
;
512 struct ast_cdr
*lto
= NULL
;
513 struct ast_cdr
*lfrom
= NULL
;
514 int discard_from
= 0;
519 /* don't merge into locked CDR's -- it's bad business */
520 if (ast_test_flag(to
, AST_CDR_FLAG_LOCKED
)) {
521 zcdr
= to
; /* safety valve? */
527 if (ast_test_flag(to
, AST_CDR_FLAG_LOCKED
)) {
528 ast_log(LOG_WARNING
, "Merging into locked CDR... no choice.");
529 to
= zcdr
; /* safety-- if all there are is locked CDR's, then.... ?? */
534 if (ast_test_flag(from
, AST_CDR_FLAG_LOCKED
)) {
537 struct ast_cdr
*llfrom
= NULL
;
538 /* insert the from stuff after lto */
541 while (lfrom
&& lfrom
->next
) {
542 if (!lfrom
->next
->next
)
546 /* rip off the last entry and put a copy of the to at the end */
550 /* save copy of the current *to cdr */
552 struct ast_cdr
*llfrom
= NULL
;
553 memcpy(&tcdr
, to
, sizeof(tcdr
));
554 /* copy in the locked from cdr */
555 memcpy(to
, from
, sizeof(*to
));
557 while (lfrom
&& lfrom
->next
) {
558 if (!lfrom
->next
->next
)
563 /* rip off the last entry and put a copy of the to at the end */
565 to
= to
->next
= ast_cdr_dup(&tcdr
);
567 to
= llfrom
->next
= ast_cdr_dup(&tcdr
);
572 if (!ast_tvzero(from
->start
)) {
573 if (!ast_tvzero(to
->start
)) {
574 if (ast_tvcmp(to
->start
, from
->start
) > 0 ) {
575 to
->start
= from
->start
; /* use the earliest time */
576 from
->start
= ast_tv(0,0); /* we actively "steal" these values */
578 /* else nothing to do */
580 to
->start
= from
->start
;
581 from
->start
= ast_tv(0,0); /* we actively "steal" these values */
584 if (!ast_tvzero(from
->answer
)) {
585 if (!ast_tvzero(to
->answer
)) {
586 if (ast_tvcmp(to
->answer
, from
->answer
) > 0 ) {
587 to
->answer
= from
->answer
; /* use the earliest time */
588 from
->answer
= ast_tv(0,0); /* we actively "steal" these values */
590 /* we got the earliest answer time, so we'll settle for that? */
592 to
->answer
= from
->answer
;
593 from
->answer
= ast_tv(0,0); /* we actively "steal" these values */
596 if (!ast_tvzero(from
->end
)) {
597 if (!ast_tvzero(to
->end
)) {
598 if (ast_tvcmp(to
->end
, from
->end
) < 0 ) {
599 to
->end
= from
->end
; /* use the latest time */
600 from
->end
= ast_tv(0,0); /* we actively "steal" these values */
601 to
->duration
= to
->end
.tv_sec
- to
->start
.tv_sec
; /* don't forget to update the duration, billsec, when we set end */
602 to
->billsec
= ast_tvzero(to
->answer
) ? 0 : to
->end
.tv_sec
- to
->answer
.tv_sec
;
604 /* else, nothing to do */
607 from
->end
= ast_tv(0,0); /* we actively "steal" these values */
608 to
->duration
= to
->end
.tv_sec
- to
->start
.tv_sec
;
609 to
->billsec
= ast_tvzero(to
->answer
) ? 0 : to
->end
.tv_sec
- to
->answer
.tv_sec
;
612 if (to
->disposition
< from
->disposition
) {
613 to
->disposition
= from
->disposition
;
614 from
->disposition
= AST_CDR_NOANSWER
;
616 if (ast_strlen_zero(to
->lastapp
) && !ast_strlen_zero(from
->lastapp
)) {
617 ast_copy_string(to
->lastapp
, from
->lastapp
, sizeof(to
->lastapp
));
618 from
->lastapp
[0] = 0; /* theft */
620 if (ast_strlen_zero(to
->lastdata
) && !ast_strlen_zero(from
->lastdata
)) {
621 ast_copy_string(to
->lastdata
, from
->lastdata
, sizeof(to
->lastdata
));
622 from
->lastdata
[0] = 0; /* theft */
624 if (ast_strlen_zero(to
->dcontext
) && !ast_strlen_zero(from
->dcontext
)) {
625 ast_copy_string(to
->dcontext
, from
->dcontext
, sizeof(to
->dcontext
));
626 from
->dcontext
[0] = 0; /* theft */
628 if (ast_strlen_zero(to
->dstchannel
) && !ast_strlen_zero(from
->dstchannel
)) {
629 ast_copy_string(to
->dstchannel
, from
->dstchannel
, sizeof(to
->dstchannel
));
630 from
->dstchannel
[0] = 0; /* theft */
632 if (!ast_strlen_zero(from
->channel
) && (ast_strlen_zero(to
->channel
) || !strncasecmp(from
->channel
, "Agent/", 6))) {
633 ast_copy_string(to
->channel
, from
->channel
, sizeof(to
->channel
));
634 from
->channel
[0] = 0; /* theft */
636 if (ast_strlen_zero(to
->src
) && !ast_strlen_zero(from
->src
)) {
637 ast_copy_string(to
->src
, from
->src
, sizeof(to
->src
));
638 from
->src
[0] = 0; /* theft */
640 if (ast_strlen_zero(to
->clid
) && !ast_strlen_zero(from
->clid
)) {
641 ast_copy_string(to
->clid
, from
->clid
, sizeof(to
->clid
));
642 from
->clid
[0] = 0; /* theft */
644 if (ast_strlen_zero(to
->dst
) && !ast_strlen_zero(from
->dst
)) {
645 ast_copy_string(to
->dst
, from
->dst
, sizeof(to
->dst
));
646 from
->dst
[0] = 0; /* theft */
649 to
->amaflags
= AST_CDR_DOCUMENTATION
;
651 from
->amaflags
= AST_CDR_DOCUMENTATION
; /* make sure both amaflags are set to something (DOC is default) */
652 if (ast_test_flag(from
, AST_CDR_FLAG_LOCKED
) || (to
->amaflags
== AST_CDR_DOCUMENTATION
&& from
->amaflags
!= AST_CDR_DOCUMENTATION
)) {
653 to
->amaflags
= from
->amaflags
;
655 if (ast_test_flag(from
, AST_CDR_FLAG_LOCKED
) || (ast_strlen_zero(to
->accountcode
) && !ast_strlen_zero(from
->accountcode
))) {
656 ast_copy_string(to
->accountcode
, from
->accountcode
, sizeof(to
->accountcode
));
658 if (ast_test_flag(from
, AST_CDR_FLAG_LOCKED
) || (ast_strlen_zero(to
->userfield
) && !ast_strlen_zero(from
->userfield
))) {
659 ast_copy_string(to
->userfield
, from
->userfield
, sizeof(to
->userfield
));
661 /* flags, varsead, ? */
662 cdr_merge_vars(from
, to
);
664 if (ast_test_flag(from
, AST_CDR_FLAG_KEEP_VARS
))
665 ast_set_flag(to
, AST_CDR_FLAG_KEEP_VARS
);
666 if (ast_test_flag(from
, AST_CDR_FLAG_POSTED
))
667 ast_set_flag(to
, AST_CDR_FLAG_POSTED
);
668 if (ast_test_flag(from
, AST_CDR_FLAG_LOCKED
))
669 ast_set_flag(to
, AST_CDR_FLAG_LOCKED
);
670 if (ast_test_flag(from
, AST_CDR_FLAG_CHILD
))
671 ast_set_flag(to
, AST_CDR_FLAG_CHILD
);
672 if (ast_test_flag(from
, AST_CDR_FLAG_POST_DISABLED
))
673 ast_set_flag(to
, AST_CDR_FLAG_POST_DISABLED
);
675 /* last, but not least, we need to merge any forked CDRs to the 'to' cdr */
677 /* just rip 'em off the 'from' and insert them on the 'to' */
679 from
->next
= zcdr
->next
;
681 /* zcdr is now ripped from the current list; */
682 ast_cdr_append(to
, zcdr
);
685 ast_cdr_discard(from
);
688 void ast_cdr_start(struct ast_cdr
*cdr
)
692 for (; cdr
; cdr
= cdr
->next
) {
693 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
694 chan
= S_OR(cdr
->channel
, "<unknown>");
696 cdr
->start
= ast_tvnow();
701 void ast_cdr_answer(struct ast_cdr
*cdr
)
704 for (; cdr
; cdr
= cdr
->next
) {
705 if (ast_test_flag(cdr
, AST_CDR_FLAG_ANSLOCKED
))
707 if (ast_test_flag(cdr
, AST_CDR_FLAG_DONT_TOUCH
) && ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
))
710 if (cdr
->disposition
< AST_CDR_ANSWERED
)
711 cdr
->disposition
= AST_CDR_ANSWERED
;
712 if (ast_tvzero(cdr
->answer
))
713 cdr
->answer
= ast_tvnow();
717 void ast_cdr_busy(struct ast_cdr
*cdr
)
720 for (; cdr
; cdr
= cdr
->next
) {
721 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
723 if (cdr
->disposition
< AST_CDR_BUSY
)
724 cdr
->disposition
= AST_CDR_BUSY
;
729 void ast_cdr_failed(struct ast_cdr
*cdr
)
731 for (; cdr
; cdr
= cdr
->next
) {
733 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
734 if (cdr
->disposition
< AST_CDR_FAILED
)
735 cdr
->disposition
= AST_CDR_FAILED
;
740 void ast_cdr_noanswer(struct ast_cdr
*cdr
)
745 chan
= !ast_strlen_zero(cdr
->channel
) ? cdr
->channel
: "<unknown>";
746 if (ast_test_flag(cdr
, AST_CDR_FLAG_POSTED
))
747 ast_log(LOG_WARNING
, "CDR on channel '%s' already posted\n", chan
);
748 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
749 if (cdr
->disposition
< AST_CDR_NOANSWER
)
750 cdr
->disposition
= AST_CDR_NOANSWER
;
756 /* everywhere ast_cdr_disposition is called, it will call ast_cdr_failed()
757 if ast_cdr_disposition returns a non-zero value */
759 int ast_cdr_disposition(struct ast_cdr
*cdr
, int cause
)
763 for (; cdr
; cdr
= cdr
->next
) {
764 switch(cause
) { /* handle all the non failure, busy cases, return 0 not to set disposition,
765 return -1 to set disposition to FAILED */
769 case AST_CAUSE_NORMAL
:
778 void ast_cdr_setdestchan(struct ast_cdr
*cdr
, const char *chann
)
780 for (; cdr
; cdr
= cdr
->next
) {
782 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
))
783 ast_copy_string(cdr
->dstchannel
, chann
, sizeof(cdr
->dstchannel
));
787 void ast_cdr_setapp(struct ast_cdr
*cdr
, char *app
, char *data
)
790 for (; cdr
; cdr
= cdr
->next
) {
791 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
793 ast_copy_string(cdr
->lastapp
, S_OR(app
, ""), sizeof(cdr
->lastapp
));
794 ast_copy_string(cdr
->lastdata
, S_OR(data
, ""), sizeof(cdr
->lastdata
));
799 /* set cid info for one record */
800 static void set_one_cid(struct ast_cdr
*cdr
, struct ast_channel
*c
)
802 /* Grab source from ANI or normal Caller*ID */
803 const char *num
= S_OR(c
->cid
.cid_ani
, c
->cid
.cid_num
);
806 if (!ast_strlen_zero(c
->cid
.cid_name
)) {
807 if (!ast_strlen_zero(num
)) /* both name and number */
808 snprintf(cdr
->clid
, sizeof(cdr
->clid
), "\"%s\" <%s>", c
->cid
.cid_name
, num
);
810 ast_copy_string(cdr
->clid
, c
->cid
.cid_name
, sizeof(cdr
->clid
));
811 } else if (!ast_strlen_zero(num
)) { /* only number */
812 ast_copy_string(cdr
->clid
, num
, sizeof(cdr
->clid
));
813 } else { /* nothing known */
816 ast_copy_string(cdr
->src
, S_OR(num
, ""), sizeof(cdr
->src
));
819 int ast_cdr_setcid(struct ast_cdr
*cdr
, struct ast_channel
*c
)
821 for (; cdr
; cdr
= cdr
->next
) {
822 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
))
828 int ast_cdr_init(struct ast_cdr
*cdr
, struct ast_channel
*c
)
832 for ( ; cdr
; cdr
= cdr
->next
) {
833 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
834 chan
= S_OR(cdr
->channel
, "<unknown>");
835 ast_copy_string(cdr
->channel
, c
->name
, sizeof(cdr
->channel
));
838 cdr
->disposition
= (c
->_state
== AST_STATE_UP
) ? AST_CDR_ANSWERED
: AST_CDR_NULL
;
839 cdr
->amaflags
= c
->amaflags
? c
->amaflags
: ast_default_amaflags
;
840 ast_copy_string(cdr
->accountcode
, c
->accountcode
, sizeof(cdr
->accountcode
));
841 /* Destination information */
842 ast_copy_string(cdr
->dst
, S_OR(c
->macroexten
,c
->exten
), sizeof(cdr
->dst
));
843 ast_copy_string(cdr
->dcontext
, S_OR(c
->macrocontext
,c
->context
), sizeof(cdr
->dcontext
));
844 /* Unique call identifier */
845 ast_copy_string(cdr
->uniqueid
, c
->uniqueid
, sizeof(cdr
->uniqueid
));
851 /* Three routines were "fixed" via 10668, and later shown that
852 users were depending on this behavior. ast_cdr_end,
853 ast_cdr_setvar and ast_cdr_answer are the three routines.
854 While most of the other routines would not touch
855 LOCKED cdr's, these three routines were designed to
856 operate on locked CDR's as a matter of course.
857 I now appreciate how this plays with the ForkCDR app,
858 which forms these cdr chains in the first place.
859 cdr_end is pretty key: all cdrs created are closed
860 together. They only vary by start time. Arithmetically,
861 users can calculate the subintervals they wish to track. */
863 void ast_cdr_end(struct ast_cdr
*cdr
)
865 for ( ; cdr
; cdr
= cdr
->next
) {
866 if (ast_test_flag(cdr
, AST_CDR_FLAG_DONT_TOUCH
) && ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
))
869 if (ast_tvzero(cdr
->end
))
870 cdr
->end
= ast_tvnow();
871 if (ast_tvzero(cdr
->start
)) {
872 ast_log(LOG_WARNING
, "CDR on channel '%s' has not started\n", S_OR(cdr
->channel
, "<unknown>"));
873 cdr
->disposition
= AST_CDR_FAILED
;
875 cdr
->duration
= cdr
->end
.tv_sec
- cdr
->start
.tv_sec
;
876 if (ast_tvzero(cdr
->answer
)) {
877 if (cdr
->disposition
== AST_CDR_ANSWERED
) {
878 ast_log(LOG_WARNING
, "CDR on channel '%s' has no answer time but is 'ANSWERED'\n", S_OR(cdr
->channel
, "<unknown>"));
879 cdr
->disposition
= AST_CDR_FAILED
;
882 cdr
->billsec
= cdr
->end
.tv_sec
- cdr
->answer
.tv_sec
;
886 char *ast_cdr_disp2str(int disposition
)
888 switch (disposition
) {
890 return "NO ANSWER"; /* by default, for backward compatibility */
891 case AST_CDR_NOANSWER
:
897 case AST_CDR_ANSWERED
:
903 /*! Converts AMA flag to printable string */
904 char *ast_cdr_flags2str(int flag
)
909 case AST_CDR_BILLING
:
911 case AST_CDR_DOCUMENTATION
:
912 return "DOCUMENTATION";
917 int ast_cdr_setaccount(struct ast_channel
*chan
, const char *account
)
919 struct ast_cdr
*cdr
= chan
->cdr
;
921 ast_string_field_set(chan
, accountcode
, account
);
922 for ( ; cdr
; cdr
= cdr
->next
) {
923 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
924 ast_copy_string(cdr
->accountcode
, chan
->accountcode
, sizeof(cdr
->accountcode
));
930 int ast_cdr_setamaflags(struct ast_channel
*chan
, const char *flag
)
933 int newflag
= ast_cdr_amaflags2int(flag
);
935 for (cdr
= chan
->cdr
; cdr
; cdr
= cdr
->next
) {
936 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
937 cdr
->amaflags
= newflag
;
945 int ast_cdr_setuserfield(struct ast_channel
*chan
, const char *userfield
)
947 struct ast_cdr
*cdr
= chan
->cdr
;
949 for ( ; cdr
; cdr
= cdr
->next
) {
950 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
))
951 ast_copy_string(cdr
->userfield
, userfield
, sizeof(cdr
->userfield
));
957 int ast_cdr_appenduserfield(struct ast_channel
*chan
, const char *userfield
)
959 struct ast_cdr
*cdr
= chan
->cdr
;
961 for ( ; cdr
; cdr
= cdr
->next
) {
962 int len
= strlen(cdr
->userfield
);
964 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
))
965 ast_copy_string(cdr
->userfield
+ len
, userfield
, sizeof(cdr
->userfield
) - len
);
971 int ast_cdr_update(struct ast_channel
*c
)
973 struct ast_cdr
*cdr
= c
->cdr
;
975 for ( ; cdr
; cdr
= cdr
->next
) {
976 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
979 /* Copy account code et-al */
980 ast_copy_string(cdr
->accountcode
, c
->accountcode
, sizeof(cdr
->accountcode
));
982 /* Destination information */ /* XXX privilege macro* ? */
983 ast_copy_string(cdr
->dst
, S_OR(c
->macroexten
, c
->exten
), sizeof(cdr
->dst
));
984 ast_copy_string(cdr
->dcontext
, S_OR(c
->macrocontext
, c
->context
), sizeof(cdr
->dcontext
));
991 int ast_cdr_amaflags2int(const char *flag
)
993 if (!strcasecmp(flag
, "default"))
995 if (!strcasecmp(flag
, "omit"))
997 if (!strcasecmp(flag
, "billing"))
998 return AST_CDR_BILLING
;
999 if (!strcasecmp(flag
, "documentation"))
1000 return AST_CDR_DOCUMENTATION
;
1004 static void post_cdr(struct ast_cdr
*cdr
)
1007 struct ast_cdr_beitem
*i
;
1009 for ( ; cdr
; cdr
= cdr
->next
) {
1010 if (!unanswered
&& cdr
->disposition
< AST_CDR_ANSWERED
&& (ast_strlen_zero(cdr
->channel
) || ast_strlen_zero(cdr
->dstchannel
))) {
1011 /* For people, who don't want to see unanswered single-channel events */
1012 ast_set_flag(cdr
, AST_CDR_FLAG_POST_DISABLED
);
1016 chan
= S_OR(cdr
->channel
, "<unknown>");
1018 if (option_verbose
> 1 && ast_tvzero(cdr
->end
))
1019 ast_verbose(VERBOSE_PREFIX_2
"CDR on channel '%s' lacks end\n", chan
);
1020 if (option_verbose
> 1 && ast_tvzero(cdr
->start
))
1021 ast_verbose(VERBOSE_PREFIX_2
"CDR on channel '%s' lacks start\n", chan
);
1022 ast_set_flag(cdr
, AST_CDR_FLAG_POSTED
);
1023 if (ast_test_flag(cdr
, AST_CDR_FLAG_POST_DISABLED
))
1025 AST_LIST_LOCK(&be_list
);
1026 AST_LIST_TRAVERSE(&be_list
, i
, list
) {
1029 AST_LIST_UNLOCK(&be_list
);
1033 void ast_cdr_reset(struct ast_cdr
*cdr
, struct ast_flags
*_flags
)
1035 struct ast_cdr
*dup
;
1036 struct ast_flags flags
= { 0 };
1039 ast_copy_flags(&flags
, _flags
, AST_FLAGS_ALL
);
1041 for ( ; cdr
; cdr
= cdr
->next
) {
1042 /* Detach if post is requested */
1043 if (ast_test_flag(&flags
, AST_CDR_FLAG_LOCKED
) || !ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
1044 if (ast_test_flag(&flags
, AST_CDR_FLAG_POSTED
)) {
1046 if ((dup
= ast_cdr_dup(cdr
))) {
1047 ast_cdr_detach(dup
);
1049 ast_set_flag(cdr
, AST_CDR_FLAG_POSTED
);
1052 /* clear variables */
1053 if (!ast_test_flag(&flags
, AST_CDR_FLAG_KEEP_VARS
)) {
1054 ast_cdr_free_vars(cdr
, 0);
1057 /* Reset to initial state */
1058 ast_clear_flag(cdr
, AST_FLAGS_ALL
);
1059 memset(&cdr
->start
, 0, sizeof(cdr
->start
));
1060 memset(&cdr
->end
, 0, sizeof(cdr
->end
));
1061 memset(&cdr
->answer
, 0, sizeof(cdr
->answer
));
1065 cdr
->disposition
= AST_CDR_NULL
;
1070 void ast_cdr_specialized_reset(struct ast_cdr
*cdr
, struct ast_flags
*_flags
)
1072 struct ast_flags flags
= { 0 };
1075 ast_copy_flags(&flags
, _flags
, AST_FLAGS_ALL
);
1077 /* Reset to initial state */
1078 if (ast_test_flag(cdr
, AST_CDR_FLAG_POST_DISABLED
)) { /* But do NOT lose the NoCDR() setting */
1079 ast_clear_flag(cdr
, AST_FLAGS_ALL
);
1080 ast_set_flag(cdr
, AST_CDR_FLAG_POST_DISABLED
);
1082 ast_clear_flag(cdr
, AST_FLAGS_ALL
);
1085 memset(&cdr
->start
, 0, sizeof(cdr
->start
));
1086 memset(&cdr
->end
, 0, sizeof(cdr
->end
));
1087 memset(&cdr
->answer
, 0, sizeof(cdr
->answer
));
1091 cdr
->disposition
= AST_CDR_NULL
;
1094 struct ast_cdr
*ast_cdr_append(struct ast_cdr
*cdr
, struct ast_cdr
*newcdr
)
1096 struct ast_cdr
*ret
;
1111 /*! \note Don't call without cdr_batch_lock */
1112 static void reset_batch(void)
1119 /*! \note Don't call without cdr_batch_lock */
1120 static int init_batch(void)
1122 /* This is the single meta-batch used to keep track of all CDRs during the entire life of the program */
1123 if (!(batch
= ast_malloc(sizeof(*batch
))))
1131 static void *do_batch_backend_process(void *data
)
1133 struct ast_cdr_batch_item
*processeditem
;
1134 struct ast_cdr_batch_item
*batchitem
= data
;
1136 /* Push each CDR into storage mechanism(s) and free all the memory */
1138 post_cdr(batchitem
->cdr
);
1139 ast_cdr_free(batchitem
->cdr
);
1140 processeditem
= batchitem
;
1141 batchitem
= batchitem
->next
;
1142 free(processeditem
);
1148 void ast_cdr_submit_batch(int shutdown
)
1150 struct ast_cdr_batch_item
*oldbatchitems
= NULL
;
1151 pthread_attr_t attr
;
1152 pthread_t batch_post_thread
= AST_PTHREADT_NULL
;
1154 /* if there's no batch, or no CDRs in the batch, then there's nothing to do */
1155 if (!batch
|| !batch
->head
)
1158 /* move the old CDRs aside, and prepare a new CDR batch */
1159 ast_mutex_lock(&cdr_batch_lock
);
1160 oldbatchitems
= batch
->head
;
1162 ast_mutex_unlock(&cdr_batch_lock
);
1164 /* if configured, spawn a new thread to post these CDRs,
1165 also try to save as much as possible if we are shutting down safely */
1166 if (batchscheduleronly
|| shutdown
) {
1168 ast_log(LOG_DEBUG
, "CDR single-threaded batch processing begins now\n");
1169 do_batch_backend_process(oldbatchitems
);
1171 pthread_attr_init(&attr
);
1172 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
1173 if (ast_pthread_create_background(&batch_post_thread
, &attr
, do_batch_backend_process
, oldbatchitems
)) {
1174 ast_log(LOG_WARNING
, "CDR processing thread could not detach, now trying in this thread\n");
1175 do_batch_backend_process(oldbatchitems
);
1178 ast_log(LOG_DEBUG
, "CDR multi-threaded batch processing begins now\n");
1180 pthread_attr_destroy(&attr
);
1184 static int submit_scheduled_batch(const void *data
)
1186 ast_cdr_submit_batch(0);
1187 /* manually reschedule from this point in time */
1188 cdr_sched
= ast_sched_add(sched
, batchtime
* 1000, submit_scheduled_batch
, NULL
);
1189 /* returning zero so the scheduler does not automatically reschedule */
1193 static void submit_unscheduled_batch(void)
1195 /* this is okay since we are not being called from within the scheduler */
1196 AST_SCHED_DEL(sched
, cdr_sched
);
1197 /* schedule the submission to occur ASAP (1 ms) */
1198 cdr_sched
= ast_sched_add(sched
, 1, submit_scheduled_batch
, NULL
);
1199 /* signal the do_cdr thread to wakeup early and do some work (that lazy thread ;) */
1200 ast_mutex_lock(&cdr_pending_lock
);
1201 ast_cond_signal(&cdr_pending_cond
);
1202 ast_mutex_unlock(&cdr_pending_lock
);
1205 void ast_cdr_detach(struct ast_cdr
*cdr
)
1207 struct ast_cdr_batch_item
*newtail
;
1213 /* maybe they disabled CDR stuff completely, so just drop it */
1216 ast_log(LOG_DEBUG
, "Dropping CDR !\n");
1217 ast_set_flag(cdr
, AST_CDR_FLAG_POST_DISABLED
);
1222 /* post stuff immediately if we are not in batch mode, this is legacy behaviour */
1229 /* otherwise, each CDR gets put into a batch list (at the end) */
1231 ast_log(LOG_DEBUG
, "CDR detaching from this thread\n");
1233 /* we'll need a new tail for every CDR */
1234 if (!(newtail
= ast_calloc(1, sizeof(*newtail
)))) {
1240 /* don't traverse a whole list (just keep track of the tail) */
1241 ast_mutex_lock(&cdr_batch_lock
);
1245 /* new batch is empty, so point the head at the new tail */
1246 batch
->head
= newtail
;
1248 /* already got a batch with something in it, so just append a new tail */
1249 batch
->tail
->next
= newtail
;
1252 batch
->tail
= newtail
;
1253 curr
= batch
->size
++;
1254 ast_mutex_unlock(&cdr_batch_lock
);
1256 /* if we have enough stuff to post, then do it */
1257 if (curr
>= (batchsize
- 1))
1258 submit_unscheduled_batch();
1261 static void *do_cdr(void *data
)
1263 struct timespec timeout
;
1269 schedms
= ast_sched_wait(sched
);
1270 /* this shouldn't happen, but provide a 1 second default just in case */
1273 now
= ast_tvadd(ast_tvnow(), ast_samp2tv(schedms
, 1000));
1274 timeout
.tv_sec
= now
.tv_sec
;
1275 timeout
.tv_nsec
= now
.tv_usec
* 1000;
1276 /* prevent stuff from clobbering cdr_pending_cond, then wait on signals sent to it until the timeout expires */
1277 ast_mutex_lock(&cdr_pending_lock
);
1278 ast_cond_timedwait(&cdr_pending_cond
, &cdr_pending_lock
, &timeout
);
1279 numevents
= ast_sched_runq(sched
);
1280 ast_mutex_unlock(&cdr_pending_lock
);
1281 if (option_debug
> 1)
1282 ast_log(LOG_DEBUG
, "Processed %d scheduled CDR batches from the run queue\n", numevents
);
1288 static int handle_cli_status(int fd
, int argc
, char *argv
[])
1290 struct ast_cdr_beitem
*beitem
=NULL
;
1292 long nextbatchtime
=0;
1295 return RESULT_SHOWUSAGE
;
1297 ast_cli(fd
, "CDR logging: %s\n", enabled
? "enabled" : "disabled");
1298 ast_cli(fd
, "CDR mode: %s\n", batchmode
? "batch" : "simple");
1300 ast_cli(fd
, "CDR output unanswered calls: %s\n", unanswered
? "yes" : "no");
1305 nextbatchtime
= ast_sched_when(sched
, cdr_sched
);
1306 ast_cli(fd
, "CDR safe shut down: %s\n", batchsafeshutdown
? "enabled" : "disabled");
1307 ast_cli(fd
, "CDR batch threading model: %s\n", batchscheduleronly
? "scheduler only" : "scheduler plus separate threads");
1308 ast_cli(fd
, "CDR current batch size: %d record%s\n", cnt
, (cnt
!= 1) ? "s" : "");
1309 ast_cli(fd
, "CDR maximum batch size: %d record%s\n", batchsize
, (batchsize
!= 1) ? "s" : "");
1310 ast_cli(fd
, "CDR maximum batch time: %d second%s\n", batchtime
, (batchtime
!= 1) ? "s" : "");
1311 ast_cli(fd
, "CDR next scheduled batch processing time: %ld second%s\n", nextbatchtime
, (nextbatchtime
!= 1) ? "s" : "");
1313 AST_LIST_LOCK(&be_list
);
1314 AST_LIST_TRAVERSE(&be_list
, beitem
, list
) {
1315 ast_cli(fd
, "CDR registered backend: %s\n", beitem
->name
);
1317 AST_LIST_UNLOCK(&be_list
);
1323 static int handle_cli_submit(int fd
, int argc
, char *argv
[])
1326 return RESULT_SHOWUSAGE
;
1328 submit_unscheduled_batch();
1329 ast_cli(fd
, "Submitted CDRs to backend engines for processing. This may take a while.\n");
1334 static struct ast_cli_entry cli_submit
= {
1335 { "cdr", "submit", NULL
},
1336 handle_cli_submit
, "Posts all pending batched CDR data",
1337 "Usage: cdr submit\n"
1338 " Posts all pending batched CDR data to the configured CDR backend engine modules.\n"
1341 static struct ast_cli_entry cli_status
= {
1342 { "cdr", "status", NULL
},
1343 handle_cli_status
, "Display the CDR status",
1344 "Usage: cdr status\n"
1345 " Displays the Call Detail Record engine system status.\n"
1348 static int do_reload(void)
1350 struct ast_config
*config
;
1351 const char *enabled_value
;
1352 const char *unanswered_value
;
1353 const char *batched_value
;
1354 const char *scheduleronly_value
;
1355 const char *batchsafeshutdown_value
;
1356 const char *size_value
;
1357 const char *time_value
;
1358 const char *end_before_h_value
;
1365 ast_mutex_lock(&cdr_batch_lock
);
1367 batchsize
= BATCH_SIZE_DEFAULT
;
1368 batchtime
= BATCH_TIME_DEFAULT
;
1369 batchscheduleronly
= BATCH_SCHEDULER_ONLY_DEFAULT
;
1370 batchsafeshutdown
= BATCH_SAFE_SHUTDOWN_DEFAULT
;
1371 was_enabled
= enabled
;
1372 was_batchmode
= batchmode
;
1376 /* don't run the next scheduled CDR posting while reloading */
1377 AST_SCHED_DEL(sched
, cdr_sched
);
1379 if ((config
= ast_config_load("cdr.conf"))) {
1380 if ((enabled_value
= ast_variable_retrieve(config
, "general", "enable"))) {
1381 enabled
= ast_true(enabled_value
);
1383 if ((unanswered_value
= ast_variable_retrieve(config
, "general", "unanswered"))) {
1384 unanswered
= ast_true(unanswered_value
);
1386 if ((batched_value
= ast_variable_retrieve(config
, "general", "batch"))) {
1387 batchmode
= ast_true(batched_value
);
1389 if ((scheduleronly_value
= ast_variable_retrieve(config
, "general", "scheduleronly"))) {
1390 batchscheduleronly
= ast_true(scheduleronly_value
);
1392 if ((batchsafeshutdown_value
= ast_variable_retrieve(config
, "general", "safeshutdown"))) {
1393 batchsafeshutdown
= ast_true(batchsafeshutdown_value
);
1395 if ((size_value
= ast_variable_retrieve(config
, "general", "size"))) {
1396 if (sscanf(size_value
, "%d", &cfg_size
) < 1)
1397 ast_log(LOG_WARNING
, "Unable to convert '%s' to a numeric value.\n", size_value
);
1398 else if (size_value
< 0)
1399 ast_log(LOG_WARNING
, "Invalid maximum batch size '%d' specified, using default\n", cfg_size
);
1401 batchsize
= cfg_size
;
1403 if ((time_value
= ast_variable_retrieve(config
, "general", "time"))) {
1404 if (sscanf(time_value
, "%d", &cfg_time
) < 1)
1405 ast_log(LOG_WARNING
, "Unable to convert '%s' to a numeric value.\n", time_value
);
1406 else if (time_value
< 0)
1407 ast_log(LOG_WARNING
, "Invalid maximum batch time '%d' specified, using default\n", cfg_time
);
1409 batchtime
= cfg_time
;
1411 if ((end_before_h_value
= ast_variable_retrieve(config
, "general", "endbeforehexten")))
1412 ast_set2_flag(&ast_options
, ast_true(end_before_h_value
), AST_OPT_FLAG_END_CDR_BEFORE_H_EXTEN
);
1415 if (enabled
&& !batchmode
) {
1416 ast_log(LOG_NOTICE
, "CDR simple logging enabled.\n");
1417 } else if (enabled
&& batchmode
) {
1418 cdr_sched
= ast_sched_add(sched
, batchtime
* 1000, submit_scheduled_batch
, NULL
);
1419 ast_log(LOG_NOTICE
, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n", batchsize
, batchtime
);
1421 ast_log(LOG_NOTICE
, "CDR logging disabled, data will be lost.\n");
1424 /* if this reload enabled the CDR batch mode, create the background thread
1425 if it does not exist */
1426 if (enabled
&& batchmode
&& (!was_enabled
|| !was_batchmode
) && (cdr_thread
== AST_PTHREADT_NULL
)) {
1427 ast_cond_init(&cdr_pending_cond
, NULL
);
1428 if (ast_pthread_create_background(&cdr_thread
, NULL
, do_cdr
, NULL
) < 0) {
1429 ast_log(LOG_ERROR
, "Unable to start CDR thread.\n");
1430 AST_SCHED_DEL(sched
, cdr_sched
);
1432 ast_cli_register(&cli_submit
);
1433 ast_register_atexit(ast_cdr_engine_term
);
1436 /* if this reload disabled the CDR and/or batch mode and there is a background thread,
1438 } else if (((!enabled
&& was_enabled
) || (!batchmode
&& was_batchmode
)) && (cdr_thread
!= AST_PTHREADT_NULL
)) {
1439 /* wake up the thread so it will exit */
1440 pthread_cancel(cdr_thread
);
1441 pthread_kill(cdr_thread
, SIGURG
);
1442 pthread_join(cdr_thread
, NULL
);
1443 cdr_thread
= AST_PTHREADT_NULL
;
1444 ast_cond_destroy(&cdr_pending_cond
);
1445 ast_cli_unregister(&cli_submit
);
1446 ast_unregister_atexit(ast_cdr_engine_term
);
1448 /* if leaving batch mode, then post the CDRs in the batch,
1449 and don't reschedule, since we are stopping CDR logging */
1450 if (!batchmode
&& was_batchmode
) {
1451 ast_cdr_engine_term();
1457 ast_mutex_unlock(&cdr_batch_lock
);
1458 ast_config_destroy(config
);
1463 int ast_cdr_engine_init(void)
1467 sched
= sched_context_create();
1469 ast_log(LOG_ERROR
, "Unable to create schedule context.\n");
1473 ast_cli_register(&cli_status
);
1477 ast_mutex_lock(&cdr_batch_lock
);
1479 ast_mutex_unlock(&cdr_batch_lock
);
1485 /* \note This actually gets called a couple of times at shutdown. Once, before we start
1486 hanging up channels, and then again, after the channel hangup timeout expires */
1487 void ast_cdr_engine_term(void)
1489 ast_cdr_submit_batch(batchsafeshutdown
);
1492 int ast_cdr_engine_reload(void)