Thu Sep 7 01:02:54 2017

Asterisk developer's documentation


cdr.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2006, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \brief Call Detail Record API
00022  *
00023  * \author Mark Spencer <markster@digium.com>
00024  *
00025  * \note Includes code and algorithms from the Zapata library.
00026  *
00027  * \note We do a lot of checking here in the CDR code to try to be sure we don't ever let a CDR slip
00028  * through our fingers somehow.  If someone allocates a CDR, it must be completely handled normally
00029  * or a WARNING shall be logged, so that we can best keep track of any escape condition where the CDR
00030  * isn't properly generated and posted.
00031  */
00032 
00033 
00034 /*** MODULEINFO
00035    <support_level>core</support_level>
00036  ***/
00037 
00038 #include "asterisk.h"
00039 
00040 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 396279 $")
00041 
00042 #include <signal.h>
00043 
00044 #include "asterisk/lock.h"
00045 #include "asterisk/channel.h"
00046 #include "asterisk/cdr.h"
00047 #include "asterisk/callerid.h"
00048 #include "asterisk/manager.h"
00049 #include "asterisk/causes.h"
00050 #include "asterisk/linkedlists.h"
00051 #include "asterisk/utils.h"
00052 #include "asterisk/sched.h"
00053 #include "asterisk/config.h"
00054 #include "asterisk/cli.h"
00055 #include "asterisk/stringfields.h"
00056 #include "asterisk/data.h"
00057 
00058 /*! Default AMA flag for billing records (CDR's) */
00059 int ast_default_amaflags = AST_CDR_DOCUMENTATION;
00060 char ast_default_accountcode[AST_MAX_ACCOUNT_CODE];
00061 
00062 struct ast_cdr_beitem {
00063    char name[20];
00064    char desc[80];
00065    ast_cdrbe be;
00066    AST_RWLIST_ENTRY(ast_cdr_beitem) list;
00067 };
00068 
00069 static AST_RWLIST_HEAD_STATIC(be_list, ast_cdr_beitem);
00070 
00071 struct ast_cdr_batch_item {
00072    struct ast_cdr *cdr;
00073    struct ast_cdr_batch_item *next;
00074 };
00075 
00076 static struct ast_cdr_batch {
00077    int size;
00078    struct ast_cdr_batch_item *head;
00079    struct ast_cdr_batch_item *tail;
00080 } *batch = NULL;
00081 
00082 
00083 static int cdr_sequence =  0;
00084 
00085 static int cdr_seq_inc(struct ast_cdr *cdr);
00086 
00087 static struct sched_context *sched;
00088 static int cdr_sched = -1;
00089 static pthread_t cdr_thread = AST_PTHREADT_NULL;
00090 
00091 static int enabled;
00092 static const int ENABLED_DEFAULT = 1;
00093 
00094 static int batchmode;
00095 static const int BATCHMODE_DEFAULT = 0;
00096 
00097 static int unanswered;
00098 static const int UNANSWERED_DEFAULT = 0;
00099 
00100 static int batchsize;
00101 static const int BATCH_SIZE_DEFAULT = 100;
00102 
00103 static int batchtime;
00104 static const int BATCH_TIME_DEFAULT = 300;
00105 
00106 static int batchscheduleronly;
00107 static const int BATCH_SCHEDULER_ONLY_DEFAULT = 0;
00108 
00109 static int batchsafeshutdown;
00110 static const int BATCH_SAFE_SHUTDOWN_DEFAULT = 1;
00111 
00112 AST_MUTEX_DEFINE_STATIC(cdr_sched_lock);
00113 
00114 AST_MUTEX_DEFINE_STATIC(cdr_batch_lock);
00115 
00116 /* these are used to wake up the CDR thread when there's work to do */
00117 AST_MUTEX_DEFINE_STATIC(cdr_pending_lock);
00118 static ast_cond_t cdr_pending_cond;
00119 
00120 int check_cdr_enabled(void)
00121 {
00122    return enabled;
00123 }
00124 
00125 /*!
00126  * \brief Register a CDR driver. Each registered CDR driver generates a CDR
00127  * \retval 0 on success.
00128  * \retval -1 on error
00129  */
00130 int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
00131 {
00132    struct ast_cdr_beitem *i = NULL;
00133 
00134    if (!name)
00135       return -1;
00136 
00137    if (!be) {
00138       ast_log(LOG_WARNING, "CDR engine '%s' lacks backend\n", name);
00139       return -1;
00140    }
00141 
00142    AST_RWLIST_WRLOCK(&be_list);
00143    AST_RWLIST_TRAVERSE(&be_list, i, list) {
00144       if (!strcasecmp(name, i->name)) {
00145          ast_log(LOG_WARNING, "Already have a CDR backend called '%s'\n", name);
00146          AST_RWLIST_UNLOCK(&be_list);
00147          return -1;
00148       }
00149    }
00150 
00151    if (!(i = ast_calloc(1, sizeof(*i))))
00152       return -1;
00153 
00154    i->be = be;
00155    ast_copy_string(i->name, name, sizeof(i->name));
00156    ast_copy_string(i->desc, desc, sizeof(i->desc));
00157 
00158    AST_RWLIST_INSERT_HEAD(&be_list, i, list);
00159    AST_RWLIST_UNLOCK(&be_list);
00160 
00161    return 0;
00162 }
00163 
00164 /*! unregister a CDR driver */
00165 void ast_cdr_unregister(const char *name)
00166 {
00167    struct ast_cdr_beitem *i = NULL;
00168 
00169    AST_RWLIST_WRLOCK(&be_list);
00170    AST_RWLIST_TRAVERSE_SAFE_BEGIN(&be_list, i, list) {
00171       if (!strcasecmp(name, i->name)) {
00172          AST_RWLIST_REMOVE_CURRENT(list);
00173          break;
00174       }
00175    }
00176    AST_RWLIST_TRAVERSE_SAFE_END;
00177    AST_RWLIST_UNLOCK(&be_list);
00178 
00179    if (i) {
00180       ast_verb(2, "Unregistered '%s' CDR backend\n", name);
00181       ast_free(i);
00182    }
00183 }
00184 
00185 int ast_cdr_isset_unanswered(void)
00186 {
00187    return unanswered;
00188 }
00189 
00190 struct ast_cdr *ast_cdr_dup_unique(struct ast_cdr *cdr)
00191 {
00192    struct ast_cdr *newcdr = ast_cdr_dup(cdr);
00193    if (!newcdr)
00194       return NULL;
00195 
00196    cdr_seq_inc(newcdr);
00197    return newcdr;
00198 }
00199 
00200 struct ast_cdr *ast_cdr_dup_unique_swap(struct ast_cdr *cdr)
00201 {
00202    struct ast_cdr *newcdr = ast_cdr_dup(cdr);
00203    if (!newcdr)
00204       return NULL;
00205 
00206    cdr_seq_inc(cdr);
00207    return newcdr;
00208 }
00209 
00210 /*! Duplicate a CDR record
00211    \returns Pointer to new CDR record
00212 */
00213 struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr)
00214 {
00215    struct ast_cdr *newcdr;
00216 
00217    if (!cdr) /* don't die if we get a null cdr pointer */
00218       return NULL;
00219    newcdr = ast_cdr_alloc();
00220    if (!newcdr)
00221       return NULL;
00222 
00223    memcpy(newcdr, cdr, sizeof(*newcdr));
00224    /* The varshead is unusable, volatile even, after the memcpy so we take care of that here */
00225    memset(&newcdr->varshead, 0, sizeof(newcdr->varshead));
00226    ast_cdr_copy_vars(newcdr, cdr);
00227    newcdr->next = NULL;
00228 
00229    return newcdr;
00230 }
00231 
00232 static const char *ast_cdr_getvar_internal(struct ast_cdr *cdr, const char *name, int recur)
00233 {
00234    if (ast_strlen_zero(name))
00235       return NULL;
00236 
00237    for (; cdr; cdr = recur ? cdr->next : NULL) {
00238       struct ast_var_t *variables;
00239       struct varshead *headp = &cdr->varshead;
00240       AST_LIST_TRAVERSE(headp, variables, entries) {
00241          if (!strcasecmp(name, ast_var_name(variables)))
00242             return ast_var_value(variables);
00243       }
00244    }
00245 
00246    return NULL;
00247 }
00248 
00249 static void cdr_get_tv(struct timeval when, const char *fmt, char *buf, int bufsize)
00250 {
00251    if (fmt == NULL) {   /* raw mode */
00252       snprintf(buf, bufsize, "%ld.%06ld", (long)when.tv_sec, (long)when.tv_usec);
00253    } else {
00254       if (when.tv_sec) {
00255          struct ast_tm tm;
00256 
00257          ast_localtime(&when, &tm, NULL);
00258          ast_strftime(buf, bufsize, fmt, &tm);
00259       }
00260    }
00261 }
00262 
00263 /*! CDR channel variable retrieval */
00264 void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int recur, int raw)
00265 {
00266    const char *fmt = "%Y-%m-%d %T";
00267    const char *varbuf;
00268 
00269    if (!cdr)  /* don't die if the cdr is null */
00270       return;
00271 
00272    *ret = NULL;
00273    /* special vars (the ones from the struct ast_cdr when requested by name)
00274       I'd almost say we should convert all the stringed vals to vars */
00275 
00276    if (!strcasecmp(name, "clid"))
00277       ast_copy_string(workspace, cdr->clid, workspacelen);
00278    else if (!strcasecmp(name, "src"))
00279       ast_copy_string(workspace, cdr->src, workspacelen);
00280    else if (!strcasecmp(name, "dst"))
00281       ast_copy_string(workspace, cdr->dst, workspacelen);
00282    else if (!strcasecmp(name, "dcontext"))
00283       ast_copy_string(workspace, cdr->dcontext, workspacelen);
00284    else if (!strcasecmp(name, "channel"))
00285       ast_copy_string(workspace, cdr->channel, workspacelen);
00286    else if (!strcasecmp(name, "dstchannel"))
00287       ast_copy_string(workspace, cdr->dstchannel, workspacelen);
00288    else if (!strcasecmp(name, "lastapp"))
00289       ast_copy_string(workspace, cdr->lastapp, workspacelen);
00290    else if (!strcasecmp(name, "lastdata"))
00291       ast_copy_string(workspace, cdr->lastdata, workspacelen);
00292    else if (!strcasecmp(name, "start"))
00293       cdr_get_tv(cdr->start, raw ? NULL : fmt, workspace, workspacelen);
00294    else if (!strcasecmp(name, "answer"))
00295       cdr_get_tv(cdr->answer, raw ? NULL : fmt, workspace, workspacelen);
00296    else if (!strcasecmp(name, "end"))
00297       cdr_get_tv(cdr->end, raw ? NULL : fmt, workspace, workspacelen);
00298    else if (!strcasecmp(name, "duration")) {
00299       snprintf(workspace, workspacelen, "%ld", cdr->end.tv_sec != 0 ? cdr->duration : (long)ast_tvdiff_ms(ast_tvnow(), cdr->start) / 1000);
00300    } else if (!strcasecmp(name, "billsec")) {
00301       snprintf(workspace, workspacelen, "%ld", (cdr->billsec || !ast_tvzero(cdr->end) || ast_tvzero(cdr->answer)) ? cdr->billsec : (long)ast_tvdiff_ms(ast_tvnow(), cdr->answer) / 1000); 
00302    } else if (!strcasecmp(name, "disposition")) {
00303       if (raw) {
00304          snprintf(workspace, workspacelen, "%ld", cdr->disposition);
00305       } else {
00306          ast_copy_string(workspace, ast_cdr_disp2str(cdr->disposition), workspacelen);
00307       }
00308    } else if (!strcasecmp(name, "amaflags")) {
00309       if (raw) {
00310          snprintf(workspace, workspacelen, "%ld", cdr->amaflags);
00311       } else {
00312          ast_copy_string(workspace, ast_cdr_flags2str(cdr->amaflags), workspacelen);
00313       }
00314    } else if (!strcasecmp(name, "accountcode"))
00315       ast_copy_string(workspace, cdr->accountcode, workspacelen);
00316    else if (!strcasecmp(name, "peeraccount"))
00317       ast_copy_string(workspace, cdr->peeraccount, workspacelen);
00318    else if (!strcasecmp(name, "uniqueid"))
00319       ast_copy_string(workspace, cdr->uniqueid, workspacelen);
00320    else if (!strcasecmp(name, "linkedid"))
00321       ast_copy_string(workspace, cdr->linkedid, workspacelen);
00322    else if (!strcasecmp(name, "userfield"))
00323       ast_copy_string(workspace, cdr->userfield, workspacelen);
00324    else if (!strcasecmp(name, "sequence"))
00325       snprintf(workspace, workspacelen, "%d", cdr->sequence);
00326    else if ((varbuf = ast_cdr_getvar_internal(cdr, name, recur)))
00327       ast_copy_string(workspace, varbuf, workspacelen);
00328    else
00329       workspace[0] = '\0';
00330 
00331    if (!ast_strlen_zero(workspace))
00332       *ret = workspace;
00333 }
00334 
00335 /* readonly cdr variables */
00336 static const char * const cdr_readonly_vars[] = { "clid", "src", "dst", "dcontext", "channel", "dstchannel",
00337                     "lastapp", "lastdata", "start", "answer", "end", "duration",
00338                     "billsec", "disposition", "amaflags", "accountcode", "uniqueid", "linkedid",
00339                     "userfield", "sequence", NULL };
00340 /*! Set a CDR channel variable
00341    \note You can't set the CDR variables that belong to the actual CDR record, like "billsec".
00342 */
00343 int ast_cdr_setvar(struct ast_cdr *cdr, const char *name, const char *value, int recur)
00344 {
00345    struct ast_var_t *newvariable;
00346    struct varshead *headp;
00347    int x;
00348 
00349    for (x = 0; cdr_readonly_vars[x]; x++) {
00350       if (!strcasecmp(name, cdr_readonly_vars[x])) {
00351          ast_log(LOG_ERROR, "Attempt to set the '%s' read-only variable!.\n", name);
00352          return -1;
00353       }
00354    }
00355 
00356    if (!cdr) {
00357       ast_log(LOG_ERROR, "Attempt to set a variable on a nonexistent CDR record.\n");
00358       return -1;
00359    }
00360 
00361    for (; cdr; cdr = recur ? cdr->next : NULL) {
00362       if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
00363          continue;
00364       headp = &cdr->varshead;
00365       AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) {
00366          if (!strcasecmp(ast_var_name(newvariable), name)) {
00367             /* there is already such a variable, delete it */
00368             AST_LIST_REMOVE_CURRENT(entries);
00369             ast_var_delete(newvariable);
00370             break;
00371          }
00372       }
00373       AST_LIST_TRAVERSE_SAFE_END;
00374 
00375       if (value && (newvariable = ast_var_assign(name, value))) {
00376          AST_LIST_INSERT_HEAD(headp, newvariable, entries);
00377       }
00378    }
00379 
00380    return 0;
00381 }
00382 
00383 int ast_cdr_copy_vars(struct ast_cdr *to_cdr, struct ast_cdr *from_cdr)
00384 {
00385    struct ast_var_t *variables, *newvariable = NULL;
00386    struct varshead *headpa, *headpb;
00387    const char *var, *val;
00388    int x = 0;
00389 
00390    if (!to_cdr || !from_cdr) /* don't die if one of the pointers is null */
00391       return 0;
00392 
00393    headpa = &from_cdr->varshead;
00394    headpb = &to_cdr->varshead;
00395 
00396    AST_LIST_TRAVERSE(headpa,variables,entries) {
00397       if (variables &&
00398           (var = ast_var_name(variables)) && (val = ast_var_value(variables)) &&
00399           !ast_strlen_zero(var) && !ast_strlen_zero(val) &&
00400           (newvariable = ast_var_assign(var, val))) {
00401          AST_LIST_INSERT_HEAD(headpb, newvariable, entries);
00402          x++;
00403       }
00404    }
00405 
00406    return x;
00407 }
00408 
00409 int ast_cdr_serialize_variables(struct ast_cdr *cdr, struct ast_str **buf, char delim, char sep, int recur)
00410 {
00411    struct ast_var_t *variables;
00412    const char *var;
00413    char *tmp;
00414    char workspace[256];
00415    int total = 0, x = 0, i;
00416 
00417    ast_str_reset(*buf);
00418 
00419    for (; cdr; cdr = recur ? cdr->next : NULL) {
00420       if (++x > 1)
00421          ast_str_append(buf, 0, "\n");
00422 
00423       AST_LIST_TRAVERSE(&cdr->varshead, variables, entries) {
00424          if (!(var = ast_var_name(variables))) {
00425             continue;
00426          }
00427 
00428          if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, var, delim, S_OR(ast_var_value(variables), ""), sep) < 0) {
00429             ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
00430             break;
00431          }
00432 
00433          total++;
00434       }
00435 
00436       for (i = 0; cdr_readonly_vars[i]; i++) {
00437          workspace[0] = 0; /* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */
00438          ast_cdr_getvar(cdr, cdr_readonly_vars[i], &tmp, workspace, sizeof(workspace), 0, 0);
00439          if (!tmp)
00440             continue;
00441 
00442          if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, tmp, sep) < 0) {
00443             ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
00444             break;
00445          } else
00446             total++;
00447       }
00448    }
00449 
00450    return total;
00451 }
00452 
00453 
00454 void ast_cdr_free_vars(struct ast_cdr *cdr, int recur)
00455 {
00456 
00457    /* clear variables */
00458    for (; cdr; cdr = recur ? cdr->next : NULL) {
00459       struct ast_var_t *vardata;
00460       struct varshead *headp = &cdr->varshead;
00461       while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries)))
00462          ast_var_delete(vardata);
00463    }
00464 }
00465 
00466 /*! \brief  print a warning if cdr already posted */
00467 static void check_post(struct ast_cdr *cdr)
00468 {
00469    if (!cdr)
00470       return;
00471    if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED))
00472       ast_log(LOG_NOTICE, "CDR on channel '%s' already posted\n", S_OR(cdr->channel, "<unknown>"));
00473 }
00474 
00475 void ast_cdr_free(struct ast_cdr *cdr)
00476 {
00477 
00478    while (cdr) {
00479       struct ast_cdr *next = cdr->next;
00480 
00481       ast_cdr_free_vars(cdr, 0);
00482       ast_free(cdr);
00483       cdr = next;
00484    }
00485 }
00486 
00487 /*! \brief the same as a cdr_free call, only with no checks; just get rid of it */
00488 void ast_cdr_discard(struct ast_cdr *cdr)
00489 {
00490    while (cdr) {
00491       struct ast_cdr *next = cdr->next;
00492 
00493       ast_cdr_free_vars(cdr, 0);
00494       ast_free(cdr);
00495       cdr = next;
00496    }
00497 }
00498 
00499 struct ast_cdr *ast_cdr_alloc(void)
00500 {
00501    struct ast_cdr *x;
00502    x = ast_calloc(1, sizeof(*x));
00503    if (!x)
00504       ast_log(LOG_ERROR,"Allocation Failure for a CDR!\n");
00505    return x;
00506 }
00507 
00508 static void cdr_merge_vars(struct ast_cdr *to, struct ast_cdr *from)
00509 {
00510    struct ast_var_t *variablesfrom,*variablesto;
00511    struct varshead *headpfrom = &to->varshead;
00512    struct varshead *headpto = &from->varshead;
00513    AST_LIST_TRAVERSE_SAFE_BEGIN(headpfrom, variablesfrom, entries) {
00514       /* for every var in from, stick it in to */
00515       const char *fromvarname, *fromvarval;
00516       const char *tovarname = NULL, *tovarval = NULL;
00517       fromvarname = ast_var_name(variablesfrom);
00518       fromvarval = ast_var_value(variablesfrom);
00519       tovarname = 0;
00520 
00521       /* now, quick see if that var is in the 'to' cdr already */
00522       AST_LIST_TRAVERSE(headpto, variablesto, entries) {
00523 
00524          /* now, quick see if that var is in the 'to' cdr already */
00525          if ( strcasecmp(fromvarname, ast_var_name(variablesto)) == 0 ) {
00526             tovarname = ast_var_name(variablesto);
00527             tovarval = ast_var_value(variablesto);
00528             break;
00529          }
00530       }
00531       if (tovarname && strcasecmp(fromvarval,tovarval) != 0) {  /* this message here to see how irritating the userbase finds it */
00532          ast_log(LOG_NOTICE, "Merging CDR's: variable %s value %s dropped in favor of value %s\n", tovarname, fromvarval, tovarval);
00533          continue;
00534       } else if (tovarname && strcasecmp(fromvarval,tovarval) == 0) /* if they are the same, the job is done */
00535          continue;
00536 
00537       /* rip this var out of the from cdr, and stick it in the to cdr */
00538       AST_LIST_MOVE_CURRENT(headpto, entries);
00539    }
00540    AST_LIST_TRAVERSE_SAFE_END;
00541 }
00542 
00543 void ast_cdr_merge(struct ast_cdr *to, struct ast_cdr *from)
00544 {
00545    struct ast_cdr *zcdr;
00546    struct ast_cdr *lto = NULL;
00547    struct ast_cdr *lfrom = NULL;
00548    int discard_from = 0;
00549 
00550    if (!to || !from)
00551       return;
00552 
00553    /* don't merge into locked CDR's -- it's bad business */
00554    if (ast_test_flag(to, AST_CDR_FLAG_LOCKED)) {
00555       zcdr = to; /* safety valve? */
00556       while (to->next) {
00557          lto = to;
00558          to = to->next;
00559       }
00560 
00561       if (ast_test_flag(to, AST_CDR_FLAG_LOCKED)) {
00562          ast_log(LOG_WARNING, "Merging into locked CDR... no choice.\n");
00563          to = zcdr; /* safety-- if all there are is locked CDR's, then.... ?? */
00564          lto = NULL;
00565       }
00566    }
00567 
00568    if (ast_test_flag(from, AST_CDR_FLAG_LOCKED)) {
00569       struct ast_cdr *llfrom = NULL;
00570       discard_from = 1;
00571       if (lto) {
00572          /* insert the from stuff after lto */
00573          lto->next = from;
00574          lfrom = from;
00575          while (lfrom && lfrom->next) {
00576             if (!lfrom->next->next)
00577                llfrom = lfrom;
00578             lfrom = lfrom->next;
00579          }
00580          /* rip off the last entry and put a copy of the to at the end */
00581          if (llfrom) {
00582             llfrom->next = to;
00583          }
00584          from = lfrom;
00585       } else {
00586          /* save copy of the current *to cdr */
00587          struct ast_cdr tcdr;
00588          memcpy(&tcdr, to, sizeof(tcdr));
00589          /* copy in the locked from cdr */
00590          memcpy(to, from, sizeof(*to));
00591          lfrom = from;
00592          while (lfrom && lfrom->next) {
00593             if (!lfrom->next->next)
00594                llfrom = lfrom;
00595             lfrom = lfrom->next;
00596          }
00597          from->next = NULL;
00598          /* rip off the last entry and put a copy of the to at the end */
00599          if (llfrom == from) {
00600             to = to->next = ast_cdr_dup(&tcdr);
00601          } else if (llfrom) {
00602             to = llfrom->next = ast_cdr_dup(&tcdr);
00603          }
00604          from = lfrom;
00605       }
00606    }
00607 
00608    if (!ast_tvzero(from->start)) {
00609       if (!ast_tvzero(to->start)) {
00610          if (ast_tvcmp(to->start, from->start) > 0 ) {
00611             to->start = from->start; /* use the earliest time */
00612             from->start = ast_tv(0,0); /* we actively "steal" these values */
00613          }
00614          /* else nothing to do */
00615       } else {
00616          to->start = from->start;
00617          from->start = ast_tv(0,0); /* we actively "steal" these values */
00618       }
00619    }
00620    if (!ast_tvzero(from->answer)) {
00621       if (!ast_tvzero(to->answer)) {
00622          if (ast_tvcmp(to->answer, from->answer) > 0 ) {
00623             to->answer = from->answer; /* use the earliest time */
00624             from->answer = ast_tv(0,0); /* we actively "steal" these values */
00625          }
00626          /* we got the earliest answer time, so we'll settle for that? */
00627       } else {
00628          to->answer = from->answer;
00629          from->answer = ast_tv(0,0); /* we actively "steal" these values */
00630       }
00631    }
00632    if (!ast_tvzero(from->end)) {
00633       if (!ast_tvzero(to->end)) {
00634          if (ast_tvcmp(to->end, from->end) < 0 ) {
00635             to->end = from->end; /* use the latest time */
00636             from->end = ast_tv(0,0); /* we actively "steal" these values */
00637             to->duration = to->end.tv_sec - to->start.tv_sec;  /* don't forget to update the duration, billsec, when we set end */
00638             to->billsec = ast_tvzero(to->answer) ? 0 : to->end.tv_sec - to->answer.tv_sec;
00639          }
00640          /* else, nothing to do */
00641       } else {
00642          to->end = from->end;
00643          from->end = ast_tv(0,0); /* we actively "steal" these values */
00644          to->duration = to->end.tv_sec - to->start.tv_sec;
00645          to->billsec = ast_tvzero(to->answer) ? 0 : to->end.tv_sec - to->answer.tv_sec;
00646       }
00647    }
00648    if (to->disposition < from->disposition) {
00649       to->disposition = from->disposition;
00650       from->disposition = AST_CDR_NOANSWER;
00651    }
00652    if (ast_strlen_zero(to->lastapp) && !ast_strlen_zero(from->lastapp)) {
00653       ast_copy_string(to->lastapp, from->lastapp, sizeof(to->lastapp));
00654       from->lastapp[0] = 0; /* theft */
00655    }
00656    if (ast_strlen_zero(to->lastdata) && !ast_strlen_zero(from->lastdata)) {
00657       ast_copy_string(to->lastdata, from->lastdata, sizeof(to->lastdata));
00658       from->lastdata[0] = 0; /* theft */
00659    }
00660    if (ast_strlen_zero(to->dcontext) && !ast_strlen_zero(from->dcontext)) {
00661       ast_copy_string(to->dcontext, from->dcontext, sizeof(to->dcontext));
00662       from->dcontext[0] = 0; /* theft */
00663    }
00664    if (ast_strlen_zero(to->dstchannel) && !ast_strlen_zero(from->dstchannel)) {
00665       ast_copy_string(to->dstchannel, from->dstchannel, sizeof(to->dstchannel));
00666       from->dstchannel[0] = 0; /* theft */
00667    }
00668    if (!ast_strlen_zero(from->channel) && (ast_strlen_zero(to->channel) || !strncasecmp(from->channel, "Agent/", 6))) {
00669       ast_copy_string(to->channel, from->channel, sizeof(to->channel));
00670       from->channel[0] = 0; /* theft */
00671    }
00672    if (ast_strlen_zero(to->src) && !ast_strlen_zero(from->src)) {
00673       ast_copy_string(to->src, from->src, sizeof(to->src));
00674       from->src[0] = 0; /* theft */
00675    }
00676    if (ast_strlen_zero(to->clid) && !ast_strlen_zero(from->clid)) {
00677       ast_copy_string(to->clid, from->clid, sizeof(to->clid));
00678       from->clid[0] = 0; /* theft */
00679    }
00680    if (ast_strlen_zero(to->dst) && !ast_strlen_zero(from->dst)) {
00681       ast_copy_string(to->dst, from->dst, sizeof(to->dst));
00682       from->dst[0] = 0; /* theft */
00683    }
00684    if (!to->amaflags)
00685       to->amaflags = AST_CDR_DOCUMENTATION;
00686    if (!from->amaflags)
00687       from->amaflags = AST_CDR_DOCUMENTATION; /* make sure both amaflags are set to something (DOC is default) */
00688    if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (to->amaflags == AST_CDR_DOCUMENTATION && from->amaflags != AST_CDR_DOCUMENTATION)) {
00689       to->amaflags = from->amaflags;
00690    }
00691    if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->accountcode) && !ast_strlen_zero(from->accountcode))) {
00692       ast_copy_string(to->accountcode, from->accountcode, sizeof(to->accountcode));
00693    }
00694    if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->peeraccount) && !ast_strlen_zero(from->peeraccount))) {
00695       ast_copy_string(to->peeraccount, from->peeraccount, sizeof(to->peeraccount));
00696    }
00697    if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->userfield) && !ast_strlen_zero(from->userfield))) {
00698       ast_copy_string(to->userfield, from->userfield, sizeof(to->userfield));
00699    }
00700    /* flags, varsead, ? */
00701    cdr_merge_vars(from, to);
00702 
00703    if (ast_test_flag(from, AST_CDR_FLAG_KEEP_VARS))
00704       ast_set_flag(to, AST_CDR_FLAG_KEEP_VARS);
00705    if (ast_test_flag(from, AST_CDR_FLAG_POSTED))
00706       ast_set_flag(to, AST_CDR_FLAG_POSTED);
00707    if (ast_test_flag(from, AST_CDR_FLAG_LOCKED))
00708       ast_set_flag(to, AST_CDR_FLAG_LOCKED);
00709    if (ast_test_flag(from, AST_CDR_FLAG_CHILD))
00710       ast_set_flag(to, AST_CDR_FLAG_CHILD);
00711    if (ast_test_flag(from, AST_CDR_FLAG_POST_DISABLED))
00712       ast_set_flag(to, AST_CDR_FLAG_POST_DISABLED);
00713 
00714    /* last, but not least, we need to merge any forked CDRs to the 'to' cdr */
00715    while (from->next) {
00716       /* just rip 'em off the 'from' and insert them on the 'to' */
00717       zcdr = from->next;
00718       from->next = zcdr->next;
00719       zcdr->next = NULL;
00720       /* zcdr is now ripped from the current list; */
00721       ast_cdr_append(to, zcdr);
00722    }
00723    if (discard_from)
00724       ast_cdr_discard(from);
00725 }
00726 
00727 void ast_cdr_start(struct ast_cdr *cdr)
00728 {
00729    for (; cdr; cdr = cdr->next) {
00730       if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
00731          check_post(cdr);
00732          cdr->start = ast_tvnow();
00733       }
00734    }
00735 }
00736 
00737 void ast_cdr_answer(struct ast_cdr *cdr)
00738 {
00739 
00740    for (; cdr; cdr = cdr->next) {
00741       if (ast_test_flag(cdr, AST_CDR_FLAG_ANSLOCKED))
00742          continue;
00743       if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
00744          continue;
00745       check_post(cdr);
00746       if (cdr->disposition < AST_CDR_ANSWERED)
00747          cdr->disposition = AST_CDR_ANSWERED;
00748       if (ast_tvzero(cdr->answer))
00749          cdr->answer = ast_tvnow();
00750    }
00751 }
00752 
00753 void ast_cdr_busy(struct ast_cdr *cdr)
00754 {
00755 
00756    for (; cdr; cdr = cdr->next) {
00757       if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
00758          check_post(cdr);
00759          cdr->disposition = AST_CDR_BUSY;
00760       }
00761    }
00762 }
00763 
00764 void ast_cdr_failed(struct ast_cdr *cdr)
00765 {
00766    for (; cdr; cdr = cdr->next) {
00767       check_post(cdr);
00768       if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
00769          check_post(cdr);
00770          if (cdr->disposition < AST_CDR_FAILED)
00771             cdr->disposition = AST_CDR_FAILED;
00772       }
00773    }
00774 }
00775 
00776 void ast_cdr_noanswer(struct ast_cdr *cdr)
00777 {
00778    while (cdr) {
00779       if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
00780          check_post(cdr);
00781          cdr->disposition = AST_CDR_NOANSWER;
00782       }
00783       cdr = cdr->next;
00784    }
00785 }
00786 
00787 /* everywhere ast_cdr_disposition is called, it will call ast_cdr_failed()
00788    if ast_cdr_disposition returns a non-zero value */
00789 
00790 int ast_cdr_disposition(struct ast_cdr *cdr, int cause)
00791 {
00792    int res = 0;
00793 
00794    for (; cdr; cdr = cdr->next) {
00795       switch (cause) {  /* handle all the non failure, busy cases, return 0 not to set disposition,
00796                      return -1 to set disposition to FAILED */
00797       case AST_CAUSE_BUSY:
00798          ast_cdr_busy(cdr);
00799          break;
00800       case AST_CAUSE_NO_ANSWER:
00801          ast_cdr_noanswer(cdr);
00802          break;
00803       case AST_CAUSE_NORMAL:
00804          break;
00805       default:
00806          res = -1;
00807       }
00808    }
00809    return res;
00810 }
00811 
00812 void ast_cdr_setdestchan(struct ast_cdr *cdr, const char *chann)
00813 {
00814    for (; cdr; cdr = cdr->next) {
00815       if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
00816          check_post(cdr);
00817          ast_copy_string(cdr->dstchannel, chann, sizeof(cdr->dstchannel));
00818       }
00819    }
00820 }
00821 
00822 void ast_cdr_setapp(struct ast_cdr *cdr, const char *app, const char *data)
00823 {
00824 
00825    for (; cdr; cdr = cdr->next) {
00826       if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
00827          check_post(cdr);
00828          ast_copy_string(cdr->lastapp, S_OR(app, ""), sizeof(cdr->lastapp));
00829          ast_copy_string(cdr->lastdata, S_OR(data, ""), sizeof(cdr->lastdata));
00830       }
00831    }
00832 }
00833 
00834 void ast_cdr_setanswer(struct ast_cdr *cdr, struct timeval t)
00835 {
00836 
00837    for (; cdr; cdr = cdr->next) {
00838       if (ast_test_flag(cdr, AST_CDR_FLAG_ANSLOCKED))
00839          continue;
00840       if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
00841          continue;
00842       check_post(cdr);
00843       cdr->answer = t;
00844    }
00845 }
00846 
00847 void ast_cdr_setdisposition(struct ast_cdr *cdr, long int disposition)
00848 {
00849 
00850    for (; cdr; cdr = cdr->next) {
00851       if (ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
00852          continue;
00853       check_post(cdr);
00854       cdr->disposition = disposition;
00855    }
00856 }
00857 
00858 /* set cid info for one record */
00859 static void set_one_cid(struct ast_cdr *cdr, struct ast_channel *c)
00860 {
00861    const char *num;
00862 
00863    if (!cdr) {
00864       return;
00865    }
00866 
00867    /* Grab source from ANI or normal Caller*ID */
00868    num = S_COR(c->caller.ani.number.valid, c->caller.ani.number.str,
00869       S_COR(c->caller.id.number.valid, c->caller.id.number.str, NULL));
00870    ast_callerid_merge(cdr->clid, sizeof(cdr->clid),
00871       S_COR(c->caller.id.name.valid, c->caller.id.name.str, NULL), num, "");
00872    ast_copy_string(cdr->src, S_OR(num, ""), sizeof(cdr->src));
00873    ast_cdr_setvar(cdr, "dnid", S_OR(c->dialed.number.str, ""), 0);
00874 
00875    if (c->caller.id.subaddress.valid) {
00876       ast_cdr_setvar(cdr, "callingsubaddr", S_OR(c->caller.id.subaddress.str, ""), 0);
00877    }
00878    if (c->dialed.subaddress.valid) {
00879       ast_cdr_setvar(cdr, "calledsubaddr", S_OR(c->dialed.subaddress.str, ""), 0);
00880    }
00881 }
00882 
00883 int ast_cdr_setcid(struct ast_cdr *cdr, struct ast_channel *c)
00884 {
00885    for (; cdr; cdr = cdr->next) {
00886       if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
00887          set_one_cid(cdr, c);
00888    }
00889    return 0;
00890 }
00891 
00892 static int cdr_seq_inc(struct ast_cdr *cdr)
00893 {
00894    return (cdr->sequence = ast_atomic_fetchadd_int(&cdr_sequence, +1));
00895 }
00896 
00897 int ast_cdr_init(struct ast_cdr *cdr, struct ast_channel *c)
00898 {
00899    for ( ; cdr ; cdr = cdr->next) {
00900       if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
00901          ast_copy_string(cdr->channel, c->name, sizeof(cdr->channel));
00902          set_one_cid(cdr, c);
00903          cdr_seq_inc(cdr);
00904 
00905          cdr->disposition = (c->_state == AST_STATE_UP) ?  AST_CDR_ANSWERED : AST_CDR_NOANSWER;
00906          cdr->amaflags = c->amaflags ? c->amaflags :  ast_default_amaflags;
00907          ast_copy_string(cdr->accountcode, c->accountcode, sizeof(cdr->accountcode));
00908          ast_copy_string(cdr->peeraccount, c->peeraccount, sizeof(cdr->peeraccount));
00909          /* Destination information */
00910          ast_copy_string(cdr->dst, S_OR(c->macroexten,c->exten), sizeof(cdr->dst));
00911          ast_copy_string(cdr->dcontext, S_OR(c->macrocontext,c->context), sizeof(cdr->dcontext));
00912          /* Unique call identifier */
00913          ast_copy_string(cdr->uniqueid, c->uniqueid, sizeof(cdr->uniqueid));
00914          /* Linked call identifier */
00915          ast_copy_string(cdr->linkedid, c->linkedid, sizeof(cdr->linkedid));
00916       }
00917    }
00918    return 0;
00919 }
00920 
00921 /* Three routines were "fixed" via 10668, and later shown that
00922    users were depending on this behavior. ast_cdr_end,
00923    ast_cdr_setvar and ast_cdr_answer are the three routines.
00924    While most of the other routines would not touch
00925    LOCKED cdr's, these three routines were designed to
00926    operate on locked CDR's as a matter of course.
00927    I now appreciate how this plays with the ForkCDR app,
00928    which forms these cdr chains in the first place.
00929    cdr_end is pretty key: all cdrs created are closed
00930    together. They only vary by start time. Arithmetically,
00931    users can calculate the subintervals they wish to track. */
00932 
00933 void ast_cdr_end(struct ast_cdr *cdr)
00934 {
00935    for ( ; cdr ; cdr = cdr->next) {
00936       if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
00937          continue;
00938       check_post(cdr);
00939       if (ast_tvzero(cdr->end))
00940          cdr->end = ast_tvnow();
00941       if (ast_tvzero(cdr->start)) {
00942          ast_log(LOG_WARNING, "CDR on channel '%s' has not started\n", S_OR(cdr->channel, "<unknown>"));
00943          cdr->disposition = AST_CDR_FAILED;
00944       } else
00945          cdr->duration = cdr->end.tv_sec - cdr->start.tv_sec;
00946       if (ast_tvzero(cdr->answer)) {
00947          if (cdr->disposition == AST_CDR_ANSWERED) {
00948             ast_log(LOG_WARNING, "CDR on channel '%s' has no answer time but is 'ANSWERED'\n", S_OR(cdr->channel, "<unknown>"));
00949             cdr->disposition = AST_CDR_FAILED;
00950          }
00951       } else {
00952          cdr->billsec = cdr->end.tv_sec - cdr->answer.tv_sec;
00953          if (ast_test_flag(&ast_options, AST_OPT_FLAG_INITIATED_SECONDS))
00954             cdr->billsec += cdr->end.tv_usec > cdr->answer.tv_usec ? 1 : 0;
00955       }
00956    }
00957 }
00958 
00959 char *ast_cdr_disp2str(int disposition)
00960 {
00961    switch (disposition) {
00962    case AST_CDR_NULL:
00963       return "NO ANSWER"; /* by default, for backward compatibility */
00964    case AST_CDR_NOANSWER:
00965       return "NO ANSWER";
00966    case AST_CDR_FAILED:
00967       return "FAILED";
00968    case AST_CDR_BUSY:
00969       return "BUSY";
00970    case AST_CDR_ANSWERED:
00971       return "ANSWERED";
00972    }
00973    return "UNKNOWN";
00974 }
00975 
00976 /*! Converts AMA flag to printable string */
00977 char *ast_cdr_flags2str(int flag)
00978 {
00979    switch (flag) {
00980    case AST_CDR_OMIT:
00981       return "OMIT";
00982    case AST_CDR_BILLING:
00983       return "BILLING";
00984    case AST_CDR_DOCUMENTATION:
00985       return "DOCUMENTATION";
00986    }
00987    return "Unknown";
00988 }
00989 
00990 int ast_cdr_setaccount(struct ast_channel *chan, const char *account)
00991 {
00992    struct ast_cdr *cdr = chan->cdr;
00993    const char *old_acct = "";
00994 
00995    if (!ast_strlen_zero(chan->accountcode)) {
00996       old_acct = ast_strdupa(chan->accountcode);
00997    }
00998 
00999    ast_string_field_set(chan, accountcode, account);
01000    for ( ; cdr ; cdr = cdr->next) {
01001       if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
01002          ast_copy_string(cdr->accountcode, chan->accountcode, sizeof(cdr->accountcode));
01003       }
01004    }
01005 
01006    ast_manager_event(chan, EVENT_FLAG_CALL, "NewAccountCode",
01007          "Channel: %s\r\n"
01008          "Uniqueid: %s\r\n"
01009          "AccountCode: %s\r\n"
01010          "OldAccountCode: %s\r\n",
01011          chan->name, chan->uniqueid, chan->accountcode, old_acct);
01012 
01013    return 0;
01014 }
01015 
01016 int ast_cdr_setpeeraccount(struct ast_channel *chan, const char *account)
01017 {
01018    struct ast_cdr *cdr = chan->cdr;
01019    const char *old_acct = "";
01020 
01021    if (!ast_strlen_zero(chan->peeraccount)) {
01022       old_acct = ast_strdupa(chan->peeraccount);
01023    }
01024 
01025    ast_string_field_set(chan, peeraccount, account);
01026    for ( ; cdr ; cdr = cdr->next) {
01027       if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
01028          ast_copy_string(cdr->peeraccount, chan->peeraccount, sizeof(cdr->peeraccount));
01029       }
01030    }
01031 
01032    ast_manager_event(chan, EVENT_FLAG_CALL, "NewPeerAccount",
01033          "Channel: %s\r\n"
01034          "Uniqueid: %s\r\n"
01035          "PeerAccount: %s\r\n"
01036          "OldPeerAccount: %s\r\n",
01037          chan->name, chan->uniqueid, chan->peeraccount, old_acct);
01038 
01039    return 0;
01040 }
01041 
01042 int ast_cdr_setamaflags(struct ast_channel *chan, const char *flag)
01043 {
01044    struct ast_cdr *cdr;
01045    int newflag = ast_cdr_amaflags2int(flag);
01046    if (newflag) {
01047       for (cdr = chan->cdr; cdr; cdr = cdr->next) {
01048          if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
01049             cdr->amaflags = newflag;
01050          }
01051       }
01052    }
01053 
01054    return 0;
01055 }
01056 
01057 int ast_cdr_setuserfield(struct ast_channel *chan, const char *userfield)
01058 {
01059    struct ast_cdr *cdr = chan->cdr;
01060 
01061    for ( ; cdr ; cdr = cdr->next) {
01062       if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
01063          ast_copy_string(cdr->userfield, userfield, sizeof(cdr->userfield));
01064    }
01065 
01066    return 0;
01067 }
01068 
01069 int ast_cdr_appenduserfield(struct ast_channel *chan, const char *userfield)
01070 {
01071    struct ast_cdr *cdr = chan->cdr;
01072 
01073    for ( ; cdr ; cdr = cdr->next) {
01074       int len = strlen(cdr->userfield);
01075 
01076       if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
01077          ast_copy_string(cdr->userfield + len, userfield, sizeof(cdr->userfield) - len);
01078    }
01079 
01080    return 0;
01081 }
01082 
01083 int ast_cdr_update(struct ast_channel *c)
01084 {
01085    struct ast_cdr *cdr = c->cdr;
01086 
01087    for ( ; cdr ; cdr = cdr->next) {
01088       if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
01089          set_one_cid(cdr, c);
01090 
01091          /* Copy account code et-al */
01092          ast_copy_string(cdr->accountcode, c->accountcode, sizeof(cdr->accountcode));
01093          ast_copy_string(cdr->peeraccount, c->peeraccount, sizeof(cdr->peeraccount));
01094          ast_copy_string(cdr->linkedid, c->linkedid, sizeof(cdr->linkedid));
01095 
01096          /* Destination information */ /* XXX privilege macro* ? */
01097          ast_copy_string(cdr->dst, S_OR(c->macroexten, c->exten), sizeof(cdr->dst));
01098          ast_copy_string(cdr->dcontext, S_OR(c->macrocontext, c->context), sizeof(cdr->dcontext));
01099       }
01100    }
01101 
01102    return 0;
01103 }
01104 
01105 int ast_cdr_amaflags2int(const char *flag)
01106 {
01107    if (!strcasecmp(flag, "default"))
01108       return 0;
01109    if (!strcasecmp(flag, "omit"))
01110       return AST_CDR_OMIT;
01111    if (!strcasecmp(flag, "billing"))
01112       return AST_CDR_BILLING;
01113    if (!strcasecmp(flag, "documentation"))
01114       return AST_CDR_DOCUMENTATION;
01115    return -1;
01116 }
01117 
01118 static void post_cdr(struct ast_cdr *cdr)
01119 {
01120    struct ast_cdr_beitem *i;
01121 
01122    for ( ; cdr ; cdr = cdr->next) {
01123       if (!unanswered && cdr->disposition < AST_CDR_ANSWERED && (ast_strlen_zero(cdr->channel) || ast_strlen_zero(cdr->dstchannel))) {
01124          /* For people, who don't want to see unanswered single-channel events */
01125          ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
01126          continue;
01127       }
01128 
01129       /* don't post CDRs that are for dialed channels unless those
01130        * channels were originated from asterisk (pbx_spool, manager,
01131        * cli) */
01132       if (ast_test_flag(cdr, AST_CDR_FLAG_DIALED) && !ast_test_flag(cdr, AST_CDR_FLAG_ORIGINATED)) {
01133          ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
01134          continue;
01135       }
01136 
01137       check_post(cdr);
01138       ast_set_flag(cdr, AST_CDR_FLAG_POSTED);
01139       if (ast_test_flag(cdr, AST_CDR_FLAG_POST_DISABLED))
01140          continue;
01141       AST_RWLIST_RDLOCK(&be_list);
01142       AST_RWLIST_TRAVERSE(&be_list, i, list) {
01143          i->be(cdr);
01144       }
01145       AST_RWLIST_UNLOCK(&be_list);
01146    }
01147 }
01148 
01149 void ast_cdr_reset(struct ast_cdr *cdr, struct ast_flags *_flags)
01150 {
01151    struct ast_cdr *duplicate;
01152    struct ast_flags flags = { 0 };
01153 
01154    if (_flags)
01155       ast_copy_flags(&flags, _flags, AST_FLAGS_ALL);
01156 
01157    for ( ; cdr ; cdr = cdr->next) {
01158       /* Detach if post is requested */
01159       if (ast_test_flag(&flags, AST_CDR_FLAG_LOCKED) || !ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
01160          if (ast_test_flag(&flags, AST_CDR_FLAG_POSTED)) {
01161             ast_cdr_end(cdr);
01162             if ((duplicate = ast_cdr_dup_unique_swap(cdr))) {
01163                ast_cdr_detach(duplicate);
01164             }
01165             ast_set_flag(cdr, AST_CDR_FLAG_POSTED);
01166          }
01167 
01168          /* enable CDR only */
01169          if (ast_test_flag(&flags, AST_CDR_FLAG_POST_ENABLE)) {
01170             ast_clear_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
01171             continue;
01172          }
01173 
01174          /* clear variables */
01175          if (!ast_test_flag(&flags, AST_CDR_FLAG_KEEP_VARS)) {
01176             ast_cdr_free_vars(cdr, 0);
01177          }
01178 
01179          /* Reset to initial state */
01180          ast_clear_flag(cdr, AST_FLAGS_ALL);
01181          memset(&cdr->start, 0, sizeof(cdr->start));
01182          memset(&cdr->end, 0, sizeof(cdr->end));
01183          memset(&cdr->answer, 0, sizeof(cdr->answer));
01184          cdr->billsec = 0;
01185          cdr->duration = 0;
01186          ast_cdr_start(cdr);
01187          cdr->disposition = AST_CDR_NOANSWER;
01188       }
01189    }
01190 }
01191 
01192 void ast_cdr_specialized_reset(struct ast_cdr *cdr, struct ast_flags *_flags)
01193 {
01194    struct ast_flags flags = { 0 };
01195 
01196    if (_flags)
01197       ast_copy_flags(&flags, _flags, AST_FLAGS_ALL);
01198 
01199    /* Reset to initial state */
01200    if (ast_test_flag(cdr, AST_CDR_FLAG_POST_DISABLED)) { /* But do NOT lose the NoCDR() setting */
01201       ast_clear_flag(cdr, AST_FLAGS_ALL);
01202       ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
01203    } else {
01204       ast_clear_flag(cdr, AST_FLAGS_ALL);
01205    }
01206 
01207    memset(&cdr->start, 0, sizeof(cdr->start));
01208    memset(&cdr->end, 0, sizeof(cdr->end));
01209    memset(&cdr->answer, 0, sizeof(cdr->answer));
01210    cdr->billsec = 0;
01211    cdr->duration = 0;
01212    ast_cdr_start(cdr);
01213    cdr->disposition = AST_CDR_NULL;
01214 }
01215 
01216 struct ast_cdr *ast_cdr_append(struct ast_cdr *cdr, struct ast_cdr *newcdr)
01217 {
01218    struct ast_cdr *ret;
01219 
01220    if (cdr) {
01221       ret = cdr;
01222 
01223       while (cdr->next)
01224          cdr = cdr->next;
01225       cdr->next = newcdr;
01226    } else {
01227       ret = newcdr;
01228    }
01229 
01230    return ret;
01231 }
01232 
01233 /*! \note Don't call without cdr_batch_lock */
01234 static void reset_batch(void)
01235 {
01236    batch->size = 0;
01237    batch->head = NULL;
01238    batch->tail = NULL;
01239 }
01240 
01241 /*! \note Don't call without cdr_batch_lock */
01242 static int init_batch(void)
01243 {
01244    /* This is the single meta-batch used to keep track of all CDRs during the entire life of the program */
01245    if (!(batch = ast_malloc(sizeof(*batch))))
01246       return -1;
01247 
01248    reset_batch();
01249 
01250    return 0;
01251 }
01252 
01253 static void *do_batch_backend_process(void *data)
01254 {
01255    struct ast_cdr_batch_item *processeditem;
01256    struct ast_cdr_batch_item *batchitem = data;
01257 
01258    /* Push each CDR into storage mechanism(s) and free all the memory */
01259    while (batchitem) {
01260       post_cdr(batchitem->cdr);
01261       ast_cdr_free(batchitem->cdr);
01262       processeditem = batchitem;
01263       batchitem = batchitem->next;
01264       ast_free(processeditem);
01265    }
01266 
01267    return NULL;
01268 }
01269 
01270 void ast_cdr_submit_batch(int do_shutdown)
01271 {
01272    struct ast_cdr_batch_item *oldbatchitems = NULL;
01273    pthread_t batch_post_thread = AST_PTHREADT_NULL;
01274 
01275    /* if there's no batch, or no CDRs in the batch, then there's nothing to do */
01276    if (!batch || !batch->head)
01277       return;
01278 
01279    /* move the old CDRs aside, and prepare a new CDR batch */
01280    ast_mutex_lock(&cdr_batch_lock);
01281    oldbatchitems = batch->head;
01282    reset_batch();
01283    ast_mutex_unlock(&cdr_batch_lock);
01284 
01285    /* if configured, spawn a new thread to post these CDRs,
01286       also try to save as much as possible if we are shutting down safely */
01287    if (batchscheduleronly || do_shutdown) {
01288       ast_debug(1, "CDR single-threaded batch processing begins now\n");
01289       do_batch_backend_process(oldbatchitems);
01290    } else {
01291       if (ast_pthread_create_detached_background(&batch_post_thread, NULL, do_batch_backend_process, oldbatchitems)) {
01292          ast_log(LOG_WARNING, "CDR processing thread could not detach, now trying in this thread\n");
01293          do_batch_backend_process(oldbatchitems);
01294       } else {
01295          ast_debug(1, "CDR multi-threaded batch processing begins now\n");
01296       }
01297    }
01298 }
01299 
01300 static int submit_scheduled_batch(const void *data)
01301 {
01302    ast_cdr_submit_batch(0);
01303    /* manually reschedule from this point in time */
01304    ast_mutex_lock(&cdr_sched_lock);
01305    cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL);
01306    ast_mutex_unlock(&cdr_sched_lock);
01307    /* returning zero so the scheduler does not automatically reschedule */
01308    return 0;
01309 }
01310 
01311 /*! Do not hold the batch lock while calling this function */
01312 static void submit_unscheduled_batch(void)
01313 {
01314    /* Prevent two deletes from happening at the same time */
01315    ast_mutex_lock(&cdr_sched_lock);
01316    /* this is okay since we are not being called from within the scheduler */
01317    AST_SCHED_DEL(sched, cdr_sched);
01318    /* schedule the submission to occur ASAP (1 ms) */
01319    cdr_sched = ast_sched_add(sched, 1, submit_scheduled_batch, NULL);
01320    ast_mutex_unlock(&cdr_sched_lock);
01321 
01322    /* signal the do_cdr thread to wakeup early and do some work (that lazy thread ;) */
01323    ast_mutex_lock(&cdr_pending_lock);
01324    ast_cond_signal(&cdr_pending_cond);
01325    ast_mutex_unlock(&cdr_pending_lock);
01326 }
01327 
01328 void ast_cdr_detach(struct ast_cdr *cdr)
01329 {
01330    struct ast_cdr_batch_item *newtail;
01331    int curr;
01332    int submit_batch = 0;
01333 
01334    if (!cdr)
01335       return;
01336 
01337    /* maybe they disabled CDR stuff completely, so just drop it */
01338    if (!enabled) {
01339       ast_debug(1, "Dropping CDR !\n");
01340       ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
01341       ast_cdr_free(cdr);
01342       return;
01343    }
01344 
01345    /* post stuff immediately if we are not in batch mode, this is legacy behaviour */
01346    if (!batchmode) {
01347       post_cdr(cdr);
01348       ast_cdr_free(cdr);
01349       return;
01350    }
01351 
01352    /* otherwise, each CDR gets put into a batch list (at the end) */
01353    ast_debug(1, "CDR detaching from this thread\n");
01354 
01355    /* we'll need a new tail for every CDR */
01356    if (!(newtail = ast_calloc(1, sizeof(*newtail)))) {
01357       post_cdr(cdr);
01358       ast_cdr_free(cdr);
01359       return;
01360    }
01361 
01362    /* don't traverse a whole list (just keep track of the tail) */
01363    ast_mutex_lock(&cdr_batch_lock);
01364    if (!batch)
01365       init_batch();
01366    if (!batch->head) {
01367       /* new batch is empty, so point the head at the new tail */
01368       batch->head = newtail;
01369    } else {
01370       /* already got a batch with something in it, so just append a new tail */
01371       batch->tail->next = newtail;
01372    }
01373    newtail->cdr = cdr;
01374    batch->tail = newtail;
01375    curr = batch->size++;
01376 
01377    /* if we have enough stuff to post, then do it */
01378    if (curr >= (batchsize - 1)) {
01379       submit_batch = 1;
01380    }
01381    ast_mutex_unlock(&cdr_batch_lock);
01382 
01383    /* Don't call submit_unscheduled_batch with the cdr_batch_lock held */
01384    if (submit_batch) {
01385       submit_unscheduled_batch();
01386    }
01387 }
01388 
01389 static void *do_cdr(void *data)
01390 {
01391    struct timespec timeout;
01392    int schedms;
01393    int numevents = 0;
01394 
01395    for (;;) {
01396       struct timeval now;
01397       schedms = ast_sched_wait(sched);
01398       /* this shouldn't happen, but provide a 1 second default just in case */
01399       if (schedms <= 0)
01400          schedms = 1000;
01401       now = ast_tvadd(ast_tvnow(), ast_samp2tv(schedms, 1000));
01402       timeout.tv_sec = now.tv_sec;
01403       timeout.tv_nsec = now.tv_usec * 1000;
01404       /* prevent stuff from clobbering cdr_pending_cond, then wait on signals sent to it until the timeout expires */
01405       ast_mutex_lock(&cdr_pending_lock);
01406       ast_cond_timedwait(&cdr_pending_cond, &cdr_pending_lock, &timeout);
01407       numevents = ast_sched_runq(sched);
01408       ast_mutex_unlock(&cdr_pending_lock);
01409       ast_debug(2, "Processed %d scheduled CDR batches from the run queue\n", numevents);
01410    }
01411 
01412    return NULL;
01413 }
01414 
01415 static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01416 {
01417    struct ast_cdr_beitem *beitem=NULL;
01418    int cnt=0;
01419    long nextbatchtime=0;
01420 
01421    switch (cmd) {
01422    case CLI_INIT:
01423       e->command = "cdr show status";
01424       e->usage =
01425          "Usage: cdr show status\n"
01426          "  Displays the Call Detail Record engine system status.\n";
01427       return NULL;
01428    case CLI_GENERATE:
01429       return NULL;
01430    }
01431 
01432    if (a->argc > 3)
01433       return CLI_SHOWUSAGE;
01434 
01435    ast_cli(a->fd, "\n");
01436    ast_cli(a->fd, "Call Detail Record (CDR) settings\n");
01437    ast_cli(a->fd, "----------------------------------\n");
01438    ast_cli(a->fd, "  Logging:                    %s\n", enabled ? "Enabled" : "Disabled");
01439    ast_cli(a->fd, "  Mode:                       %s\n", batchmode ? "Batch" : "Simple");
01440    if (enabled) {
01441       ast_cli(a->fd, "  Log unanswered calls:       %s\n\n", unanswered ? "Yes" : "No");
01442       if (batchmode) {
01443          ast_cli(a->fd, "* Batch Mode Settings\n");
01444          ast_cli(a->fd, "  -------------------\n");
01445          if (batch)
01446             cnt = batch->size;
01447          if (cdr_sched > -1)
01448             nextbatchtime = ast_sched_when(sched, cdr_sched);
01449          ast_cli(a->fd, "  Safe shutdown:              %s\n", batchsafeshutdown ? "Enabled" : "Disabled");
01450          ast_cli(a->fd, "  Threading model:            %s\n", batchscheduleronly ? "Scheduler only" : "Scheduler plus separate threads");
01451          ast_cli(a->fd, "  Current batch size:         %d record%s\n", cnt, ESS(cnt));
01452          ast_cli(a->fd, "  Maximum batch size:         %d record%s\n", batchsize, ESS(batchsize));
01453          ast_cli(a->fd, "  Maximum batch time:         %d second%s\n", batchtime, ESS(batchtime));
01454          ast_cli(a->fd, "  Next batch processing time: %ld second%s\n\n", nextbatchtime, ESS(nextbatchtime));
01455       }
01456       ast_cli(a->fd, "* Registered Backends\n");
01457       ast_cli(a->fd, "  -------------------\n");
01458       AST_RWLIST_RDLOCK(&be_list);
01459       if (AST_RWLIST_EMPTY(&be_list)) {
01460          ast_cli(a->fd, "    (none)\n");
01461       } else {
01462          AST_RWLIST_TRAVERSE(&be_list, beitem, list) {
01463             ast_cli(a->fd, "    %s\n", beitem->name);
01464          }
01465       }
01466       AST_RWLIST_UNLOCK(&be_list);
01467       ast_cli(a->fd, "\n");
01468    }
01469 
01470    return CLI_SUCCESS;
01471 }
01472 
01473 static char *handle_cli_submit(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01474 {
01475    switch (cmd) {
01476    case CLI_INIT:
01477       e->command = "cdr submit";
01478       e->usage =
01479          "Usage: cdr submit\n"
01480          "       Posts all pending batched CDR data to the configured CDR backend engine modules.\n";
01481       return NULL;
01482    case CLI_GENERATE:
01483       return NULL;
01484    }
01485    if (a->argc > 2)
01486       return CLI_SHOWUSAGE;
01487 
01488    submit_unscheduled_batch();
01489    ast_cli(a->fd, "Submitted CDRs to backend engines for processing.  This may take a while.\n");
01490 
01491    return CLI_SUCCESS;
01492 }
01493 
01494 static struct ast_cli_entry cli_submit = AST_CLI_DEFINE(handle_cli_submit, "Posts all pending batched CDR data");
01495 static struct ast_cli_entry cli_status = AST_CLI_DEFINE(handle_cli_status, "Display the CDR status");
01496 
01497 static void do_reload(int reload)
01498 {
01499    struct ast_config *config;
01500    const char *enabled_value;
01501    const char *unanswered_value;
01502    const char *batched_value;
01503    const char *scheduleronly_value;
01504    const char *batchsafeshutdown_value;
01505    const char *size_value;
01506    const char *time_value;
01507    const char *end_before_h_value;
01508    const char *initiatedseconds_value;
01509    int cfg_size;
01510    int cfg_time;
01511    int was_enabled;
01512    int was_batchmode;
01513    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
01514 
01515    if ((config = ast_config_load2("cdr.conf", "cdr", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
01516       return;
01517    }
01518 
01519    ast_mutex_lock(&cdr_batch_lock);
01520 
01521    was_enabled = enabled;
01522    was_batchmode = batchmode;
01523 
01524    batchsize = BATCH_SIZE_DEFAULT;
01525    batchtime = BATCH_TIME_DEFAULT;
01526    batchscheduleronly = BATCH_SCHEDULER_ONLY_DEFAULT;
01527    batchsafeshutdown = BATCH_SAFE_SHUTDOWN_DEFAULT;
01528    enabled = ENABLED_DEFAULT;
01529    batchmode = BATCHMODE_DEFAULT;
01530    unanswered = UNANSWERED_DEFAULT;
01531 
01532    if (config == CONFIG_STATUS_FILEMISSING || config == CONFIG_STATUS_FILEINVALID) {
01533       ast_mutex_unlock(&cdr_batch_lock);
01534       return;
01535    }
01536 
01537    /* don't run the next scheduled CDR posting while reloading */
01538    ast_mutex_lock(&cdr_sched_lock);
01539    AST_SCHED_DEL(sched, cdr_sched);
01540    ast_mutex_unlock(&cdr_sched_lock);
01541 
01542    if (config) {
01543       if ((enabled_value = ast_variable_retrieve(config, "general", "enable"))) {
01544          enabled = ast_true(enabled_value);
01545       }
01546       if ((unanswered_value = ast_variable_retrieve(config, "general", "unanswered"))) {
01547          unanswered = ast_true(unanswered_value);
01548       }
01549       if ((batched_value = ast_variable_retrieve(config, "general", "batch"))) {
01550          batchmode = ast_true(batched_value);
01551       }
01552       if ((scheduleronly_value = ast_variable_retrieve(config, "general", "scheduleronly"))) {
01553          batchscheduleronly = ast_true(scheduleronly_value);
01554       }
01555       if ((batchsafeshutdown_value = ast_variable_retrieve(config, "general", "safeshutdown"))) {
01556          batchsafeshutdown = ast_true(batchsafeshutdown_value);
01557       }
01558       if ((size_value = ast_variable_retrieve(config, "general", "size"))) {
01559          if (sscanf(size_value, "%30d", &cfg_size) < 1)
01560             ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", size_value);
01561          else if (cfg_size < 0)
01562             ast_log(LOG_WARNING, "Invalid maximum batch size '%d' specified, using default\n", cfg_size);
01563          else
01564             batchsize = cfg_size;
01565       }
01566       if ((time_value = ast_variable_retrieve(config, "general", "time"))) {
01567          if (sscanf(time_value, "%30d", &cfg_time) < 1)
01568             ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", time_value);
01569          else if (cfg_time < 0)
01570             ast_log(LOG_WARNING, "Invalid maximum batch time '%d' specified, using default\n", cfg_time);
01571          else
01572             batchtime = cfg_time;
01573       }
01574       if ((end_before_h_value = ast_variable_retrieve(config, "general", "endbeforehexten")))
01575          ast_set2_flag(&ast_options, ast_true(end_before_h_value), AST_OPT_FLAG_END_CDR_BEFORE_H_EXTEN);
01576       if ((initiatedseconds_value = ast_variable_retrieve(config, "general", "initiatedseconds")))
01577          ast_set2_flag(&ast_options, ast_true(initiatedseconds_value), AST_OPT_FLAG_INITIATED_SECONDS);
01578    }
01579 
01580    if (enabled && !batchmode) {
01581       ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
01582    } else if (enabled && batchmode) {
01583       ast_mutex_lock(&cdr_sched_lock);
01584       cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL);
01585       ast_mutex_unlock(&cdr_sched_lock);
01586       ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n", batchsize, batchtime);
01587    } else {
01588       ast_log(LOG_NOTICE, "CDR logging disabled, data will be lost.\n");
01589    }
01590 
01591    /* if this reload enabled the CDR batch mode, create the background thread
01592       if it does not exist */
01593    if (enabled && batchmode && (!was_enabled || !was_batchmode) && (cdr_thread == AST_PTHREADT_NULL)) {
01594       ast_cond_init(&cdr_pending_cond, NULL);
01595       if (ast_pthread_create_background(&cdr_thread, NULL, do_cdr, NULL) < 0) {
01596          ast_log(LOG_ERROR, "Unable to start CDR thread.\n");
01597          ast_mutex_lock(&cdr_sched_lock);
01598          AST_SCHED_DEL(sched, cdr_sched);
01599          ast_mutex_unlock(&cdr_sched_lock);
01600       } else {
01601          ast_cli_register(&cli_submit);
01602          ast_register_atexit(ast_cdr_engine_term);
01603       }
01604    /* if this reload disabled the CDR and/or batch mode and there is a background thread,
01605       kill it */
01606    } else if (((!enabled && was_enabled) || (!batchmode && was_batchmode)) && (cdr_thread != AST_PTHREADT_NULL)) {
01607       /* wake up the thread so it will exit */
01608       pthread_cancel(cdr_thread);
01609       pthread_kill(cdr_thread, SIGURG);
01610       pthread_join(cdr_thread, NULL);
01611       cdr_thread = AST_PTHREADT_NULL;
01612       ast_cond_destroy(&cdr_pending_cond);
01613       ast_cli_unregister(&cli_submit);
01614       ast_unregister_atexit(ast_cdr_engine_term);
01615       /* if leaving batch mode, then post the CDRs in the batch,
01616          and don't reschedule, since we are stopping CDR logging */
01617       if (!batchmode && was_batchmode) {
01618          ast_cdr_engine_term();
01619       }
01620    }
01621 
01622    ast_mutex_unlock(&cdr_batch_lock);
01623    ast_config_destroy(config);
01624    manager_event(EVENT_FLAG_SYSTEM, "Reload", "Module: CDR\r\nMessage: CDR subsystem reload requested\r\n");
01625 }
01626 
01627 static void cdr_engine_shutdown(void)
01628 {
01629    if (cdr_thread != AST_PTHREADT_NULL) {
01630       /* wake up the thread so it will exit */
01631       pthread_cancel(cdr_thread);
01632       pthread_kill(cdr_thread, SIGURG);
01633       pthread_join(cdr_thread, NULL);
01634       cdr_thread = AST_PTHREADT_NULL;
01635       ast_cond_destroy(&cdr_pending_cond);
01636    }
01637    ast_cli_unregister(&cli_submit);
01638 
01639    ast_cli_unregister(&cli_status);
01640    sched_context_destroy(sched);
01641    sched = NULL;
01642    ast_free(batch);
01643    batch = NULL;
01644 }
01645 
01646 int ast_cdr_engine_init(void)
01647 {
01648    sched = sched_context_create();
01649    if (!sched) {
01650       ast_log(LOG_ERROR, "Unable to create schedule context.\n");
01651       return -1;
01652    }
01653 
01654    ast_cli_register(&cli_status);
01655    do_reload(0);
01656    ast_register_atexit(cdr_engine_shutdown);
01657 
01658    return 0;
01659 }
01660 
01661 /* \note This actually gets called a couple of times at shutdown.  Once, before we start
01662    hanging up channels, and then again, after the channel hangup timeout expires */
01663 void ast_cdr_engine_term(void)
01664 {
01665    ast_cdr_submit_batch(batchsafeshutdown);
01666 }
01667 
01668 int ast_cdr_engine_reload(void)
01669 {
01670    do_reload(1);
01671    return 0;
01672 }
01673 
01674 int ast_cdr_data_add_structure(struct ast_data *tree, struct ast_cdr *cdr, int recur)
01675 {
01676    struct ast_cdr *tmpcdr;
01677    struct ast_data *level;
01678    struct ast_var_t *variables;
01679    const char *var, *val;
01680    int x = 1, i;
01681    char workspace[256];
01682    char *tmp;
01683 
01684    if (!cdr) {
01685       return -1;
01686    }
01687 
01688    for (tmpcdr = cdr; tmpcdr; tmpcdr = (recur ? tmpcdr->next : NULL)) {
01689       level = ast_data_add_node(tree, "level");
01690       if (!level) {
01691          continue;
01692       }
01693 
01694       ast_data_add_int(level, "level_number", x);
01695 
01696       AST_LIST_TRAVERSE(&tmpcdr->varshead, variables, entries) {
01697          if (variables && (var = ast_var_name(variables)) &&
01698                (val = ast_var_value(variables)) && !ast_strlen_zero(var)
01699                && !ast_strlen_zero(val)) {
01700             ast_data_add_str(level, var, val);
01701          } else {
01702             break;
01703          }
01704       }
01705 
01706       for (i = 0; cdr_readonly_vars[i]; i++) {
01707          workspace[0] = 0; /* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */
01708          ast_cdr_getvar(tmpcdr, cdr_readonly_vars[i], &tmp, workspace, sizeof(workspace), 0, 0);
01709          if (!tmp) {
01710             continue;
01711          }
01712          ast_data_add_str(level, cdr_readonly_vars[i], tmp);
01713       }
01714 
01715       x++;
01716    }
01717 
01718    return 0;
01719 }
01720 

Generated on 7 Sep 2017 for Asterisk - The Open Source Telephony Project by  doxygen 1.6.1