Skip to content
Snippets Groups Projects
res_config_mysql.c 52.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • 						ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' cannot be type '%s' (need %s)\n",
    							database, tablename, column->name, column->type,
    							type == RQ_CHAR ? "char" : type == RQ_FLOAT ? "float" :
    							type == RQ_DATETIME ? "datetime" : type == RQ_DATE ? "date" : "a rather stiff drink");
    						res = -1;
    
    					} else if (strncasecmp(column->type, "tinyint", 1) == 0) {
    						if (type != RQ_UINTEGER1) {
    
    							ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for "            \
    								"the required data length: %d (detected stringtype)\n",                                      \
    								tablename, database, column->name, size);                                                    \
    							res = -1;                                                                                        \
    
    						}
    					} else if (strncasecmp(column->type, "smallint", 1) == 0) {
    						if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_UINTEGER2) {
    
    							ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for "            \
    								"the required data length: %d (detected stringtype)\n",                                      \
    								tablename, database, column->name, size);                                                    \
    							res = -1;                                                                                        \
    
    						}
    					} else if (strncasecmp(column->type, "mediumint", 1) == 0) {
    						if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
    							type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
    							type != RQ_UINTEGER3) {
    
    							ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for "            \
    								"the required data length: %d (detected stringtype)\n",                                      \
    								tablename, database, column->name, size);                                                    \
    							res = -1;                                                                                        \
    
    						}
    					} else if (strncasecmp(column->type, "int", 1) == 0) {
    						if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
    							type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
    							type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
    							type != RQ_UINTEGER4) {
    
    							ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for "            \
    								"the required data length: %d (detected stringtype)\n",                                      \
    								tablename, database, column->name, size);                                                    \
    							res = -1;                                                                                        \
    
    						}
    					} else if (strncasecmp(column->type, "bigint", 1) == 0) {
    						if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
    							type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
    							type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
    							type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
    							type != RQ_UINTEGER8) {
    
    							ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for "            \
    								"the required data length: %d (detected stringtype)\n",                                      \
    								tablename, database, column->name, size);                                                    \
    							res = -1;                                                                                        \
    
    						}
    					}
    				} else if (strcasestr(column->type, "int")) {
    					if (!ast_rq_is_int(type)) {
    
    						ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' cannot be type '%s' (need %s)\n",
    							database, tablename, column->name, column->type,
    							type == RQ_CHAR ? "char" : type == RQ_FLOAT ? "float" :
    							type == RQ_DATETIME ? "datetime" : type == RQ_DATE ? "date" :
    							"to get a life, rather than writing silly error messages");
    						res = -1;
    
    					} else if (strncasecmp(column->type, "tinyint", 1) == 0) {
    						if (type != RQ_INTEGER1) {
    
    							ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for "            \
    								"the required data length: %d (detected stringtype)\n",                                      \
    								tablename, database, column->name, size);                                                    \
    							res = -1;                                                                                        \
    
    						}
    					} else if (strncasecmp(column->type, "smallint", 1) == 0) {
    						if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_INTEGER2) {
    
    							ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for "            \
    								"the required data length: %d (detected stringtype)\n",                                      \
    								tablename, database, column->name, size);                                                    \
    							res = -1;                                                                                        \
    
    						}
    					} else if (strncasecmp(column->type, "mediumint", 1) == 0) {
    						if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
    							type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
    							type != RQ_INTEGER3) {
    
    							ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for "            \
    								"the required data length: %d (detected stringtype)\n",                                      \
    								tablename, database, column->name, size);                                                    \
    							res = -1;                                                                                        \
    
    						}
    					} else if (strncasecmp(column->type, "int", 1) == 0) {
    						if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
    							type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
    							type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
    							type != RQ_INTEGER4) {
    
    							ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for "            \
    								"the required data length: %d (detected stringtype)\n",                                      \
    								tablename, database, column->name, size);                                                    \
    							res = -1;                                                                                        \
    
    						}
    					} else if (strncasecmp(column->type, "bigint", 1) == 0) {
    						if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
    							type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
    							type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
    							type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
    							type != RQ_INTEGER8) {
    
    							ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for "            \
    								"the required data length: %d (detected stringtype)\n",                                      \
    								tablename, database, column->name, size);                                                    \
    							res = -1;                                                                                        \
    
    				} else if (strncmp(column->type, "float", 5) == 0) {
    					if (!ast_rq_is_int(type) && type != RQ_FLOAT) {
    
    						ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
    						res = -1;
    
    				} else if (strncmp(column->type, "datetime", 8) == 0 || strncmp(column->type, "timestamp", 9) == 0) {
    					if (type != RQ_DATETIME) {
    
    						ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
    						res = -1;
    
    				} else if (strncmp(column->type, "date", 4) == 0) {
    					if (type != RQ_DATE) {
    
    						ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
    
    				} else { /* Other, possibly unsupported types? */
    					ast_log(LOG_WARNING, "Possibly unsupported column type '%s' on column '%s'\n", column->type, column->name);
    					res = -1;
    
    			ast_log(LOG_WARNING, "Table %s requires a column '%s' of size '%d', but no such column exists.\n", tablename, elm, size);
    
    		}
    	}
    	release_table(table);
    
    	return res;
    }
    
    static struct ast_config_engine mysql_engine = {
    	.name = "mysql",
    	.load_func = config_mysql,
    	.realtime_func = realtime_mysql,
    	.realtime_multi_func = realtime_multi_mysql,
    	.store_func = store_mysql,
    	.destroy_func = destroy_mysql,
    	.update_func = update_mysql,
    	.update2_func = update2_mysql,
    	.require_func = require_mysql,
    	.unload_func = unload_mysql,
    };
    
    static int load_module(void)
    {
    	parse_config(0);
    
    	ast_config_engine_register(&mysql_engine);
    
    	ast_cli_register_multiple(cli_realtime_mysql_status, sizeof(cli_realtime_mysql_status) / sizeof(struct ast_cli_entry));
    	return 0;
    }
    
    static int unload_module(void)
    {
    	struct mysql_conn *cur;
    	struct tables *table;
    
    	ast_cli_unregister_multiple(cli_realtime_mysql_status, sizeof(cli_realtime_mysql_status) / sizeof(struct ast_cli_entry));
    	ast_config_engine_deregister(&mysql_engine);
    
    
    	AST_RWLIST_WRLOCK(&databases);
    	while ((cur = AST_RWLIST_REMOVE_HEAD(&databases, list))) {
    		mysql_close(&cur->handle);
    		ast_mutex_destroy(&cur->lock);
    		ast_free(cur);
    	}
    	AST_RWLIST_UNLOCK(&databases);
    
    	/* Destroy cached table info */
    	AST_LIST_LOCK(&mysql_tables);
    	while ((table = AST_LIST_REMOVE_HEAD(&mysql_tables, list))) {
    		destroy_table(table);
    	}
    	AST_LIST_UNLOCK(&mysql_tables);
    
    	return 0;
    }
    
    static int reload(void)
    {
    	parse_config(1);
    
    	ast_verb(2, "MySQL RealTime reloaded.\n");
    
    	return 0;
    }
    
    static int parse_config(int reload)
    {
    	struct ast_config *config = NULL;
    	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
    	const char *catg;
    	struct mysql_conn *cur;
    
    	if ((config = ast_config_load(RES_CONFIG_MYSQL_CONF, config_flags)) == CONFIG_STATUS_FILEMISSING) {
    
    		/* Support old config file name */
    		config = ast_config_load(RES_CONFIG_MYSQL_CONF_OLD, config_flags);
    	}
    
    	if (config == CONFIG_STATUS_FILEMISSING) {
    
    		return 0;
    	} else if (config == CONFIG_STATUS_FILEUNCHANGED) {
    		return 0;
    	} else if (config == CONFIG_STATUS_FILEINVALID) {
    		ast_log(LOG_ERROR, "Not %sloading " RES_CONFIG_MYSQL_CONF "\n", reload ? "re" : "");
    	}
    
    	AST_RWLIST_WRLOCK(&databases);
    	for (catg = ast_category_browse(config, NULL); catg; catg = ast_category_browse(config, catg)) {
    		/* Does this category already exist? */
    		AST_RWLIST_TRAVERSE(&databases, cur, list) {
    			if (!strcmp(cur->unique_name, catg)) {
    				break;
    			}
    		}
    
    		if (!cur) {
    			if (!(cur = ast_calloc(1, sizeof(*cur) + strlen(catg) + 1))) {
    				ast_log(LOG_WARNING, "Could not allocate space for MySQL database '%s'\n", catg);
    				continue;
    			}
    
    			strcpy(cur->unique_name, catg); /* SAFE */
    			ast_mutex_init(&cur->lock);
    			AST_RWLIST_INSERT_TAIL(&databases, cur, list);
    		}
    
    		load_mysql_config(config, catg, cur);
    	}
    	AST_RWLIST_UNLOCK(&databases);
    
    	ast_config_destroy(config);
    
    	return 0;
    }
    
    static int load_mysql_config(struct ast_config *config, const char *category, struct mysql_conn *conn)
    {
    	const char *s;
    
    	if (!(s = ast_variable_retrieve(config, category, "dbuser"))) {
    		ast_log(LOG_WARNING, "MySQL RealTime: No database user found, using 'asterisk' as default.\n");
    		s = "asterisk";
    	}
    	ast_copy_string(conn->user, s, sizeof(conn->user));
    
    	if (!(s = ast_variable_retrieve(config, category, "dbpass"))) {
    		ast_log(LOG_WARNING, "MySQL RealTime: No database password found, using 'asterisk' as default.\n");
    		s = "asterisk";
    	}
    	ast_copy_string(conn->pass, s, sizeof(conn->pass));
    
    	if (!(s = ast_variable_retrieve(config, category, "dbhost"))) {
    		ast_log(LOG_WARNING, "MySQL RealTime: No database host found, using localhost via socket.\n");
    		s = "";
    	}
    	ast_copy_string(conn->host, s, sizeof(conn->host));
    
    	if (!(s = ast_variable_retrieve(config, category, "dbname"))) {
    		ast_log(LOG_WARNING, "MySQL RealTime: No database name found, using 'asterisk' as default.\n");
    		s = "asterisk";
    	}
    	ast_copy_string(conn->name, s, sizeof(conn->name));
    
    	if (!(s = ast_variable_retrieve(config, category, "dbport"))) {
    		ast_log(LOG_WARNING, "MySQL RealTime: No database port found, using 3306 as default.\n");
    		conn->port = 3306;
    	} else
    		conn->port = atoi(s);
    
    	if (!(s = ast_variable_retrieve(config, category, "dbsock"))) {
    		if (ast_strlen_zero(conn->host)) {
    			char *paths[3] = { "/tmp/mysql.sock", "/var/lib/mysql/mysql.sock", "/var/run/mysqld/mysqld.sock" };
    			struct stat st;
    			int i;
    			for (i = 0; i < 3; i++) {
    				if (!stat(paths[i], &st)) {
    					ast_log(LOG_WARNING, "MySQL RealTime: No database socket found, using '%s' as default.\n", paths[i]);
    					ast_copy_string(conn->sock, paths[i], sizeof(conn->sock));
    				}
    			}
    			if (i == 3) {
    				ast_log(LOG_WARNING, "MySQL RealTime: No database socket found (and unable to detect a suitable path).\n");
    				return 0;
    			}
    		}
    	} else
    		ast_copy_string(conn->sock, s, sizeof(conn->sock));
    
    
    	if ((s = ast_variable_retrieve(config, category, "dbcharset"))) {
    		ast_copy_string(conn->charset, s, sizeof(conn->charset));
    	}
    
    
    	if (!(s = ast_variable_retrieve(config, category, "requirements"))) {
    		ast_log(LOG_WARNING, "MySQL realtime: no requirements setting found, using 'warn' as default.\n");
    		conn->requirements = RQ_WARN;
    	} else if (!strcasecmp(s, "createclose")) {
    		conn->requirements = RQ_CREATECLOSE;
    	} else if (!strcasecmp(s, "createchar")) {
    		conn->requirements = RQ_CREATECHAR;
    	} else if (!strcasecmp(s, "warn")) {
    		conn->requirements = RQ_WARN;
    	} else {
    		ast_log(LOG_WARNING, "MySQL realtime: unrecognized requirements setting '%s', using 'warn'\n", s);
    		conn->requirements = RQ_WARN;
    	}
    
    	if (!ast_strlen_zero(conn->host)) {
    		ast_debug(1, "MySQL RealTime host: %s\n", conn->host);
    		ast_debug(1, "MySQL RealTime port: %i\n", conn->port);
    	} else
    		ast_debug(1, "MySQL RealTime socket: %s\n", conn->sock);
    	ast_debug(1, "MySQL RealTime database name: %s\n", conn->name);
    	ast_debug(1, "MySQL RealTime user: %s\n", conn->user);
    	ast_debug(1, "MySQL RealTime password: %s\n", conn->pass);
    
    	if(!ast_strlen_zero(conn->charset))
    
    		ast_debug(1, "MySQL RealTime charset: %s\n", conn->charset);
    
    
    	return 1;
    }
    
    static int mysql_reconnect(struct mysql_conn *conn)
    {
    #ifdef MYSQL_OPT_RECONNECT
    	my_bool trueval = 1;
    #endif
    
    	/* mutex lock should have been locked before calling this function. */
    
    reconnect_tryagain:
    
    	if ((!conn->connected) && (!ast_strlen_zero(conn->host) || !ast_strlen_zero(conn->sock)) && !ast_strlen_zero(conn->user) && !ast_strlen_zero(conn->name)) {
    
    		if (!mysql_init(&conn->handle)) {
    			ast_log(LOG_WARNING, "MySQL RealTime: Insufficient memory to allocate MySQL resource.\n");
    			conn->connected = 0;
    			return 0;
    		}
    
    		if(strlen(conn->charset) > 2){
    
    			char set_names[255];
    			char statement[512];
    			snprintf(set_names, sizeof(set_names), "SET NAMES %s", conn->charset);
    			mysql_real_escape_string(&conn->handle, statement, set_names, sizeof(set_names));
    			mysql_options(&conn->handle, MYSQL_INIT_COMMAND, set_names);
    			mysql_options(&conn->handle, MYSQL_SET_CHARSET_NAME, conn->charset);
    		}
    
    
    		if (mysql_real_connect(&conn->handle, conn->host, conn->user, conn->pass, conn->name, conn->port, conn->sock, 0)) {
    #ifdef MYSQL_OPT_RECONNECT
    			/* The default is no longer to automatically reconnect on failure,
    			 * (as of 5.0.3) so we have to set that option here. */
    			mysql_options(&conn->handle, MYSQL_OPT_RECONNECT, &trueval);
    #endif
    			ast_debug(1, "MySQL RealTime: Successfully connected to database.\n");
    			conn->connected = 1;
    			conn->connect_time = time(NULL);
    			return 1;
    		} else {
    			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));
    			ast_debug(1, "MySQL RealTime: Cannot Connect (%d): %s\n", mysql_errno(&conn->handle), mysql_error(&conn->handle));
    			conn->connected = 0;
    
    			conn->connect_time = 0;
    
    			return 0;
    		}
    	} else {
    		/* MySQL likes to return an error, even if it reconnects successfully.
    		 * So the postman pings twice. */
    		if (mysql_ping(&conn->handle) != 0 && (usleep(1) + 2 > 0) && mysql_ping(&conn->handle) != 0) {
    			conn->connected = 0;
    
    			conn->connect_time = 0;
    
    			ast_log(LOG_ERROR, "MySQL RealTime: Ping failed (%d).  Trying an explicit reconnect.\n", mysql_errno(&conn->handle));
    			ast_debug(1, "MySQL RealTime: Server Error (%d): %s\n", mysql_errno(&conn->handle), mysql_error(&conn->handle));
    			goto reconnect_tryagain;
    		}
    
    
    		if (!conn->connected) {
    			conn->connected = 1;
    			conn->connect_time = time(NULL);
    		}
    
    
    		if (mysql_select_db(&conn->handle, conn->name) != 0) {
    			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));
    			return 0;
    		}
    
    		ast_debug(1, "MySQL RealTime: Connection okay.\n");
    		return 1;
    	}
    }
    
    static char *handle_cli_realtime_mysql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	struct tables *cur;
    	int l, which;
    	char *ret = NULL;
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "realtime mysql cache";
    		e->usage =
    			"Usage: realtime mysql cache [<database> <table>]\n"
    			"       Shows table cache for the MySQL RealTime driver\n";
    		return NULL;
    	case CLI_GENERATE:
    		if (a->argc < 4 || a->argc > 5) {
    			return NULL;
    		}
    		l = strlen(a->word);
    		which = 0;
    		if (a->argc == 5) {
    			AST_LIST_LOCK(&mysql_tables);
    			AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
    				if (!strcasecmp(a->argv[3], cur->database->unique_name) && !strncasecmp(a->word, cur->name, l) && ++which > a->n) {
    					ret = ast_strdup(cur->name);
    					break;
    				}
    			}
    			AST_LIST_UNLOCK(&mysql_tables);
    		} else {
    			struct mysql_conn *cur;
    			AST_RWLIST_RDLOCK(&databases);
    			AST_RWLIST_TRAVERSE(&databases, cur, list) {
    				if (!strncasecmp(a->word, cur->unique_name, l) && ++which > a->n) {
    					ret = ast_strdup(cur->unique_name);
    					break;
    				}
    			}
    			AST_RWLIST_UNLOCK(&databases);
    		}
    		return ret;
    	}
    
    	if (a->argc == 3) {
    		/* List of tables */
    		AST_LIST_LOCK(&mysql_tables);
    		AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
    			ast_cli(a->fd, "%20.20s %s\n", cur->database->unique_name, cur->name);
    		}
    		AST_LIST_UNLOCK(&mysql_tables);
    	} else if (a->argc == 4) {
    		int found = 0;
    		/* List of tables */
    		AST_LIST_LOCK(&mysql_tables);
    		AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
    			if (!strcasecmp(cur->database->unique_name, a->argv[3])) {
    				ast_cli(a->fd, "%s\n", cur->name);
    				found = 1;
    			}
    		}
    		AST_LIST_UNLOCK(&mysql_tables);
    		if (!found) {
    			ast_cli(a->fd, "No tables cached within %s database\n", a->argv[3]);
    		}
    	} else if (a->argc == 5) {
    		/* List of columns */
    		if ((cur = find_table(a->argv[3], a->argv[4]))) {
    			struct columns *col;
    			ast_cli(a->fd, "Columns for Table Cache '%s':\n", a->argv[3]);
    			ast_cli(a->fd, "%-20.20s %-20.20s %-3.3s\n", "Name", "Type", "Len");
    			AST_LIST_TRAVERSE(&cur->columns, col, list) {
    				ast_cli(a->fd, "%-20.20s %-20.20s %3d\n", col->name, col->type, col->len);
    			}
    
    			release_table(cur);
    
    		} else {
    			ast_cli(a->fd, "No such table '%s'\n", a->argv[3]);
    		}
    	}
    	return CLI_SUCCESS;
    }
    
    static char *handle_cli_realtime_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	char status[256], status2[100] = "", type[20];
    	char *ret = NULL;
    	int ctime = 0, found = 0;
    	struct mysql_conn *cur;
    	int l = 0, which = 0;
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "realtime mysql status";
    		e->usage =
    			"Usage: realtime mysql status [<database>]\n"
    			"       Shows connection information for the MySQL RealTime driver\n";
    		return NULL;
    	case CLI_GENERATE:
    		if (a->argc == 4) {
    			AST_RWLIST_RDLOCK(&databases);
    			AST_RWLIST_TRAVERSE(&databases, cur, list) {
    				if (!strncasecmp(a->word, cur->unique_name, l) && ++which > a->n) {
    					ret = ast_strdup(cur->unique_name);
    					break;
    				}
    			}
    			AST_RWLIST_UNLOCK(&databases);
    		}
    		return ret;
    	}
    
    	if (a->argc != 3)
    		return CLI_SHOWUSAGE;
    
    	AST_RWLIST_RDLOCK(&databases);
    	AST_RWLIST_TRAVERSE(&databases, cur, list) {
    		if (a->argc == 3 || (a->argc == 4 && !strcasecmp(a->argv[3], cur->unique_name))) {
    			found = 1;
    
    			if (mysql_reconnect(cur)) {
    				snprintf(type, sizeof(type), "connected to");
    				ctime = time(NULL) - cur->connect_time;
    			} else {
    				snprintf(type, sizeof(type), "configured for");
    				ctime = -1;
    			}
    
    			if (!ast_strlen_zero(cur->host)) {
    				snprintf(status, sizeof(status), "%s %s %s@%s, port %d", cur->unique_name, type, cur->name, cur->host, cur->port);
    			} else {
    				snprintf(status, sizeof(status), "%s %s %s on socket file %s", cur->unique_name, type, cur->name, cur->sock);
    			}
    
    			if (!ast_strlen_zero(cur->user)) {
    				snprintf(status2, sizeof(status2), " with username %s", cur->user);
    			} else {
    				status2[0] = '\0';
    			}
    
    			if (ctime > 31536000) {
    				ast_cli(a->fd, "%s%s for %.1f years.\n", status, status2, (double)ctime / 31536000.0);
    			} else if (ctime > 86400 * 30) {
    				ast_cli(a->fd, "%s%s for %d days.\n", status, status2, ctime / 86400);
    			} else if (ctime > 86400) {
    				ast_cli(a->fd, "%s%s for %d days, %d hours.\n", status, status2, ctime / 86400, (ctime % 86400) / 3600);
    			} else if (ctime > 3600) {
    				ast_cli(a->fd, "%s%s for %d hours, %d minutes.\n", status, status2, ctime / 3600, (ctime % 3600) / 60);
    			} else if (ctime > 60) {
    				ast_cli(a->fd, "%s%s for %d minutes.\n", status, status2, ctime / 60);
    			} else if (ctime > -1) {
    				ast_cli(a->fd, "%s%s for %d seconds.\n", status, status2, ctime);
    			} else {
    				ast_cli(a->fd, "%s%s.\n", status, status2);
    			}
    		}
    	}
    	AST_RWLIST_UNLOCK(&databases);
    
    	if (!found) {
    		ast_cli(a->fd, "No connections configured.\n");
    	}
    	return CLI_SUCCESS;
    }
    
    
    AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "MySQL RealTime Configuration Driver",
    
    	.support_level = AST_MODULE_SUPPORT_EXTENDED,
    	.load = load_module,
    	.unload = unload_module,
    	.reload = reload,
    	.load_pri = AST_MODPRI_REALTIME_DRIVER,
    
    	.requires = "extconfig",