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