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