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