Merged revisions 9493,9528 via svnmerge from
[asterisk/asterisk.git] / cdr.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2006, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
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.
13  *
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.
17  */
18
19 /*! \file
20  *
21  * \brief Call Detail Record API 
22  *
23  * \author Mark Spencer <markster@digium.com>
24  * 
25  * \note Includes code and algorithms from the Zapata library.
26  *
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.
31  */
32
33
34 #include <unistd.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <stdio.h>
38 #include <signal.h>
39
40 #include "asterisk.h"
41
42 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
43
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/module.h"
57 #include "asterisk/stringfields.h"
58
59 /*! Default AMA flag for billing records (CDR's) */
60 int ast_default_amaflags = AST_CDR_DOCUMENTATION;
61 char ast_default_accountcode[AST_MAX_ACCOUNT_CODE] = "";
62
63 struct ast_cdr_beitem {
64         char name[20];
65         char desc[80];
66         ast_cdrbe be;
67         AST_LIST_ENTRY(ast_cdr_beitem) list;
68 };
69
70 static AST_LIST_HEAD_STATIC(be_list, ast_cdr_beitem);
71
72 struct ast_cdr_batch_item {
73         struct ast_cdr *cdr;
74         struct ast_cdr_batch_item *next;
75 };
76
77 static struct ast_cdr_batch {
78         int size;
79         struct ast_cdr_batch_item *head;
80         struct ast_cdr_batch_item *tail;
81 } *batch = NULL;
82
83 static struct sched_context *sched;
84 static int cdr_sched = -1;
85 static pthread_t cdr_thread = AST_PTHREADT_NULL;
86
87 #define BATCH_SIZE_DEFAULT 100
88 #define BATCH_TIME_DEFAULT 300
89 #define BATCH_SCHEDULER_ONLY_DEFAULT 0
90 #define BATCH_SAFE_SHUTDOWN_DEFAULT 1
91
92 static int enabled;
93 static int batchmode;
94 static int batchsize;
95 static int batchtime;
96 static int batchscheduleronly;
97 static int batchsafeshutdown;
98
99 AST_MUTEX_DEFINE_STATIC(cdr_batch_lock);
100
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;
104
105
106 /*! Register a CDR driver. Each registered CDR driver generates a CDR 
107         \return 0 on success, -1 on failure 
108 */
109 int ast_cdr_register(char *name, char *desc, ast_cdrbe be)
110 {
111         struct ast_cdr_beitem *i;
112
113         if (!name)
114                 return -1;
115         if (!be) {
116                 ast_log(LOG_WARNING, "CDR engine '%s' lacks backend\n", name);
117                 return -1;
118         }
119
120         AST_LIST_LOCK(&be_list);
121         AST_LIST_TRAVERSE(&be_list, i, list) {
122                 if (!strcasecmp(name, i->name))
123                         break;
124         }
125         AST_LIST_UNLOCK(&be_list);
126
127         if (i) {
128                 ast_log(LOG_WARNING, "Already have a CDR backend called '%s'\n", name);
129                 return -1;
130         }
131
132         i = malloc(sizeof(*i));
133         if (!i)         
134                 return -1;
135
136         memset(i, 0, sizeof(*i));
137         i->be = be;
138         ast_copy_string(i->name, name, sizeof(i->name));
139         ast_copy_string(i->desc, desc, sizeof(i->desc));
140
141         AST_LIST_LOCK(&be_list);
142         AST_LIST_INSERT_HEAD(&be_list, i, list);
143         AST_LIST_UNLOCK(&be_list);
144
145         return 0;
146 }
147
148 /*! unregister a CDR driver */
149 void ast_cdr_unregister(char *name)
150 {
151         struct ast_cdr_beitem *i = NULL;
152
153         AST_LIST_LOCK(&be_list);
154         AST_LIST_TRAVERSE_SAFE_BEGIN(&be_list, i, list) {
155                 if (!strcasecmp(name, i->name)) {
156                         AST_LIST_REMOVE_CURRENT(&be_list, list);
157                         if (option_verbose > 1)
158                                 ast_verbose(VERBOSE_PREFIX_2 "Unregistered '%s' CDR backend\n", name);
159                         free(i);
160                         break;
161                 }
162         }
163         AST_LIST_TRAVERSE_SAFE_END;
164         AST_LIST_UNLOCK(&be_list);
165 }
166
167 /*! Duplicate a CDR record 
168         \returns Pointer to new CDR record
169 */
170 struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr) 
171 {
172         struct ast_cdr *newcdr;
173
174         if (!(newcdr = ast_cdr_alloc())) {
175                 ast_log(LOG_ERROR, "Memory Error!\n");
176                 return NULL;
177         }
178
179         memcpy(newcdr, cdr, sizeof(*newcdr));
180         /* The varshead is unusable, volatile even, after the memcpy so we take care of that here */
181         memset(&newcdr->varshead, 0, sizeof(newcdr->varshead));
182         ast_cdr_copy_vars(newcdr, cdr);
183         newcdr->next = NULL;
184
185         return newcdr;
186 }
187
188 static const char *ast_cdr_getvar_internal(struct ast_cdr *cdr, const char *name, int recur) 
189 {
190         struct ast_var_t *variables;
191         struct varshead *headp;
192
193         if (ast_strlen_zero(name))
194                 return NULL;
195
196         while (cdr) {
197                 headp = &cdr->varshead;
198                 AST_LIST_TRAVERSE(headp, variables, entries) {
199                         if (!strcasecmp(name, ast_var_name(variables)))
200                                 return ast_var_value(variables);
201                 }
202                 if (!recur)
203                         break;
204                 cdr = cdr->next;
205         }
206
207         return NULL;
208 }
209
210 /*! CDR channel variable retrieval */
211 void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int recur) 
212 {
213         struct tm tm;
214         time_t t;
215         const char *fmt = "%Y-%m-%d %T";
216         const char *varbuf;
217
218         *ret = NULL;
219         /* special vars (the ones from the struct ast_cdr when requested by name) 
220            I'd almost say we should convert all the stringed vals to vars */
221
222         if (!strcasecmp(name, "clid"))
223                 ast_copy_string(workspace, cdr->clid, workspacelen);
224         else if (!strcasecmp(name, "src"))
225                 ast_copy_string(workspace, cdr->src, workspacelen);
226         else if (!strcasecmp(name, "dst"))
227                 ast_copy_string(workspace, cdr->dst, workspacelen);
228         else if (!strcasecmp(name, "dcontext"))
229                 ast_copy_string(workspace, cdr->dcontext, workspacelen);
230         else if (!strcasecmp(name, "channel"))
231                 ast_copy_string(workspace, cdr->channel, workspacelen);
232         else if (!strcasecmp(name, "dstchannel"))
233                 ast_copy_string(workspace, cdr->dstchannel, workspacelen);
234         else if (!strcasecmp(name, "lastapp"))
235                 ast_copy_string(workspace, cdr->lastapp, workspacelen);
236         else if (!strcasecmp(name, "lastdata"))
237                 ast_copy_string(workspace, cdr->lastdata, workspacelen);
238         else if (!strcasecmp(name, "start")) {
239                 t = cdr->start.tv_sec;
240                 if (t) {
241                         localtime_r(&t, &tm);
242                         strftime(workspace, workspacelen, fmt, &tm);
243                 }
244         } else if (!strcasecmp(name, "answer")) {
245                 t = cdr->answer.tv_sec;
246                 if (t) {
247                         localtime_r(&t, &tm);
248                         strftime(workspace, workspacelen, fmt, &tm);
249                 }
250         } else if (!strcasecmp(name, "end")) {
251                 t = cdr->end.tv_sec;
252                 if (t) {
253                         localtime_r(&t, &tm);
254                         strftime(workspace, workspacelen, fmt, &tm);
255                 }
256         } else if (!strcasecmp(name, "duration"))
257                 snprintf(workspace, workspacelen, "%d", cdr->duration);
258         else if (!strcasecmp(name, "billsec"))
259                 snprintf(workspace, workspacelen, "%d", cdr->billsec);
260         else if (!strcasecmp(name, "disposition"))
261                 ast_copy_string(workspace, ast_cdr_disp2str(cdr->disposition), workspacelen);
262         else if (!strcasecmp(name, "amaflags"))
263                 ast_copy_string(workspace, ast_cdr_flags2str(cdr->amaflags), workspacelen);
264         else if (!strcasecmp(name, "accountcode"))
265                 ast_copy_string(workspace, cdr->accountcode, workspacelen);
266         else if (!strcasecmp(name, "uniqueid"))
267                 ast_copy_string(workspace, cdr->uniqueid, workspacelen);
268         else if (!strcasecmp(name, "userfield"))
269                 ast_copy_string(workspace, cdr->userfield, workspacelen);
270         else if ((varbuf = ast_cdr_getvar_internal(cdr, name, recur)))
271                 ast_copy_string(workspace, varbuf, workspacelen);
272
273         if (!ast_strlen_zero(workspace))
274                 *ret = workspace;
275 }
276
277 /* readonly cdr variables */
278 static  const char *cdr_readonly_vars[] = { "clid", "src", "dst", "dcontext", "channel", "dstchannel",
279                                     "lastapp", "lastdata", "start", "answer", "end", "duration",
280                                     "billsec", "disposition", "amaflags", "accountcode", "uniqueid",
281                                     "userfield", NULL };
282 /*! Set a CDR channel variable 
283         \note You can't set the CDR variables that belong to the actual CDR record, like "billsec".
284 */
285 int ast_cdr_setvar(struct ast_cdr *cdr, const char *name, const char *value, int recur) 
286 {
287         struct ast_var_t *newvariable;
288         struct varshead *headp;
289         int x;
290         
291         for(x = 0; cdr_readonly_vars[x]; x++) {
292                 if (!strcasecmp(name, cdr_readonly_vars[x])) {
293                         ast_log(LOG_ERROR, "Attempt to set a read-only variable!.\n");
294                         return -1;
295                 }
296         }
297
298         if (!cdr) {
299                 ast_log(LOG_ERROR, "Attempt to set a variable on a nonexistent CDR record.\n");
300                 return -1;
301         }
302
303         while (cdr) {
304                 headp = &cdr->varshead;
305                 AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) {
306                         if (!strcasecmp(ast_var_name(newvariable), name)) {
307                                 /* there is already such a variable, delete it */
308                                 AST_LIST_REMOVE_CURRENT(headp, entries);
309                                 ast_var_delete(newvariable);
310                                 break;
311                         }
312                 }
313                 AST_LIST_TRAVERSE_SAFE_END;
314
315                 if (value) {
316                         newvariable = ast_var_assign(name, value);
317                         AST_LIST_INSERT_HEAD(headp, newvariable, entries);
318                 }
319
320                 if (!recur) {
321                         break;
322                 }
323
324                 cdr = cdr->next;
325         }
326
327         return 0;
328 }
329
330 int ast_cdr_copy_vars(struct ast_cdr *to_cdr, struct ast_cdr *from_cdr)
331 {
332         struct ast_var_t *variables, *newvariable = NULL;
333         struct varshead *headpa, *headpb;
334         const char *var, *val;
335         int x = 0;
336
337         headpa = &from_cdr->varshead;
338         headpb = &to_cdr->varshead;
339
340         AST_LIST_TRAVERSE(headpa,variables,entries) {
341                 if (variables &&
342                     (var = ast_var_name(variables)) && (val = ast_var_value(variables)) &&
343                     !ast_strlen_zero(var) && !ast_strlen_zero(val)) {
344                         newvariable = ast_var_assign(var, val);
345                         AST_LIST_INSERT_HEAD(headpb, newvariable, entries);
346                         x++;
347                 }
348         }
349
350         return x;
351 }
352
353 int ast_cdr_serialize_variables(struct ast_cdr *cdr, char *buf, size_t size, char delim, char sep, int recur) 
354 {
355         struct ast_var_t *variables;
356         const char *var, *val;
357         char *tmp;
358         char workspace[256];
359         int total = 0, x = 0, i;
360
361         memset(buf, 0, size);
362
363         for (; cdr; cdr = recur ? cdr->next : NULL) {
364                 if (++x > 1)
365                         ast_build_string(&buf, &size, "\n");
366
367                 AST_LIST_TRAVERSE(&cdr->varshead, variables, entries) {
368                         if (variables &&
369                             (var = ast_var_name(variables)) && (val = ast_var_value(variables)) &&
370                             !ast_strlen_zero(var) && !ast_strlen_zero(val)) {
371                                 if (ast_build_string(&buf, &size, "level %d: %s%c%s%c", x, var, delim, val, sep)) {
372                                         ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
373                                         break;
374                                 } else
375                                         total++;
376                         } else 
377                                 break;
378                 }
379
380                 for (i = 0; cdr_readonly_vars[i]; i++) {
381                         ast_cdr_getvar(cdr, cdr_readonly_vars[i], &tmp, workspace, sizeof(workspace), 0);
382                         if (!tmp)
383                                 continue;
384                         
385                         if (ast_build_string(&buf, &size, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, tmp, sep)) {
386                                 ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
387                                 break;
388                         } else
389                                 total++;
390                 }
391         }
392
393         return total;
394 }
395
396
397 void ast_cdr_free_vars(struct ast_cdr *cdr, int recur)
398 {
399         struct varshead *headp;
400         struct ast_var_t *vardata;
401
402         /* clear variables */
403         while (cdr) {
404                 headp = &cdr->varshead;
405                 while (!AST_LIST_EMPTY(headp)) {
406                         vardata = AST_LIST_REMOVE_HEAD(headp, entries);
407                         ast_var_delete(vardata);
408                 }
409
410                 if (!recur) {
411                         break;
412                 }
413
414                 cdr = cdr->next;
415         }
416 }
417
418 void ast_cdr_free(struct ast_cdr *cdr)
419 {
420         char *chan;
421         struct ast_cdr *next; 
422
423         while (cdr) {
424                 next = cdr->next;
425                 chan = !ast_strlen_zero(cdr->channel) ? cdr->channel : "<unknown>";
426                 if (!ast_test_flag(cdr, AST_CDR_FLAG_POSTED) && !ast_test_flag(cdr, AST_CDR_FLAG_POST_DISABLED))
427                         ast_log(LOG_WARNING, "CDR on channel '%s' not posted\n", chan);
428                 if (ast_tvzero(cdr->end))
429                         ast_log(LOG_WARNING, "CDR on channel '%s' lacks end\n", chan);
430                 if (ast_tvzero(cdr->start))
431                         ast_log(LOG_WARNING, "CDR on channel '%s' lacks start\n", chan);
432
433                 ast_cdr_free_vars(cdr, 0);
434                 free(cdr);
435                 cdr = next;
436         }
437 }
438
439 struct ast_cdr *ast_cdr_alloc(void)
440 {
441         struct ast_cdr *cdr;
442
443         cdr = malloc(sizeof(*cdr));
444         if (cdr)
445                 memset(cdr, 0, sizeof(*cdr));
446
447         return cdr;
448 }
449
450 void ast_cdr_start(struct ast_cdr *cdr)
451 {
452         char *chan; 
453
454         while (cdr) {
455                 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
456                         chan = !ast_strlen_zero(cdr->channel) ? cdr->channel : "<unknown>";
457                         if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED))
458                                 ast_log(LOG_WARNING, "CDR on channel '%s' already posted\n", chan);
459                         if (!ast_tvzero(cdr->start))
460                                 ast_log(LOG_WARNING, "CDR on channel '%s' already started\n", chan);
461                         cdr->start = ast_tvnow();
462                 }
463                 cdr = cdr->next;
464         }
465 }
466
467 void ast_cdr_answer(struct ast_cdr *cdr)
468 {
469         char *chan; 
470
471         while (cdr) {
472                 chan = !ast_strlen_zero(cdr->channel) ? cdr->channel : "<unknown>";
473                 if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED))
474                         ast_log(LOG_WARNING, "CDR on channel '%s' already posted\n", chan);
475                 if (cdr->disposition < AST_CDR_ANSWERED)
476                         cdr->disposition = AST_CDR_ANSWERED;
477                 if (ast_tvzero(cdr->answer))
478                         cdr->answer = ast_tvnow();
479                 cdr = cdr->next;
480         }
481 }
482
483 void ast_cdr_busy(struct ast_cdr *cdr)
484 {
485         char *chan; 
486
487         while (cdr) {
488                 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
489                         chan = !ast_strlen_zero(cdr->channel) ? cdr->channel : "<unknown>";
490                         if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED))
491                                 ast_log(LOG_WARNING, "CDR on channel '%s' already posted\n", chan);
492                         if (cdr->disposition < AST_CDR_BUSY)
493                                 cdr->disposition = AST_CDR_BUSY;
494                 }
495                 cdr = cdr->next;
496         }
497 }
498
499 void ast_cdr_failed(struct ast_cdr *cdr)
500 {
501         char *chan; 
502
503         while (cdr) {
504                 chan = !ast_strlen_zero(cdr->channel) ? cdr->channel : "<unknown>";
505                 if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED))
506                         ast_log(LOG_WARNING, "CDR on channel '%s' already posted\n", chan);
507                 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
508                         cdr->disposition = AST_CDR_FAILED;
509                 cdr = cdr->next;
510         }
511 }
512
513 int ast_cdr_disposition(struct ast_cdr *cdr, int cause)
514 {
515         int res = 0;
516
517         while (cdr) {
518                 switch(cause) {
519                 case AST_CAUSE_BUSY:
520                         ast_cdr_busy(cdr);
521                         break;
522                 case AST_CAUSE_FAILURE:
523                         ast_cdr_failed(cdr);
524                         break;
525                 case AST_CAUSE_NORMAL:
526                         break;
527                 case AST_CAUSE_NOTDEFINED:
528                         res = -1;
529                         break;
530                 default:
531                         res = -1;
532                         ast_log(LOG_WARNING, "Cause not handled\n");
533                 }
534                 cdr = cdr->next;
535         }
536         return res;
537 }
538
539 void ast_cdr_setdestchan(struct ast_cdr *cdr, const char *chann)
540 {
541         char *chan; 
542
543         while (cdr) {
544                 chan = !ast_strlen_zero(cdr->channel) ? cdr->channel : "<unknown>";
545                 if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED))
546                         ast_log(LOG_WARNING, "CDR on channel '%s' already posted\n", chan);
547                 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
548                         ast_copy_string(cdr->dstchannel, chann, sizeof(cdr->dstchannel));
549                 cdr = cdr->next;
550         }
551 }
552
553 void ast_cdr_setapp(struct ast_cdr *cdr, char *app, char *data)
554 {
555         char *chan; 
556
557         while (cdr) {
558                 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
559                         chan = !ast_strlen_zero(cdr->channel) ? cdr->channel : "<unknown>";
560                         if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED))
561                                 ast_log(LOG_WARNING, "CDR on channel '%s' already posted\n", chan);
562                         if (!app)
563                                 app = "";
564                         ast_copy_string(cdr->lastapp, app, sizeof(cdr->lastapp));
565                         if (!data)
566                                 data = "";
567                         ast_copy_string(cdr->lastdata, data, sizeof(cdr->lastdata));
568                 }
569                 cdr = cdr->next;
570         }
571 }
572
573 int ast_cdr_setcid(struct ast_cdr *cdr, struct ast_channel *c)
574 {
575         char tmp[AST_MAX_EXTENSION] = "";
576         char *num;
577
578         while (cdr) {
579                 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
580                         /* Grab source from ANI or normal Caller*ID */
581                         num = c->cid.cid_ani ? c->cid.cid_ani : c->cid.cid_num;
582                         
583                         if (c->cid.cid_name && num)
584                                 snprintf(tmp, sizeof(tmp), "\"%s\" <%s>", c->cid.cid_name, num);
585                         else if (c->cid.cid_name)
586                                 ast_copy_string(tmp, c->cid.cid_name, sizeof(tmp));
587                         else if (num)
588                                 ast_copy_string(tmp, num, sizeof(tmp));
589                         ast_copy_string(cdr->clid, tmp, sizeof(cdr->clid));
590                         ast_copy_string(cdr->src, num ? num : "", sizeof(cdr->src));
591                 }
592                 cdr = cdr->next;
593         }
594
595         return 0;
596 }
597
598
599 int ast_cdr_init(struct ast_cdr *cdr, struct ast_channel *c)
600 {
601         char *chan;
602         char *num;
603         char tmp[AST_MAX_EXTENSION] = "";
604
605         while (cdr) {
606                 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
607                         chan = !ast_strlen_zero(cdr->channel) ? cdr->channel : "<unknown>";
608                         if (!ast_strlen_zero(cdr->channel)) 
609                                 ast_log(LOG_WARNING, "CDR already initialized on '%s'\n", chan); 
610                         ast_copy_string(cdr->channel, c->name, sizeof(cdr->channel));
611                         /* Grab source from ANI or normal Caller*ID */
612                         num = c->cid.cid_ani ? c->cid.cid_ani : c->cid.cid_num;
613                         
614                         if (c->cid.cid_name && num)
615                                 snprintf(tmp, sizeof(tmp), "\"%s\" <%s>", c->cid.cid_name, num);
616                         else if (c->cid.cid_name)
617                                 ast_copy_string(tmp, c->cid.cid_name, sizeof(tmp));
618                         else if (num)
619                                 ast_copy_string(tmp, num, sizeof(tmp));
620                         ast_copy_string(cdr->clid, tmp, sizeof(cdr->clid));
621                         ast_copy_string(cdr->src, num ? num : "", sizeof(cdr->src));
622
623                         cdr->disposition = (c->_state == AST_STATE_UP) ?  AST_CDR_ANSWERED : AST_CDR_NOANSWER;
624                         cdr->amaflags = c->amaflags ? c->amaflags :  ast_default_amaflags;
625                         ast_copy_string(cdr->accountcode, c->accountcode, sizeof(cdr->accountcode));
626                         /* Destination information */
627                         ast_copy_string(cdr->dst, c->exten, sizeof(cdr->dst));
628                         ast_copy_string(cdr->dcontext, c->context, sizeof(cdr->dcontext));
629                         /* Unique call identifier */
630                         ast_copy_string(cdr->uniqueid, c->uniqueid, sizeof(cdr->uniqueid));
631                 }
632                 cdr = cdr->next;
633         }
634         return 0;
635 }
636
637 void ast_cdr_end(struct ast_cdr *cdr)
638 {
639         char *chan;
640
641         while (cdr) {
642                 chan = !ast_strlen_zero(cdr->channel) ? cdr->channel : "<unknown>";
643                 if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED))
644                         ast_log(LOG_WARNING, "CDR on channel '%s' already posted\n", chan);
645                 if (ast_tvzero(cdr->start))
646                         ast_log(LOG_WARNING, "CDR on channel '%s' has not started\n", chan);
647                 if (ast_tvzero(cdr->end))
648                         cdr->end = ast_tvnow();
649                 cdr = cdr->next;
650         }
651 }
652
653 char *ast_cdr_disp2str(int disposition)
654 {
655         switch (disposition) {
656         case AST_CDR_NOANSWER:
657                 return "NO ANSWER";
658         case AST_CDR_FAILED:
659                 return "FAILED";                
660         case AST_CDR_BUSY:
661                 return "BUSY";          
662         case AST_CDR_ANSWERED:
663                 return "ANSWERED";
664         }
665         return "UNKNOWN";
666 }
667
668 /*! Converts AMA flag to printable string */
669 char *ast_cdr_flags2str(int flag)
670 {
671         switch(flag) {
672         case AST_CDR_OMIT:
673                 return "OMIT";
674         case AST_CDR_BILLING:
675                 return "BILLING";
676         case AST_CDR_DOCUMENTATION:
677                 return "DOCUMENTATION";
678         }
679         return "Unknown";
680 }
681
682 int ast_cdr_setaccount(struct ast_channel *chan, const char *account)
683 {
684         struct ast_cdr *cdr = chan->cdr;
685
686         ast_string_field_set(chan, accountcode, account);
687         while (cdr) {
688                 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
689                         ast_copy_string(cdr->accountcode, chan->accountcode, sizeof(cdr->accountcode));
690                 cdr = cdr->next;
691         }
692         return 0;
693 }
694
695 int ast_cdr_setamaflags(struct ast_channel *chan, const char *flag)
696 {
697         struct ast_cdr *cdr;
698         int newflag;
699
700         newflag = ast_cdr_amaflags2int(flag);
701         if (newflag) {
702                 for (cdr = chan->cdr; cdr; cdr = cdr->next) {
703                         cdr->amaflags = newflag;
704                 }
705         }
706
707         return 0;
708 }
709
710 int ast_cdr_setuserfield(struct ast_channel *chan, const char *userfield)
711 {
712         struct ast_cdr *cdr = chan->cdr;
713
714         while (cdr) {
715                 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) 
716                         ast_copy_string(cdr->userfield, userfield, sizeof(cdr->userfield));
717                 cdr = cdr->next;
718         }
719
720         return 0;
721 }
722
723 int ast_cdr_appenduserfield(struct ast_channel *chan, const char *userfield)
724 {
725         struct ast_cdr *cdr = chan->cdr;
726
727         while (cdr) {
728                 int len = strlen(cdr->userfield);
729
730                 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
731                         strncpy(cdr->userfield+len, userfield, sizeof(cdr->userfield) - len - 1);
732
733                 cdr = cdr->next;
734         }
735
736         return 0;
737 }
738
739 int ast_cdr_update(struct ast_channel *c)
740 {
741         struct ast_cdr *cdr = c->cdr;
742         char *num;
743         char tmp[AST_MAX_EXTENSION] = "";
744
745         while (cdr) {
746                 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
747                         num = c->cid.cid_ani ? c->cid.cid_ani : c->cid.cid_num;
748                         
749                         if (c->cid.cid_name && num)
750                                 snprintf(tmp, sizeof(tmp), "\"%s\" <%s>", c->cid.cid_name, num);
751                         else if (c->cid.cid_name)
752                                 ast_copy_string(tmp, c->cid.cid_name, sizeof(tmp));
753                         else if (num)
754                                 ast_copy_string(tmp, num, sizeof(tmp));
755                         ast_copy_string(cdr->clid, tmp, sizeof(cdr->clid));
756                         ast_copy_string(cdr->src, num ? num : "", sizeof(cdr->src));
757
758                         /* Copy account code et-al */   
759                         ast_copy_string(cdr->accountcode, c->accountcode, sizeof(cdr->accountcode));
760                         /* Destination information */
761                         ast_copy_string(cdr->dst, (ast_strlen_zero(c->macroexten)) ? c->exten : c->macroexten, sizeof(cdr->dst));
762                         ast_copy_string(cdr->dcontext, (ast_strlen_zero(c->macrocontext)) ? c->context : c->macrocontext, sizeof(cdr->dcontext));
763                 }
764                 cdr = cdr->next;
765         }
766
767         return 0;
768 }
769
770 int ast_cdr_amaflags2int(const char *flag)
771 {
772         if (!strcasecmp(flag, "default"))
773                 return 0;
774         if (!strcasecmp(flag, "omit"))
775                 return AST_CDR_OMIT;
776         if (!strcasecmp(flag, "billing"))
777                 return AST_CDR_BILLING;
778         if (!strcasecmp(flag, "documentation"))
779                 return AST_CDR_DOCUMENTATION;
780         return -1;
781 }
782
783 static void post_cdr(struct ast_cdr *cdr)
784 {
785         char *chan;
786         struct ast_cdr_beitem *i;
787
788         while (cdr) {
789                 chan = !ast_strlen_zero(cdr->channel) ? cdr->channel : "<unknown>";
790                 if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED))
791                         ast_log(LOG_WARNING, "CDR on channel '%s' already posted\n", chan);
792                 if (ast_tvzero(cdr->end))
793                         ast_log(LOG_WARNING, "CDR on channel '%s' lacks end\n", chan);
794                 if (ast_tvzero(cdr->start))
795                         ast_log(LOG_WARNING, "CDR on channel '%s' lacks start\n", chan);
796                 cdr->duration = cdr->end.tv_sec - cdr->start.tv_sec + (cdr->end.tv_usec - cdr->start.tv_usec) / 1000000;
797                 if (!ast_tvzero(cdr->answer))
798                         cdr->billsec = cdr->end.tv_sec - cdr->answer.tv_sec + (cdr->end.tv_usec - cdr->answer.tv_usec) / 1000000;
799                 else
800                         cdr->billsec = 0;
801                 ast_set_flag(cdr, AST_CDR_FLAG_POSTED);
802                 AST_LIST_LOCK(&be_list);
803                 AST_LIST_TRAVERSE(&be_list, i, list) {
804                         i->be(cdr);
805                 }
806                 AST_LIST_UNLOCK(&be_list);
807                 cdr = cdr->next;
808         }
809 }
810
811 void ast_cdr_reset(struct ast_cdr *cdr, struct ast_flags *_flags)
812 {
813         struct ast_cdr *dup;
814         struct ast_flags flags = { 0 };
815
816         if (_flags)
817                 ast_copy_flags(&flags, _flags, AST_FLAGS_ALL);
818
819         while (cdr) {
820                 /* Detach if post is requested */
821                 if (ast_test_flag(&flags, AST_CDR_FLAG_LOCKED) || !ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
822                         if (ast_test_flag(&flags, AST_CDR_FLAG_POSTED)) {
823                                 ast_cdr_end(cdr);
824                                 if ((dup = ast_cdr_dup(cdr))) {
825                                         ast_cdr_detach(dup);
826                                 }
827                                 ast_set_flag(cdr, AST_CDR_FLAG_POSTED);
828                         }
829
830                         /* clear variables */
831                         if (!ast_test_flag(&flags, AST_CDR_FLAG_KEEP_VARS)) {
832                                 ast_cdr_free_vars(cdr, 0);
833                         }
834
835                         /* Reset to initial state */
836                         ast_clear_flag(cdr, AST_FLAGS_ALL);     
837                         memset(&cdr->start, 0, sizeof(cdr->start));
838                         memset(&cdr->end, 0, sizeof(cdr->end));
839                         memset(&cdr->answer, 0, sizeof(cdr->answer));
840                         cdr->billsec = 0;
841                         cdr->duration = 0;
842                         ast_cdr_start(cdr);
843                         cdr->disposition = AST_CDR_NOANSWER;
844                 }
845                         
846                 cdr = cdr->next;
847         }
848 }
849
850 struct ast_cdr *ast_cdr_append(struct ast_cdr *cdr, struct ast_cdr *newcdr) 
851 {
852         struct ast_cdr *ret;
853
854         if (cdr) {
855                 ret = cdr;
856
857                 while (cdr->next)
858                         cdr = cdr->next;
859                 cdr->next = newcdr;
860         } else {
861                 ret = newcdr;
862         }
863
864         return ret;
865 }
866
867 /*! \note Don't call without cdr_batch_lock */
868 static void reset_batch(void)
869 {
870         batch->size = 0;
871         batch->head = NULL;
872         batch->tail = NULL;
873 }
874
875 /*! \note Don't call without cdr_batch_lock */
876 static int init_batch(void)
877 {
878         /* This is the single meta-batch used to keep track of all CDRs during the entire life of the program */
879         batch = malloc(sizeof(*batch));
880         if (!batch) {
881                 ast_log(LOG_WARNING, "CDR: out of memory while trying to handle batched records, data will most likely be lost\n");
882                 return -1;
883         }
884
885         reset_batch();
886
887         return 0;
888 }
889
890 static void *do_batch_backend_process(void *data)
891 {
892         struct ast_cdr_batch_item *processeditem;
893         struct ast_cdr_batch_item *batchitem = data;
894
895         /* Push each CDR into storage mechanism(s) and free all the memory */
896         while (batchitem) {
897                 post_cdr(batchitem->cdr);
898                 ast_cdr_free(batchitem->cdr);
899                 processeditem = batchitem;
900                 batchitem = batchitem->next;
901                 free(processeditem);
902         }
903
904         return NULL;
905 }
906
907 void ast_cdr_submit_batch(int shutdown)
908 {
909         struct ast_cdr_batch_item *oldbatchitems = NULL;
910         pthread_attr_t attr;
911         pthread_t batch_post_thread = AST_PTHREADT_NULL;
912
913         /* if there's no batch, or no CDRs in the batch, then there's nothing to do */
914         if (!batch || !batch->head)
915                 return;
916
917         /* move the old CDRs aside, and prepare a new CDR batch */
918         ast_mutex_lock(&cdr_batch_lock);
919         oldbatchitems = batch->head;
920         reset_batch();
921         ast_mutex_unlock(&cdr_batch_lock);
922
923         /* if configured, spawn a new thread to post these CDRs,
924            also try to save as much as possible if we are shutting down safely */
925         if (batchscheduleronly || shutdown) {
926                 if (option_debug)
927                         ast_log(LOG_DEBUG, "CDR single-threaded batch processing begins now\n");
928                 do_batch_backend_process(oldbatchitems);
929         } else {
930                 pthread_attr_init(&attr);
931                 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
932                 if (ast_pthread_create(&batch_post_thread, &attr, do_batch_backend_process, oldbatchitems)) {
933                         ast_log(LOG_WARNING, "CDR processing thread could not detach, now trying in this thread\n");
934                         do_batch_backend_process(oldbatchitems);
935                 } else {
936                         if (option_debug)
937                                 ast_log(LOG_DEBUG, "CDR multi-threaded batch processing begins now\n");
938                 }
939         }
940 }
941
942 static int submit_scheduled_batch(void *data)
943 {
944         ast_cdr_submit_batch(0);
945         /* manually reschedule from this point in time */
946         cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL);
947         /* returning zero so the scheduler does not automatically reschedule */
948         return 0;
949 }
950
951 static void submit_unscheduled_batch(void)
952 {
953         /* this is okay since we are not being called from within the scheduler */
954         if (cdr_sched > -1)
955                 ast_sched_del(sched, cdr_sched);
956         /* schedule the submission to occur ASAP (1 ms) */
957         cdr_sched = ast_sched_add(sched, 1, submit_scheduled_batch, NULL);
958         /* signal the do_cdr thread to wakeup early and do some work (that lazy thread ;) */
959         ast_mutex_lock(&cdr_pending_lock);
960         ast_cond_signal(&cdr_pending_cond);
961         ast_mutex_unlock(&cdr_pending_lock);
962 }
963
964 void ast_cdr_detach(struct ast_cdr *cdr)
965 {
966         struct ast_cdr_batch_item *newtail;
967         int curr;
968
969         /* maybe they disabled CDR stuff completely, so just drop it */
970         if (!enabled) {
971                 if (option_debug)
972                         ast_log(LOG_DEBUG, "Dropping CDR !\n");
973                 ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
974                 ast_cdr_free(cdr);
975                 return;
976         }
977
978         /* post stuff immediately if we are not in batch mode, this is legacy behaviour */
979         if (!batchmode) {
980                 post_cdr(cdr);
981                 ast_cdr_free(cdr);
982                 return;
983         }
984
985         /* otherwise, each CDR gets put into a batch list (at the end) */
986         if (option_debug)
987                 ast_log(LOG_DEBUG, "CDR detaching from this thread\n");
988
989         /* we'll need a new tail for every CDR */
990         newtail = malloc(sizeof(*newtail));
991         if (!newtail) {
992                 ast_log(LOG_WARNING, "CDR: out of memory while trying to detach, will try in this thread instead\n");
993                 post_cdr(cdr);
994                 ast_cdr_free(cdr);
995                 return;
996         }
997         memset(newtail, 0, sizeof(*newtail));
998
999         /* don't traverse a whole list (just keep track of the tail) */
1000         ast_mutex_lock(&cdr_batch_lock);
1001         if (!batch)
1002                 init_batch();
1003         if (!batch->head) {
1004                 /* new batch is empty, so point the head at the new tail */
1005                 batch->head = newtail;
1006         } else {
1007                 /* already got a batch with something in it, so just append a new tail */
1008                 batch->tail->next = newtail;
1009         }
1010         newtail->cdr = cdr;
1011         batch->tail = newtail;
1012         curr = batch->size++;
1013         ast_mutex_unlock(&cdr_batch_lock);
1014
1015         /* if we have enough stuff to post, then do it */
1016         if (curr >= (batchsize - 1))
1017                 submit_unscheduled_batch();
1018 }
1019
1020 static void *do_cdr(void *data)
1021 {
1022         struct timespec timeout;
1023         int schedms;
1024         int numevents = 0;
1025
1026         for(;;) {
1027                 struct timeval now = ast_tvnow();
1028                 schedms = ast_sched_wait(sched);
1029                 /* this shouldn't happen, but provide a 1 second default just in case */
1030                 if (schedms <= 0)
1031                         schedms = 1000;
1032                 timeout.tv_sec = now.tv_sec + (schedms / 1000);
1033                 timeout.tv_nsec = (now.tv_usec * 1000) + ((schedms % 1000) * 1000);
1034                 /* prevent stuff from clobbering cdr_pending_cond, then wait on signals sent to it until the timeout expires */
1035                 ast_mutex_lock(&cdr_pending_lock);
1036                 ast_cond_timedwait(&cdr_pending_cond, &cdr_pending_lock, &timeout);
1037                 numevents = ast_sched_runq(sched);
1038                 ast_mutex_unlock(&cdr_pending_lock);
1039                 if (option_debug > 1)
1040                         ast_log(LOG_DEBUG, "Processed %d scheduled CDR batches from the run queue\n", numevents);
1041         }
1042
1043         return NULL;
1044 }
1045
1046 static int handle_cli_status(int fd, int argc, char *argv[])
1047 {
1048         struct ast_cdr_beitem *beitem=NULL;
1049         int cnt=0;
1050         long nextbatchtime=0;
1051
1052         if (argc > 2)
1053                 return RESULT_SHOWUSAGE;
1054
1055         ast_cli(fd, "CDR logging: %s\n", enabled ? "enabled" : "disabled");
1056         ast_cli(fd, "CDR mode: %s\n", batchmode ? "batch" : "simple");
1057         if (enabled) {
1058                 if (batchmode) {
1059                         if (batch)
1060                                 cnt = batch->size;
1061                         if (cdr_sched > -1)
1062                                 nextbatchtime = ast_sched_when(sched, cdr_sched);
1063                         ast_cli(fd, "CDR safe shut down: %s\n", batchsafeshutdown ? "enabled" : "disabled");
1064                         ast_cli(fd, "CDR batch threading model: %s\n", batchscheduleronly ? "scheduler only" : "scheduler plus separate threads");
1065                         ast_cli(fd, "CDR current batch size: %d record%s\n", cnt, (cnt != 1) ? "s" : "");
1066                         ast_cli(fd, "CDR maximum batch size: %d record%s\n", batchsize, (batchsize != 1) ? "s" : "");
1067                         ast_cli(fd, "CDR maximum batch time: %d second%s\n", batchtime, (batchtime != 1) ? "s" : "");
1068                         ast_cli(fd, "CDR next scheduled batch processing time: %ld second%s\n", nextbatchtime, (nextbatchtime != 1) ? "s" : "");
1069                 }
1070                 AST_LIST_LOCK(&be_list);
1071                 AST_LIST_TRAVERSE(&be_list, beitem, list) {
1072                         ast_cli(fd, "CDR registered backend: %s\n", beitem->name);
1073                 }
1074                 AST_LIST_UNLOCK(&be_list);
1075         }
1076
1077         return 0;
1078 }
1079
1080 static int handle_cli_submit(int fd, int argc, char *argv[])
1081 {
1082         if (argc > 2)
1083                 return RESULT_SHOWUSAGE;
1084
1085         submit_unscheduled_batch();
1086         ast_cli(fd, "Submitted CDRs to backend engines for processing.  This may take a while.\n");
1087
1088         return 0;
1089 }
1090
1091 static struct ast_cli_entry cli_submit = {
1092         .cmda = { "cdr", "submit", NULL },
1093         .handler = handle_cli_submit,
1094         .summary = "Posts all pending batched CDR data",
1095         .usage =
1096         "Usage: cdr submit\n"
1097         "       Posts all pending batched CDR data to the configured CDR backend engine modules.\n"
1098 };
1099
1100 static struct ast_cli_entry cli_status = {
1101         .cmda = { "cdr", "status", NULL },
1102         .handler = handle_cli_status,
1103         .summary = "Display the CDR status",
1104         .usage =
1105         "Usage: cdr status\n"
1106         "       Displays the Call Detail Record engine system status.\n"
1107 };
1108
1109 static int do_reload(void)
1110 {
1111         struct ast_config *config;
1112         const char *enabled_value;
1113         const char *batched_value;
1114         const char *scheduleronly_value;
1115         const char *batchsafeshutdown_value;
1116         const char *size_value;
1117         const char *time_value;
1118         int cfg_size;
1119         int cfg_time;
1120         int was_enabled;
1121         int was_batchmode;
1122         int res=0;
1123
1124         ast_mutex_lock(&cdr_batch_lock);
1125
1126         batchsize = BATCH_SIZE_DEFAULT;
1127         batchtime = BATCH_TIME_DEFAULT;
1128         batchscheduleronly = BATCH_SCHEDULER_ONLY_DEFAULT;
1129         batchsafeshutdown = BATCH_SAFE_SHUTDOWN_DEFAULT;
1130         was_enabled = enabled;
1131         was_batchmode = batchmode;
1132         enabled = 1;
1133         batchmode = 0;
1134
1135         /* don't run the next scheduled CDR posting while reloading */
1136         if (cdr_sched > -1)
1137                 ast_sched_del(sched, cdr_sched);
1138
1139         if ((config = ast_config_load("cdr.conf"))) {
1140                 if ((enabled_value = ast_variable_retrieve(config, "general", "enable"))) {
1141                         enabled = ast_true(enabled_value);
1142                 }
1143                 if ((batched_value = ast_variable_retrieve(config, "general", "batch"))) {
1144                         batchmode = ast_true(batched_value);
1145                 }
1146                 if ((scheduleronly_value = ast_variable_retrieve(config, "general", "scheduleronly"))) {
1147                         batchscheduleronly = ast_true(scheduleronly_value);
1148                 }
1149                 if ((batchsafeshutdown_value = ast_variable_retrieve(config, "general", "safeshutdown"))) {
1150                         batchsafeshutdown = ast_true(batchsafeshutdown_value);
1151                 }
1152                 if ((size_value = ast_variable_retrieve(config, "general", "size"))) {
1153                         if (sscanf(size_value, "%d", &cfg_size) < 1)
1154                                 ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", size_value);
1155                         else if (size_value < 0)
1156                                 ast_log(LOG_WARNING, "Invalid maximum batch size '%d' specified, using default\n", cfg_size);
1157                         else
1158                                 batchsize = cfg_size;
1159                 }
1160                 if ((time_value = ast_variable_retrieve(config, "general", "time"))) {
1161                         if (sscanf(time_value, "%d", &cfg_time) < 1)
1162                                 ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", time_value);
1163                         else if (time_value < 0)
1164                                 ast_log(LOG_WARNING, "Invalid maximum batch time '%d' specified, using default\n", cfg_time);
1165                         else
1166                                 batchtime = cfg_time;
1167                 }
1168         }
1169
1170         if (enabled && !batchmode) {
1171                 ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
1172         } else if (enabled && batchmode) {
1173                 cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL);
1174                 ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n", batchsize, batchtime);
1175         } else {
1176                 ast_log(LOG_NOTICE, "CDR logging disabled, data will be lost.\n");
1177         }
1178
1179         /* if this reload enabled the CDR batch mode, create the background thread
1180            if it does not exist */
1181         if (enabled && batchmode && (!was_enabled || !was_batchmode) && (cdr_thread == AST_PTHREADT_NULL)) {
1182                 ast_cond_init(&cdr_pending_cond, NULL);
1183                 if (ast_pthread_create(&cdr_thread, NULL, do_cdr, NULL) < 0) {
1184                         ast_log(LOG_ERROR, "Unable to start CDR thread.\n");
1185                         ast_sched_del(sched, cdr_sched);
1186                 } else {
1187                         ast_cli_register(&cli_submit);
1188                         ast_register_atexit(ast_cdr_engine_term);
1189                         res = 0;
1190                 }
1191         /* if this reload disabled the CDR and/or batch mode and there is a background thread,
1192            kill it */
1193         } else if (((!enabled && was_enabled) || (!batchmode && was_batchmode)) && (cdr_thread != AST_PTHREADT_NULL)) {
1194                 /* wake up the thread so it will exit */
1195                 pthread_cancel(cdr_thread);
1196                 pthread_kill(cdr_thread, SIGURG);
1197                 pthread_join(cdr_thread, NULL);
1198                 cdr_thread = AST_PTHREADT_NULL;
1199                 ast_cond_destroy(&cdr_pending_cond);
1200                 ast_cli_unregister(&cli_submit);
1201                 ast_unregister_atexit(ast_cdr_engine_term);
1202                 res = 0;
1203                 /* if leaving batch mode, then post the CDRs in the batch,
1204                    and don't reschedule, since we are stopping CDR logging */
1205                 if (!batchmode && was_batchmode) {
1206                         ast_cdr_engine_term();
1207                 }
1208         } else {
1209                 res = 0;
1210         }
1211
1212         ast_mutex_unlock(&cdr_batch_lock);
1213         ast_config_destroy(config);
1214
1215         return res;
1216 }
1217
1218 int ast_cdr_engine_init(void)
1219 {
1220         int res;
1221
1222         sched = sched_context_create();
1223         if (!sched) {
1224                 ast_log(LOG_ERROR, "Unable to create schedule context.\n");
1225                 return -1;
1226         }
1227
1228         ast_cli_register(&cli_status);
1229
1230         res = do_reload();
1231         if (res) {
1232                 ast_mutex_lock(&cdr_batch_lock);
1233                 res = init_batch();
1234                 ast_mutex_unlock(&cdr_batch_lock);
1235         }
1236
1237         return res;
1238 }
1239
1240 /* \note This actually gets called a couple of times at shutdown.  Once, before we start
1241    hanging up channels, and then again, after the channel hangup timeout expires */
1242 void ast_cdr_engine_term(void)
1243 {
1244         ast_cdr_submit_batch(batchsafeshutdown);
1245 }
1246
1247 void ast_cdr_engine_reload(void)
1248 {
1249         do_reload();
1250 }
1251