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