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