2 * Asterisk -- A telephony toolkit for Linux.
4 * Copyright (C) 1999-2005, Digium, Inc.
6 * Manuel Guesdon <mguesdon@oxymium.net> - Postgresql RealTime Driver Author/Adaptor
7 * Mark Spencer <markster@digium.com> - Asterisk Author
8 * Matthew Boehm <mboehm@cytelcom.com> - MySQL RealTime Driver Author
10 * res_config_pgsql.c <Postgresql plugin for RealTime configuration engine>
12 * v1.0 - (07-11-05) - Initial version based on res_config_mysql v2.0
17 * \brief Postgresql plugin for Asterisk RealTime Architecture
19 * \author Mark Spencer <markster@digium.com>
20 * \author Manuel Guesdon <mguesdon@oxymium.net> - Postgresql RealTime Driver Author/Adaptor
22 * \arg http://www.postgresql.org
27 #include <libpq-fe.h> /* PostgreSQL */
31 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33 #include "asterisk/file.h"
34 #include "asterisk/logger.h"
35 #include "asterisk/channel.h"
36 #include "asterisk/pbx.h"
37 #include "asterisk/config.h"
38 #include "asterisk/module.h"
39 #include "asterisk/lock.h"
40 #include "asterisk/options.h"
41 #include "asterisk/utils.h"
42 #include "asterisk/cli.h"
44 static char *res_config_pgsql_desc = "Postgresql RealTime Configuration Driver";
46 AST_MUTEX_DEFINE_STATIC(pgsql_lock);
48 #define RES_CONFIG_PGSQL_CONF "res_pgsql.conf"
50 PGconn *pgsqlConn = NULL;
52 #define MAX_DB_OPTION_SIZE 64
54 static char dbhost[MAX_DB_OPTION_SIZE] = "";
55 static char dbuser[MAX_DB_OPTION_SIZE] = "";
56 static char dbpass[MAX_DB_OPTION_SIZE] = "";
57 static char dbname[MAX_DB_OPTION_SIZE] = "";
58 static char dbsock[MAX_DB_OPTION_SIZE] = "";
59 static int dbport = 5432;
60 static time_t connect_time = 0;
62 static int parse_config(void);
63 static int pgsql_reconnect(const char *database);
64 static int realtime_pgsql_status(int fd, int argc, char **argv);
68 static char cli_realtime_pgsql_status_usage[] =
69 "Usage: realtime pgsql status\n"
70 " Shows connection information for the Postgresql RealTime driver\n";
72 static struct ast_cli_entry cli_realtime_pgsql_status = {
73 { "realtime", "pgsql", "status", NULL }, realtime_pgsql_status,
74 "Shows connection information for the Postgresql RealTime driver",
75 cli_realtime_pgsql_status_usage, NULL
78 static struct ast_variable *realtime_pgsql(const char *database, const char *table, va_list ap)
80 PGresult *result = NULL;
86 const char *newparam, *newval;
87 struct ast_variable *var = NULL, *prev = NULL;
90 ast_log(LOG_WARNING, "Postgresql RealTime: No table specified.\n");
94 /* Get the first parameter and first value in our list of passed paramater/value pairs */
95 newparam = va_arg(ap, const char *);
96 newval = va_arg(ap, const char *);
97 if (!newparam || !newval) {
99 "Postgresql RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
107 /* Create the first part of the query using the first parameter/value pairs we just extracted
108 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
109 op = strchr(newparam, ' ') ? "" : " =";
111 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op,
113 while ((newparam = va_arg(ap, const char *))) {
114 newval = va_arg(ap, const char *);
115 if (!strchr(newparam, ' '))
119 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam,
124 /* We now have our complete statement; Lets connect to the server and execute it. */
125 ast_mutex_lock(&pgsql_lock);
126 if (!pgsql_reconnect(database)) {
127 ast_mutex_unlock(&pgsql_lock);
131 if (!(result = PQexec(pgsqlConn, sql))) {
133 "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
134 ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
135 ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s\n",
136 PQerrorMessage(pgsqlConn));
137 ast_mutex_unlock(&pgsql_lock);
140 ExecStatusType result_status = PQresultStatus(result);
141 if (result_status != PGRES_COMMAND_OK
142 && result_status != PGRES_TUPLES_OK
143 && result_status != PGRES_NONFATAL_ERROR) {
145 "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
146 ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
147 ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s (%s)\n",
148 PQresultErrorMessage(result), PQresStatus(result_status));
149 ast_mutex_unlock(&pgsql_lock);
154 ast_log(LOG_DEBUG, "1Postgresql RealTime: Result=%p Query: %s\n", result, sql);
156 if ((num_rows = PQntuples(result)) > 0) {
159 int numFields = PQnfields(result);
160 char **fieldnames = NULL;
162 ast_log(LOG_DEBUG, "Postgresql RealTime: Found %d rows.\n", num_rows);
164 if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
165 ast_mutex_unlock(&pgsql_lock);
169 for (i = 0; i < numFields; i++)
170 fieldnames[i] = PQfname(result, i);
171 for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
172 for (i = 0; i < numFields; i++) {
173 stringp = PQgetvalue(result, rowIndex, i);
175 chunk = strsep(&stringp, ";");
176 if (chunk && !ast_strlen_zero(ast_strip(chunk))) {
178 prev->next = ast_variable_new(fieldnames[i], chunk);
183 prev = var = ast_variable_new(fieldnames[i], chunk);
192 "Postgresql RealTime: Could not find any rows in table %s.\n", table);
195 ast_mutex_unlock(&pgsql_lock);
201 static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, va_list ap)
203 PGresult *result = NULL;
206 const char *initfield = NULL;
210 const char *newparam, *newval;
211 struct ast_realloca ra;
212 struct ast_variable *var = NULL;
213 struct ast_config *cfg = NULL;
214 struct ast_category *cat = NULL;
217 ast_log(LOG_WARNING, "Postgresql RealTime: No table specified.\n");
221 memset(&ra, 0, sizeof(ra));
223 if (!(cfg = ast_config_new()))
226 /* Get the first parameter and first value in our list of passed paramater/value pairs */
227 newparam = va_arg(ap, const char *);
228 newval = va_arg(ap, const char *);
229 if (!newparam || !newval) {
231 "Postgresql RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
239 initfield = ast_strdupa(newparam);
240 if (initfield && (op = strchr(initfield, ' '))) {
244 /* Create the first part of the query using the first parameter/value pairs we just extracted
245 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
247 if (!strchr(newparam, ' '))
252 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op,
254 while ((newparam = va_arg(ap, const char *))) {
255 newval = va_arg(ap, const char *);
256 if (!strchr(newparam, ' '))
260 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam,
265 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield);
270 /* We now have our complete statement; Lets connect to the server and execute it. */
271 ast_mutex_lock(&pgsql_lock);
272 if (!pgsql_reconnect(database)) {
273 ast_mutex_unlock(&pgsql_lock);
277 if (!(result = PQexec(pgsqlConn, sql))) {
279 "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
280 ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
281 ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s\n",
282 PQerrorMessage(pgsqlConn));
283 ast_mutex_unlock(&pgsql_lock);
286 ExecStatusType result_status = PQresultStatus(result);
287 if (result_status != PGRES_COMMAND_OK
288 && result_status != PGRES_TUPLES_OK
289 && result_status != PGRES_NONFATAL_ERROR) {
291 "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
292 ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
293 ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s (%s)\n",
294 PQresultErrorMessage(result), PQresStatus(result_status));
295 ast_mutex_unlock(&pgsql_lock);
300 ast_log(LOG_DEBUG, "2Postgresql RealTime: Result=%p Query: %s\n", result, sql);
302 if ((num_rows = PQntuples(result)) > 0) {
303 int numFields = PQnfields(result);
306 char **fieldnames = NULL;
308 ast_log(LOG_DEBUG, "Postgresql RealTime: Found %d rows.\n", num_rows);
310 if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
311 ast_mutex_unlock(&pgsql_lock);
315 for (i = 0; i < numFields; i++)
316 fieldnames[i] = PQfname(result, i);
318 for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
320 if (!(cat = ast_category_new("")))
322 for (i = 0; i < numFields; i++) {
323 stringp = PQgetvalue(result, rowIndex, i);
325 chunk = strsep(&stringp, ";");
326 if (chunk && !ast_strlen_zero(ast_strip(chunk))) {
327 if (initfield && !strcmp(initfield, fieldnames[i])) {
328 ast_category_rename(cat, chunk);
330 var = ast_variable_new(fieldnames[i], chunk);
331 ast_variable_append(cat, var);
335 ast_category_append(cfg, cat);
340 "Postgresql RealTime: Could not find any rows in table %s.\n", table);
343 ast_mutex_unlock(&pgsql_lock);
349 static int update_pgsql(const char *database, const char *table, const char *keyfield,
350 const char *lookup, va_list ap)
352 PGresult *result = NULL;
355 const char *newparam, *newval;
358 ast_log(LOG_WARNING, "Postgresql RealTime: No table specified.\n");
362 /* Get the first parameter and first value in our list of passed paramater/value pairs */
363 newparam = va_arg(ap, const char *);
364 newval = va_arg(ap, const char *);
365 if (!newparam || !newval) {
367 "Postgresql RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
375 /* Create the first part of the query using the first parameter/value pairs we just extracted
376 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
378 snprintf(sql, sizeof(sql), "UPDATE %s SET %s = '%s'", table, newparam, newval);
379 while ((newparam = va_arg(ap, const char *))) {
380 newval = va_arg(ap, const char *);
381 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s = '%s'", newparam,
385 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s = '%s'", keyfield,
388 ast_log(LOG_DEBUG, "Postgresql RealTime: Update SQL: %s\n", sql);
390 /* We now have our complete statement; Lets connect to the server and execute it. */
391 ast_mutex_lock(&pgsql_lock);
392 if (!pgsql_reconnect(database)) {
393 ast_mutex_unlock(&pgsql_lock);
397 if (!(result = PQexec(pgsqlConn, sql))) {
399 "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
400 ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
401 ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s\n",
402 PQerrorMessage(pgsqlConn));
403 ast_mutex_unlock(&pgsql_lock);
406 ExecStatusType result_status = PQresultStatus(result);
407 if (result_status != PGRES_COMMAND_OK
408 && result_status != PGRES_TUPLES_OK
409 && result_status != PGRES_NONFATAL_ERROR) {
411 "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
412 ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
413 ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s (%s)\n",
414 PQresultErrorMessage(result), PQresStatus(result_status));
415 ast_mutex_unlock(&pgsql_lock);
420 numrows = atoi(PQcmdTuples(result));
421 ast_mutex_unlock(&pgsql_lock);
423 ast_log(LOG_DEBUG, "Postgresql RealTime: Updated %d rows on table: %s\n", numrows,
426 /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
427 * An integer greater than zero indicates the number of rows affected
428 * Zero indicates that no records were updated
429 * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
433 return (int) numrows;
438 static struct ast_config *config_pgsql(const char *database, const char *table,
439 const char *file, struct ast_config *cfg)
441 PGresult *result = NULL;
443 struct ast_variable *new_v;
444 struct ast_category *cur_cat = NULL;
447 int last_cat_metric = 0;
451 if (!file || !strcmp(file, RES_CONFIG_PGSQL_CONF)) {
452 ast_log(LOG_WARNING, "Postgresql RealTime: Cannot configure myself.\n");
456 snprintf(sql, sizeof(sql),
457 "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",
460 ast_log(LOG_DEBUG, "Postgresql RealTime: Static SQL: %s\n", sql);
462 /* We now have our complete statement; Lets connect to the server and execute it. */
463 ast_mutex_lock(&pgsql_lock);
464 if (!pgsql_reconnect(database)) {
465 ast_mutex_unlock(&pgsql_lock);
469 if (!(result = PQexec(pgsqlConn, sql))) {
471 "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
472 ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
473 ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s\n",
474 PQerrorMessage(pgsqlConn));
475 ast_mutex_unlock(&pgsql_lock);
478 ExecStatusType result_status = PQresultStatus(result);
479 if (result_status != PGRES_COMMAND_OK
480 && result_status != PGRES_TUPLES_OK
481 && result_status != PGRES_NONFATAL_ERROR) {
483 "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
484 ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
485 ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s (%s)\n",
486 PQresultErrorMessage(result), PQresStatus(result_status));
487 ast_mutex_unlock(&pgsql_lock);
492 if ((num_rows = PQntuples(result)) > 0) {
493 int numFields = PQnfields(result);
496 char **fieldnames = NULL;
498 ast_log(LOG_DEBUG, "Postgresql RealTime: Found %ld rows.\n", num_rows);
500 if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
501 ast_mutex_unlock(&pgsql_lock);
505 for (i = 0; i < numFields; i++)
506 fieldnames[i] = PQfname(result, i);
508 for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
509 char *field_category = PQgetvalue(result, rowIndex, 0);
510 char *field_var_name = PQgetvalue(result, rowIndex, 1);
511 char *field_var_val = PQgetvalue(result, rowIndex, 2);
512 char *field_cat_metric = PQgetvalue(result, rowIndex, 3);
513 if (!strcmp(field_var_name, "#include")) {
514 if (!ast_config_internal_load(field_var_val, cfg)) {
516 ast_mutex_unlock(&pgsql_lock);
522 if (strcmp(last, field_category) || last_cat_metric != atoi(field_cat_metric)) {
523 cur_cat = ast_category_new(field_category);
526 strcpy(last, field_category);
527 last_cat_metric = atoi(field_cat_metric);
528 ast_category_append(cfg, cur_cat);
530 new_v = ast_variable_new(field_var_name, field_var_val);
531 ast_variable_append(cur_cat, new_v);
535 "Postgresql RealTime: Could not find config '%s' in database.\n", file);
539 ast_mutex_unlock(&pgsql_lock);
544 static struct ast_config_engine pgsql_engine = {
546 .load_func = config_pgsql,
547 .realtime_func = realtime_pgsql,
548 .realtime_multi_func = realtime_multi_pgsql,
549 .update_func = update_pgsql
552 int load_module(void)
556 ast_mutex_lock(&pgsql_lock);
558 if (!pgsql_reconnect(NULL)) {
560 "Postgresql RealTime: Couldn't establish connection. Check debug.\n");
561 ast_log(LOG_DEBUG, "Postgresql RealTime: Cannot Connect: %s\n",
562 PQerrorMessage(pgsqlConn));
565 ast_config_engine_register(&pgsql_engine);
566 if (option_verbose) {
567 ast_verbose("Postgresql RealTime driver loaded.\n");
569 ast_cli_register(&cli_realtime_pgsql_status);
571 ast_mutex_unlock(&pgsql_lock);
576 int unload_module(void)
578 /* Aquire control before doing anything to the module itself. */
579 ast_mutex_lock(&pgsql_lock);
585 ast_cli_unregister(&cli_realtime_pgsql_status);
586 ast_config_engine_deregister(&pgsql_engine);
587 if (option_verbose) {
588 ast_verbose("Postgresql RealTime unloaded.\n");
591 STANDARD_HANGUP_LOCALUSERS;
593 /* Unlock so something else can destroy the lock. */
594 ast_mutex_unlock(&pgsql_lock);
601 /* Aquire control before doing anything to the module itself. */
602 ast_mutex_lock(&pgsql_lock);
610 if (!pgsql_reconnect(NULL)) {
612 "Postgresql RealTime: Couldn't establish connection. Check debug.\n");
613 ast_log(LOG_DEBUG, "Postgresql RealTime: Cannot Connect: %s\n",
614 PQerrorMessage(pgsqlConn));
617 ast_verbose(VERBOSE_PREFIX_2 "Postgresql RealTime reloaded.\n");
619 /* Done reloading. Release lock so others can now use driver. */
620 ast_mutex_unlock(&pgsql_lock);
625 int parse_config(void)
627 struct ast_config *config;
630 config = ast_config_load(RES_CONFIG_PGSQL_CONF);
633 if (!(s = ast_variable_retrieve(config, "general", "dbuser"))) {
635 "Postgresql RealTime: No database user found, using 'asterisk' as default.\n");
636 strcpy(dbuser, "asterisk");
638 ast_copy_string(dbuser, s, sizeof(dbuser));
641 if (!(s = ast_variable_retrieve(config, "general", "dbpass"))) {
643 "Postgresql RealTime: No database password found, using 'asterisk' as default.\n");
644 strcpy(dbpass, "asterisk");
646 ast_copy_string(dbpass, s, sizeof(dbpass));
649 if (!(s = ast_variable_retrieve(config, "general", "dbhost"))) {
651 "Postgresql RealTime: No database host found, using localhost via socket.\n");
654 ast_copy_string(dbhost, s, sizeof(dbhost));
657 if (!(s = ast_variable_retrieve(config, "general", "dbname"))) {
659 "Postgresql RealTime: No database name found, using 'asterisk' as default.\n");
660 strcpy(dbname, "asterisk");
662 ast_copy_string(dbname, s, sizeof(dbname));
665 if (!(s = ast_variable_retrieve(config, "general", "dbport"))) {
667 "Postgresql RealTime: No database port found, using 5432 as default.\n");
673 if (dbhost && !(s = ast_variable_retrieve(config, "general", "dbsock"))) {
675 "Postgresql RealTime: No database socket found, using '/tmp/pgsql.sock' as default.\n");
676 strcpy(dbsock, "/tmp/pgsql.sock");
678 ast_copy_string(dbsock, s, sizeof(dbsock));
681 ast_config_destroy(config);
684 ast_log(LOG_DEBUG, "Postgresql RealTime Host: %s\n", dbhost);
685 ast_log(LOG_DEBUG, "Postgresql RealTime Port: %i\n", dbport);
687 ast_log(LOG_DEBUG, "Postgresql RealTime Socket: %s\n", dbsock);
689 ast_log(LOG_DEBUG, "Postgresql RealTime User: %s\n", dbuser);
690 ast_log(LOG_DEBUG, "Postgresql RealTime Password: %s\n", dbpass);
691 ast_log(LOG_DEBUG, "Postgresql RealTime DBName: %s\n", dbname);
696 const char *description(void)
698 return res_config_pgsql_desc;
703 /* Try and get a lock. If unsuccessful, than that means another thread is using the pgsql object. */
704 if (ast_mutex_trylock(&pgsql_lock)) {
705 ast_log(LOG_DEBUG, "Postgresql RealTime: Module usage count is 1.\n");
708 ast_mutex_unlock(&pgsql_lock);
714 return ASTERISK_GPL_KEY;
717 static int pgsql_reconnect(const char *database)
719 char my_database[50];
721 ast_copy_string(my_database, S_OR(database, dbname), sizeof(my_database));
723 /* mutex lock should have been locked before calling this function. */
725 if (pgsqlConn && PQstatus(pgsqlConn) != CONNECTION_OK) {
730 if ((!pgsqlConn) && (dbhost || dbsock) && dbuser && dbpass && my_database) {
731 char *connInfo = NULL;
732 unsigned int size = 100 + strlen(dbhost)
735 + strlen(my_database);
737 if (!(connInfo = ast_malloc(size)))
740 sprintf(connInfo, "host=%s port=%d dbname=%s user=%s password=%s",
741 dbhost, dbport, my_database, dbuser, dbpass);
742 ast_log(LOG_DEBUG, "%u connInfo=%s\n", size, connInfo);
743 pgsqlConn = PQconnectdb(connInfo);
744 ast_log(LOG_DEBUG, "%u connInfo=%s\n", size, connInfo);
747 ast_log(LOG_DEBUG, "pgsqlConn=%p\n", pgsqlConn);
749 ast_log(LOG_DEBUG, "Postgresql RealTime: Successfully connected to database.\n");
750 connect_time = time(NULL);
754 "Postgresql RealTime: Failed to connect database server %s on %s. Check debug for more info.\n",
756 ast_log(LOG_DEBUG, "Postgresql RealTime: Cannot Connect: %s\n",
757 PQresultErrorMessage(NULL));
761 ast_log(LOG_DEBUG, "Postgresql RealTime: Everything is fine.\n");
766 static int realtime_pgsql_status(int fd, int argc, char **argv)
768 char status[256], status2[100] = "";
769 int ctime = time(NULL) - connect_time;
771 if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) {
773 snprintf(status, 255, "Connected to %s@%s, port %d", dbname, dbhost, dbport);
775 snprintf(status, 255, "Connected to %s on socket file %s", dbname, dbsock);
777 snprintf(status, 255, "Connected to %s@%s", dbname, dbhost);
780 if (dbuser && *dbuser) {
781 snprintf(status2, 99, " with username %s", dbuser);
784 if (ctime > 31536000) {
785 ast_cli(fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n",
786 status, status2, ctime / 31536000, (ctime % 31536000) / 86400,
787 (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60);
788 } else if (ctime > 86400) {
789 ast_cli(fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n", status,
790 status2, ctime / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60,
792 } else if (ctime > 3600) {
793 ast_cli(fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, status2,
794 ctime / 3600, (ctime % 3600) / 60, ctime % 60);
795 } else if (ctime > 60) {
796 ast_cli(fd, "%s%s for %d minutes, %d seconds.\n", status, status2, ctime / 60,
799 ast_cli(fd, "%s%s for %d seconds.\n", status, status2, ctime);
802 return RESULT_SUCCESS;
804 return RESULT_FAILURE;