Merge "app_stasis: Don't hang up if app is not registered"
[asterisk/asterisk.git] / addons / cdr_mysql.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * James Sharp <jsharp@psychoses.org>
5  *
6  * Modified August 2003
7  * Tilghman Lesher <asterisk__cdr__cdr_mysql__200308@the-tilghman.com>
8  *
9  * Modified August 6, 2005
10  * Joseph Benden <joe@thrallingpenguin.com>
11  * Added mysql connection timeout parameter
12  * Added an automatic reconnect as to not lose a cdr record
13  * Cleaned up the original code to match the coding guidelines
14  *
15  * Modified Juli 2006
16  * Martin Portmann <map@infinitum.ch>
17  * Added mysql ssl support
18  *
19  * See http://www.asterisk.org for more information about
20  * the Asterisk project. Please do not directly contact
21  * any of the maintainers of this project for assistance;
22  * the project provides a web site, mailing lists and IRC
23  * channels for your use.
24  *
25  * This program is free software, distributed under the terms of
26  * the GNU General Public License Version 2. See the LICENSE file
27  * at the top of the source tree.
28  */
29
30 /*!
31  * \file
32  * \brief MySQL CDR backend
33  * \ingroup cdr_drivers
34  */
35
36 /*** MODULEINFO
37         <depend>mysqlclient</depend>
38         <defaultenabled>no</defaultenabled>
39         <support_level>deprecated</support_level>
40         <replacement>cdr_adaptive_odbc</replacement>
41  ***/
42
43 #include "asterisk.h"
44
45 ASTERISK_REGISTER_FILE()
46
47 #include <mysql/mysql.h>
48 #include <mysql/errmsg.h>
49
50 #include "asterisk/config.h"
51 #include "asterisk/options.h"
52 #include "asterisk/channel.h"
53 #include "asterisk/cdr.h"
54 #include "asterisk/module.h"
55 #include "asterisk/logger.h"
56 #include "asterisk/cli.h"
57 #include "asterisk/strings.h"
58 #include "asterisk/linkedlists.h"
59 #include "asterisk/threadstorage.h"
60
61 #define DATE_FORMAT "%Y-%m-%d %T"
62
63 AST_THREADSTORAGE(sql1_buf);
64 AST_THREADSTORAGE(sql2_buf);
65 AST_THREADSTORAGE(escape_buf);
66
67 static const char desc[] = "MySQL CDR Backend";
68 static const char name[] = "mysql";
69 static const char config[] = "cdr_mysql.conf";
70
71 static struct ast_str *hostname = NULL, *dbname = NULL, *dbuser = NULL, *password = NULL, *dbsock = NULL, *dbtable = NULL, *dbcharset = NULL, *cdrzone = NULL;
72
73 static struct ast_str *ssl_ca = NULL, *ssl_cert = NULL, *ssl_key = NULL;
74
75 static int dbport = 0;
76 static int connected = 0;
77 static time_t connect_time = 0;
78 static int records = 0;
79 static int totalrecords = 0;
80 static int timeout = 0;
81 static int calldate_compat = 0;
82
83 AST_MUTEX_DEFINE_STATIC(mysql_lock);
84
85 struct unload_string {
86         AST_LIST_ENTRY(unload_string) entry;
87         struct ast_str *str;
88 };
89
90 static AST_LIST_HEAD_STATIC(unload_strings, unload_string);
91
92 struct column {
93         char *name;
94         char *cdrname;
95         char *staticvalue;
96         char *type;
97         AST_LIST_ENTRY(column) list;
98 };
99
100 /* Protected with mysql_lock */
101 static AST_RWLIST_HEAD_STATIC(columns, column);
102
103 static MYSQL mysql = { { NULL }, };
104
105 static char *handle_cli_cdr_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
106 {
107         switch (cmd) {
108         case CLI_INIT:
109                 e->command = "cdr mysql status";
110                 e->usage =
111                         "Usage: cdr mysql status\n"
112                         "       Shows current connection status for cdr_mysql\n";
113                 return NULL;
114         case CLI_GENERATE:
115                 return NULL;
116         }
117
118         if (a->argc != 3)
119                 return CLI_SHOWUSAGE;
120
121         if (connected) {
122                 char status[256];
123                 char status2[100] = "";
124                 char buf[362]; /* 256+100+" for "+NULL */
125                 int ctime = time(NULL) - connect_time;
126                 if (dbport)
127                         snprintf(status, 255, "Connected to %s@%s, port %d", ast_str_buffer(dbname), ast_str_buffer(hostname), dbport);
128                 else if (dbsock)
129                         snprintf(status, 255, "Connected to %s on socket file %s", ast_str_buffer(dbname), S_OR(ast_str_buffer(dbsock), "default"));
130                 else
131                         snprintf(status, 255, "Connected to %s@%s", ast_str_buffer(dbname), ast_str_buffer(hostname));
132
133                 if (!ast_strlen_zero(ast_str_buffer(dbuser)))
134                         snprintf(status2, 99, " with username %s", ast_str_buffer(dbuser));
135                 if (ast_str_strlen(dbtable))
136                         snprintf(status2, 99, " using table %s", ast_str_buffer(dbtable));
137
138                 snprintf(buf, sizeof(buf), "%s%s for ", status, status2);
139                 ast_cli_print_timestr_fromseconds(a->fd, ctime, buf);
140
141                 if (records == totalrecords)
142                         ast_cli(a->fd, "  Wrote %d records since last restart.\n", totalrecords);
143                 else
144                         ast_cli(a->fd, "  Wrote %d records since last restart and %d records since last reconnect.\n", totalrecords, records);
145         } else {
146                 ast_cli(a->fd, "Not currently connected to a MySQL server.\n");
147         }
148
149         return CLI_SUCCESS;
150 }
151
152 static struct ast_cli_entry cdr_mysql_status_cli[] = {
153         AST_CLI_DEFINE(handle_cli_cdr_mysql_status, "Show connection status of cdr_mysql"),
154 };
155
156 static int mysql_log(struct ast_cdr *cdr)
157 {
158         struct ast_str *sql1 = ast_str_thread_get(&sql1_buf, 1024), *sql2 = ast_str_thread_get(&sql2_buf, 1024);
159         int retries = 5;
160 #if MYSQL_VERSION_ID >= 50013
161         my_bool my_bool_true = 1;
162 #endif
163
164         if (!sql1 || !sql2) {
165                 ast_log(LOG_ERROR, "Memory error\n");
166                 return -1;
167         }
168
169         ast_mutex_lock(&mysql_lock);
170
171 db_reconnect:
172         if ((!connected) && (hostname || dbsock) && dbuser && password && dbname && dbtable ) {
173                 /* Attempt to connect */
174                 mysql_init(&mysql);
175                 /* Add option to quickly timeout the connection */
176                 if (timeout && mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *)&timeout) != 0) {
177                         ast_log(LOG_ERROR, "mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
178                 }
179 #if MYSQL_VERSION_ID >= 50013
180                 /* Add option for automatic reconnection */
181                 if (mysql_options(&mysql, MYSQL_OPT_RECONNECT, &my_bool_true) != 0) {
182                         ast_log(LOG_ERROR, "mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
183                 }
184 #endif
185                 if (ssl_ca || ssl_cert || ssl_key) {
186                         mysql_ssl_set(&mysql, ssl_key ? ast_str_buffer(ssl_key) : NULL, ssl_cert ? ast_str_buffer(ssl_cert) : NULL, ssl_ca ? ast_str_buffer(ssl_ca) : NULL, NULL, NULL);
187                 }
188                 if (mysql_real_connect(&mysql, ast_str_buffer(hostname), ast_str_buffer(dbuser), ast_str_buffer(password), ast_str_buffer(dbname), dbport, dbsock && ast_str_strlen(dbsock) ? ast_str_buffer(dbsock) : NULL, ssl_ca ? CLIENT_SSL : 0)) {
189                         connected = 1;
190                         connect_time = time(NULL);
191                         records = 0;
192                         if (dbcharset) {
193                                 ast_str_set(&sql1, 0, "SET NAMES '%s'", ast_str_buffer(dbcharset));
194                                 mysql_real_query(&mysql, ast_str_buffer(sql1), ast_str_strlen(sql1));
195                                 ast_debug(1, "SQL command as follows: %s\n", ast_str_buffer(sql1));
196                         }
197                 } else {
198                         ast_log(LOG_ERROR, "Cannot connect to database server %s: (%d) %s\n", ast_str_buffer(hostname), mysql_errno(&mysql), mysql_error(&mysql));
199                         connected = 0;
200                 }
201         } else {
202                 /* Long connection - ping the server */
203                 int error;
204                 if ((error = mysql_ping(&mysql))) {
205                         connected = 0;
206                         records = 0;
207                         switch (mysql_errno(&mysql)) {
208                                 case CR_SERVER_GONE_ERROR:
209                                 case CR_SERVER_LOST:
210                                         ast_log(LOG_ERROR, "Server has gone away. Attempting to reconnect.\n");
211                                         break;
212                                 default:
213                                         ast_log(LOG_ERROR, "Unknown connection error: (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
214                         }
215                         retries--;
216                         if (retries) {
217                                 goto db_reconnect;
218                         } else {
219                                 ast_log(LOG_ERROR, "Retried to connect five times, giving up.\n");
220                         }
221                 }
222         }
223
224         if (connected) {
225                 int column_count = 0;
226                 char *cdrname;
227                 char workspace[2048], *value = NULL;
228                 struct column *entry;
229                 struct ast_str *escape = ast_str_thread_get(&escape_buf, 16);
230
231                 ast_str_set(&sql1, 0, "INSERT INTO %s (", AS_OR(dbtable, "cdr"));
232                 ast_str_set(&sql2, 0, ") VALUES (");
233
234                 AST_RWLIST_RDLOCK(&columns);
235                 AST_RWLIST_TRAVERSE(&columns, entry, list) {
236                         if (!strcmp(entry->name, "calldate")) {
237                                 /*!\note
238                                  * For some dumb reason, "calldate" used to be formulated using
239                                  * the datetime the record was posted, rather than the start
240                                  * time of the call.  If someone really wants the old compatible
241                                  * behavior, it's provided here.
242                                  */
243                                 if (calldate_compat) {
244                                         struct timeval tv = ast_tvnow();
245                                         struct ast_tm tm;
246                                         char timestr[128];
247                                         ast_localtime(&tv, &tm, ast_str_strlen(cdrzone) ? ast_str_buffer(cdrzone) : NULL);
248                                         ast_strftime(timestr, sizeof(timestr), "%Y-%m-%d %T", &tm);
249                                         value = ast_strdupa(timestr);
250                                         cdrname = "calldate";
251                                 } else {
252                                         cdrname = "start";
253                                 }
254                         } else {
255                                 cdrname = entry->cdrname;
256                         }
257
258                         /* Construct SQL */
259
260                         /* Need the type and value to determine if we want the raw value or not */
261                         if (entry->staticvalue) {
262                                 value = ast_strdupa(entry->staticvalue);
263                         } else if ((!strcmp(cdrname, "start") ||
264                                  !strcmp(cdrname, "answer") ||
265                                  !strcmp(cdrname, "end") ||
266                                  !strcmp(cdrname, "disposition") ||
267                                  !strcmp(cdrname, "amaflags")) &&
268                                 (strstr(entry->type, "int") ||
269                                  strstr(entry->type, "dec") ||
270                                  strstr(entry->type, "float") ||
271                                  strstr(entry->type, "double") ||
272                                  strstr(entry->type, "real") ||
273                                  strstr(entry->type, "numeric") ||
274                                  strstr(entry->type, "fixed"))) {
275                                 ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 1);
276                         } else if (!strcmp(cdrname, "calldate")) {
277                                 /* Skip calldate - the value has already been dup'd */
278                         } else {
279                                 ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 0);
280                         }
281
282                         if (value) {
283                                 size_t valsz;
284
285                                 if (column_count++) {
286                                         ast_str_append(&sql1, 0, ",");
287                                         ast_str_append(&sql2, 0, ",");
288                                 }
289
290                                 if (!strcasecmp(cdrname, "billsec") &&
291                                         (strstr(entry->type, "float") ||
292                                         strstr(entry->type, "double") ||
293                                         strstr(entry->type, "decimal") ||
294                                         strstr(entry->type, "numeric") ||
295                                         strstr(entry->type, "real"))) {
296
297                                         if (!ast_tvzero(cdr->answer)) {
298                                                 snprintf(workspace, sizeof(workspace), "%lf",
299                                                         (double) (ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0));
300                                         } else {
301                                                 ast_copy_string(workspace, "0", sizeof(workspace));
302                                         }
303
304                                         if (!ast_strlen_zero(workspace)) {
305                                                 value = workspace;
306                                         }
307                                 }
308
309                                 if (!strcasecmp(cdrname, "duration") &&
310                                         (strstr(entry->type, "float") ||
311                                         strstr(entry->type, "double") ||
312                                         strstr(entry->type, "decimal") ||
313                                         strstr(entry->type, "numeric") ||
314                                         strstr(entry->type, "real"))) {
315
316                                         snprintf(workspace, sizeof(workspace), "%lf",
317                                                 (double) (ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0));
318
319                                         if (!ast_strlen_zero(workspace)) {
320                                                 value = workspace;
321                                         }
322                                 }
323
324                                 ast_str_make_space(&escape, (valsz = strlen(value)) * 2 + 1);
325                                 mysql_real_escape_string(&mysql, ast_str_buffer(escape), value, valsz);
326
327                                 ast_str_append(&sql1, 0, "`%s`", entry->name);
328                                 ast_str_append(&sql2, 0, "'%s'", ast_str_buffer(escape));
329                         }
330                 }
331                 AST_RWLIST_UNLOCK(&columns);
332
333                 ast_debug(1, "Inserting a CDR record.\n");
334                 ast_str_append(&sql1, 0, "%s)", ast_str_buffer(sql2));
335
336                 ast_debug(1, "SQL command as follows: %s\n", ast_str_buffer(sql1));
337
338                 if (mysql_real_query(&mysql, ast_str_buffer(sql1), ast_str_strlen(sql1))) {
339                         ast_log(LOG_ERROR, "Failed to insert into database: (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
340                         mysql_close(&mysql);
341                         connected = 0;
342                 } else {
343                         records++;
344                         totalrecords++;
345                 }
346         }
347         ast_mutex_unlock(&mysql_lock);
348         return 0;
349 }
350
351 static int my_unload_module(int reload)
352
353         struct unload_string *us;
354         struct column *entry;
355
356         ast_cli_unregister_multiple(cdr_mysql_status_cli, sizeof(cdr_mysql_status_cli) / sizeof(struct ast_cli_entry));
357
358         if (connected) {
359                 mysql_close(&mysql);
360                 connected = 0;
361                 records = 0;
362         }
363
364         AST_LIST_LOCK(&unload_strings);
365         while ((us = AST_LIST_REMOVE_HEAD(&unload_strings, entry))) {
366                 ast_free(us->str);
367                 ast_free(us);
368         }
369         AST_LIST_UNLOCK(&unload_strings);
370
371         if (!reload) {
372                 AST_RWLIST_WRLOCK(&columns);
373         }
374         while ((entry = AST_RWLIST_REMOVE_HEAD(&columns, list))) {
375                 ast_free(entry);
376         }
377         if (!reload) {
378                 AST_RWLIST_UNLOCK(&columns);
379         }
380
381         dbport = 0;
382         if (reload) {
383                 return ast_cdr_backend_suspend(name);
384         } else {
385                 return ast_cdr_unregister(name);
386         }
387 }
388
389 static int my_load_config_string(struct ast_config *cfg, const char *category, const char *variable, struct ast_str **field, const char *def)
390 {
391         struct unload_string *us;
392         const char *tmp;
393
394         if (!(us = ast_calloc(1, sizeof(*us))))
395                 return -1;
396
397         if (!(*field = ast_str_create(16))) {
398                 ast_free(us);
399                 return -1;
400         }
401
402         tmp = ast_variable_retrieve(cfg, category, variable);
403
404         ast_str_set(field, 0, "%s", tmp ? tmp : def);
405
406         us->str = *field;
407
408         AST_LIST_LOCK(&unload_strings);
409         AST_LIST_INSERT_HEAD(&unload_strings, us, entry);
410         AST_LIST_UNLOCK(&unload_strings);
411
412         return 0;
413 }
414
415 static int my_load_config_number(struct ast_config *cfg, const char *category, const char *variable, int *field, int def)
416 {
417         const char *tmp;
418
419         tmp = ast_variable_retrieve(cfg, category, variable);
420
421         if (!tmp || sscanf(tmp, "%30d", field) < 1)
422                 *field = def;
423
424         return 0;
425 }
426
427 static int my_load_module(int reload)
428 {
429         int res;
430         struct ast_config *cfg;
431         struct ast_variable *var;
432         /* CONFIG_STATUS_FILEUNCHANGED is impossible when config_flags is always 0,
433          * and it has to be zero, so a reload can be sent to tell the driver to
434          * rescan the table layout. */
435         struct ast_flags config_flags = { 0 };
436         struct column *entry;
437         char *temp;
438         struct ast_str *compat;
439         MYSQL_ROW row;
440         MYSQL_RES *result;
441         char sqldesc[128];
442 #if MYSQL_VERSION_ID >= 50013
443         my_bool my_bool_true = 1;
444 #endif
445
446         /* Cannot use a conditionally different flag, because the table layout may
447          * have changed, which is not detectable by config file change detection,
448          * but should still cause the configuration to be re-parsed. */
449         cfg = ast_config_load(config, config_flags);
450         if (cfg == CONFIG_STATUS_FILEMISSING) {
451                 ast_log(LOG_WARNING, "Unable to load config for mysql CDR's: %s\n", config);
452                 return AST_MODULE_LOAD_SUCCESS;
453         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
454                 ast_log(LOG_ERROR, "Unable to load configuration file '%s'\n", config);
455                 return AST_MODULE_LOAD_DECLINE;
456         }
457
458         if (reload) {
459                 AST_RWLIST_WRLOCK(&columns);
460                 my_unload_module(1);
461         }
462
463         var = ast_variable_browse(cfg, "global");
464         if (!var) {
465                 /* nothing configured */
466                 if (reload) {
467                         AST_RWLIST_UNLOCK(&columns);
468                 }
469                 ast_config_destroy(cfg);
470                 return AST_MODULE_LOAD_SUCCESS;
471         }
472
473         res = 0;
474
475         res |= my_load_config_string(cfg, "global", "hostname", &hostname, "localhost");
476         res |= my_load_config_string(cfg, "global", "dbname", &dbname, "astriskcdrdb");
477         res |= my_load_config_string(cfg, "global", "user", &dbuser, "root");
478         res |= my_load_config_string(cfg, "global", "sock", &dbsock, "");
479         res |= my_load_config_string(cfg, "global", "table", &dbtable, "cdr");
480         res |= my_load_config_string(cfg, "global", "password", &password, "");
481
482         res |= my_load_config_string(cfg, "global", "charset", &dbcharset, "");
483
484         res |= my_load_config_string(cfg, "global", "ssl_ca", &ssl_ca, "");
485         res |= my_load_config_string(cfg, "global", "ssl_cert", &ssl_cert, "");
486         res |= my_load_config_string(cfg, "global", "ssl_key", &ssl_key, "");
487
488         res |= my_load_config_number(cfg, "global", "port", &dbport, 0);
489         res |= my_load_config_number(cfg, "global", "timeout", &timeout, 0);
490         res |= my_load_config_string(cfg, "global", "compat", &compat, "no");
491         res |= my_load_config_string(cfg, "global", "cdrzone", &cdrzone, "");
492         if (ast_str_strlen(cdrzone) == 0) {
493                 for (; var; var = var->next) {
494                         if (!strcasecmp(var->name, "usegmtime") && ast_true(var->value)) {
495                                 ast_str_set(&cdrzone, 0, "UTC");
496                         }
497                 }
498         }
499
500         if (ast_true(ast_str_buffer(compat))) {
501                 calldate_compat = 1;
502         } else {
503                 calldate_compat = 0;
504         }
505
506         if (res < 0) {
507                 if (reload) {
508                         AST_RWLIST_UNLOCK(&columns);
509                 }
510                 ast_config_destroy(cfg);
511                 return AST_MODULE_LOAD_FAILURE;
512         }
513
514         /* Check for any aliases */
515         if (!reload) {
516                 /* Lock, if not already */
517                 AST_RWLIST_WRLOCK(&columns);
518         }
519         while ((entry = AST_LIST_REMOVE_HEAD(&columns, list))) {
520                 ast_free(entry);
521         }
522
523         ast_debug(1, "Got hostname of %s\n", ast_str_buffer(hostname));
524         ast_debug(1, "Got port of %d\n", dbport);
525         ast_debug(1, "Got a timeout of %d\n", timeout);
526         if (dbsock)
527                 ast_debug(1, "Got sock file of %s\n", ast_str_buffer(dbsock));
528         ast_debug(1, "Got user of %s\n", ast_str_buffer(dbuser));
529         ast_debug(1, "Got dbname of %s\n", ast_str_buffer(dbname));
530         ast_debug(1, "Got password of %s\n", ast_str_buffer(password));
531         ast_debug(1, "%sunning in calldate compatibility mode\n", calldate_compat ? "R" : "Not r");
532         ast_debug(1, "Dates and times are localized to %s\n", S_OR(ast_str_buffer(cdrzone), "local timezone"));
533
534         if (dbcharset) {
535                 ast_debug(1, "Got DB charset of %s\n", ast_str_buffer(dbcharset));
536         }
537
538         mysql_init(&mysql);
539
540         if (timeout && mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *)&timeout) != 0) {
541                 ast_log(LOG_ERROR, "cdr_mysql: mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
542         }
543
544 #if MYSQL_VERSION_ID >= 50013
545         /* Add option for automatic reconnection */
546         if (mysql_options(&mysql, MYSQL_OPT_RECONNECT, &my_bool_true) != 0) {
547                 ast_log(LOG_ERROR, "cdr_mysql: mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
548         }
549 #endif
550
551         if ((ssl_ca && ast_str_strlen(ssl_ca)) || (ssl_cert && ast_str_strlen(ssl_cert)) || (ssl_key && ast_str_strlen(ssl_key))) {
552                 mysql_ssl_set(&mysql,
553                         ssl_key ? ast_str_buffer(ssl_key) : NULL,
554                         ssl_cert ? ast_str_buffer(ssl_cert) : NULL,
555                         ssl_ca ? ast_str_buffer(ssl_ca) : NULL,
556                         NULL, NULL);
557         }
558         temp = dbsock && ast_str_strlen(dbsock) ? ast_str_buffer(dbsock) : NULL;
559         if (!mysql_real_connect(&mysql, ast_str_buffer(hostname), ast_str_buffer(dbuser), ast_str_buffer(password), ast_str_buffer(dbname), dbport, temp, ssl_ca && ast_str_strlen(ssl_ca) ? CLIENT_SSL : 0)) {
560                 ast_log(LOG_ERROR, "Failed to connect to mysql database %s on %s.\n", ast_str_buffer(dbname), ast_str_buffer(hostname));
561                 connected = 0;
562                 records = 0;
563         } else {
564                 ast_debug(1, "Successfully connected to MySQL database.\n");
565                 connected = 1;
566                 records = 0;
567                 connect_time = time(NULL);
568                 if (dbcharset) {
569                         snprintf(sqldesc, sizeof(sqldesc), "SET NAMES '%s'", ast_str_buffer(dbcharset));
570                         mysql_real_query(&mysql, sqldesc, strlen(sqldesc));
571                         ast_debug(1, "SQL command as follows: %s\n", sqldesc);
572                 }
573
574                 /* Get table description */
575                 snprintf(sqldesc, sizeof(sqldesc), "DESC %s", dbtable ? ast_str_buffer(dbtable) : "cdr");
576                 if (mysql_query(&mysql, sqldesc)) {
577                         ast_log(LOG_ERROR, "Unable to query table description!!  Logging disabled.\n");
578                         mysql_close(&mysql);
579                         connected = 0;
580                         AST_RWLIST_UNLOCK(&columns);
581                         ast_config_destroy(cfg);
582                         return AST_MODULE_LOAD_FAILURE;
583                 }
584
585                 if (!(result = mysql_store_result(&mysql))) {
586                         ast_log(LOG_ERROR, "Unable to query table description!!  Logging disabled.\n");
587                         mysql_close(&mysql);
588                         connected = 0;
589                         AST_RWLIST_UNLOCK(&columns);
590                         ast_config_destroy(cfg);
591                         return AST_MODULE_LOAD_FAILURE;
592                 }
593
594                 while ((row = mysql_fetch_row(result))) {
595                         struct column *entry;
596                         char *cdrvar = "", *staticvalue = "";
597
598                         ast_debug(1, "Got a field '%s' of type '%s'\n", row[0], row[1]);
599                         /* Check for an alias or a static value */
600                         for (var = ast_variable_browse(cfg, "columns"); var; var = var->next) {
601                                 if (strncmp(var->name, "alias", 5) == 0 && strcasecmp(var->value, row[0]) == 0 ) {
602                                         char *alias = ast_strdupa(var->name + 5);
603                                         cdrvar = ast_strip(alias);
604                                         ast_verb(3, "Found alias %s for column %s\n", cdrvar, row[0]);
605                                         break;
606                                 } else if (strncmp(var->name, "static", 6) == 0 && strcasecmp(var->value, row[0]) == 0) {
607                                         char *item = ast_strdupa(var->name + 6);
608                                         item = ast_strip(item);
609                                         if (item[0] == '"' && item[strlen(item) - 1] == '"') {
610                                                 /* Remove surrounding quotes */
611                                                 item[strlen(item) - 1] = '\0';
612                                                 item++;
613                                         }
614                                         staticvalue = item;
615                                 }
616                         }
617
618                         entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(row[0]) + 1 + strlen(cdrvar) + 1 + strlen(staticvalue) + 1 + strlen(row[1]) + 1);
619                         if (!entry) {
620                                 ast_log(LOG_ERROR, "Out of memory creating entry for column '%s'\n", row[0]);
621                                 res = -1;
622                                 break;
623                         }
624
625                         entry->name = (char *)entry + sizeof(*entry);
626                         strcpy(entry->name, row[0]);
627
628                         if (!ast_strlen_zero(cdrvar)) {
629                                 entry->cdrname = entry->name + strlen(row[0]) + 1;
630                                 strcpy(entry->cdrname, cdrvar);
631                         } else { /* Point to same place as the column name */
632                                 entry->cdrname = (char *)entry + sizeof(*entry);
633                         }
634
635                         if (!ast_strlen_zero(staticvalue)) {
636                                 entry->staticvalue = entry->cdrname + strlen(entry->cdrname) + 1;
637                                 strcpy(entry->staticvalue, staticvalue);
638                                 ast_debug(1, "staticvalue length: %d\n", (int) strlen(staticvalue) );
639                                 entry->type = entry->staticvalue + strlen(entry->staticvalue) + 1;
640                         } else {
641                                 entry->type = entry->cdrname + strlen(entry->cdrname) + 1;
642                         }
643                         strcpy(entry->type, row[1]);
644
645                         ast_debug(1, "Entry name '%s'\n", entry->name);
646                         ast_debug(1, "   cdrname '%s'\n", entry->cdrname);
647                         ast_debug(1, "    static '%s'\n", entry->staticvalue);
648                         ast_debug(1, "      type '%s'\n", entry->type);
649
650                         AST_LIST_INSERT_TAIL(&columns, entry, list);
651                 }
652                 mysql_free_result(result);
653         }
654         AST_RWLIST_UNLOCK(&columns);
655         ast_config_destroy(cfg);
656         if (res < 0) {
657                 return AST_MODULE_LOAD_FAILURE;
658         }
659
660         if (!reload) {
661                 res = ast_cdr_register(name, desc, mysql_log);
662         } else {
663                 res = ast_cdr_backend_unsuspend(name);
664         }
665         if (res) {
666                 ast_log(LOG_ERROR, "Unable to register MySQL CDR handling\n");
667         } else {
668                 res = ast_cli_register_multiple(cdr_mysql_status_cli, sizeof(cdr_mysql_status_cli) / sizeof(struct ast_cli_entry));
669         }
670
671         return res;
672 }
673
674 static int load_module(void)
675 {
676         return my_load_module(0);
677 }
678
679 static int unload_module(void)
680 {
681         return my_unload_module(0);
682 }
683
684 static int reload(void)
685 {
686         int ret;
687
688         ast_mutex_lock(&mysql_lock);
689         ret = my_load_module(1);
690         ast_mutex_unlock(&mysql_lock);
691
692         return ret;
693 }
694
695 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "MySQL CDR Backend",
696         .support_level = AST_MODULE_SUPPORT_DEPRECATED,
697         .load = load_module,
698         .unload = unload_module,
699         .reload = reload,
700 );
701