2fd98db2c7443d4d3b041da38035cc6b7806b6fa
[asterisk/asterisk.git] / cdr / cdr_custom.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2009, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * Includes code and algorithms from the Zapata library.
9  *
10  * See http://www.asterisk.org for more information about
11  * the Asterisk project. Please do not directly contact
12  * any of the maintainers of this project for assistance;
13  * the project provides a web site, mailing lists and IRC
14  * channels for your use.
15  *
16  * This program is free software, distributed under the terms of
17  * the GNU General Public License Version 2. See the LICENSE file
18  * at the top of the source tree.
19  */
20
21 /*! \file
22  *
23  * \brief Custom Comma Separated Value CDR records.
24  *
25  * \author Mark Spencer <markster@digium.com>
26  *
27  * \arg See also \ref AstCDR
28  *
29  * Logs in LOG_DIR/cdr_custom
30  * \ingroup cdr_drivers
31  */
32
33 #include "asterisk.h"
34
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36
37 #include <time.h>
38
39 #include "asterisk/paths.h"     /* use ast_config_AST_LOG_DIR */
40 #include "asterisk/channel.h"
41 #include "asterisk/cdr.h"
42 #include "asterisk/module.h"
43 #include "asterisk/config.h"
44 #include "asterisk/pbx.h"
45 #include "asterisk/utils.h"
46 #include "asterisk/lock.h"
47 #include "asterisk/threadstorage.h"
48 #include "asterisk/strings.h"
49
50 #define CUSTOM_LOG_DIR "/cdr_custom"
51 #define CONFIG         "cdr_custom.conf"
52 #define DATE_FORMAT    "%Y-%m-%d %T"
53
54 AST_THREADSTORAGE(custom_buf);
55
56 static char *name = "cdr-custom";
57
58 struct cdr_config {
59         AST_DECLARE_STRING_FIELDS(
60                 AST_STRING_FIELD(filename);
61                 AST_STRING_FIELD(format);
62                 );
63         ast_mutex_t lock;
64         AST_RWLIST_ENTRY(cdr_config) list;
65 };
66
67 static AST_RWLIST_HEAD_STATIC(sinks, cdr_config);
68
69 static void free_config(void)
70 {
71         struct cdr_config *sink;
72         while ((sink = AST_RWLIST_REMOVE_HEAD(&sinks, list))) {
73                 ast_mutex_destroy(&sink->lock);
74                 ast_free(sink);
75         }
76 }
77
78 static int load_config(void)
79 {
80         struct ast_config *cfg;
81         struct ast_variable *var;
82         struct ast_flags config_flags = { 0 };
83         int res = 0;
84
85         cfg = ast_config_load(CONFIG, config_flags);
86         if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
87                 ast_log(LOG_ERROR, "Unable to load " CONFIG ". Not custom CSV CDRs.\n");
88                 return -1;
89         }
90
91         var = ast_variable_browse(cfg, "mappings");
92         while (var) {
93                 if (!ast_strlen_zero(var->name) && !ast_strlen_zero(var->value)) {
94                         struct cdr_config *sink = ast_calloc_with_stringfields(1, struct cdr_config, 1024);
95
96                         if (!sink) {
97                                 ast_log(LOG_ERROR, "Unable to allocate memory for configuration settings.\n");
98                                 res = -2;
99                                 break;
100                         }
101
102                         ast_string_field_build(sink, format, "%s\n", var->value);
103                         ast_string_field_build(sink, filename, "%s/%s/%s", ast_config_AST_LOG_DIR, name, var->name);
104                         ast_mutex_init(&sink->lock);
105
106                         AST_RWLIST_INSERT_TAIL(&sinks, sink, list);
107                 } else {
108                         ast_log(LOG_NOTICE, "Mapping must have both a filename and a format at line %d\n", var->lineno);
109                 }
110                 var = var->next;
111         }
112         ast_config_destroy(cfg);
113
114         return res;
115 }
116
117 static int custom_log(struct ast_cdr *cdr)
118 {
119         struct ast_channel dummy;
120         struct ast_str *str;
121         struct cdr_config *config;
122
123         /* Batching saves memory management here.  Otherwise, it's the same as doing an allocation and free each time. */
124         if (!(str = ast_str_thread_get(&custom_buf, 16))) {
125                 return -1;
126         }
127
128         /* Quite possibly the first use of a static struct ast_channel, we need it so the var funcs will work */
129         memset(&dummy, 0, sizeof(dummy));
130         dummy.cdr = cdr;
131
132         AST_RWLIST_RDLOCK(&sinks);
133
134         AST_LIST_TRAVERSE(&sinks, config, list) {
135                 FILE *out;
136
137                 ast_str_reset(str);
138                 ast_str_substitute_variables(&str, 0, &dummy, config->format);
139
140                 /* Even though we have a lock on the list, we could be being chased by
141                    another thread and this lock ensures that we won't step on anyone's
142                    toes.  Once each CDR backend gets it's own thread, this lock can be
143                    removed. */
144                 ast_mutex_lock(&config->lock);
145
146                 /* Because of the absolutely unconditional need for the
147                    highest reliability possible in writing billing records,
148                    we open write and close the log file each time */
149                 if ((out = fopen(config->filename, "a"))) {
150                         fputs(ast_str_buffer(str), out);
151                         fflush(out); /* be particularly anal here */
152                         fclose(out);
153                 } else {
154                         ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", config->filename, strerror(errno));
155                 }
156
157                 ast_mutex_unlock(&config->lock);
158         }
159
160         AST_RWLIST_UNLOCK(&sinks);
161
162         return 0;
163 }
164
165 static int unload_module(void)
166 {
167         ast_cdr_unregister(name);
168
169         if (AST_RWLIST_WRLOCK(&sinks)) {
170                 ast_cdr_register(name, ast_module_info->description, custom_log);
171                 ast_log(LOG_ERROR, "Unable to lock sink list.  Unload failed.\n");
172                 return -1;
173         }
174
175         free_config();
176         AST_RWLIST_UNLOCK(&sinks);
177         return 0;
178 }
179
180 static int load_module(void)
181 {
182         if (AST_RWLIST_WRLOCK(&sinks)) {
183                 ast_log(LOG_ERROR, "Unable to lock sink list.  Load failed.\n");
184                 return AST_MODULE_LOAD_FAILURE;
185         }
186
187         load_config();
188         AST_RWLIST_UNLOCK(&sinks);
189         ast_cdr_register(name, ast_module_info->description, custom_log);
190         return AST_MODULE_LOAD_SUCCESS;
191 }
192
193 static int reload(void)
194 {
195         if (AST_RWLIST_WRLOCK(&sinks)) {
196                 ast_log(LOG_ERROR, "Unable to lock sink list.  Load failed.\n");
197                 return AST_MODULE_LOAD_FAILURE;
198         }
199
200         free_config();
201         load_config();
202         AST_RWLIST_UNLOCK(&sinks);
203         return AST_MODULE_LOAD_SUCCESS;
204 }
205
206 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Customizable Comma Separated Values CDR Backend",
207                 .load = load_module,
208                 .unload = unload_module,
209                 .reload = reload,
210                );
211