![]() |
Home page |
Mailing list |
Docs
Asterisk developer's documentation :: Codename Pineapple
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: 53128 $") 00037 00038 #include <unistd.h> 00039 #include <stdlib.h> 00040 #include <string.h> 00041 #include <stdio.h> 00042 #include <signal.h> 00043 00044 #include "asterisk/lock.h" 00045 #include "asterisk/channel.h" 00046 #include "asterisk/cdr.h" 00047 #include "asterisk/logger.h" 00048 #include "asterisk/callerid.h" 00049 #include "asterisk/causes.h" 00050 #include "asterisk/options.h" 00051 #include "asterisk/linkedlists.h" 00052 #include "asterisk/utils.h" 00053 #include "asterisk/sched.h" 00054 #include "asterisk/config.h" 00055 #include "asterisk/cli.h" 00056 #include "asterisk/stringfields.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_LIST_ENTRY(ast_cdr_beitem) list; 00067 }; 00068 00069 static AST_LIST_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; 00081 00082 static struct sched_context *sched; 00083 static int cdr_sched = -1; 00084 static pthread_t cdr_thread = AST_PTHREADT_NULL; 00085 00086 #define BATCH_SIZE_DEFAULT 100 00087 #define BATCH_TIME_DEFAULT 300 00088 #define BATCH_SCHEDULER_ONLY_DEFAULT 0 00089 #define BATCH_SAFE_SHUTDOWN_DEFAULT 1 00090 00091 static int enabled; 00092 static int batchmode; 00093 static int batchsize; 00094 static int batchtime; 00095 static int batchscheduleronly; 00096 static int batchsafeshutdown; 00097 00098 AST_MUTEX_DEFINE_STATIC(cdr_batch_lock); 00099 00100 /* these are used to wake up the CDR thread when there's work to do */ 00101 AST_MUTEX_DEFINE_STATIC(cdr_pending_lock); 00102 static ast_cond_t cdr_pending_cond; 00103 00104 00105 /*! Register a CDR driver. Each registered CDR driver generates a CDR 00106 \return 0 on success, -1 on failure 00107 */ 00108 int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be) 00109 { 00110 struct ast_cdr_beitem *i; 00111 00112 if (!name) 00113 return -1; 00114 if (!be) { 00115 ast_log(LOG_WARNING, "CDR engine '%s' lacks backend\n", name); 00116 return -1; 00117 } 00118 00119 AST_LIST_LOCK(&be_list); 00120 AST_LIST_TRAVERSE(&be_list, i, list) { 00121 if (!strcasecmp(name, i->name)) 00122 break; 00123 } 00124 AST_LIST_UNLOCK(&be_list); 00125 00126 if (i) { 00127 ast_log(LOG_WARNING, "Already have a CDR backend called '%s'\n", name); 00128 return -1; 00129 } 00130 00131 if (!(i = ast_calloc(1, sizeof(*i)))) 00132 return -1; 00133 00134 i->be = be; 00135 ast_copy_string(i->name, name, sizeof(i->name)); 00136 ast_copy_string(i->desc, desc, sizeof(i->desc)); 00137 00138 AST_LIST_LOCK(&be_list); 00139 AST_LIST_INSERT_HEAD(&be_list, i, list); 00140 AST_LIST_UNLOCK(&be_list); 00141 00142 return 0; 00143 } 00144 00145 /*! unregister a CDR driver */ 00146 void ast_cdr_unregister(const char *name) 00147 { 00148 struct ast_cdr_beitem *i = NULL; 00149 00150 AST_LIST_LOCK(&be_list); 00151 AST_LIST_TRAVERSE_SAFE_BEGIN(&be_list, i, list) { 00152 if (!strcasecmp(name, i->name)) { 00153 AST_LIST_REMOVE_CURRENT(&be_list, list); 00154 if (option_verbose > 1) 00155 ast_verbose(VERBOSE_PREFIX_2 "Unregistered '%s' CDR backend\n", name); 00156 free(i); 00157 break; 00158 } 00159 } 00160 AST_LIST_TRAVERSE_SAFE_END; 00161 AST_LIST_UNLOCK(&be_list); 00162 } 00163 00164 /*! Duplicate a CDR record 00165 \returns Pointer to new CDR record 00166 */ 00167 struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr) 00168 { 00169 struct ast_cdr *newcdr = ast_cdr_alloc(); 00170 00171 if (!newcdr) 00172 return NULL; 00173 00174 memcpy(newcdr, cdr, sizeof(*newcdr)); 00175 /* The varshead is unusable, volatile even, after the memcpy so we take care of that here */ 00176 memset(&newcdr->varshead, 0, sizeof(newcdr->varshead)); 00177 ast_cdr_copy_vars(newcdr, cdr); 00178 newcdr->next = NULL; 00179 00180 return newcdr; 00181 } 00182 00183 static const char *ast_cdr_getvar_internal(struct ast_cdr *cdr, const char *name, int recur) 00184 { 00185 if (ast_strlen_zero(name)) 00186 return NULL; 00187 00188 for (; cdr; cdr = recur ? cdr->next : NULL) { 00189 struct ast_var_t *variables; 00190 struct varshead *headp = &cdr->varshead; 00191 AST_LIST_TRAVERSE(headp, variables, entries) { 00192 if (!strcasecmp(name, ast_var_name(variables))) 00193 return ast_var_value(variables); 00194 } 00195 } 00196 00197 return NULL; 00198 } 00199 00200 static void cdr_get_tv(struct timeval tv, const char *fmt, char *buf, int bufsize) 00201 { 00202 if (fmt == NULL) { /* raw mode */ 00203 snprintf(buf, bufsize, "%ld.%06ld", (long)tv.tv_sec, (long)tv.tv_usec); 00204 } else { 00205 time_t t = tv.tv_sec; 00206 if (t) { 00207 struct tm tm; 00208 localtime_r(&t, &tm); 00209 strftime(buf, bufsize, fmt, &tm); 00210 } 00211 } 00212 } 00213 00214 /*! CDR channel variable retrieval */ 00215 void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int recur, int raw) 00216 { 00217 const char *fmt = "%Y-%m-%d %T"; 00218 const char *varbuf; 00219 00220 *ret = NULL; 00221 /* special vars (the ones from the struct ast_cdr when requested by name) 00222 I'd almost say we should convert all the stringed vals to vars */ 00223 00224 if (!strcasecmp(name, "clid")) 00225 ast_copy_string(workspace, cdr->clid, workspacelen); 00226 else if (!strcasecmp(name, "src")) 00227 ast_copy_string(workspace, cdr->src, workspacelen); 00228 else if (!strcasecmp(name, "dst")) 00229 ast_copy_string(workspace, cdr->dst, workspacelen); 00230 else if (!strcasecmp(name, "dcontext")) 00231 ast_copy_string(workspace, cdr->dcontext, workspacelen); 00232 else if (!strcasecmp(name, "channel")) 00233 ast_copy_string(workspace, cdr->channel, workspacelen); 00234 else if (!strcasecmp(name, "dstchannel")) 00235 ast_copy_string(workspace, cdr->dstchannel, workspacelen); 00236 else if (!strcasecmp(name, "lastapp")) 00237 ast_copy_string(workspace, cdr->lastapp, workspacelen); 00238 else if (!strcasecmp(name, "lastdata")) 00239 ast_copy_string(workspace, cdr->lastdata, workspacelen); 00240 else if (!strcasecmp(name, "start")) 00241 cdr_get_tv(cdr->start, raw ? NULL : fmt, workspace, workspacelen); 00242 else if (!strcasecmp(name, "answer")) 00243 cdr_get_tv(cdr->answer, raw ? NULL : fmt, workspace, workspacelen); 00244 else if (!strcasecmp(name, "end")) 00245 cdr_get_tv(cdr->end, raw ? NULL : fmt, workspace, workspacelen); 00246 else if (!strcasecmp(name, "duration")) 00247 snprintf(workspace, workspacelen, "%ld", cdr->duration); 00248 else if (!strcasecmp(name, "billsec")) 00249 snprintf(workspace, workspacelen, "%ld", cdr->billsec); 00250 else if (!strcasecmp(name, "disposition")) { 00251 if (raw) { 00252 snprintf(workspace, workspacelen, "%ld", cdr->disposition); 00253 } else { 00254 ast_copy_string(workspace, ast_cdr_disp2str(cdr->disposition), workspacelen); 00255 } 00256 } else if (!strcasecmp(name, "amaflags")) { 00257 if (raw) { 00258 snprintf(workspace, workspacelen, "%ld", cdr->amaflags); 00259 } else { 00260 ast_copy_string(workspace, ast_cdr_flags2str(cdr->amaflags), workspacelen); 00261 } 00262 } else if (!strcasecmp(name, "accountcode")) 00263 ast_copy_string(workspace, cdr->accountcode, workspacelen); 00264 else if (!strcasecmp(name, "uniqueid")) 00265 ast_copy_string(workspace, cdr->uniqueid, workspacelen); 00266 else if (!strcasecmp(name, "userfield")) 00267 ast_copy_string(workspace, cdr->userfield, workspacelen); 00268 else if ((varbuf = ast_cdr_getvar_internal(cdr, name, recur))) 00269 ast_copy_string(workspace, varbuf, workspacelen); 00270 else 00271 workspace[0] = '\0'; 00272 00273 if (!ast_strlen_zero(workspace)) 00274 *ret = workspace; 00275 } 00276 00277 /* readonly cdr variables */ 00278 static const char *cdr_readonly_vars[] = { "clid", "src", "dst", "dcontext", "channel", "dstchannel", 00279 "lastapp", "lastdata", "start", "answer", "end", "duration", 00280 "billsec", "disposition", "amaflags", "accountcode", "uniqueid", 00281 "userfield", NULL }; 00282 /*! Set a CDR channel variable 00283 \note You can't set the CDR variables that belong to the actual CDR record, like "billsec". 00284 */ 00285 int ast_cdr_setvar(struct ast_cdr *cdr, const char *name, const char *value, int recur) 00286 { 00287 struct ast_var_t *newvariable; 00288 struct varshead *headp; 00289 int x; 00290 00291 for (x = 0; cdr_readonly_vars[x]; x++) { 00292 if (!strcasecmp(name, cdr_readonly_vars[x])) { 00293 ast_log(LOG_ERROR, "Attempt to set the '%s' read-only variable!.\n", name); 00294 return -1; 00295 } 00296 } 00297 00298 if (!cdr) { 00299 ast_log(LOG_ERROR, "Attempt to set a variable on a nonexistent CDR record.\n"); 00300 return -1; 00301 } 00302 00303 for (; cdr; cdr = recur ? cdr->next : NULL) { 00304 headp = &cdr->varshead; 00305 AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) { 00306 if (!strcasecmp(ast_var_name(newvariable), name)) { 00307 /* there is already such a variable, delete it */ 00308 AST_LIST_REMOVE_CURRENT(headp, entries); 00309 ast_var_delete(newvariable); 00310 break; 00311 } 00312 } 00313 AST_LIST_TRAVERSE_SAFE_END; 00314 00315 if (value) { 00316 newvariable = ast_var_assign(name, value); 00317 AST_LIST_INSERT_HEAD(headp, newvariable, entries); 00318 } 00319 } 00320 00321 return 0; 00322 } 00323 00324 int ast_cdr_copy_vars(struct ast_cdr *to_cdr, struct ast_cdr *from_cdr) 00325 { 00326 struct ast_var_t *variables, *newvariable = NULL; 00327 struct varshead *headpa, *headpb; 00328 const char *var, *val; 00329 int x = 0; 00330 00331 headpa = &from_cdr->varshead; 00332 headpb = &to_cdr->varshead; 00333 00334 AST_LIST_TRAVERSE(headpa,variables,entries) { 00335 if (variables && 00336 (var = ast_var_name(variables)) && (val = ast_var_value(variables)) && 00337 !ast_strlen_zero(var) && !ast_strlen_zero(val)) { 00338 newvariable = ast_var_assign(var, val); 00339 AST_LIST_INSERT_HEAD(headpb, newvariable, entries); 00340 x++; 00341 } 00342 } 00343 00344 return x; 00345 } 00346 00347 int ast_cdr_serialize_variables(struct ast_cdr *cdr, struct ast_str **buf, char delim, char sep, int recur) 00348 { 00349 struct ast_var_t *variables; 00350 const char *var, *val; 00351 char *tmp; 00352 char workspace[256]; 00353 int total = 0, x = 0, i; 00354 00355 (*buf)->used = 0; 00356 (*buf)->str[0] = '\0'; 00357 00358 for (; cdr; cdr = recur ? cdr->next : NULL) { 00359 if (++x > 1) 00360 ast_str_append(buf, 0, "\n"); 00361 00362 AST_LIST_TRAVERSE(&cdr->varshead, variables, entries) { 00363 if (variables && 00364 (var = ast_var_name(variables)) && (val = ast_var_value(variables)) && 00365 !ast_strlen_zero(var) && !ast_strlen_zero(val)) { 00366 if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, var, delim, val, sep) < 0) { 00367 ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n"); 00368 break; 00369 } else 00370 total++; 00371 } else 00372 break; 00373 } 00374 00375 for (i = 0; cdr_readonly_vars[i]; i++) { 00376 ast_cdr_getvar(cdr, cdr_readonly_vars[i], &tmp, workspace, sizeof(workspace), 0, 0); 00377 if (!tmp) 00378 continue; 00379 00380 if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, tmp, sep) < 0) { 00381 ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n"); 00382 break; 00383 } else 00384 total++; 00385 } 00386 } 00387 00388 return total; 00389 } 00390 00391 00392 void ast_cdr_free_vars(struct ast_cdr *cdr, int recur) 00393 { 00394 00395 /* clear variables */ 00396 for (; cdr; cdr = recur ? cdr->next : NULL) { 00397 struct ast_var_t *vardata; 00398 struct varshead *headp = &cdr->varshead; 00399 while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) 00400 ast_var_delete(vardata); 00401 } 00402 } 00403 00404 /*! \brief print a warning if cdr already posted */ 00405 static void check_post(struct ast_cdr *cdr) 00406 { 00407 if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED)) 00408 ast_log(LOG_NOTICE, "CDR on channel '%s' already posted\n", S_OR(cdr->channel, "<unknown>")); 00409 } 00410 00411 /*! \brief print a warning if cdr already started */ 00412 static void check_start(struct ast_cdr *cdr) 00413 { 00414 if (!ast_tvzero(cdr->start)) 00415 ast_log(LOG_NOTICE, "CDR on channel '%s' already started\n", S_OR(cdr->channel, "<unknown>")); 00416 } 00417 00418 void ast_cdr_free(struct ast_cdr *cdr) 00419 { 00420 00421 while (cdr) { 00422 struct ast_cdr *next = cdr->next; 00423 char *chan = S_OR(cdr->channel, "<unknown>"); 00424 if (!ast_test_flag(cdr, AST_CDR_FLAG_POSTED) && !ast_test_flag(cdr, AST_CDR_FLAG_POST_DISABLED)) 00425 ast_log(LOG_NOTICE, "CDR on channel '%s' not posted\n", chan); 00426 if (ast_tvzero(cdr->end)) 00427 ast_log(LOG_NOTICE, "CDR on channel '%s' lacks end\n", chan); 00428 if (ast_tvzero(cdr->start)) 00429 ast_log(LOG_NOTICE, "CDR on channel '%s' lacks start\n", chan); 00430 00431 ast_cdr_free_vars(cdr, 0); 00432 free(cdr); 00433 cdr = next; 00434 } 00435 } 00436 00437 struct ast_cdr *ast_cdr_alloc(void) 00438 { 00439 return ast_calloc(1, sizeof(struct ast_cdr)); 00440 } 00441 00442 void ast_cdr_start(struct ast_cdr *cdr) 00443 { 00444 char *chan; 00445 00446 for (; cdr; cdr = cdr->next) { 00447 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { 00448 chan = S_OR(cdr->channel, "<unknown>"); 00449 check_post(cdr); 00450 check_start(cdr); 00451 cdr->start = ast_tvnow(); 00452 } 00453 } 00454 } 00455 00456 void ast_cdr_answer(struct ast_cdr *cdr) 00457 { 00458 00459 for (; cdr; cdr = cdr->next) { 00460 check_post(cdr); 00461 if (cdr->disposition < AST_CDR_ANSWERED) 00462 cdr->disposition = AST_CDR_ANSWERED; 00463 if (ast_tvzero(cdr->answer)) 00464 cdr->answer = ast_tvnow(); 00465 } 00466 } 00467 00468 void ast_cdr_busy(struct ast_cdr *cdr) 00469 { 00470 00471 for (; cdr; cdr = cdr->next) { 00472 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { 00473 check_post(cdr); 00474 if (cdr->disposition < AST_CDR_BUSY) 00475 cdr->disposition = AST_CDR_BUSY; 00476 } 00477 } 00478 } 00479 00480 void ast_cdr_failed(struct ast_cdr *cdr) 00481 { 00482 for (; cdr; cdr = cdr->next) { 00483 check_post(cdr); 00484 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { 00485 if (cdr->disposition < AST_CDR_FAILED) 00486 cdr->disposition = AST_CDR_FAILED; 00487 } 00488 } 00489 } 00490 00491 int ast_cdr_disposition(struct ast_cdr *cdr, int cause) 00492 { 00493 int res = 0; 00494 00495 for (; cdr; cdr = cdr->next) { 00496 switch (cause) { 00497 case AST_CAUSE_BUSY: 00498 ast_cdr_busy(cdr); 00499 break; 00500 case AST_CAUSE_FAILURE: 00501 ast_cdr_failed(cdr); 00502 break; 00503 case AST_CAUSE_NORMAL: 00504 break; 00505 case AST_CAUSE_NOTDEFINED: 00506 res = -1; 00507 break; 00508 default: 00509 res = -1; 00510 ast_log(LOG_WARNING, "Cause not handled\n"); 00511 } 00512 } 00513 return res; 00514 } 00515 00516 void ast_cdr_setdestchan(struct ast_cdr *cdr, const char *chann) 00517 { 00518 for (; cdr; cdr = cdr->next) { 00519 check_post(cdr); 00520 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) 00521 ast_copy_string(cdr->dstchannel, chann, sizeof(cdr->dstchannel)); 00522 } 00523 } 00524 00525 void ast_cdr_setapp(struct ast_cdr *cdr, char *app, char *data) 00526 { 00527 00528 for (; cdr; cdr = cdr->next) { 00529 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { 00530 check_post(cdr); 00531 if (!app) 00532 app = ""; 00533 ast_copy_string(cdr->lastapp, app, sizeof(cdr->lastapp)); 00534 if (!data) 00535 data = ""; 00536 ast_copy_string(cdr->lastdata, data, sizeof(cdr->lastdata)); 00537 } 00538 } 00539 } 00540 00541 /* set cid info for one record */ 00542 static void set_one_cid(struct ast_cdr *cdr, struct ast_channel *c) 00543 { 00544 /* Grab source from ANI or normal Caller*ID */ 00545 const char *num = S_OR(c->cid.cid_ani, c->cid.cid_num); 00546 00547 if (!ast_strlen_zero(c->cid.cid_name)) { 00548 if (!ast_strlen_zero(num)) /* both name and number */ 00549 snprintf(cdr->clid, sizeof(cdr->clid), "\"%s\" <%s>", c->cid.cid_name, num); 00550 else /* only name */ 00551 ast_copy_string(cdr->clid, c->cid.cid_name, sizeof(cdr->clid)); 00552 } else if (!ast_strlen_zero(num)) { /* only number */ 00553 ast_copy_string(cdr->clid, num, sizeof(cdr->clid)); 00554 } else { /* nothing known */ 00555 cdr->clid[0] = '\0'; 00556 } 00557 ast_copy_string(cdr->src, S_OR(num, ""), sizeof(cdr->src)); 00558 00559 } 00560 int ast_cdr_setcid(struct ast_cdr *cdr, struct ast_channel *c) 00561 { 00562 for (; cdr; cdr = cdr->next) { 00563 if (ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) 00564 set_one_cid(cdr, c); 00565 } 00566 return 0; 00567 } 00568 00569 int ast_cdr_init(struct ast_cdr *cdr, struct ast_channel *c) 00570 { 00571 char *chan; 00572 00573 for ( ; cdr ; cdr = cdr->next) { 00574 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { 00575 chan = S_OR(cdr->channel, "<unknown>"); 00576 if (!ast_strlen_zero(cdr->channel)) 00577 ast_log(LOG_WARNING, "CDR already initialized on '%s'\n", chan); 00578 ast_copy_string(cdr->channel, c->name, sizeof(cdr->channel)); 00579 set_one_cid(cdr, c); 00580 00581 cdr->disposition = (c->_state == AST_STATE_UP) ? AST_CDR_ANSWERED : AST_CDR_NOANSWER; 00582 cdr->amaflags = c->amaflags ? c->amaflags : ast_default_amaflags; 00583 ast_copy_string(cdr->accountcode, c->accountcode, sizeof(cdr->accountcode)); 00584 /* Destination information */ 00585 ast_copy_string(cdr->dst, c->exten, sizeof(cdr->dst)); 00586 ast_copy_string(cdr->dcontext, c->context, sizeof(cdr->dcontext)); 00587 /* Unique call identifier */ 00588 ast_copy_string(cdr->uniqueid, c->uniqueid, sizeof(cdr->uniqueid)); 00589 } 00590 } 00591 return 0; 00592 } 00593 00594 void ast_cdr_end(struct ast_cdr *cdr) 00595 { 00596 for ( ; cdr ; cdr = cdr->next) { 00597 check_post(cdr); 00598 if (ast_tvzero(cdr->end)) 00599 cdr->end = ast_tvnow(); 00600 if (ast_tvzero(cdr->start)) { 00601 ast_log(LOG_WARNING, "CDR on channel '%s' has not started\n", S_OR(cdr->channel, "<unknown>")); 00602 cdr->disposition = AST_CDR_FAILED; 00603 } else 00604 cdr->duration = cdr->end.tv_sec - cdr->start.tv_sec; 00605 cdr->billsec = ast_tvzero(cdr->answer) ? 0 : cdr->end.tv_sec - cdr->answer.tv_sec; 00606 } 00607 } 00608 00609 char *ast_cdr_disp2str(int disposition) 00610 { 00611 switch (disposition) { 00612 case AST_CDR_NOANSWER: 00613 return "NO ANSWER"; 00614 case AST_CDR_FAILED: 00615 return "FAILED"; 00616 case AST_CDR_BUSY: 00617 return "BUSY"; 00618 case AST_CDR_ANSWERED: 00619 return "ANSWERED"; 00620 } 00621 return "UNKNOWN"; 00622 } 00623 00624 /*! Converts AMA flag to printable string */ 00625 char *ast_cdr_flags2str(int flag) 00626 { 00627 switch (flag) { 00628 case AST_CDR_OMIT: 00629 return "OMIT"; 00630 case AST_CDR_BILLING: 00631 return "BILLING"; 00632 case AST_CDR_DOCUMENTATION: 00633 return "DOCUMENTATION"; 00634 } 00635 return "Unknown"; 00636 } 00637 00638 int ast_cdr_setaccount(struct ast_channel *chan, const char *account) 00639 { 00640 struct ast_cdr *cdr = chan->cdr; 00641 00642 ast_string_field_set(chan, accountcode, account); 00643 for ( ; cdr ; cdr = cdr->next) { 00644 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) 00645 ast_copy_string(cdr->accountcode, chan->accountcode, sizeof(cdr->accountcode)); 00646 } 00647 return 0; 00648 } 00649 00650 int ast_cdr_setamaflags(struct ast_channel *chan, const char *flag) 00651 { 00652 struct ast_cdr *cdr; 00653 int newflag = ast_cdr_amaflags2int(flag); 00654 if (newflag) { 00655 for (cdr = chan->cdr; cdr; cdr = cdr->next) 00656 cdr->amaflags = newflag; 00657 } 00658 00659 return 0; 00660 } 00661 00662 int ast_cdr_setuserfield(struct ast_channel *chan, const char *userfield) 00663 { 00664 struct ast_cdr *cdr = chan->cdr; 00665 00666 for ( ; cdr ; cdr = cdr->next) { 00667 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) 00668 ast_copy_string(cdr->userfield, userfield, sizeof(cdr->userfield)); 00669 } 00670 00671 return 0; 00672 } 00673 00674 int ast_cdr_appenduserfield(struct ast_channel *chan, const char *userfield) 00675 { 00676 struct ast_cdr *cdr = chan->cdr; 00677 00678 for ( ; cdr ; cdr = cdr->next) { 00679 int len = strlen(cdr->userfield); 00680 00681 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) 00682 ast_copy_string(cdr->userfield + len, userfield, sizeof(cdr->userfield) - len); 00683 } 00684 00685 return 0; 00686 } 00687 00688 int ast_cdr_update(struct ast_channel *c) 00689 { 00690 struct ast_cdr *cdr = c->cdr; 00691 00692 for ( ; cdr ; cdr = cdr->next) { 00693 if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { 00694 set_one_cid(cdr, c); 00695 00696 /* Copy account code et-al */ 00697 ast_copy_string(cdr->accountcode, c->accountcode, sizeof(cdr->accountcode)); 00698 /* Destination information */ /* XXX privilege macro* ? */ 00699 ast_copy_string(cdr->dst, S_OR(c->macroexten, c->exten), sizeof(cdr->dst)); 00700 ast_copy_string(cdr->dcontext, S_OR(c->macrocontext, c->context), sizeof(cdr->dcontext)); 00701 } 00702 } 00703 00704 return 0; 00705 } 00706 00707 int ast_cdr_amaflags2int(const char *flag) 00708 { 00709 if (!strcasecmp(flag, "default")) 00710 return 0; 00711 if (!strcasecmp(flag, "omit")) 00712 return AST_CDR_OMIT; 00713 if (!strcasecmp(flag, "billing")) 00714 return AST_CDR_BILLING; 00715 if (!strcasecmp(flag, "documentation")) 00716 return AST_CDR_DOCUMENTATION; 00717 return -1; 00718 } 00719 00720 static void post_cdr(struct ast_cdr *cdr) 00721 { 00722 char *chan; 00723 struct ast_cdr_beitem *i; 00724 00725 for ( ; cdr ; cdr = cdr->next) { 00726 chan = S_OR(cdr->channel, "<unknown>"); 00727 check_post(cdr); 00728 if (ast_tvzero(cdr->end)) 00729 ast_log(LOG_WARNING, "CDR on channel '%s' lacks end\n", chan); 00730 if (ast_tvzero(cdr->start)) 00731 ast_log(LOG_WARNING, "CDR on channel '%s' lacks start\n", chan); 00732 ast_set_flag(cdr, AST_CDR_FLAG_POSTED); 00733 AST_LIST_LOCK(&be_list); 00734 AST_LIST_TRAVERSE(&be_list, i, list) { 00735 i->be(cdr); 00736 } 00737 AST_LIST_UNLOCK(&be_list); 00738 } 00739 } 00740 00741 void ast_cdr_reset(struct ast_cdr *cdr, struct ast_flags *_flags) 00742 { 00743 struct ast_cdr *dup; 00744 struct ast_flags flags = { 0 }; 00745 00746 if (_flags) 00747 ast_copy_flags(&flags, _flags, AST_FLAGS_ALL); 00748 00749 for ( ; cdr ; cdr = cdr->next) { 00750 /* Detach if post is requested */ 00751 if (ast_test_flag(&flags, AST_CDR_FLAG_LOCKED) || !ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { 00752 if (ast_test_flag(&flags, AST_CDR_FLAG_POSTED)) { 00753 ast_cdr_end(cdr); 00754 if ((dup = ast_cdr_dup(cdr))) { 00755 ast_cdr_detach(dup); 00756 } 00757 ast_set_flag(cdr, AST_CDR_FLAG_POSTED); 00758 } 00759 00760 /* clear variables */ 00761 if (!ast_test_flag(&flags, AST_CDR_FLAG_KEEP_VARS)) { 00762 ast_cdr_free_vars(cdr, 0); 00763 } 00764 00765 /* Reset to initial state */ 00766 ast_clear_flag(cdr, AST_FLAGS_ALL); 00767 memset(&cdr->start, 0, sizeof(cdr->start)); 00768 memset(&cdr->end, 0, sizeof(cdr->end)); 00769 memset(&cdr->answer, 0, sizeof(cdr->answer)); 00770 cdr->billsec = 0; 00771 cdr->duration = 0; 00772 ast_cdr_start(cdr); 00773 cdr->disposition = AST_CDR_NOANSWER; 00774 } 00775 } 00776 } 00777 00778 struct ast_cdr *ast_cdr_append(struct ast_cdr *cdr, struct ast_cdr *newcdr) 00779 { 00780 struct ast_cdr *ret; 00781 00782 if (cdr) { 00783 ret = cdr; 00784 00785 while (cdr->next) 00786 cdr = cdr->next; 00787 cdr->next = newcdr; 00788 } else { 00789 ret = newcdr; 00790 } 00791 00792 return ret; 00793 } 00794 00795 /*! \note Don't call without cdr_batch_lock */ 00796 static void reset_batch(void) 00797 { 00798 batch->size = 0; 00799 batch->head = NULL; 00800 batch->tail = NULL; 00801 } 00802 00803 /*! \note Don't call without cdr_batch_lock */ 00804 static int init_batch(void) 00805 { 00806 /* This is the single meta-batch used to keep track of all CDRs during the entire life of the program */ 00807 if (!(batch = ast_malloc(sizeof(*batch)))) 00808 return -1; 00809 00810 reset_batch(); 00811 00812 return 0; 00813 } 00814 00815 static void *do_batch_backend_process(void *data) 00816 { 00817 struct ast_cdr_batch_item *processeditem; 00818 struct ast_cdr_batch_item *batchitem = data; 00819 00820 /* Push each CDR into storage mechanism(s) and free all the memory */ 00821 while (batchitem) { 00822 post_cdr(batchitem->cdr); 00823 ast_cdr_free(batchitem->cdr); 00824 processeditem = batchitem; 00825 batchitem = batchitem->next; 00826 free(processeditem); 00827 } 00828 00829 return NULL; 00830 } 00831 00832 void ast_cdr_submit_batch(int shutdown) 00833 { 00834 struct ast_cdr_batch_item *oldbatchitems = NULL; 00835 pthread_attr_t attr; 00836 pthread_t batch_post_thread = AST_PTHREADT_NULL; 00837 00838 /* if there's no batch, or no CDRs in the batch, then there's nothing to do */ 00839 if (!batch || !batch->head) 00840 return; 00841 00842 /* move the old CDRs aside, and prepare a new CDR batch */ 00843 ast_mutex_lock(&cdr_batch_lock); 00844 oldbatchitems = batch->head; 00845 reset_batch(); 00846 ast_mutex_unlock(&cdr_batch_lock); 00847 00848 /* if configured, spawn a new thread to post these CDRs, 00849 also try to save as much as possible if we are shutting down safely */ 00850 if (batchscheduleronly || shutdown) { 00851 if (option_debug) 00852 ast_log(LOG_DEBUG, "CDR single-threaded batch processing begins now\n"); 00853 do_batch_backend_process(oldbatchitems); 00854 } else { 00855 pthread_attr_init(&attr); 00856 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 00857 if (ast_pthread_create_background(&batch_post_thread, &attr, do_batch_backend_process, oldbatchitems)) { 00858 ast_log(LOG_WARNING, "CDR processing thread could not detach, now trying in this thread\n"); 00859 do_batch_backend_process(oldbatchitems); 00860 } else { 00861 if (option_debug) 00862 ast_log(LOG_DEBUG, "CDR multi-threaded batch processing begins now\n"); 00863 } 00864 pthread_attr_destroy(&attr); 00865 } 00866 } 00867 00868 static int submit_scheduled_batch(void *data) 00869 { 00870 ast_cdr_submit_batch(0); 00871 /* manually reschedule from this point in time */ 00872 cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL); 00873 /* returning zero so the scheduler does not automatically reschedule */ 00874 return 0; 00875 } 00876 00877 static void submit_unscheduled_batch(void) 00878 { 00879 /* this is okay since we are not being called from within the scheduler */ 00880 if (cdr_sched > -1) 00881 ast_sched_del(sched, cdr_sched); 00882 /* schedule the submission to occur ASAP (1 ms) */ 00883 cdr_sched = ast_sched_add(sched, 1, submit_scheduled_batch, NULL); 00884 /* signal the do_cdr thread to wakeup early and do some work (that lazy thread ;) */ 00885 ast_mutex_lock(&cdr_pending_lock); 00886 ast_cond_signal(&cdr_pending_cond); 00887 ast_mutex_unlock(&cdr_pending_lock); 00888 } 00889 00890 void ast_cdr_detach(struct ast_cdr *cdr) 00891 { 00892 struct ast_cdr_batch_item *newtail; 00893 int curr; 00894 00895 /* maybe they disabled CDR stuff completely, so just drop it */ 00896 if (!enabled) { 00897 if (option_debug) 00898 ast_log(LOG_DEBUG, "Dropping CDR !\n"); 00899 ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED); 00900 ast_cdr_free(cdr); 00901 return; 00902 } 00903 00904 /* post stuff immediately if we are not in batch mode, this is legacy behaviour */ 00905 if (!batchmode) { 00906 post_cdr(cdr); 00907 ast_cdr_free(cdr); 00908 return; 00909 } 00910 00911 /* otherwise, each CDR gets put into a batch list (at the end) */ 00912 if (option_debug) 00913 ast_log(LOG_DEBUG, "CDR detaching from this thread\n"); 00914 00915 /* we'll need a new tail for every CDR */ 00916 if (!(newtail = ast_calloc(1, sizeof(*newtail)))) { 00917 post_cdr(cdr); 00918 ast_cdr_free(cdr); 00919 return; 00920 } 00921 00922 /* don't traverse a whole list (just keep track of the tail) */ 00923 ast_mutex_lock(&cdr_batch_lock); 00924 if (!batch) 00925 init_batch(); 00926 if (!batch->head) { 00927 /* new batch is empty, so point the head at the new tail */ 00928 batch->head = newtail; 00929 } else { 00930 /* already got a batch with something in it, so just append a new tail */ 00931 batch->tail->next = newtail; 00932 } 00933 newtail->cdr = cdr; 00934 batch->tail = newtail; 00935 curr = batch->size++; 00936 ast_mutex_unlock(&cdr_batch_lock); 00937 00938 /* if we have enough stuff to post, then do it */ 00939 if (curr >= (batchsize - 1)) 00940 submit_unscheduled_batch(); 00941 } 00942 00943 static void *do_cdr(void *data) 00944 { 00945 struct timespec timeout; 00946 int schedms; 00947 int numevents = 0; 00948 00949 for (;;) { 00950 struct timeval now; 00951 schedms = ast_sched_wait(sched); 00952 /* this shouldn't happen, but provide a 1 second default just in case */ 00953 if (schedms <= 0) 00954 schedms = 1000; 00955 now = ast_tvadd(ast_tvnow(), ast_samp2tv(schedms, 1000)); 00956 timeout.tv_sec = now.tv_sec; 00957 timeout.tv_nsec = now.tv_usec * 1000; 00958 /* prevent stuff from clobbering cdr_pending_cond, then wait on signals sent to it until the timeout expires */ 00959 ast_mutex_lock(&cdr_pending_lock); 00960 ast_cond_timedwait(&cdr_pending_cond, &cdr_pending_lock, &timeout); 00961 numevents = ast_sched_runq(sched); 00962 ast_mutex_unlock(&cdr_pending_lock); 00963 if (option_debug > 1) 00964 ast_log(LOG_DEBUG, "Processed %d scheduled CDR batches from the run queue\n", numevents); 00965 } 00966 00967 return NULL; 00968 } 00969 00970 static int handle_cli_status(int fd, int argc, char *argv[]) 00971 { 00972 struct ast_cdr_beitem *beitem=NULL; 00973 int cnt=0; 00974 long nextbatchtime=0; 00975 00976 if (argc > 2) 00977 return RESULT_SHOWUSAGE; 00978 00979 ast_cli(fd, "CDR logging: %s\n", enabled ? "enabled" : "disabled"); 00980 ast_cli(fd, "CDR mode: %s\n", batchmode ? "batch" : "simple"); 00981 if (enabled) { 00982 if (batchmode) { 00983 if (batch) 00984 cnt = batch->size; 00985 if (cdr_sched > -1) 00986 nextbatchtime = ast_sched_when(sched, cdr_sched); 00987 ast_cli(fd, "CDR safe shut down: %s\n", batchsafeshutdown ? "enabled" : "disabled"); 00988 ast_cli(fd, "CDR batch threading model: %s\n", batchscheduleronly ? "scheduler only" : "scheduler plus separate threads"); 00989 ast_cli(fd, "CDR current batch size: %d record%s\n", cnt, ESS(cnt)); 00990 ast_cli(fd, "CDR maximum batch size: %d record%s\n", batchsize, ESS(batchsize)); 00991 ast_cli(fd, "CDR maximum batch time: %d second%s\n", batchtime, ESS(batchtime)); 00992 ast_cli(fd, "CDR next scheduled batch processing time: %ld second%s\n", nextbatchtime, ESS(nextbatchtime)); 00993 } 00994 AST_LIST_LOCK(&be_list); 00995 AST_LIST_TRAVERSE(&be_list, beitem, list) { 00996 ast_cli(fd, "CDR registered backend: %s\n", beitem->name); 00997 } 00998 AST_LIST_UNLOCK(&be_list); 00999 } 01000 01001 return 0; 01002 } 01003 01004 static int handle_cli_submit(int fd, int argc, char *argv[]) 01005 { 01006 if (argc > 2) 01007 return RESULT_SHOWUSAGE; 01008 01009 submit_unscheduled_batch(); 01010 ast_cli(fd, "Submitted CDRs to backend engines for processing. This may take a while.\n"); 01011 01012 return 0; 01013 } 01014 01015 static struct ast_cli_entry cli_submit = { 01016 { "cdr", "submit", NULL }, 01017 handle_cli_submit, "Posts all pending batched CDR data", 01018 "Usage: cdr submit\n" 01019 " Posts all pending batched CDR data to the configured CDR backend engine modules.\n" 01020 }; 01021 01022 static struct ast_cli_entry cli_status = { 01023 { "cdr", "status", NULL }, 01024 handle_cli_status, "Display the CDR status", 01025 "Usage: cdr status\n" 01026 " Displays the Call Detail Record engine system status.\n" 01027 }; 01028 01029 static int do_reload(void) 01030 { 01031 struct ast_config *config; 01032 const char *enabled_value; 01033 const char *batched_value; 01034 const char *scheduleronly_value; 01035 const char *batchsafeshutdown_value; 01036 const char *size_value; 01037 const char *time_value; 01038 const char *end_before_h_value; 01039 int cfg_size; 01040 int cfg_time; 01041 int was_enabled; 01042 int was_batchmode; 01043 int res=0; 01044 01045 ast_mutex_lock(&cdr_batch_lock); 01046 01047 batchsize = BATCH_SIZE_DEFAULT; 01048 batchtime = BATCH_TIME_DEFAULT; 01049 batchscheduleronly = BATCH_SCHEDULER_ONLY_DEFAULT; 01050 batchsafeshutdown = BATCH_SAFE_SHUTDOWN_DEFAULT; 01051 was_enabled = enabled; 01052 was_batchmode = batchmode; 01053 enabled = 1; 01054 batchmode = 0; 01055 01056 /* don't run the next scheduled CDR posting while reloading */ 01057 if (cdr_sched > -1) 01058 ast_sched_del(sched, cdr_sched); 01059 01060 if ((config = ast_config_load("cdr.conf"))) { 01061 if ((enabled_value = ast_variable_retrieve(config, "general", "enable"))) { 01062 enabled = ast_true(enabled_value); 01063 } 01064 if ((batched_value = ast_variable_retrieve(config, "general", "batch"))) { 01065 batchmode = ast_true(batched_value); 01066 } 01067 if ((scheduleronly_value = ast_variable_retrieve(config, "general", "scheduleronly"))) { 01068 batchscheduleronly = ast_true(scheduleronly_value); 01069 } 01070 if ((batchsafeshutdown_value = ast_variable_retrieve(config, "general", "safeshutdown"))) { 01071 batchsafeshutdown = ast_true(batchsafeshutdown_value); 01072 } 01073 if ((size_value = ast_variable_retrieve(config, "general", "size"))) { 01074 if (sscanf(size_value, "%d", &cfg_size) < 1) 01075 ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", size_value); 01076 else if (size_value < 0) 01077 ast_log(LOG_WARNING, "Invalid maximum batch size '%d' specified, using default\n", cfg_size); 01078 else 01079 batchsize = cfg_size; 01080 } 01081 if ((time_value = ast_variable_retrieve(config, "general", "time"))) { 01082 if (sscanf(time_value, "%d", &cfg_time) < 1) 01083 ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", time_value); 01084 else if (time_value < 0) 01085 ast_log(LOG_WARNING, "Invalid maximum batch time '%d' specified, using default\n", cfg_time); 01086 else 01087 batchtime = cfg_time; 01088 } 01089 if ((end_before_h_value = ast_variable_retrieve(config, "general", "endbeforehexten"))) 01090 ast_set2_flag(&ast_options, ast_true(end_before_h_value), AST_OPT_FLAG_END_CDR_BEFORE_H_EXTEN); 01091 } 01092 01093 if (enabled && !batchmode) { 01094 ast_log(LOG_NOTICE, "CDR simple logging enabled.\n"); 01095 } else if (enabled && batchmode) { 01096 cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL); 01097 ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n", batchsize, batchtime); 01098 } else { 01099 ast_log(LOG_NOTICE, "CDR logging disabled, data will be lost.\n"); 01100 } 01101 01102 /* if this reload enabled the CDR batch mode, create the background thread 01103 if it does not exist */ 01104 if (enabled && batchmode && (!was_enabled || !was_batchmode) && (cdr_thread == AST_PTHREADT_NULL)) { 01105 ast_cond_init(&cdr_pending_cond, NULL); 01106 if (ast_pthread_create_background(&cdr_thread, NULL, do_cdr, NULL) < 0) { 01107 ast_log(LOG_ERROR, "Unable to start CDR thread.\n"); 01108 ast_sched_del(sched, cdr_sched); 01109 } else { 01110 ast_cli_register(&cli_submit); 01111 ast_register_atexit(ast_cdr_engine_term); 01112 res = 0; 01113 } 01114 /* if this reload disabled the CDR and/or batch mode and there is a background thread, 01115 kill it */ 01116 } else if (((!enabled && was_enabled) || (!batchmode && was_batchmode)) && (cdr_thread != AST_PTHREADT_NULL)) { 01117 /* wake up the thread so it will exit */ 01118 pthread_cancel(cdr_thread); 01119 pthread_kill(cdr_thread, SIGURG); 01120 pthread_join(cdr_thread, NULL); 01121 cdr_thread = AST_PTHREADT_NULL; 01122 ast_cond_destroy(&cdr_pending_cond); 01123 ast_cli_unregister(&cli_submit); 01124 ast_unregister_atexit(ast_cdr_engine_term); 01125 res = 0; 01126 /* if leaving batch mode, then post the CDRs in the batch, 01127 and don't reschedule, since we are stopping CDR logging */ 01128 if (!batchmode && was_batchmode) { 01129 ast_cdr_engine_term(); 01130 } 01131 } else { 01132 res = 0; 01133 } 01134 01135 ast_mutex_unlock(&cdr_batch_lock); 01136 ast_config_destroy(config); 01137 01138 return res; 01139 } 01140 01141 int ast_cdr_engine_init(void) 01142 { 01143 int res; 01144 01145 sched = sched_context_create(); 01146 if (!sched) { 01147 ast_log(LOG_ERROR, "Unable to create schedule context.\n"); 01148 return -1; 01149 } 01150 01151 ast_cli_register(&cli_status); 01152 01153 res = do_reload(); 01154 if (res) { 01155 ast_mutex_lock(&cdr_batch_lock); 01156 res = init_batch(); 01157 ast_mutex_unlock(&cdr_batch_lock); 01158 } 01159 01160 return res; 01161 } 01162 01163 /* \note This actually gets called a couple of times at shutdown. Once, before we start 01164 hanging up channels, and then again, after the channel hangup timeout expires */ 01165 void ast_cdr_engine_term(void) 01166 { 01167 ast_cdr_submit_batch(batchsafeshutdown); 01168 } 01169 01170 int ast_cdr_engine_reload(void) 01171 { 01172 return do_reload(); 01173 } 01174