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