Merged revisions 377506,377512 via svnmerge from
[asterisk/asterisk.git] / addons / res_config_mysql.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999-2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>  - Asterisk Author
7  * Matthew Boehm <mboehm@cytelcom.com> - MySQL RealTime Driver Author
8  *
9  * See http://www.asterisk.org for more information about
10  * the Asterisk project. Please do not directly contact
11  * any of the maintainers of this project for assistance;
12  * the project provides a web site, mailing lists and IRC
13  * channels for your use.
14  *
15  * This program is free software, distributed under the terms of
16  * the GNU General Public License Version 2. See the LICENSE file
17  * at the top of the source tree.
18  */
19
20 /*!
21  * \file
22  * \brief MySQL CDR backend
23  */
24
25 /*** MODULEINFO
26         <depend>mysqlclient</depend>
27         <defaultenabled>no</defaultenabled>
28         <support_level>extended</support_level>
29  ***/
30
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include <sys/stat.h>
36
37 #include <mysql/mysql.h>
38 #include <mysql/mysql_version.h>
39 #include <mysql/errmsg.h>
40
41 #include "asterisk/channel.h"
42 #include "asterisk/logger.h"
43 #include "asterisk/config.h"
44 #include "asterisk/module.h"
45 #include "asterisk/lock.h"
46 #include "asterisk/options.h"
47 #include "asterisk/cli.h"
48 #include "asterisk/utils.h"
49 #include "asterisk/threadstorage.h"
50 #include "asterisk/strings.h"
51
52 #define RES_CONFIG_MYSQL_CONF "res_config_mysql.conf"
53 #define RES_CONFIG_MYSQL_CONF_OLD "res_mysql.conf"
54 #define READHANDLE      0
55 #define WRITEHANDLE     1
56
57 #define ESCAPE_STRING(buf, var) \
58         do { \
59                 struct ast_str *semi = ast_str_thread_get(&scratch2_buf, strlen(var) * 3 + 1); \
60                 const char *chunk = var; \
61                 ast_str_reset(semi); \
62                 for (; *chunk; chunk++) { \
63                         if (strchr(";^", *chunk)) { \
64                                 ast_str_append(&semi, 0, "^%02hhX", *chunk); \
65                         } else { \
66                                 ast_str_append(&semi, 0, "%c", *chunk); \
67                         } \
68                 } \
69                 if (ast_str_strlen(semi) * 2 + 1 > ast_str_size(buf)) { \
70                         ast_str_make_space(&(buf), ast_str_strlen(semi) * 2 + 1); \
71                 } \
72                 mysql_real_escape_string(&dbh->handle, ast_str_buffer(buf), ast_str_buffer(semi), ast_str_strlen(semi)); \
73         } while (0)
74
75 AST_THREADSTORAGE(sql_buf);
76 AST_THREADSTORAGE(sql2_buf);
77 AST_THREADSTORAGE(find_buf);
78 AST_THREADSTORAGE(scratch_buf);
79 AST_THREADSTORAGE(scratch2_buf);
80 AST_THREADSTORAGE(modify_buf);
81 AST_THREADSTORAGE(modify2_buf);
82 AST_THREADSTORAGE(modify3_buf);
83
84 enum requirements { RQ_WARN, RQ_CREATECLOSE, RQ_CREATECHAR };
85
86 struct mysql_conn {
87         AST_RWLIST_ENTRY(mysql_conn) list;
88         ast_mutex_t     lock;
89         MYSQL       handle;
90         char        host[50];
91         char        name[50];
92         char        user[50];
93         char        pass[50];
94         char        sock[50];
95         char        charset[50];
96         int         port;
97         int         connected;
98         time_t      connect_time;
99         enum requirements requirements;
100         char        unique_name[0];
101 };
102
103 struct columns {
104         char *name;
105         char *type;
106         char *dflt;
107         char null;
108         int len;
109         AST_LIST_ENTRY(columns) list;
110 };
111
112 struct tables {
113         ast_mutex_t lock;
114         AST_LIST_HEAD_NOLOCK(mysql_columns, columns) columns;
115         AST_LIST_ENTRY(tables) list;
116         struct mysql_conn *database;
117         char name[0];
118 };
119
120 static AST_LIST_HEAD_STATIC(mysql_tables, tables);
121 static AST_RWLIST_HEAD_STATIC(databases, mysql_conn);
122
123 static int parse_config(int reload);
124 static int mysql_reconnect(struct mysql_conn *conn);
125 static char *handle_cli_realtime_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
126 static char *handle_cli_realtime_mysql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
127 static int load_mysql_config(struct ast_config *config, const char *category, struct mysql_conn *conn);
128 static int require_mysql(const char *database, const char *tablename, va_list ap);
129 static int internal_require(const char *database, const char *table, ...) attribute_sentinel;
130
131 static struct ast_cli_entry cli_realtime_mysql_status[] = {
132         AST_CLI_DEFINE(handle_cli_realtime_mysql_status, "Shows connection information for the MySQL RealTime driver"),
133         AST_CLI_DEFINE(handle_cli_realtime_mysql_cache, "Shows cached tables within the MySQL realtime driver"),
134 };
135
136 static struct mysql_conn *find_database(const char *database, int for_write)
137 {
138         char *whichdb;
139         const char *ptr;
140         struct mysql_conn *cur;
141
142         if ((ptr = strchr(database, '/'))) {
143                 /* Multiple databases encoded within string */
144                 if (for_write) {
145                         whichdb = ast_strdupa(ptr + 1);
146                 } else {
147                         whichdb = ast_alloca(ptr - database + 1);
148                         strncpy(whichdb, database, ptr - database);
149                         whichdb[ptr - database] = '\0';
150                 }
151         } else {
152                 whichdb = ast_strdupa(database);
153         }
154
155         AST_RWLIST_RDLOCK(&databases);
156         AST_RWLIST_TRAVERSE(&databases, cur, list) {
157                 if (!strcmp(cur->unique_name, whichdb)) {
158                         ast_mutex_lock(&cur->lock);
159                         break;
160                 }
161         }
162         AST_RWLIST_UNLOCK(&databases);
163         return cur;
164 }
165
166 #define release_database(a)     ast_mutex_unlock(&(a)->lock)
167
168 static int internal_require(const char *database, const char *table, ...)
169 {
170         va_list ap;
171         int res;
172         va_start(ap, table);
173         res = require_mysql(database, table, ap);
174         va_end(ap);
175         return res;
176 }
177
178 static void destroy_table(struct tables *table)
179 {
180         struct columns *column;
181         ast_mutex_lock(&table->lock);
182         while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
183                 ast_free(column);
184         }
185         ast_mutex_unlock(&table->lock);
186         ast_mutex_destroy(&table->lock);
187         ast_free(table);
188 }
189
190 static struct tables *find_table(const char *database, const char *tablename)
191 {
192         struct columns *column;
193         struct tables *table;
194         struct ast_str *sql = ast_str_thread_get(&find_buf, 30);
195         char *fname, *ftype, *flen, *fdflt, *fnull;
196         struct mysql_conn *dbh;
197         MYSQL_RES *result;
198         MYSQL_ROW row;
199
200         if (!(dbh = find_database(database, 1))) {
201                 return NULL;
202         }
203
204         AST_LIST_LOCK(&mysql_tables);
205         AST_LIST_TRAVERSE(&mysql_tables, table, list) {
206                 if (!strcasecmp(table->name, tablename)) {
207                         ast_mutex_lock(&table->lock);
208                         AST_LIST_UNLOCK(&mysql_tables);
209                         release_database(dbh);
210                         return table;
211                 }
212         }
213
214         /* Not found, scan the table */
215         ast_str_set(&sql, 0, "DESC %s", tablename);
216
217         if (!mysql_reconnect(dbh)) {
218                 release_database(dbh);
219                 AST_LIST_UNLOCK(&mysql_tables);
220                 return NULL;
221         }
222
223         if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
224                 ast_log(LOG_ERROR, "Failed to query database '%s', table '%s' columns: %s\n", database, tablename, mysql_error(&dbh->handle));
225                 release_database(dbh);
226                 AST_LIST_UNLOCK(&mysql_tables);
227                 return NULL;
228         }
229
230         if (!(table = ast_calloc(1, sizeof(*table) + strlen(tablename) + 1))) {
231                 ast_log(LOG_ERROR, "Unable to allocate memory for new table structure\n");
232                 release_database(dbh);
233                 AST_LIST_UNLOCK(&mysql_tables);
234                 return NULL;
235         }
236         strcpy(table->name, tablename); /* SAFE */
237         table->database = dbh;
238         ast_mutex_init(&table->lock);
239         AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
240
241         if ((result = mysql_store_result(&dbh->handle))) {
242                 while ((row = mysql_fetch_row(result))) {
243                         fname = row[0];
244                         ftype = row[1];
245                         fnull = row[2];
246                         fdflt = row[4];
247                         ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
248
249                         if (fdflt == NULL) {
250                                 fdflt = "";
251                         }
252
253                         if (!(column = ast_calloc(1, sizeof(*column) + strlen(fname) + strlen(ftype) + strlen(fdflt) + 3))) {
254                                 ast_log(LOG_ERROR, "Unable to allocate column element %s for %s\n", fname, tablename);
255                                 destroy_table(table);
256                                 release_database(dbh);
257                                 AST_LIST_UNLOCK(&mysql_tables);
258                                 return NULL;
259                         }
260
261                         if ((flen = strchr(ftype, '('))) {
262                                 sscanf(flen, "(%30d)", &column->len);
263                         } else {
264                                 /* Columns like dates, times, and timestamps don't have a length */
265                                 column->len = -1;
266                         }
267
268                         column->name = (char *)column + sizeof(*column);
269                         column->type = (char *)column + sizeof(*column) + strlen(fname) + 1;
270                         column->dflt = (char *)column + sizeof(*column) + strlen(fname) + 1 + strlen(ftype) + 1;
271                         strcpy(column->name, fname);
272                         strcpy(column->type, ftype);
273                         strcpy(column->dflt, fdflt);
274                         column->null = (strcmp(fnull, "YES") == 0 ? 1 : 0);
275                         AST_LIST_INSERT_TAIL(&table->columns, column, list);
276                 }
277                 mysql_free_result(result);
278         }
279
280         AST_LIST_INSERT_TAIL(&mysql_tables, table, list);
281         ast_mutex_lock(&table->lock);
282         AST_LIST_UNLOCK(&mysql_tables);
283         release_database(dbh);
284         return table;
285 }
286
287 static void release_table(struct tables *table)
288 {
289         if (table) {
290                 ast_mutex_unlock(&table->lock);
291         }
292 }
293
294 static struct columns *find_column(struct tables *table, const char *colname)
295 {
296         struct columns *column;
297
298         AST_LIST_TRAVERSE(&table->columns, column, list) {
299                 if (strcmp(column->name, colname) == 0) {
300                         break;
301                 }
302         }
303
304         return column;
305 }
306
307 static char *decode_chunk(char *chunk)
308 {
309         char *orig = chunk;
310         for (; *chunk; chunk++) {
311                 if (*chunk == '^' && strchr("0123456789ABCDEFabcdef", chunk[1]) && strchr("0123456789ABCDEFabcdef", chunk[2])) {
312                         sscanf(chunk + 1, "%02hhX", chunk);
313                         memmove(chunk + 1, chunk + 3, strlen(chunk + 3) + 1);
314                 }
315         }
316         return orig;
317 }
318
319 static struct ast_variable *realtime_mysql(const char *database, const char *table, va_list ap)
320 {
321         struct mysql_conn *dbh;
322         MYSQL_RES *result;
323         MYSQL_ROW row;
324         MYSQL_FIELD *fields;
325         int numFields, i;
326         struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
327         struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
328         char *stringp;
329         char *chunk;
330         char *op;
331         const char *newparam, *newval;
332         struct ast_variable *var=NULL, *prev=NULL;
333
334         if (!(dbh = find_database(database, 0))) {
335                 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: %s (check res_mysql.conf)\n", database);
336                 return NULL;
337         }
338
339         if (!table) {
340                 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
341                 release_database(dbh);
342                 return NULL;
343         }
344
345         /* Get the first parameter and first value in our list of passed paramater/value pairs */
346         newparam = va_arg(ap, const char *);
347         newval = va_arg(ap, const char *);
348         if (!newparam || !newval)  {
349                 ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
350                 release_database(dbh);
351                 return NULL;
352         }
353
354         /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
355         if (!mysql_reconnect(dbh)) {
356                 release_database(dbh);
357                 return NULL;
358         }
359
360         /* Create the first part of the query using the first parameter/value pairs we just extracted
361            If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
362
363         if (!strchr(newparam, ' ')) 
364                 op = " ="; 
365         else 
366                 op = "";
367
368         ESCAPE_STRING(buf, newval);
369         ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(buf));
370         while ((newparam = va_arg(ap, const char *))) {
371                 newval = va_arg(ap, const char *);
372                 if (!strchr(newparam, ' ')) 
373                         op = " ="; 
374                 else
375                         op = "";
376                 ESCAPE_STRING(buf, newval);
377                 ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(buf));
378         }
379         va_end(ap);
380
381         ast_debug(1, "MySQL RealTime: Retrieve SQL: %s\n", ast_str_buffer(sql));
382
383         /* Execution. */
384         if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
385                 ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
386                 release_database(dbh);
387                 return NULL;
388         }
389
390         if ((result = mysql_store_result(&dbh->handle))) {
391                 numFields = mysql_num_fields(result);
392                 fields = mysql_fetch_fields(result);
393
394                 while ((row = mysql_fetch_row(result))) {
395                         for (i = 0; i < numFields; i++) {
396                                 /* Encode NULL values separately from blank values, for the Realtime API */
397                                 if (row[i] == NULL) {
398                                         row[i] = "";
399                                 } else if (ast_strlen_zero(row[i])) {
400                                         row[i] = " ";
401                                 }
402                                 for (stringp = row[i], chunk = strsep(&stringp, ";"); chunk; chunk = strsep(&stringp, ";")) {
403                                         if (prev) {
404                                                 if ((prev->next = ast_variable_new(fields[i].name, decode_chunk(chunk), ""))) {
405                                                         prev = prev->next;
406                                                 }
407                                         } else {
408                                                 prev = var = ast_variable_new(fields[i].name, decode_chunk(chunk), "");
409                                         }
410                                 }
411                         }
412                 }
413         } else {
414                 ast_debug(1, "MySQL RealTime: Could not find any rows in table %s.\n", table);
415         }
416
417         release_database(dbh);
418         mysql_free_result(result);
419
420         return var;
421 }
422
423 static struct ast_config *realtime_multi_mysql(const char *database, const char *table, va_list ap)
424 {
425         struct mysql_conn *dbh;
426         MYSQL_RES *result;
427         MYSQL_ROW row;
428         MYSQL_FIELD *fields;
429         int numFields, i;
430         struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
431         struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
432         const char *initfield = NULL;
433         char *stringp;
434         char *chunk;
435         char *op;
436         const char *newparam, *newval;
437         struct ast_variable *var = NULL;
438         struct ast_config *cfg = NULL;
439         struct ast_category *cat = NULL;
440
441         if (!(dbh = find_database(database, 0))) {
442                 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
443                 return NULL;
444         }
445
446         if (!table) {
447                 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
448                 release_database(dbh);
449                 return NULL;
450         }
451         
452         if (!(cfg = ast_config_new())) {
453                 /* If I can't alloc memory at this point, why bother doing anything else? */
454                 ast_log(LOG_WARNING, "Out of memory!\n");
455                 release_database(dbh);
456                 return NULL;
457         }
458
459         /* Get the first parameter and first value in our list of passed paramater/value pairs */
460         newparam = va_arg(ap, const char *);
461         newval = va_arg(ap, const char *);
462         if (!newparam || !newval)  {
463                 ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
464                 ast_config_destroy(cfg);
465                 release_database(dbh);
466                 return NULL;
467         }
468
469         initfield = ast_strdupa(newparam);
470         if ((op = strchr(initfield, ' '))) {
471                 *op = '\0';
472         }
473
474         /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
475         if (!mysql_reconnect(dbh)) {
476                 release_database(dbh);
477                 ast_config_destroy(cfg);
478                 return NULL;
479         }
480
481         /* Create the first part of the query using the first parameter/value pairs we just extracted
482            If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
483
484         if (!strchr(newparam, ' '))
485                 op = " =";
486         else
487                 op = "";
488
489         ESCAPE_STRING(buf, newval);
490         ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(buf));
491         while ((newparam = va_arg(ap, const char *))) {
492                 newval = va_arg(ap, const char *);
493                 if (!strchr(newparam, ' ')) op = " ="; else op = "";
494                 ESCAPE_STRING(buf, newval);
495                 ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(buf));
496         }
497
498         if (initfield) {
499                 ast_str_append(&sql, 0, " ORDER BY %s", initfield);
500         }
501
502         va_end(ap);
503
504         ast_debug(1, "MySQL RealTime: Retrieve SQL: %s\n", ast_str_buffer(sql));
505
506         /* Execution. */
507         if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
508                 ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
509                 release_database(dbh);
510                 ast_config_destroy(cfg);
511                 return NULL;
512         }
513
514         if ((result = mysql_store_result(&dbh->handle))) {
515                 numFields = mysql_num_fields(result);
516                 fields = mysql_fetch_fields(result);
517
518                 while ((row = mysql_fetch_row(result))) {
519                         var = NULL;
520                         cat = ast_category_new("", "", -1);
521                         if (!cat) {
522                                 ast_log(LOG_WARNING, "Out of memory!\n");
523                                 continue;
524                         }
525                         for (i = 0; i < numFields; i++) {
526                                 if (ast_strlen_zero(row[i]))
527                                         continue;
528                                 for (stringp = row[i], chunk = strsep(&stringp, ";"); chunk; chunk = strsep(&stringp, ";")) {
529                                         if (chunk && !ast_strlen_zero(decode_chunk(ast_strip(chunk)))) {
530                                                 if (initfield && !strcmp(initfield, fields[i].name)) {
531                                                         ast_category_rename(cat, chunk);
532                                                 }
533                                                 var = ast_variable_new(fields[i].name, chunk, "");
534                                                 ast_variable_append(cat, var);
535                                         }
536                                 }
537                         }
538                         ast_category_append(cfg, cat);
539                 }
540         } else {
541                 ast_debug(1, "MySQL RealTime: Could not find any rows in table %s.\n", table);
542         }
543
544         release_database(dbh);
545         mysql_free_result(result);
546
547         return cfg;
548 }
549
550 static int update_mysql(const char *database, const char *tablename, const char *keyfield, const char *lookup, va_list ap)
551 {
552         struct mysql_conn *dbh;
553         my_ulonglong numrows;
554         const char *newparam, *newval;
555         struct ast_str *sql = ast_str_thread_get(&sql_buf, 100), *buf = ast_str_thread_get(&scratch_buf, 100);
556         struct tables *table;
557         struct columns *column = NULL;
558
559         if (!(dbh = find_database(database, 1))) {
560                 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
561                 return -1;
562         }
563
564         if (!tablename) {
565                 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
566                 release_database(dbh);
567                 return -1;
568         }
569
570         if (!(table = find_table(database, tablename))) {
571                 ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
572                 release_database(dbh);
573                 return -1;
574         }
575
576         if (!(column = find_column(table, keyfield))) {
577                 ast_log(LOG_ERROR, "MySQL RealTime: Updating on column '%s', but that column does not exist within the table '%s' (db '%s')!\n", keyfield, tablename, database);
578                 release_table(table);
579                 release_database(dbh);
580                 return -1;
581         }
582
583         /* Get the first parameter and first value in our list of passed paramater/value pairs */
584         newparam = va_arg(ap, const char *);
585         newval = va_arg(ap, const char *);
586         if (!newparam || !newval)  {
587                 ast_log(LOG_WARNING, "MySQL RealTime: Realtime update requires at least 1 parameter and 1 value to update.\n");
588                 release_table(table);
589                 release_database(dbh);
590                 return -1;
591         }
592
593         /* Check that the column exists in the table */
594         if (!(column = find_column(table, newparam))) {
595                 ast_log(LOG_ERROR, "MySQL RealTime: Updating column '%s', but that column does not exist within the table '%s' (first pair MUST exist)!\n", newparam, tablename);
596                 release_table(table);
597                 release_database(dbh);
598                 return -1;
599         }
600
601         /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
602         if (!mysql_reconnect(dbh)) {
603                 release_table(table);
604                 release_database(dbh);
605                 return -1;
606         }
607
608         /* Create the first part of the query using the first parameter/value pairs we just extracted
609            If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
610
611         ESCAPE_STRING(buf, newval);
612         ast_str_set(&sql, 0, "UPDATE %s SET `%s` = '%s'", tablename, newparam, ast_str_buffer(buf));
613
614         /* If the column length isn't long enough, give a chance to lengthen it. */
615         if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
616                 internal_require(database, tablename, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL);
617         }
618
619         while ((newparam = va_arg(ap, const char *))) {
620                 newval = va_arg(ap, const char *);
621
622                 /* If the column is not within the table, then skip it */
623                 if (!(column = find_column(table, newparam))) {
624                         ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename);
625                         continue;
626                 }
627
628                 ESCAPE_STRING(buf, newval);
629                 ast_str_append(&sql, 0, ", `%s` = '%s'", newparam, ast_str_buffer(buf));
630
631                 /* If the column length isn't long enough, give a chance to lengthen it. */
632                 if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
633                         internal_require(database, tablename, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL);
634                 }
635         }
636         va_end(ap);
637
638         ESCAPE_STRING(buf, lookup);
639         ast_str_append(&sql, 0, " WHERE `%s` = '%s'", keyfield, ast_str_buffer(buf));
640
641         ast_debug(1, "MySQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
642
643         /* Execution. */
644         if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
645                 ast_log(LOG_WARNING, "MySQL RealTime: Failed to update database: %s\n", mysql_error(&dbh->handle));
646                 release_table(table);
647                 release_database(dbh);
648                 return -1;
649         }
650
651         numrows = mysql_affected_rows(&dbh->handle);
652         release_table(table);
653         release_database(dbh);
654
655         ast_debug(1, "MySQL RealTime: Updated %llu rows on table: %s\n", numrows, tablename);
656
657         /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
658          * An integer greater than zero indicates the number of rows affected
659          * Zero indicates that no records were updated
660          * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
661         */
662
663         return (int)numrows;
664 }
665
666 static int update2_mysql(const char *database, const char *tablename, va_list ap)
667 {
668         struct mysql_conn *dbh;
669         my_ulonglong numrows;
670         int first = 1;
671         const char *newparam, *newval;
672         struct ast_str *sql = ast_str_thread_get(&sql_buf, 100), *buf = ast_str_thread_get(&scratch_buf, 100);
673         struct ast_str *where = ast_str_thread_get(&sql2_buf, 100);
674         struct tables *table;
675         struct columns *column = NULL;
676
677         if (!tablename) {
678                 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
679                 return -1;
680         }
681
682         if (!(dbh = find_database(database, 1))) {
683                 ast_log(LOG_ERROR, "Invalid database specified: %s\n", database);
684                 return -1;
685         }
686
687         if (!(table = find_table(database, tablename))) {
688                 ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
689                 release_database(dbh);
690                 return -1;
691         }
692
693         if (!sql || !buf || !where) {
694                 release_database(dbh);
695                 release_table(table);
696                 return -1;
697         }
698
699         ast_str_set(&sql, 0, "UPDATE %s SET", tablename);
700         ast_str_set(&where, 0, "WHERE");
701
702         /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
703         if (!mysql_reconnect(dbh)) {
704                 release_table(table);
705                 release_database(dbh);
706                 return -1;
707         }
708
709         while ((newparam = va_arg(ap, const char *))) {
710                 if (!(column = find_column(table, newparam))) {
711                         ast_log(LOG_ERROR, "Updating on column '%s', but that column does not exist within the table '%s'!\n", newparam, tablename);
712                         release_table(table);
713                         release_database(dbh);
714                         return -1;
715                 }
716                 if (!(newval = va_arg(ap, const char *))) {
717                         ast_log(LOG_ERROR, "Invalid arguments: no value specified for column '%s' on '%s@%s'\n", newparam, tablename, database);
718                         release_table(table);
719                         release_database(dbh);
720                         return -1;
721                 }
722                 ESCAPE_STRING(buf, newval);
723                 ast_str_append(&where, 0, "%s `%s` = '%s'", first ? "" : " AND", newparam, ast_str_buffer(buf));
724                 first = 0;
725
726                 /* If the column length isn't long enough, give a chance to lengthen it. */
727                 if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
728                         internal_require(database, tablename, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL);
729                 }
730         }
731
732         first = 1;
733         while ((newparam = va_arg(ap, const char *))) {
734                 if (!(newval = va_arg(ap, const char *))) {
735                         ast_log(LOG_ERROR, "Invalid arguments: no value specified for column '%s' on '%s@%s'\n", newparam, tablename, database);
736                         release_table(table);
737                         release_database(dbh);
738                         return -1;
739                 }
740
741                 /* If the column is not within the table, then skip it */
742                 if (!(column = find_column(table, newparam))) {
743                         ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename);
744                         continue;
745                 }
746
747                 ESCAPE_STRING(buf, newval);
748                 ast_str_append(&sql, 0, "%s `%s` = '%s'", first ? "" : ",", newparam, ast_str_buffer(buf));
749                 first = 0;
750
751                 /* If the column length isn't long enough, give a chance to lengthen it. */
752                 if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
753                         internal_require(database, tablename, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL);
754                 }
755         }
756         va_end(ap);
757         release_table(table);
758
759         ast_str_append(&sql, 0, " %s", ast_str_buffer(where));
760
761         ast_debug(1, "MySQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
762
763         /* Execution. */
764         if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
765                 ast_log(LOG_WARNING, "MySQL RealTime: Failed to update database: %s\n", mysql_error(&dbh->handle));
766                 release_table(table);
767                 release_database(dbh);
768                 return -1;
769         }
770
771         numrows = mysql_affected_rows(&dbh->handle);
772         release_database(dbh);
773
774         ast_debug(1, "MySQL RealTime: Updated %llu rows on table: %s\n", numrows, tablename);
775
776         /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
777          * An integer greater than zero indicates the number of rows affected
778          * Zero indicates that no records were updated
779          * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
780         */
781
782         return (int)numrows;
783 }
784  
785 static int store_mysql(const char *database, const char *table, va_list ap)
786 {
787         struct mysql_conn *dbh;
788         my_ulonglong insertid;
789         struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
790         struct ast_str *sql2 = ast_str_thread_get(&sql2_buf, 16);
791         struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
792         const char *newparam, *newval;
793
794         if (!(dbh = find_database(database, 1))) {
795                 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
796                 return -1;
797         }
798
799         if (!table) {
800                 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
801                 release_database(dbh);
802                 return -1;
803         }
804         /* Get the first parameter and first value in our list of passed paramater/value pairs */
805         newparam = va_arg(ap, const char *);
806         newval = va_arg(ap, const char *);
807         if (!newparam || !newval)  {
808                 ast_log(LOG_WARNING, "MySQL RealTime: Realtime storage requires at least 1 parameter and 1 value to search on.\n");
809                 release_database(dbh);
810                 return -1;
811         }
812         /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
813         if (!mysql_reconnect(dbh)) {
814                 release_database(dbh);
815                 return -1;
816         }
817         /* Create the first part of the query using the first parameter/value pairs we just extracted
818                 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
819         ESCAPE_STRING(buf, newval);
820         ast_str_set(&sql, 0, "INSERT INTO %s (`%s`", table, newparam);
821         ast_str_set(&sql2, 0, ") VALUES ('%s'", ast_str_buffer(buf));
822
823         internal_require(database, table, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL);
824
825         while ((newparam = va_arg(ap, const char *))) {
826                 if ((newval = va_arg(ap, const char *))) {
827                         ESCAPE_STRING(buf, newval);
828                 } else {
829                         ast_str_reset(buf);
830                 }
831                 if (internal_require(database, table, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL) == 0) {
832                         ast_str_append(&sql, 0, ", `%s`", newparam);
833                         ast_str_append(&sql2, 0, ", '%s'", ast_str_buffer(buf));
834                 }
835         }
836         va_end(ap);
837         ast_str_append(&sql, 0, "%s)", ast_str_buffer(sql2));
838         ast_debug(1,"MySQL RealTime: Insert SQL: %s\n", ast_str_buffer(sql));
839
840         /* Execution. */
841         if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
842                 ast_log(LOG_WARNING, "MySQL RealTime: Failed to insert into database: %s\n", mysql_error(&dbh->handle));
843                 release_database(dbh);
844                 return -1;
845         }
846
847         /*!\note The return value is non-portable and may change in future versions. */
848         insertid = mysql_insert_id(&dbh->handle);
849         release_database(dbh);
850
851         ast_debug(1, "MySQL RealTime: row inserted on table: %s, id: %llu\n", table, insertid);
852
853         /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
854          * An integer greater than zero indicates the number of rows affected
855          * Zero indicates that no records were updated
856          * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
857         */
858         return (int)insertid;
859 }
860
861 static int destroy_mysql(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
862 {
863         struct mysql_conn *dbh;
864         my_ulonglong numrows;
865         struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
866         struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
867         const char *newparam, *newval;
868
869         if (!(dbh = find_database(database, 1))) {
870                 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
871                 return -1;
872         }
873
874         if (!table) {
875                 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
876                 release_database(dbh);
877                 return -1;
878         }
879
880         /* Get the first parameter and first value in our list of passed paramater/value pairs */
881         /* newparam = va_arg(ap, const char *);
882         newval = va_arg(ap, const char *);*/
883         if (ast_strlen_zero(keyfield) || ast_strlen_zero(lookup))  {
884                 ast_log(LOG_WARNING, "MySQL RealTime: Realtime destroying requires at least 1 parameter and 1 value to search on.\n");
885                 release_database(dbh);
886                 return -1;
887         }
888
889         /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
890         if (!mysql_reconnect(dbh)) {
891                 release_database(dbh);
892                 return -1;
893         }
894
895         /* Create the first part of the query using the first parameter/value pairs we just extracted
896            If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
897         ESCAPE_STRING(buf, lookup);
898         ast_str_set(&sql, 0, "DELETE FROM %s WHERE `%s` = '%s'", table, keyfield, ast_str_buffer(buf));
899         while ((newparam = va_arg(ap, const char *))) {
900                 newval = va_arg(ap, const char *);
901                 ESCAPE_STRING(buf, newval);
902                 ast_str_append(&sql, 0, " AND `%s` = '%s'", newparam, ast_str_buffer(buf));
903         }
904         va_end(ap);
905
906         ast_debug(1, "MySQL RealTime: Delete SQL: %s\n", ast_str_buffer(sql));
907
908         /* Execution. */
909         if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
910                 ast_log(LOG_WARNING, "MySQL RealTime: Failed to delete from database: %s\n", mysql_error(&dbh->handle));
911                 release_database(dbh);
912                 return -1;
913         }
914
915         numrows = mysql_affected_rows(&dbh->handle);
916         release_database(dbh);
917
918         ast_debug(1, "MySQL RealTime: Deleted %llu rows on table: %s\n", numrows, table);
919
920         /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
921          * An integer greater than zero indicates the number of rows affected
922          * Zero indicates that no records were updated
923          * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
924         */
925
926         return (int)numrows;
927 }
928  
929 static struct ast_config *config_mysql(const char *database, const char *table, const char *file, struct ast_config *cfg, struct ast_flags config_flags, const char *unused, const char *who_asked)
930 {
931         struct mysql_conn *dbh;
932         MYSQL_RES *result;
933         MYSQL_ROW row;
934         my_ulonglong num_rows;
935         struct ast_variable *new_v;
936         struct ast_category *cur_cat = NULL;
937         struct ast_str *sql = ast_str_thread_get(&sql_buf, 200);
938         char last[80] = "";
939         int last_cat_metric = 0;
940
941         ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
942
943         if (!file || !strcmp(file, RES_CONFIG_MYSQL_CONF)) {
944                 ast_log(LOG_WARNING, "MySQL RealTime: Cannot configure myself.\n");
945                 return NULL;
946         }
947
948         if (!(dbh = find_database(database, 0))) {
949                 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
950                 return NULL;
951         }
952
953         ast_str_set(&sql, 0, "SELECT category, var_name, var_val, cat_metric FROM %s WHERE filename='%s' and commented=0 ORDER BY filename, cat_metric desc, var_metric asc, category, var_name, var_val, id", table, file);
954
955         ast_debug(1, "MySQL RealTime: Static SQL: %s\n", ast_str_buffer(sql));
956
957         /* We now have our complete statement; Lets connect to the server and execute it. */
958         if (!mysql_reconnect(dbh)) {
959                 return NULL;
960         }
961
962         if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
963                 ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
964                 ast_debug(1, "MySQL RealTime: Query: %s\n", ast_str_buffer(sql));
965                 ast_debug(1, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&dbh->handle));
966                 release_database(dbh);
967                 return NULL;
968         }
969
970         if ((result = mysql_store_result(&dbh->handle))) {
971                 num_rows = mysql_num_rows(result);
972                 ast_debug(1, "MySQL RealTime: Found %llu rows.\n", num_rows);
973
974                 /* There might exist a better way to access the column names other than counting,
975                  * but I believe that would require another loop that we don't need. */
976
977                 while ((row = mysql_fetch_row(result))) {
978                         if (!strcmp(row[1], "#include")) {
979                                 if (!ast_config_internal_load(row[2], cfg, config_flags, "", who_asked)) {
980                                         mysql_free_result(result);
981                                         release_database(dbh);
982                                         return NULL;
983                                 }
984                                 continue;
985                         }
986
987                         if (strcmp(last, row[0]) || last_cat_metric != atoi(row[3])) {
988                                 if (!(cur_cat = ast_category_new(row[0], "", -1))) {
989                                         ast_log(LOG_WARNING, "Out of memory!\n");
990                                         break;
991                                 }
992                                 strcpy(last, row[0]);
993                                 last_cat_metric = atoi(row[3]);
994                                 ast_category_append(cfg, cur_cat);
995                         }
996                         new_v = ast_variable_new(row[1], row[2], "");
997                         if (cur_cat)
998                                 ast_variable_append(cur_cat, new_v);
999                 }
1000         } else {
1001                 ast_log(LOG_WARNING, "MySQL RealTime: Could not find config '%s' in database.\n", file);
1002         }
1003
1004         mysql_free_result(result);
1005         release_database(dbh);
1006
1007         return cfg;
1008 }
1009
1010 static int unload_mysql(const char *database, const char *tablename)
1011 {
1012         struct tables *cur;
1013         AST_LIST_LOCK(&mysql_tables);
1014         AST_LIST_TRAVERSE_SAFE_BEGIN(&mysql_tables, cur, list) {
1015                 if (strcmp(cur->name, tablename) == 0) {
1016                         AST_LIST_REMOVE_CURRENT(list);
1017                         destroy_table(cur);
1018                         break;
1019                 }
1020         }
1021         AST_LIST_TRAVERSE_SAFE_END
1022         AST_LIST_UNLOCK(&mysql_tables);
1023         return cur ? 0 : -1;
1024 }
1025
1026 static int modify_mysql(const char *database, const char *tablename, struct columns *column, require_type type, int len)
1027 {
1028         /*!\note Cannot use ANY of the same scratch space as is used in other functions, as this one is interspersed. */
1029         struct ast_str *sql = ast_str_thread_get(&modify_buf, 100), *escbuf = ast_str_thread_get(&modify2_buf, 100);
1030         struct ast_str *typestr = ast_str_thread_get(&modify3_buf, 30);
1031         int waschar = strncasecmp(column->type, "char", 4) == 0 ? 1 : 0;
1032         int wasvarchar = strncasecmp(column->type, "varchar", 7) == 0 ? 1 : 0;
1033         int res = 0;
1034         struct mysql_conn *dbh;
1035
1036         if (!(dbh = find_database(database, 1))) {
1037                 return -1;
1038         }
1039
1040         do {
1041                 if (type == RQ_CHAR || waschar || wasvarchar) {
1042                         if (wasvarchar) {
1043                                 ast_str_set(&typestr, 0, "VARCHAR(%d)", len);
1044                         } else {
1045                                 ast_str_set(&typestr, 0, "CHAR(%d)", len);
1046                         }
1047                 } else if (type == RQ_UINTEGER1) {
1048                         ast_str_set(&typestr, 0, "tinyint(3) unsigned");
1049                 } else if (type == RQ_INTEGER1) {
1050                         ast_str_set(&typestr, 0, "tinyint(4)");
1051                 } else if (type == RQ_UINTEGER2) {
1052                         ast_str_set(&typestr, 0, "smallint(5) unsigned");
1053                 } else if (type == RQ_INTEGER2) {
1054                         ast_str_set(&typestr, 0, "smallint(6)");
1055                 } else if (type == RQ_UINTEGER3) {
1056                         ast_str_set(&typestr, 0, "mediumint(8) unsigned");
1057                 } else if (type == RQ_INTEGER3) {
1058                         ast_str_set(&typestr, 0, "mediumint(8)");
1059                 } else if (type == RQ_UINTEGER4) {
1060                         ast_str_set(&typestr, 0, "int(10) unsigned");
1061                 } else if (type == RQ_INTEGER4) {
1062                         ast_str_set(&typestr, 0, "int(11)");
1063                 } else if (type == RQ_UINTEGER8) {
1064                         ast_str_set(&typestr, 0, "bigint(19) unsigned");
1065                 } else if (type == RQ_INTEGER8) {
1066                         ast_str_set(&typestr, 0, "bigint(20)");
1067                 } else if (type == RQ_DATETIME) {
1068                         ast_str_set(&typestr, 0, "datetime");
1069                 } else if (type == RQ_DATE) {
1070                         ast_str_set(&typestr, 0, "date");
1071                 } else if (type == RQ_FLOAT) {
1072                         ast_str_set(&typestr, 0, "FLOAT(%d,2)", len);
1073                 } else {
1074                         ast_log(LOG_ERROR, "Unknown type (should NEVER happen)\n");
1075                         res = -1;
1076                         break;
1077                 }
1078                 ast_str_set(&sql, 0, "ALTER TABLE %s MODIFY `%s` %s", tablename, column->name, ast_str_buffer(typestr));
1079                 if (!column->null) {
1080                         ast_str_append(&sql, 0, " NOT NULL");
1081                 }
1082                 if (!ast_strlen_zero(column->dflt)) {
1083                         ESCAPE_STRING(escbuf, column->dflt);
1084                         ast_str_append(&sql, 0, " DEFAULT '%s'", ast_str_buffer(escbuf));
1085                 }
1086
1087                 if (!mysql_reconnect(dbh)) {
1088                         ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
1089                         res = -1;
1090                         break;
1091                 }
1092
1093                 /* Execution. */
1094                 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
1095                         ast_log(LOG_WARNING, "MySQL RealTime: Failed to modify database: %s\n", mysql_error(&dbh->handle));
1096                         ast_debug(1, "MySQL RealTime: Query: %s\n", ast_str_buffer(sql));
1097                         res = -1;
1098                 }
1099         } while (0);
1100
1101         release_database(dbh);
1102         return res;
1103 }
1104
1105 #define PICK_WHICH_ALTER_ACTION(stringtype) \
1106         if (table->database->requirements == RQ_WARN) {                                                                       \
1107                 ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for "            \
1108                         "the required data length: %d (detected stringtype)\n",                                      \
1109                         tablename, database, column->name, size);                                                    \
1110                 res = -1;                                                                                        \
1111         } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {     \
1112                 table_altered = 1;                                                                               \
1113         } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {   \
1114                 table_altered = 1;                                                                               \
1115         } else {                                                                                             \
1116                 res = -1;                                                                                        \
1117         }
1118
1119 static int require_mysql(const char *database, const char *tablename, va_list ap)
1120 {
1121         struct columns *column;
1122         struct tables *table = find_table(database, tablename);
1123         char *elm;
1124         int type, size, res = 0, table_altered = 0;
1125
1126         if (!table) {
1127                 ast_log(LOG_WARNING, "Table %s not found in database.  This table should exist if you're using realtime.\n", tablename);
1128                 return -1;
1129         }
1130
1131         while ((elm = va_arg(ap, char *))) {
1132                 type = va_arg(ap, require_type);
1133                 size = va_arg(ap, int);
1134                 AST_LIST_TRAVERSE(&table->columns, column, list) {
1135                         if (strcmp(column->name, elm) == 0) {
1136                                 /* Char can hold anything, as long as it is large enough */
1137                                 if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
1138                                         if ((size > column->len) && column->len != -1) {
1139                                                 if (table->database->requirements == RQ_WARN) {
1140                                                         ast_log(LOG_WARNING, "Realtime table %s@%s: Column '%s' should be at least %d long, but is only %d long.\n", database, tablename, column->name, size, column->len);
1141                                                         res = -1;
1142                                                 } else if (modify_mysql(database, tablename, column, type, size) == 0) {
1143                                                         table_altered = 1;
1144                                                 } else {
1145                                                         res = -1;
1146                                                 }
1147                                         }
1148                                 } else if (strcasestr(column->type, "unsigned")) {
1149                                         if (!ast_rq_is_int(type)) {
1150                                                 if (table->database->requirements == RQ_WARN) {
1151                                                         ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' cannot be type '%s' (need %s)\n",
1152                                                                 database, tablename, column->name, column->type,
1153                                                                 type == RQ_CHAR ? "char" : type == RQ_FLOAT ? "float" :
1154                                                                 type == RQ_DATETIME ? "datetime" : type == RQ_DATE ? "date" : "a rather stiff drink");
1155                                                         res = -1;
1156                                                 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1157                                                         table_altered = 1;
1158                                                 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1159                                                         table_altered = 1;
1160                                                 } else {
1161                                                         res = -1;
1162                                                 }
1163                                         } else if (strncasecmp(column->type, "tinyint", 1) == 0) {
1164                                                 if (type != RQ_UINTEGER1) {
1165                                                         PICK_WHICH_ALTER_ACTION(unsigned tinyint)
1166                                                 }
1167                                         } else if (strncasecmp(column->type, "smallint", 1) == 0) {
1168                                                 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_UINTEGER2) {
1169                                                         PICK_WHICH_ALTER_ACTION(unsigned smallint)
1170                                                 }
1171                                         } else if (strncasecmp(column->type, "mediumint", 1) == 0) {
1172                                                 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1173                                                         type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1174                                                         type != RQ_UINTEGER3) {
1175                                                         PICK_WHICH_ALTER_ACTION(unsigned mediumint)
1176                                                 }
1177                                         } else if (strncasecmp(column->type, "int", 1) == 0) {
1178                                                 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1179                                                         type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1180                                                         type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1181                                                         type != RQ_UINTEGER4) {
1182                                                         PICK_WHICH_ALTER_ACTION(unsigned int)
1183                                                 }
1184                                         } else if (strncasecmp(column->type, "bigint", 1) == 0) {
1185                                                 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1186                                                         type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1187                                                         type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1188                                                         type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
1189                                                         type != RQ_UINTEGER8) {
1190                                                         PICK_WHICH_ALTER_ACTION(unsigned bigint)
1191                                                 }
1192                                         }
1193                                 } else if (strcasestr(column->type, "int")) {
1194                                         if (!ast_rq_is_int(type)) {
1195                                                 if (table->database->requirements == RQ_WARN) {
1196                                                         ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' cannot be type '%s' (need %s)\n",
1197                                                                 database, tablename, column->name, column->type,
1198                                                                 type == RQ_CHAR ? "char" : type == RQ_FLOAT ? "float" :
1199                                                                 type == RQ_DATETIME ? "datetime" : type == RQ_DATE ? "date" :
1200                                                                 "to get a life, rather than writing silly error messages");
1201                                                         res = -1;
1202                                                 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1203                                                         table_altered = 1;
1204                                                 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1205                                                         table_altered = 1;
1206                                                 } else {
1207                                                         res = -1;
1208                                                 }
1209                                         } else if (strncasecmp(column->type, "tinyint", 1) == 0) {
1210                                                 if (type != RQ_INTEGER1) {
1211                                                         PICK_WHICH_ALTER_ACTION(tinyint)
1212                                                 }
1213                                         } else if (strncasecmp(column->type, "smallint", 1) == 0) {
1214                                                 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_INTEGER2) {
1215                                                         PICK_WHICH_ALTER_ACTION(smallint)
1216                                                 }
1217                                         } else if (strncasecmp(column->type, "mediumint", 1) == 0) {
1218                                                 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1219                                                         type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1220                                                         type != RQ_INTEGER3) {
1221                                                         PICK_WHICH_ALTER_ACTION(mediumint)
1222                                                 }
1223                                         } else if (strncasecmp(column->type, "int", 1) == 0) {
1224                                                 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1225                                                         type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1226                                                         type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1227                                                         type != RQ_INTEGER4) {
1228                                                         PICK_WHICH_ALTER_ACTION(int)
1229                                                 }
1230                                         } else if (strncasecmp(column->type, "bigint", 1) == 0) {
1231                                                 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1232                                                         type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1233                                                         type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1234                                                         type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
1235                                                         type != RQ_INTEGER8) {
1236                                                         PICK_WHICH_ALTER_ACTION(bigint)
1237                                                 }
1238                                         }
1239                                 } else if (strncmp(column->type, "float", 5) == 0 && !ast_rq_is_int(type) && type != RQ_FLOAT) {
1240                                         if (table->database->requirements == RQ_WARN) {
1241                                                 ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
1242                                                 res = -1;
1243                                         } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1244                                                 table_altered = 1;
1245                                         } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1246                                                 table_altered = 1;
1247                                         } else {
1248                                                 res = -1;
1249                                         }
1250                                 } else if ((strncmp(column->type, "datetime", 8) == 0 || strncmp(column->type, "timestamp", 9) == 0) && type != RQ_DATETIME) {
1251                                         if (table->database->requirements == RQ_WARN) {
1252                                                 ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
1253                                                 res = -1;
1254                                         } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1255                                                 table_altered = 1;
1256                                         } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1257                                                 table_altered = 1;
1258                                         } else {
1259                                                 res = -1;
1260                                         }
1261                                 } else if ((strncmp(column->type, "date", 4) == 0) && type != RQ_DATE) {
1262                                         if (table->database->requirements == RQ_WARN) {
1263                                                 ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
1264                                                 res = -1;
1265                                         } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1266                                                 table_altered = 1;
1267                                         } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1268                                                 table_altered = 1;
1269                                         } else {
1270                                                 res = -1;
1271                                         }
1272                                 } else { /* Other, possibly unsupported types? */
1273                                         if (table->database->requirements == RQ_WARN) {
1274                                                 ast_log(LOG_WARNING, "Possibly unsupported column type '%s' on column '%s'\n", column->type, column->name);
1275                                                 res = -1;
1276                                         } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1277                                                 table_altered = 1;
1278                                         } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1279                                                 table_altered = 1;
1280                                         } else {
1281                                         }
1282                                 }
1283                                 break;
1284                         }
1285                 }
1286
1287                 if (!column) {
1288                         if (table->database->requirements == RQ_WARN) {
1289                                 ast_log(LOG_WARNING, "Table %s requires a column '%s' of size '%d', but no such column exists.\n", tablename, elm, size);
1290                         } else {
1291                                 struct ast_str *sql = ast_str_thread_get(&modify_buf, 100), *fieldtype = ast_str_thread_get(&modify3_buf, 16);
1292
1293                                 if (table->database->requirements == RQ_CREATECHAR || type == RQ_CHAR) {
1294                                         ast_str_set(&fieldtype, 0, "CHAR(%d)", size);
1295                                 } else if (type == RQ_UINTEGER1 || type == RQ_UINTEGER2 || type == RQ_UINTEGER3 || type == RQ_UINTEGER4 || type == RQ_UINTEGER8) {
1296                                         if (type == RQ_UINTEGER1) {
1297                                                 ast_str_set(&fieldtype, 0, "TINYINT(3) UNSIGNED");
1298                                         } else if (type == RQ_UINTEGER2) {
1299                                                 ast_str_set(&fieldtype, 0, "SMALLINT(5) UNSIGNED");
1300                                         } else if (type == RQ_UINTEGER3) {
1301                                                 ast_str_set(&fieldtype, 0, "MEDIUMINT(8) UNSIGNED");
1302                                         } else if (type == RQ_UINTEGER4) {
1303                                                 ast_str_set(&fieldtype, 0, "INT(10) UNSIGNED");
1304                                         } else if (type == RQ_UINTEGER8) {
1305                                                 ast_str_set(&fieldtype, 0, "BIGINT(20) UNSIGNED");
1306                                         } else {
1307                                                 ast_log(LOG_WARNING, "Somebody should check this code for a rather large bug... it's about to squash Tokyo.\n");
1308                                                 continue;
1309                                         }
1310                                 } else if (ast_rq_is_int(type)) {
1311                                         if (type == RQ_INTEGER1) {
1312                                                 ast_str_set(&fieldtype, 0, "TINYINT(3)");
1313                                         } else if (type == RQ_INTEGER2) {
1314                                                 ast_str_set(&fieldtype, 0, "SMALLINT(5)");
1315                                         } else if (type == RQ_INTEGER3) {
1316                                                 ast_str_set(&fieldtype, 0, "MEDIUMINT(8)");
1317                                         } else if (type == RQ_INTEGER4) {
1318                                                 ast_str_set(&fieldtype, 0, "INT(10)");
1319                                         } else if (type == RQ_INTEGER8) {
1320                                                 ast_str_set(&fieldtype, 0, "BIGINT(20)");
1321                                         } else {
1322                                                 ast_log(LOG_WARNING, "Somebody should check this code for a rather large bug... it's about to eat Cincinnati.\n");
1323                                                 continue;
1324                                         }
1325                                 } else if (type == RQ_FLOAT) {
1326                                         ast_str_set(&fieldtype, 0, "FLOAT");
1327                                 } else if (type == RQ_DATE) {
1328                                         ast_str_set(&fieldtype, 0, "DATE");
1329                                 } else if (type == RQ_DATETIME) {
1330                                         ast_str_set(&fieldtype, 0, "DATETIME");
1331                                 } else {
1332                                         continue;
1333                                 }
1334                                 ast_str_set(&sql, 0, "ALTER TABLE %s ADD COLUMN %s %s", tablename, elm, ast_str_buffer(fieldtype));
1335
1336                                 ast_mutex_lock(&table->database->lock);
1337                                 if (!mysql_reconnect(table->database)) {
1338                                         ast_mutex_unlock(&table->database->lock);
1339                                         ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
1340                                         continue;
1341                                 }
1342
1343                                 /* Execution. */
1344                                 if (mysql_real_query(&table->database->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
1345                                         ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
1346                                         ast_debug(1, "MySQL RealTime: Query: %s\n", ast_str_buffer(sql));
1347                                         ast_debug(1, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&table->database->handle));
1348                                 } else {
1349                                         table_altered = 1;
1350                                 }
1351                         }
1352                 }
1353         }
1354         release_table(table);
1355
1356         /* If we altered the table, we must refresh the cache */
1357         if (table_altered) {
1358                 unload_mysql(database, tablename);
1359                 release_table(find_table(database, tablename));
1360         }
1361         return res;
1362 }
1363
1364 static struct ast_config_engine mysql_engine = {
1365         .name = "mysql",
1366         .load_func = config_mysql,
1367         .realtime_func = realtime_mysql,
1368         .realtime_multi_func = realtime_multi_mysql,
1369         .store_func = store_mysql,
1370         .destroy_func = destroy_mysql,
1371         .update_func = update_mysql,
1372         .update2_func = update2_mysql,
1373         .require_func = require_mysql,
1374         .unload_func = unload_mysql,
1375 };
1376
1377 static int load_module(void)
1378 {
1379         parse_config(0);
1380
1381         ast_config_engine_register(&mysql_engine);
1382         ast_verb(2, "MySQL RealTime driver loaded.\n");
1383         ast_cli_register_multiple(cli_realtime_mysql_status, sizeof(cli_realtime_mysql_status) / sizeof(struct ast_cli_entry));
1384         return 0;
1385 }
1386
1387 static int unload_module(void)
1388 {
1389         struct mysql_conn *cur;
1390         struct tables *table;
1391
1392         ast_cli_unregister_multiple(cli_realtime_mysql_status, sizeof(cli_realtime_mysql_status) / sizeof(struct ast_cli_entry));
1393         ast_config_engine_deregister(&mysql_engine);
1394         ast_verb(2, "MySQL RealTime unloaded.\n");
1395
1396         AST_RWLIST_WRLOCK(&databases);
1397         while ((cur = AST_RWLIST_REMOVE_HEAD(&databases, list))) {
1398                 mysql_close(&cur->handle);
1399                 ast_mutex_destroy(&cur->lock);
1400                 ast_free(cur);
1401         }
1402         AST_RWLIST_UNLOCK(&databases);
1403
1404         /* Destroy cached table info */
1405         AST_LIST_LOCK(&mysql_tables);
1406         while ((table = AST_LIST_REMOVE_HEAD(&mysql_tables, list))) {
1407                 destroy_table(table);
1408         }
1409         AST_LIST_UNLOCK(&mysql_tables);
1410
1411         return 0;
1412 }
1413
1414 static int reload(void)
1415 {
1416         parse_config(1);
1417         ast_verb(2, "MySQL RealTime reloaded.\n");
1418         return 0;
1419 }
1420
1421 static int parse_config(int reload)
1422 {
1423         struct ast_config *config = NULL;
1424         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
1425         const char *catg;
1426         struct mysql_conn *cur;
1427
1428         if ((config = ast_config_load(RES_CONFIG_MYSQL_CONF, config_flags)) == CONFIG_STATUS_FILEMISSING) {
1429                 /* Support old config file name */
1430                 config = ast_config_load(RES_CONFIG_MYSQL_CONF_OLD, config_flags);
1431         }
1432
1433         if (config == CONFIG_STATUS_FILEMISSING) {
1434                 return 0;
1435         } else if (config == CONFIG_STATUS_FILEUNCHANGED) {
1436                 return 0;
1437         } else if (config == CONFIG_STATUS_FILEINVALID) {
1438                 ast_log(LOG_ERROR, "Not %sloading " RES_CONFIG_MYSQL_CONF "\n", reload ? "re" : "");
1439         }
1440
1441         AST_RWLIST_WRLOCK(&databases);
1442         for (catg = ast_category_browse(config, NULL); catg; catg = ast_category_browse(config, catg)) {
1443                 /* Does this category already exist? */
1444                 AST_RWLIST_TRAVERSE(&databases, cur, list) {
1445                         if (!strcmp(cur->unique_name, catg)) {
1446                                 break;
1447                         }
1448                 }
1449
1450                 if (!cur) {
1451                         if (!(cur = ast_calloc(1, sizeof(*cur) + strlen(catg) + 1))) {
1452                                 ast_log(LOG_WARNING, "Could not allocate space for MySQL database '%s'\n", catg);
1453                                 continue;
1454                         }
1455
1456                         strcpy(cur->unique_name, catg); /* SAFE */
1457                         ast_mutex_init(&cur->lock);
1458                         AST_RWLIST_INSERT_TAIL(&databases, cur, list);
1459                 }
1460
1461                 load_mysql_config(config, catg, cur);
1462         }
1463         AST_RWLIST_UNLOCK(&databases);
1464
1465         ast_config_destroy(config);
1466
1467         return 0;
1468 }
1469
1470 static int load_mysql_config(struct ast_config *config, const char *category, struct mysql_conn *conn)
1471 {
1472         const char *s;
1473
1474         if (!(s = ast_variable_retrieve(config, category, "dbuser"))) {
1475                 ast_log(LOG_WARNING, "MySQL RealTime: No database user found, using 'asterisk' as default.\n");
1476                 s = "asterisk";
1477         }
1478         ast_copy_string(conn->user, s, sizeof(conn->user));
1479
1480         if (!(s = ast_variable_retrieve(config, category, "dbpass"))) {
1481                 ast_log(LOG_WARNING, "MySQL RealTime: No database password found, using 'asterisk' as default.\n");
1482                 s = "asterisk";
1483         }
1484         ast_copy_string(conn->pass, s, sizeof(conn->pass));
1485
1486         if (!(s = ast_variable_retrieve(config, category, "dbhost"))) {
1487                 ast_log(LOG_WARNING, "MySQL RealTime: No database host found, using localhost via socket.\n");
1488                 s = "";
1489         }
1490         ast_copy_string(conn->host, s, sizeof(conn->host));
1491
1492         if (!(s = ast_variable_retrieve(config, category, "dbname"))) {
1493                 ast_log(LOG_WARNING, "MySQL RealTime: No database name found, using 'asterisk' as default.\n");
1494                 s = "asterisk";
1495         }
1496         ast_copy_string(conn->name, s, sizeof(conn->name));
1497
1498         if (!(s = ast_variable_retrieve(config, category, "dbport"))) {
1499                 ast_log(LOG_WARNING, "MySQL RealTime: No database port found, using 3306 as default.\n");
1500                 conn->port = 3306;
1501         } else
1502                 conn->port = atoi(s);
1503
1504         if (!(s = ast_variable_retrieve(config, category, "dbsock"))) {
1505                 if (ast_strlen_zero(conn->host)) {
1506                         char *paths[3] = { "/tmp/mysql.sock", "/var/lib/mysql/mysql.sock", "/var/run/mysqld/mysqld.sock" };
1507                         struct stat st;
1508                         int i;
1509                         for (i = 0; i < 3; i++) {
1510                                 if (!stat(paths[i], &st)) {
1511                                         ast_log(LOG_WARNING, "MySQL RealTime: No database socket found, using '%s' as default.\n", paths[i]);
1512                                         ast_copy_string(conn->sock, paths[i], sizeof(conn->sock));
1513                                 }
1514                         }
1515                         if (i == 3) {
1516                                 ast_log(LOG_WARNING, "MySQL RealTime: No database socket found (and unable to detect a suitable path).\n");
1517                                 return 0;
1518                         }
1519                 }
1520         } else
1521                 ast_copy_string(conn->sock, s, sizeof(conn->sock));
1522
1523         if ((s = ast_variable_retrieve(config, category, "dbcharset"))) {
1524                 ast_copy_string(conn->charset, s, sizeof(conn->charset));
1525         }
1526
1527         if (!(s = ast_variable_retrieve(config, category, "requirements"))) {
1528                 ast_log(LOG_WARNING, "MySQL realtime: no requirements setting found, using 'warn' as default.\n");
1529                 conn->requirements = RQ_WARN;
1530         } else if (!strcasecmp(s, "createclose")) {
1531                 conn->requirements = RQ_CREATECLOSE;
1532         } else if (!strcasecmp(s, "createchar")) {
1533                 conn->requirements = RQ_CREATECHAR;
1534         } else if (!strcasecmp(s, "warn")) {
1535                 conn->requirements = RQ_WARN;
1536         } else {
1537                 ast_log(LOG_WARNING, "MySQL realtime: unrecognized requirements setting '%s', using 'warn'\n", s);
1538                 conn->requirements = RQ_WARN;
1539         }
1540
1541         if (!ast_strlen_zero(conn->host)) {
1542                 ast_debug(1, "MySQL RealTime host: %s\n", conn->host);
1543                 ast_debug(1, "MySQL RealTime port: %i\n", conn->port);
1544         } else
1545                 ast_debug(1, "MySQL RealTime socket: %s\n", conn->sock);
1546         ast_debug(1, "MySQL RealTime database name: %s\n", conn->name);
1547         ast_debug(1, "MySQL RealTime user: %s\n", conn->user);
1548         ast_debug(1, "MySQL RealTime password: %s\n", conn->pass);
1549         if(conn->charset)
1550                 ast_debug(1, "MySQL RealTime charset: %s\n", conn->charset);
1551
1552         return 1;
1553 }
1554
1555 static int mysql_reconnect(struct mysql_conn *conn)
1556 {
1557 #ifdef MYSQL_OPT_RECONNECT
1558         my_bool trueval = 1;
1559 #endif
1560
1561         /* mutex lock should have been locked before calling this function. */
1562
1563 reconnect_tryagain:
1564         if ((!conn->connected) && (!ast_strlen_zero(conn->host) || conn->sock) && !ast_strlen_zero(conn->user) && !ast_strlen_zero(conn->name)) {
1565                 if (!mysql_init(&conn->handle)) {
1566                         ast_log(LOG_WARNING, "MySQL RealTime: Insufficient memory to allocate MySQL resource.\n");
1567                         conn->connected = 0;
1568                         return 0;
1569                 }
1570                 if(conn->charset && strlen(conn->charset) > 2){
1571                         char set_names[255];
1572                         char statement[512];
1573                         snprintf(set_names, sizeof(set_names), "SET NAMES %s", conn->charset);
1574                         mysql_real_escape_string(&conn->handle, statement, set_names, sizeof(set_names));
1575                         mysql_options(&conn->handle, MYSQL_INIT_COMMAND, set_names);
1576                         mysql_options(&conn->handle, MYSQL_SET_CHARSET_NAME, conn->charset);
1577                 }
1578
1579                 if (mysql_real_connect(&conn->handle, conn->host, conn->user, conn->pass, conn->name, conn->port, conn->sock, 0)) {
1580 #ifdef MYSQL_OPT_RECONNECT
1581                         /* The default is no longer to automatically reconnect on failure,
1582                          * (as of 5.0.3) so we have to set that option here. */
1583                         mysql_options(&conn->handle, MYSQL_OPT_RECONNECT, &trueval);
1584 #endif
1585                         ast_debug(1, "MySQL RealTime: Successfully connected to database.\n");
1586                         conn->connected = 1;
1587                         conn->connect_time = time(NULL);
1588                         return 1;
1589                 } else {
1590                         ast_log(LOG_ERROR, "MySQL RealTime: Failed to connect database server %s on %s (err %d). Check debug for more info.\n", conn->name, !ast_strlen_zero(conn->host) ? conn->host : conn->sock, mysql_errno(&conn->handle));
1591                         ast_debug(1, "MySQL RealTime: Cannot Connect (%d): %s\n", mysql_errno(&conn->handle), mysql_error(&conn->handle));
1592                         conn->connected = 0;
1593                         conn->connect_time = 0;
1594                         return 0;
1595                 }
1596         } else {
1597                 /* MySQL likes to return an error, even if it reconnects successfully.
1598                  * So the postman pings twice. */
1599                 if (mysql_ping(&conn->handle) != 0 && (usleep(1) + 2 > 0) && mysql_ping(&conn->handle) != 0) {
1600                         conn->connected = 0;
1601                         conn->connect_time = 0;
1602                         ast_log(LOG_ERROR, "MySQL RealTime: Ping failed (%d).  Trying an explicit reconnect.\n", mysql_errno(&conn->handle));
1603                         ast_debug(1, "MySQL RealTime: Server Error (%d): %s\n", mysql_errno(&conn->handle), mysql_error(&conn->handle));
1604                         goto reconnect_tryagain;
1605                 }
1606
1607                 if (!conn->connected) {
1608                         conn->connected = 1;
1609                         conn->connect_time = time(NULL);
1610                 }
1611
1612                 if (mysql_select_db(&conn->handle, conn->name) != 0) {
1613                         ast_log(LOG_WARNING, "MySQL RealTime: Unable to select database: %s. Still Connected (%u) - %s.\n", conn->name, mysql_errno(&conn->handle), mysql_error(&conn->handle));
1614                         return 0;
1615                 }
1616
1617                 ast_debug(1, "MySQL RealTime: Connection okay.\n");
1618                 return 1;
1619         }
1620 }
1621
1622 static char *handle_cli_realtime_mysql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1623 {
1624         struct tables *cur;
1625         int l, which;
1626         char *ret = NULL;
1627
1628         switch (cmd) {
1629         case CLI_INIT:
1630                 e->command = "realtime mysql cache";
1631                 e->usage =
1632                         "Usage: realtime mysql cache [<database> <table>]\n"
1633                         "       Shows table cache for the MySQL RealTime driver\n";
1634                 return NULL;
1635         case CLI_GENERATE:
1636                 if (a->argc < 4 || a->argc > 5) {
1637                         return NULL;
1638                 }
1639                 l = strlen(a->word);
1640                 which = 0;
1641                 if (a->argc == 5) {
1642                         AST_LIST_LOCK(&mysql_tables);
1643                         AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
1644                                 if (!strcasecmp(a->argv[3], cur->database->unique_name) && !strncasecmp(a->word, cur->name, l) && ++which > a->n) {
1645                                         ret = ast_strdup(cur->name);
1646                                         break;
1647                                 }
1648                         }
1649                         AST_LIST_UNLOCK(&mysql_tables);
1650                 } else {
1651                         struct mysql_conn *cur;
1652                         AST_RWLIST_RDLOCK(&databases);
1653                         AST_RWLIST_TRAVERSE(&databases, cur, list) {
1654                                 if (!strncasecmp(a->word, cur->unique_name, l) && ++which > a->n) {
1655                                         ret = ast_strdup(cur->unique_name);
1656                                         break;
1657                                 }
1658                         }
1659                         AST_RWLIST_UNLOCK(&databases);
1660                 }
1661                 return ret;
1662         }
1663
1664         if (a->argc == 3) {
1665                 /* List of tables */
1666                 AST_LIST_LOCK(&mysql_tables);
1667                 AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
1668                         ast_cli(a->fd, "%20.20s %s\n", cur->database->unique_name, cur->name);
1669                 }
1670                 AST_LIST_UNLOCK(&mysql_tables);
1671         } else if (a->argc == 4) {
1672                 int found = 0;
1673                 /* List of tables */
1674                 AST_LIST_LOCK(&mysql_tables);
1675                 AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
1676                         if (!strcasecmp(cur->database->unique_name, a->argv[3])) {
1677                                 ast_cli(a->fd, "%s\n", cur->name);
1678                                 found = 1;
1679                         }
1680                 }
1681                 AST_LIST_UNLOCK(&mysql_tables);
1682                 if (!found) {
1683                         ast_cli(a->fd, "No tables cached within %s database\n", a->argv[3]);
1684                 }
1685         } else if (a->argc == 5) {
1686                 /* List of columns */
1687                 if ((cur = find_table(a->argv[3], a->argv[4]))) {
1688                         struct columns *col;
1689                         ast_cli(a->fd, "Columns for Table Cache '%s':\n", a->argv[3]);
1690                         ast_cli(a->fd, "%-20.20s %-20.20s %-3.3s\n", "Name", "Type", "Len");
1691                         AST_LIST_TRAVERSE(&cur->columns, col, list) {
1692                                 ast_cli(a->fd, "%-20.20s %-20.20s %3d\n", col->name, col->type, col->len);
1693                         }
1694                         release_table(cur);
1695                 } else {
1696                         ast_cli(a->fd, "No such table '%s'\n", a->argv[3]);
1697                 }
1698         }
1699         return CLI_SUCCESS;
1700 }
1701
1702 static char *handle_cli_realtime_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1703 {
1704         char status[256], status2[100] = "", type[20];
1705         char *ret = NULL;
1706         int ctime = 0, found = 0;
1707         struct mysql_conn *cur;
1708         int l = 0, which = 0;
1709
1710         switch (cmd) {
1711         case CLI_INIT:
1712                 e->command = "realtime mysql status";
1713                 e->usage =
1714                         "Usage: realtime mysql status [<database>]\n"
1715                         "       Shows connection information for the MySQL RealTime driver\n";
1716                 return NULL;
1717         case CLI_GENERATE:
1718                 if (a->argc == 4) {
1719                         AST_RWLIST_RDLOCK(&databases);
1720                         AST_RWLIST_TRAVERSE(&databases, cur, list) {
1721                                 if (!strncasecmp(a->word, cur->unique_name, l) && ++which > a->n) {
1722                                         ret = ast_strdup(cur->unique_name);
1723                                         break;
1724                                 }
1725                         }
1726                         AST_RWLIST_UNLOCK(&databases);
1727                 }
1728                 return ret;
1729         }
1730
1731         if (a->argc != 3)
1732                 return CLI_SHOWUSAGE;
1733
1734         AST_RWLIST_RDLOCK(&databases);
1735         AST_RWLIST_TRAVERSE(&databases, cur, list) {
1736                 if (a->argc == 3 || (a->argc == 4 && !strcasecmp(a->argv[3], cur->unique_name))) {
1737                         found = 1;
1738
1739                         if (mysql_reconnect(cur)) {
1740                                 snprintf(type, sizeof(type), "connected to");
1741                                 ctime = time(NULL) - cur->connect_time;
1742                         } else {
1743                                 snprintf(type, sizeof(type), "configured for");
1744                                 ctime = -1;
1745                         }
1746
1747                         if (!ast_strlen_zero(cur->host)) {
1748                                 snprintf(status, sizeof(status), "%s %s %s@%s, port %d", cur->unique_name, type, cur->name, cur->host, cur->port);
1749                         } else {
1750                                 snprintf(status, sizeof(status), "%s %s %s on socket file %s", cur->unique_name, type, cur->name, cur->sock);
1751                         }
1752
1753                         if (!ast_strlen_zero(cur->user)) {
1754                                 snprintf(status2, sizeof(status2), " with username %s", cur->user);
1755                         } else {
1756                                 status2[0] = '\0';
1757                         }
1758
1759                         if (ctime > 31536000) {
1760                                 ast_cli(a->fd, "%s%s for %.1f years.\n", status, status2, (double)ctime / 31536000.0);
1761                         } else if (ctime > 86400 * 30) {
1762                                 ast_cli(a->fd, "%s%s for %d days.\n", status, status2, ctime / 86400);
1763                         } else if (ctime > 86400) {
1764                                 ast_cli(a->fd, "%s%s for %d days, %d hours.\n", status, status2, ctime / 86400, (ctime % 86400) / 3600);
1765                         } else if (ctime > 3600) {
1766                                 ast_cli(a->fd, "%s%s for %d hours, %d minutes.\n", status, status2, ctime / 3600, (ctime % 3600) / 60);
1767                         } else if (ctime > 60) {
1768                                 ast_cli(a->fd, "%s%s for %d minutes.\n", status, status2, ctime / 60);
1769                         } else if (ctime > -1) {
1770                                 ast_cli(a->fd, "%s%s for %d seconds.\n", status, status2, ctime);
1771                         } else {
1772                                 ast_cli(a->fd, "%s%s.\n", status, status2);
1773                         }
1774                 }
1775         }
1776         AST_RWLIST_UNLOCK(&databases);
1777
1778         if (!found) {
1779                 ast_cli(a->fd, "No connections configured.\n");
1780         }
1781         return CLI_SUCCESS;
1782 }
1783
1784 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "MySQL RealTime Configuration Driver",
1785                 .load = load_module,
1786                 .unload = unload_module,
1787                 .reload = reload,
1788                 .load_pri = AST_MODPRI_REALTIME_DRIVER,
1789                 );
1790