Sat Feb 11 06:33:05 2012

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

Generated on Sat Feb 11 06:33:05 2012 for Asterisk - The Open Source Telephony Project by  doxygen 1.5.6