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