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