Index: cdr/cdr_csv.c =================================================================== RCS file: /usr/cvsroot/asterisk/cdr/cdr_csv.c,v retrieving revision 1.12 diff -u -r1.12 cdr_csv.c --- cdr/cdr_csv.c 19 Dec 2004 21:13:41 -0000 1.12 +++ cdr/cdr_csv.c 19 Jan 2005 17:59:44 -0000 @@ -20,12 +20,10 @@ #include #include #include +#include #include "../asterisk.h" #include "../astconf.h" -#define CSV_LOG_DIR "/cdr-csv" -#define CSV_MASTER "/Master.csv" - #define DATE_FORMAT "%Y-%m-%d %T" /* #define CSV_LOGUNIQUEID 1 */ @@ -73,6 +71,38 @@ static FILE *mf = NULL; +static struct { + unsigned multiplefiles :1; /* Write one file per accountcode? */ + unsigned loguniqueid :1; /* Write out the unique id? */ + unsigned loguserfield :1; /* Write the user field? */ + unsigned useutc :1; +} settings; + +#define DEFAULT_CSV_LOGDIR "cdr-csv" +#define DEFAULT_CSV_MASTERFILE "Master" +#define DEFAULT_CSV_MASTERFILE_EXT ".csv" + +static char logdir[AST_CONFIG_MAX_PATH] = {0}; +static char masterfile[64]; +static char masterfile_ext[64]; + +static const char *datecode_format; + +AST_MUTEX_DEFINE_STATIC(csv_lock); + +/* Next bit for old compile-time options, to maintain defaults */ +#ifdef CSV_LOGUNIQUEID +#define COMPILED_LOGUNIQUEID 1 +#else +#define COMPILED_LOGUNIQUEID 0 +#endif +#ifdef CSV_LOGUSERFIELD +#define COMPILED_LOGUSERFIELD 1 +#else +#define COMPILED_LOGUSERFIELD 0 +#endif + + static int append_string(char *buf, char *s, size_t bufsize) { int pos = strlen(buf); @@ -124,7 +154,13 @@ strncat(buf, ",", bufsize - strlen(buf) - 1); return 0; } - localtime_r(&t,&tm); + + if (settings.useutc) { + gmtime_r(&t, &tm); + } else { + localtime_r(&t, &tm); + } + strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm); return append_string(buf, tmp, bufsize); } @@ -166,14 +202,15 @@ /* AMA Flags */ append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize); -#ifdef CSV_LOGUNIQUEID - /* Unique ID */ - append_string(buf, cdr->uniqueid, bufsize); -#endif -#ifdef CSV_LOGUSERFIELD - /* append the user field */ - append_string(buf, cdr->userfield,bufsize); -#endif + if (settings.loguniqueid) { + /* Unique ID */ + append_string(buf, cdr->uniqueid, bufsize); + } + if (settings.loguserfield) { + /* append the user field */ + append_string(buf, cdr->userfield, bufsize); + } + /* If we hit the end of our buffer, log an error */ if (strlen(buf) < bufsize - 5) { /* Trim off trailing comma */ @@ -184,56 +221,150 @@ return -1; } -static int writefile(char *s, char *acc) +static int writefile(char *data, char *filename) { - char tmp[AST_CONFIG_MAX_PATH]; - FILE *f; - if (strchr(acc, '/') || (acc[0] == '.')) { - ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc); + /* because of the absolutely unconditional need for the + highest reliability possible in writing billing records, + we open write and close the log file each time */ + mf = fopen(filename, "a"); + if (!mf) { + ast_log(LOG_ERROR, "Unable to open file %s : %s\n", filename, strerror(errno)); return -1; + } else { + fputs(data, mf); + fflush(mf); /* be particularly anal here */ + fclose(mf); + mf = NULL; + return 0; } - snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", (char *)ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc); - f = fopen(tmp, "a"); - if (!f) - return -1; - fputs(s, f); - fflush(f); - fclose(f); - return 0; } +static void build_log_time(char *buffer, size_t size, struct ast_cdr *cdr) +{ + time_t time = cdr->end.tv_sec; + struct tm tm; + + if (datecode_format) { + if (settings.useutc) { + gmtime_r(&time, &tm); + } else { + localtime_r(&time, &tm); + } + strftime(buffer, size, datecode_format, &tm); + } else { + buffer[0] = '\0'; + } +} + static int csv_log(struct ast_cdr *cdr) { + int master_write_result = -1; + char record[1024]; + char datecode[20] = {0}; + char filename[AST_CONFIG_MAX_PATH] = {0}; +/* printf("[CDR] %s ('%s' -> '%s') Dur: %ds Bill: %ds Disp: %s Flags: %s Account: [%s]\n", cdr->channel, cdr->src, cdr->dst, cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), cdr->accountcode);*/ + + ast_mutex_lock(&csv_lock); + /* Make sure we have a big enough buf */ - char buf[1024]; - char csvmaster[AST_CONFIG_MAX_PATH]; - snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER); -#if 0 - printf("[CDR] %s ('%s' -> '%s') Dur: %ds Bill: %ds Disp: %s Flags: %s Account: [%s]\n", cdr->channel, cdr->src, cdr->dst, cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), cdr->accountcode); -#endif - if (build_csv_record(buf, sizeof(buf), cdr)) { - ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes. CDR not recorded!\n", (int)sizeof(buf)); + if (build_csv_record(record, sizeof(record), cdr)) { + ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes. CDR not recorded!\n", (int)sizeof(record)); } else { - /* because of the absolutely unconditional need for the - highest reliability possible in writing billing records, - we open write and close the log file each time */ - mf = fopen(csvmaster, "a"); - if (!mf) { - ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno)); + build_log_time(datecode, sizeof(datecode), cdr); + + snprintf(filename, sizeof(filename) - 1, "%s/%s%s%s", logdir, masterfile, datecode, masterfile_ext); + master_write_result = writefile(record, filename); + + /* Possibly write to account code file */ + if (settings.multiplefiles & !ast_strlen_zero(cdr->accountcode)) { + if (strchr(cdr->accountcode, '/') || (cdr->accountcode[0] == '.')) { + ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", cdr->accountcode); + } else { + snprintf(filename, sizeof(filename) - 1, "%s/%s%s.csv", logdir, cdr->accountcode, datecode); + writefile(record, filename); + } } - if (mf) { - fputs(buf, mf); - fflush(mf); /* be particularly anal here */ - fclose(mf); - mf = NULL; + } + + ast_mutex_unlock(&csv_lock); + return master_write_result; +} + +static void configure(void) +{ + char *var; + struct ast_config *config; + + /* setup defaults */ + settings.loguniqueid = COMPILED_LOGUNIQUEID; + settings.loguserfield = COMPILED_LOGUSERFIELD; + settings.multiplefiles = 1; + settings.useutc = 0; + datecode_format = NULL; + snprintf(logdir, sizeof(logdir) - 1, "%s/%s", ast_config_AST_LOG_DIR, DEFAULT_CSV_LOGDIR); + strncpy(masterfile, DEFAULT_CSV_MASTERFILE, sizeof(masterfile) - 1); + strncpy(masterfile_ext, DEFAULT_CSV_MASTERFILE_EXT, sizeof(masterfile_ext) - 1); + + config = ast_load("cdr_csv.conf"); + if (!config) return; + + /* Split off the extension of the master file, so we can inject the date format */ + var = ast_variable_retrieve(config, "general", "masterfile"); + if (var) { + strncpy(masterfile, var, sizeof(masterfile) - 1); + var = strrchr(masterfile, '.'); + if (var) { + strncpy(masterfile_ext, var, sizeof(masterfile_ext) - 1); + var[0] = '\0'; + } else { + masterfile_ext[0] = '\0'; } - if (!ast_strlen_zero(cdr->accountcode)) { - if (writefile(buf, cdr->accountcode)) - ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno)); + } + + var = ast_variable_retrieve(config, "general", "logdir"); + if (var) { + if (var[0] != '/') { /* Append to Asterisk log dir if path not rooted. */ + snprintf(logdir, sizeof(logdir) - 1, "%s/%s", ast_config_AST_LOG_DIR, var); + } else { + strncpy(logdir, var, sizeof(logdir) - 1); } } - return 0; + + var = ast_variable_retrieve(config, "general", "loguniqueid"); + if (var) settings.loguniqueid = ast_true(var); + + var = ast_variable_retrieve(config, "general", "loguserfield"); + if (var) settings.loguserfield = ast_true(var); + + var = ast_variable_retrieve(config, "general", "multiplefiles"); + if (var) settings.multiplefiles = ast_true(var); + + var = ast_variable_retrieve(config, "general", "useutc"); + if (var) settings.useutc = ast_true(var); + + var = ast_variable_retrieve(config, "general", "newlogperiod"); + if (var) { + switch(var[0]) { + case 'h': + case 'H': + datecode_format = "%Y-%m-%d-%H"; + break; + case 'd': + case 'D': + datecode_format = "%Y-%m-%d"; + break; + case 'm': + case 'M': + datecode_format = "%Y-%m"; + break; + default : + datecode_format = NULL; + } + } + + ast_destroy(config); + return; } char *description(void) @@ -243,9 +374,11 @@ int unload_module(void) { + ast_cdr_unregister(name); + ast_mutex_lock(&csv_lock); if (mf) fclose(mf); - ast_cdr_unregister(name); + ast_mutex_unlock(&csv_lock); return 0; } @@ -253,17 +386,24 @@ { int res; + configure(); + ast_log(LOG_DEBUG, "master: %s ext: %s dir: %s", masterfile, masterfile_ext, logdir); + res = ast_cdr_register(name, desc, csv_log); if (res) { ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n"); if (mf) fclose(mf); + return res; } - return res; + return 0; } int reload(void) { + ast_mutex_lock(&csv_lock); + configure(); + ast_mutex_unlock(&csv_lock); return 0; }