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