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