diff -ruN asterisk-1.2.0-old/configs/res_mysql.conf.sample asterisk-1.2.0-new/configs/res_mysql.conf.sample --- asterisk-1.2.0-old/configs/res_mysql.conf.sample 1970-01-01 01:00:00.000000000 +0100 +++ asterisk-1.2.0-new/configs/res_mysql.conf.sample 2004-12-03 15:33:44.000000000 +0100 @@ -0,0 +1,15 @@ +; +; Sample configuration for res_config_mysql.c +; +; The value of dbhost may be either a hostname or an IP address. +; If dbhost is commented out or the string "localhost", a connection +; to the local host is assumed and dbsock is used instead of TCP/IP +; to connect to the server. +; +[general] +;dbhost = 127.0.0.1 +;dbname = asterisk +;dbuser = myuser +;dbpass = mypass +;dbport = 3306 +;dbsock = /tmp/mysql.sock diff -ruN asterisk-1.2.0-old/res/res_config_mysql.c asterisk-1.2.0-new/res/res_config_mysql.c --- asterisk-1.2.0-old/res/res_config_mysql.c 1970-01-01 01:00:00.000000000 +0100 +++ asterisk-1.2.0-new/res/res_config_mysql.c 2005-10-13 21:43:54.000000000 +0200 @@ -0,0 +1,675 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Copyright (C) 1999-2005, Digium, Inc. + * + * Mark Spencer - Asterisk Author + * Matthew Boehm - MySQL RealTime Driver Author + * + * res_config_mysql.c + * + * v2.0 - (10-07-05) - mutex_lock fixes (bug #4973, comment #0034602) + * + * v1.9 - (08-19-05) - Added support to correctly honor the family database specified + * in extconfig.conf (bug #4973) + * + * v1.8 - (04-21-05) - Modified return values of update_mysql to better indicate + * what really happened. + * + * v1.7 - (01-28-05) - Fixed non-initialization of ast_category struct + * in realtime_multi_mysql function which caused segfault. + * + * v1.6 - (00-00-00) - Skipped to bring comments into sync with version number in CVS. + * + * v1.5.1 - (01-26-05) - Added better(?) locking stuff + * + * v1.5 - (01-26-05) - Brought up to date with new config.h changes (bug #3406) + * - Added in extra locking provided by georg (bug #3248) + * + * v1.4 - (12-02-04) - Added realtime_multi_mysql function + * This function will return an ast_config with categories, + * unlike standard realtime_mysql which only returns + * a linked list of ast_variables + * + * v1.3 - (12-01-04) - Added support other operators + * Ex: =, !=, LIKE, NOT LIKE, RLIKE, etc... + * + * v1.2 - (11-DD-04) - Added reload. Updated load and unload. + * Code beautification (doc/CODING-GUIDELINES) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char *res_config_mysql_desc = "MySQL RealTime Configuration Driver"; + +AST_MUTEX_DEFINE_STATIC(mysql_lock); +#define RES_CONFIG_MYSQL_CONF "res_mysql.conf" +MYSQL mysql; +static char dbhost[50]; +static char dbuser[50]; +static char dbpass[50]; +static char dbname[50]; +static char dbsock[50]; +static int dbport; +static int connected; +static time_t connect_time; + +static int parse_config(void); +static int mysql_reconnect(const char *database); +static int realtime_mysql_status(int fd, int argc, char **argv); + +STANDARD_LOCAL_USER; + +LOCAL_USER_DECL; + +static char cli_realtime_mysql_status_usage[] = +"Usage: realtime mysql status\n" +" Shows connection information for the MySQL RealTime driver\n"; + +static struct ast_cli_entry cli_realtime_mysql_status = { + { "realtime", "mysql", "status", NULL }, realtime_mysql_status, + "Shows connection information for the MySQL RealTime driver", cli_realtime_mysql_status_usage, NULL }; + +static struct ast_variable *realtime_mysql(const char *database, const char *table, va_list ap) +{ + MYSQL_RES *result; + MYSQL_ROW row; + MYSQL_FIELD *fields; + int numFields, i; + char sql[256]; + char *stringp; + char *chunk; + char *op; + const char *newparam, *newval; + struct ast_variable *var=NULL, *prev=NULL; + + if(!table) { + ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n"); + return NULL; + } + + /* Get the first parameter and first value in our list of passed paramater/value pairs */ + newparam = va_arg(ap, const char *); + newval = va_arg(ap, const char *); + if(!newparam || !newval) { + ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n"); + mysql_close(&mysql); + return NULL; + } + + /* Create the first part of the query using the first parameter/value pairs we just extracted + If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ + + if(!strchr(newparam, ' ')) op = " ="; else op = ""; + + snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, newval); + while((newparam = va_arg(ap, const char *))) { + newval = va_arg(ap, const char *); + if(!strchr(newparam, ' ')) op = " ="; else op = ""; + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam, op, newval); + } + va_end(ap); + + ast_log(LOG_DEBUG, "MySQL RealTime: Retrieve SQL: %s\n", sql); + + /* We now have our complete statement; Lets connect to the server and execute it. */ + ast_mutex_lock(&mysql_lock); + if(!mysql_reconnect(database)) { + ast_mutex_unlock(&mysql_lock); + return NULL; + } + + if(mysql_real_query(&mysql, sql, strlen(sql))) { + ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_log(LOG_DEBUG, "MySQL RealTime: Query: %s\n", sql); + ast_log(LOG_DEBUG, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&mysql)); + ast_mutex_unlock(&mysql_lock); + return NULL; + } + + if((result = mysql_store_result(&mysql))) { + numFields = mysql_num_fields(result); + fields = mysql_fetch_fields(result); + + while((row = mysql_fetch_row(result))) { + for(i = 0; i < numFields; i++) { + stringp = row[i]; + while(stringp) { + chunk = strsep(&stringp, ";"); + if(chunk && !ast_strlen_zero(ast_strip(chunk))) { + if(prev) { + prev->next = ast_variable_new(fields[i].name, chunk); + if (prev->next) { + prev = prev->next; + } + } else { + prev = var = ast_variable_new(fields[i].name, chunk); + } + } + } + } + } + } else { + ast_log(LOG_WARNING, "MySQL RealTime: Could not find any rows in table %s.\n", table); + } + + ast_mutex_unlock(&mysql_lock); + mysql_free_result(result); + + return var; +} + +static struct ast_config *realtime_multi_mysql(const char *database, const char *table, va_list ap) +{ + MYSQL_RES *result; + MYSQL_ROW row; + MYSQL_FIELD *fields; + int numFields, i; + char sql[256]; + const char *initfield = NULL; + char *stringp; + char *chunk; + char *op; + const char *newparam, *newval; + struct ast_realloca ra; + struct ast_variable *var=NULL; + struct ast_config *cfg = NULL; + struct ast_category *cat = NULL; + + if(!table) { + ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n"); + return NULL; + } + + memset(&ra, 0, sizeof(ra)); + + cfg = ast_config_new(); + if (!cfg) { + /* If I can't alloc memory at this point, why bother doing anything else? */ + ast_log(LOG_WARNING, "Out of memory!\n"); + return NULL; + } + + /* Get the first parameter and first value in our list of passed paramater/value pairs */ + newparam = va_arg(ap, const char *); + newval = va_arg(ap, const char *); + if(!newparam || !newval) { + ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n"); + mysql_close(&mysql); + return NULL; + } + + initfield = ast_strdupa(newparam); + if(initfield && (op = strchr(initfield, ' '))) { + *op = '\0'; + } + + /* Create the first part of the query using the first parameter/value pairs we just extracted + If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ + + if(!strchr(newparam, ' ')) op = " ="; else op = ""; + + snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, newval); + while((newparam = va_arg(ap, const char *))) { + newval = va_arg(ap, const char *); + if(!strchr(newparam, ' ')) op = " ="; else op = ""; + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam, op, newval); + } + + if(initfield) { + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield); + } + + va_end(ap); + + ast_log(LOG_DEBUG, "MySQL RealTime: Retrieve SQL: %s\n", sql); + + /* We now have our complete statement; Lets connect to the server and execute it. */ + ast_mutex_lock(&mysql_lock); + if(!mysql_reconnect(database)) { + ast_mutex_unlock(&mysql_lock); + return NULL; + } + + if(mysql_real_query(&mysql, sql, strlen(sql))) { + ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_log(LOG_DEBUG, "MySQL RealTime: Query: %s\n", sql); + ast_log(LOG_DEBUG, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&mysql)); + ast_mutex_unlock(&mysql_lock); + return NULL; + } + + if((result = mysql_store_result(&mysql))) { + numFields = mysql_num_fields(result); + fields = mysql_fetch_fields(result); + + while((row = mysql_fetch_row(result))) { + var = NULL; + cat = ast_category_new(""); + if(!cat) { + ast_log(LOG_WARNING, "Out of memory!\n"); + continue; + } + for(i = 0; i < numFields; i++) { + stringp = row[i]; + while(stringp) { + chunk = strsep(&stringp, ";"); + if(chunk && !ast_strlen_zero(ast_strip(chunk))) { + if(initfield && !strcmp(initfield, fields[i].name)) { + ast_category_rename(cat, chunk); + } + var = ast_variable_new(fields[i].name, chunk); + ast_variable_append(cat, var); + } + } + } + ast_category_append(cfg, cat); + } + } else { + ast_log(LOG_WARNING, "MySQL RealTime: Could not find any rows in table %s.\n", table); + } + + ast_mutex_unlock(&mysql_lock); + mysql_free_result(result); + + return cfg; +} + +static int update_mysql(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap) +{ + my_ulonglong numrows; + char sql[256]; + const char *newparam, *newval; + + if(!table) { + ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n"); + return -1; + } + + /* Get the first parameter and first value in our list of passed paramater/value pairs */ + newparam = va_arg(ap, const char *); + newval = va_arg(ap, const char *); + if(!newparam || !newval) { + ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n"); + mysql_close(&mysql); + return -1; + } + + /* Create the first part of the query using the first parameter/value pairs we just extracted + If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ + + snprintf(sql, sizeof(sql), "UPDATE %s SET %s = '%s'", table, newparam, newval); + while((newparam = va_arg(ap, const char *))) { + newval = va_arg(ap, const char *); + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s = '%s'", newparam, newval); + } + va_end(ap); + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s = '%s'", keyfield, lookup); + + ast_log(LOG_DEBUG,"MySQL RealTime: Update SQL: %s\n", sql); + + /* We now have our complete statement; Lets connect to the server and execute it. */ + ast_mutex_lock(&mysql_lock); + if(!mysql_reconnect(database)) { + ast_mutex_unlock(&mysql_lock); + return -1; + } + + if(mysql_real_query(&mysql, sql, strlen(sql))) { + ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_log(LOG_DEBUG, "MySQL RealTime: Query: %s\n", sql); + ast_log(LOG_DEBUG, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&mysql)); + ast_mutex_unlock(&mysql_lock); + return -1; + } + + numrows = mysql_affected_rows(&mysql); + ast_mutex_unlock(&mysql_lock); + + ast_log(LOG_DEBUG,"MySQL RealTime: Updated %llu rows on table: %s\n", numrows, table); + + /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html + * An integer greater than zero indicates the number of rows affected + * Zero indicates that no records were updated + * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.) + */ + + if(numrows >= 0) + return (int)numrows; + + return -1; +} + +static struct ast_config *config_mysql(const char *database, const char *table, const char *file, struct ast_config *cfg) +{ + MYSQL_RES *result; + MYSQL_ROW row; + my_ulonglong num_rows; + struct ast_config *new; + struct ast_variable *cur_v, *new_v; + struct ast_category *cur_cat, *new_cat; + char sql[250] = ""; + char last[80] = ""; + int cat_started = 0; + int var_started = 0; + int last_cat_metric = 0; + + last[0] = '\0'; + + if(!file || !strcmp(file, RES_CONFIG_MYSQL_CONF)) { + ast_log(LOG_WARNING, "MySQL RealTime: Cannot configure myself.\n"); + return NULL; + } + + snprintf(sql, sizeof(sql), "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); + + ast_log(LOG_DEBUG, "MySQL RealTime: Static SQL: %s\n", sql); + + /* We now have our complete statement; Lets connect to the server and execute it. */ + ast_mutex_lock(&mysql_lock); + if(!mysql_reconnect(database)) { + ast_mutex_unlock(&mysql_lock); + return NULL; + } + + if(mysql_real_query(&mysql, sql, strlen(sql))) { + ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_log(LOG_DEBUG, "MySQL RealTime: Query: %s\n", sql); + ast_log(LOG_DEBUG, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&mysql)); + ast_mutex_unlock(&mysql_lock); + return NULL; + } + + if((result = mysql_store_result(&mysql))) { + num_rows = mysql_num_rows(result); + ast_log(LOG_DEBUG, "MySQL RealTime: Found %llu rows.\n", num_rows); + + /* There might exist a better way to access the column names other than counting, + but I believe that would require another loop that we don't need. */ + + while((row = mysql_fetch_row(result))) { + if(!strcmp(row[1], "#include")) { + if (!ast_config_internal_load(row[2], cfg)) { + mysql_free_result(result); + ast_mutex_unlock(&mysql_lock); + return NULL; + } + continue; + } + + if(strcmp(last, row[0]) || last_cat_metric != atoi(row[3])) { + cur_cat = ast_category_new(row[0]); + if (!cur_cat) { + ast_log(LOG_WARNING, "Out of memory!\n"); + break; + } + strcpy(last, row[0]); + last_cat_metric = atoi(row[3]); + ast_category_append(cfg, cur_cat); + } + new_v = ast_variable_new(row[1], row[2]); + ast_variable_append(cur_cat, new_v); + } + } else { + ast_log(LOG_WARNING, "MySQL RealTime: Could not find config '%s' in database.\n", file); + } + + mysql_free_result(result); + ast_mutex_unlock(&mysql_lock); + + return cfg; +} + +static struct ast_config_engine mysql_engine = { + .name = "mysql", + .load_func = config_mysql, + .realtime_func = realtime_mysql, + .realtime_multi_func = realtime_multi_mysql, + .update_func = update_mysql +}; + +int load_module (void) +{ + parse_config(); + + ast_mutex_lock(&mysql_lock); + + if(!mysql_reconnect(NULL)) { + ast_log(LOG_WARNING, "MySQL RealTime: Couldn't establish connection. Check debug.\n"); + ast_log(LOG_DEBUG, "MySQL RealTime: Cannot Connect: %s\n", mysql_error(&mysql)); + } + + ast_config_engine_register(&mysql_engine); + if(option_verbose) { + ast_verbose("MySQL RealTime driver loaded.\n"); + } + ast_cli_register(&cli_realtime_mysql_status); + + ast_mutex_unlock(&mysql_lock); + + return 0; +} + +int unload_module (void) +{ + /* Aquire control before doing anything to the module itself. */ + ast_mutex_lock(&mysql_lock); + + mysql_close(&mysql); + ast_cli_unregister(&cli_realtime_mysql_status); + ast_config_engine_deregister(&mysql_engine); + if(option_verbose) { + ast_verbose("MySQL RealTime unloaded.\n"); + } + + STANDARD_HANGUP_LOCALUSERS; + + /* Unlock so something else can destroy the lock. */ + ast_mutex_unlock(&mysql_lock); + + return 0; +} + +int reload (void) +{ + /* Aquire control before doing anything to the module itself. */ + ast_mutex_lock(&mysql_lock); + + mysql_close(&mysql); + connected = 0; + parse_config(); + + if(!mysql_reconnect(NULL)) { + ast_log(LOG_WARNING, "MySQL RealTime: Couldn't establish connection. Check debug.\n"); + ast_log(LOG_DEBUG, "MySQL RealTime: Cannot Connect: %s\n", mysql_error(&mysql)); + } + + ast_verbose(VERBOSE_PREFIX_2 "MySQL RealTime reloaded.\n"); + + /* Done reloading. Release lock so others can now use driver. */ + ast_mutex_unlock(&mysql_lock); + + return 0; +} + +int parse_config (void) +{ + struct ast_config *config; + char *s; + + config = ast_config_load(RES_CONFIG_MYSQL_CONF); + + if(config) { + if(!(s=ast_variable_retrieve(config, "general", "dbuser"))) { + ast_log(LOG_WARNING, "MySQL RealTime: No database user found, using 'asterisk' as default.\n"); + strncpy(dbuser, "asterisk", sizeof(dbuser) - 1); + } else { + strncpy(dbuser, s, sizeof(dbuser) - 1); + } + + if(!(s=ast_variable_retrieve(config, "general", "dbpass"))) { + ast_log(LOG_WARNING, "MySQL RealTime: No database password found, using 'asterisk' as default.\n"); + strncpy(dbpass, "asterisk", sizeof(dbpass) - 1); + } else { + strncpy(dbpass, s, sizeof(dbpass) - 1); + } + + if(!(s=ast_variable_retrieve(config, "general", "dbhost"))) { + ast_log(LOG_WARNING, "MySQL RealTime: No database host found, using localhost via socket.\n"); + dbhost[0] = '\0'; + } else { + strncpy(dbhost, s, sizeof(dbhost) - 1); + } + + if(!(s=ast_variable_retrieve(config, "general", "dbname"))) { + ast_log(LOG_WARNING, "MySQL RealTime: No database name found, using 'asterisk' as default.\n"); + strncpy(dbname, "asterisk", sizeof(dbname) - 1); + } else { + strncpy(dbname, s, sizeof(dbname) - 1); + } + + if(!(s=ast_variable_retrieve(config, "general", "dbport"))) { + ast_log(LOG_WARNING, "MySQL RealTime: No database port found, using 3306 as default.\n"); + dbport = 3306; + } else { + dbport = atoi(s); + } + + if(dbhost && !(s=ast_variable_retrieve(config, "general", "dbsock"))) { + ast_log(LOG_WARNING, "MySQL RealTime: No database socket found, using '/tmp/mysql.sock' as default.\n"); + strncpy(dbsock, "/tmp/mysql.sock", sizeof(dbsock) - 1); + } else { + strncpy(dbsock, s, sizeof(dbsock) - 1); + } + } + ast_config_destroy(config); + + if(dbhost) { + ast_log(LOG_DEBUG, "MySQL RealTime Host: %s\n", dbhost); + ast_log(LOG_DEBUG, "MySQL RealTime Port: %i\n", dbport); + } else { + ast_log(LOG_DEBUG, "MySQL RealTime Socket: %s\n", dbsock); + } + ast_log(LOG_DEBUG, "MySQL RealTime User: %s\n", dbuser); + ast_log(LOG_DEBUG, "MySQL RealTime Password: %s\n", dbpass); + + return 1; +} + +char *description (void) +{ + return res_config_mysql_desc; +} + +int usecount (void) +{ + /* Try and get a lock. If unsuccessful, than that means another thread is using the mysql object. */ + if(ast_mutex_trylock(&mysql_lock)) { + ast_log(LOG_DEBUG, "MySQL RealTime: Module usage count is 1.\n"); + return 1; + } + ast_mutex_unlock(&mysql_lock); + return 0; +} + +char *key () +{ + return ASTERISK_GPL_KEY; +} + +static int mysql_reconnect(const char *database) +{ + char my_database[50]; + + if(!database || ast_strlen_zero(database)) + ast_copy_string(my_database, dbname, sizeof(my_database)); + else + ast_copy_string(my_database, database, sizeof(my_database)); + + /* mutex lock should have been locked before calling this function. */ + + if((!connected) && (dbhost || dbsock) && dbuser && dbpass && my_database) { + if(!mysql_init(&mysql)) { + ast_log(LOG_WARNING, "MySQL RealTime: Insufficient memory to allocate MySQL resource.\n"); + connected = 0; + return 0; + } + if(mysql_real_connect(&mysql, dbhost, dbuser, dbpass, my_database, dbport, dbsock, 0)) { + ast_log(LOG_DEBUG, "MySQL RealTime: Successfully connected to database.\n"); + connected = 1; + connect_time = time(NULL); + return 1; + } else { + ast_log(LOG_ERROR, "MySQL RealTime: Failed to connect database server %s on %s. Check debug for more info.\n", dbname, dbhost); + ast_log(LOG_DEBUG, "MySQL RealTime: Cannot Connect: %s\n", mysql_error(&mysql)); + connected = 0; + return 0; + } + } else { + if(mysql_ping(&mysql) != 0) { + connected = 0; + ast_log(LOG_ERROR, "MySQL RealTime: Failed to reconnect. Check debug for more info.\n"); + ast_log(LOG_DEBUG, "MySQL RealTime: Server Error: %s\n", mysql_error(&mysql)); + return 0; + } + + connected = 1; + + if(mysql_select_db(&mysql, my_database) != 0) { + ast_log(LOG_WARNING, "MySQL RealTime: Unable to select database: %s. Still Connected.\n", my_database); + ast_log(LOG_DEBUG, "MySQL RealTime: Database Select Failed: %s\n", mysql_error(&mysql)); + return 0; + } + + ast_log(LOG_DEBUG, "MySQL RealTime: Everything is fine.\n"); + return 1; + } +} + +static int realtime_mysql_status(int fd, int argc, char **argv) +{ + char status[256], status2[100] = ""; + int ctime = time(NULL) - connect_time; + + if(mysql_reconnect(NULL)) { + if(dbhost) { + snprintf(status, 255, "Connected to %s@%s, port %d", dbname, dbhost, dbport); + } else if(dbsock) { + snprintf(status, 255, "Connected to %s on socket file %s", dbname, dbsock); + } else { + snprintf(status, 255, "Connected to %s@%s", dbname, dbhost); + } + + if(dbuser && *dbuser) { + snprintf(status2, 99, " with username %s", dbuser); + } + + if (ctime > 31536000) { + ast_cli(fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 31536000, (ctime % 31536000) / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60); + } else if (ctime > 86400) { + ast_cli(fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60); + } else if (ctime > 3600) { + ast_cli(fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 3600, (ctime % 3600) / 60, ctime % 60); + } else if (ctime > 60) { + ast_cli(fd, "%s%s for %d minutes, %d seconds.\n", status, status2, ctime / 60, ctime % 60); + } else { + ast_cli(fd, "%s%s for %d seconds.\n", status, status2, ctime); + } + + return RESULT_SUCCESS; + } else { + return RESULT_FAILURE; + } +}