res_config_pgsql.c 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 1999-2010, Digium, Inc.
  5. *
  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
  9. *
  10. * res_config_pgsql.c <PostgreSQL plugin for RealTime configuration engine>
  11. *
  12. * v1.0 - (07-11-05) - Initial version based on res_config_mysql v2.0
  13. */
  14. /*! \file
  15. *
  16. * \brief PostgreSQL plugin for Asterisk RealTime Architecture
  17. *
  18. * \author Mark Spencer <markster@digium.com>
  19. * \author Manuel Guesdon <mguesdon@oxymium.net> - PostgreSQL RealTime Driver Author/Adaptor
  20. *
  21. * PostgreSQL http://www.postgresql.org
  22. */
  23. /*** MODULEINFO
  24. <depend>pgsql</depend>
  25. <support_level>extended</support_level>
  26. ***/
  27. #include "asterisk.h"
  28. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  29. #include <libpq-fe.h> /* PostgreSQL */
  30. #include "asterisk/file.h"
  31. #include "asterisk/channel.h"
  32. #include "asterisk/pbx.h"
  33. #include "asterisk/config.h"
  34. #include "asterisk/module.h"
  35. #include "asterisk/lock.h"
  36. #include "asterisk/utils.h"
  37. #include "asterisk/cli.h"
  38. AST_MUTEX_DEFINE_STATIC(pgsql_lock);
  39. AST_THREADSTORAGE(sql_buf);
  40. AST_THREADSTORAGE(findtable_buf);
  41. AST_THREADSTORAGE(where_buf);
  42. AST_THREADSTORAGE(escapebuf_buf);
  43. AST_THREADSTORAGE(semibuf_buf);
  44. #define RES_CONFIG_PGSQL_CONF "res_pgsql.conf"
  45. static PGconn *pgsqlConn = NULL;
  46. static int version;
  47. #define has_schema_support (version > 70300 ? 1 : 0)
  48. #define MAX_DB_OPTION_SIZE 64
  49. struct columns {
  50. char *name;
  51. char *type;
  52. int len;
  53. unsigned int notnull:1;
  54. unsigned int hasdefault:1;
  55. AST_LIST_ENTRY(columns) list;
  56. };
  57. struct tables {
  58. ast_rwlock_t lock;
  59. AST_LIST_HEAD_NOLOCK(psql_columns, columns) columns;
  60. AST_LIST_ENTRY(tables) list;
  61. char name[0];
  62. };
  63. static AST_LIST_HEAD_STATIC(psql_tables, tables);
  64. static char dbhost[MAX_DB_OPTION_SIZE] = "";
  65. static char dbuser[MAX_DB_OPTION_SIZE] = "";
  66. static char dbpass[MAX_DB_OPTION_SIZE] = "";
  67. static char dbname[MAX_DB_OPTION_SIZE] = "";
  68. static char dbsock[MAX_DB_OPTION_SIZE] = "";
  69. static int dbport = 5432;
  70. static time_t connect_time = 0;
  71. static int parse_config(int reload);
  72. static int pgsql_reconnect(const char *database);
  73. static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
  74. static char *handle_cli_realtime_pgsql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
  75. static enum { RQ_WARN, RQ_CREATECLOSE, RQ_CREATECHAR } requirements;
  76. static struct ast_cli_entry cli_realtime[] = {
  77. AST_CLI_DEFINE(handle_cli_realtime_pgsql_status, "Shows connection information for the PostgreSQL RealTime driver"),
  78. AST_CLI_DEFINE(handle_cli_realtime_pgsql_cache, "Shows cached tables within the PostgreSQL realtime driver"),
  79. };
  80. #define ESCAPE_STRING(buffer, stringname) \
  81. do { \
  82. int len = strlen(stringname); \
  83. struct ast_str *semi = ast_str_thread_get(&semibuf_buf, len * 3 + 1); \
  84. const char *chunk = stringname; \
  85. ast_str_reset(semi); \
  86. for (; *chunk; chunk++) { \
  87. if (strchr(";^", *chunk)) { \
  88. ast_str_append(&semi, 0, "^%02hhX", *chunk); \
  89. } else { \
  90. ast_str_append(&semi, 0, "%c", *chunk); \
  91. } \
  92. } \
  93. if (ast_str_strlen(semi) > (ast_str_size(buffer) - 1) / 2) { \
  94. ast_str_make_space(&buffer, ast_str_strlen(semi) * 2 + 1); \
  95. } \
  96. PQescapeStringConn(pgsqlConn, ast_str_buffer(buffer), ast_str_buffer(semi), ast_str_size(buffer), &pgresult); \
  97. } while (0)
  98. static void destroy_table(struct tables *table)
  99. {
  100. struct columns *column;
  101. ast_rwlock_wrlock(&table->lock);
  102. while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
  103. ast_free(column);
  104. }
  105. ast_rwlock_unlock(&table->lock);
  106. ast_rwlock_destroy(&table->lock);
  107. ast_free(table);
  108. }
  109. /*! \brief Helper function for pgsql_exec. For running querys, use pgsql_exec()
  110. *
  111. * Connect if not currently connected. Run the given query.
  112. *
  113. * \param database database name we are connected to (used for error logging)
  114. * \param tablename table name we are connected to (used for error logging)
  115. * \param sql sql query string to execute
  116. * \param result pointer for where to store the result handle
  117. *
  118. * \return -1 on fatal query error
  119. * \return -2 on query failure that resulted in disconnection
  120. * \return 0 on success
  121. *
  122. * \note see pgsql_exec for full example
  123. */
  124. static int _pgsql_exec(const char *database, const char *tablename, const char *sql, PGresult **result)
  125. {
  126. ExecStatusType result_status;
  127. if (!pgsqlConn) {
  128. ast_debug(1, "PostgreSQL connection not defined, connecting\n");
  129. if (pgsql_reconnect(database) != 1) {
  130. ast_log(LOG_NOTICE, "reconnect failed\n");
  131. *result = NULL;
  132. return -1;
  133. }
  134. ast_debug(1, "PostgreSQL connection successful\n");
  135. }
  136. *result = PQexec(pgsqlConn, sql);
  137. result_status = PQresultStatus(*result);
  138. if (result_status != PGRES_COMMAND_OK
  139. && result_status != PGRES_TUPLES_OK
  140. && result_status != PGRES_NONFATAL_ERROR) {
  141. ast_log(LOG_ERROR, "PostgreSQL RealTime: Failed to query '%s@%s'.\n", tablename, database);
  142. ast_log(LOG_ERROR, "PostgreSQL RealTime: Query Failed: %s\n", sql);
  143. ast_log(LOG_ERROR, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
  144. PQresultErrorMessage(*result),
  145. PQresStatus(result_status));
  146. /* we may have tried to run a command on a disconnected/disconnecting handle */
  147. /* are we no longer connected to the database... if not try again */
  148. if (PQstatus(pgsqlConn) != CONNECTION_OK) {
  149. PQfinish(pgsqlConn);
  150. pgsqlConn = NULL;
  151. return -2;
  152. }
  153. /* connection still okay, which means the query is just plain bad */
  154. return -1;
  155. }
  156. ast_debug(1, "PostgreSQL query successful: %s\n", sql);
  157. return 0;
  158. }
  159. /*! \brief Do a postgres query, with reconnection support
  160. *
  161. * Connect if not currently connected. Run the given query
  162. * and if we're disconnected afterwards, reconnect and query again.
  163. *
  164. * \param database database name we are connected to (used for error logging)
  165. * \param tablename table name we are connected to (used for error logging)
  166. * \param sql sql query string to execute
  167. * \param result pointer for where to store the result handle
  168. *
  169. * \return -1 on query failure
  170. * \return 0 on success
  171. *
  172. * \code
  173. * int i, rows;
  174. * PGresult *result;
  175. * char *field_name, *field_type, *field_len, *field_notnull, *field_default;
  176. *
  177. * pgsql_exec("db", "table", "SELECT 1", &result)
  178. *
  179. * rows = PQntuples(result);
  180. * for (i = 0; i < rows; i++) {
  181. * field_name = PQgetvalue(result, i, 0);
  182. * field_type = PQgetvalue(result, i, 1);
  183. * field_len = PQgetvalue(result, i, 2);
  184. * field_notnull = PQgetvalue(result, i, 3);
  185. * field_default = PQgetvalue(result, i, 4);
  186. * }
  187. * \endcode
  188. */
  189. static int pgsql_exec(const char *database, const char *tablename, const char *sql, PGresult **result)
  190. {
  191. int attempts = 0;
  192. int res;
  193. /* Try the query, note failure if any */
  194. /* On first failure, reconnect and try again (_pgsql_exec handles reconnect) */
  195. /* On second failure, treat as fatal query error */
  196. while (attempts++ < 2) {
  197. ast_debug(1, "PostgreSQL query attempt %d\n", attempts);
  198. res = _pgsql_exec(database, tablename, sql, result);
  199. if (res == 0) {
  200. if (attempts > 1) {
  201. ast_log(LOG_NOTICE, "PostgreSQL RealTime: Query finally succeeded: %s\n", sql);
  202. }
  203. return 0;
  204. }
  205. if (res == -1) {
  206. return -1; /* Still connected to db, but could not process query (fatal error) */
  207. }
  208. /* res == -2 (query on a disconnected handle) */
  209. ast_debug(1, "PostgreSQL query attempt %d failed, trying again\n", attempts);
  210. }
  211. return -1;
  212. }
  213. static struct tables *find_table(const char *database, const char *orig_tablename)
  214. {
  215. struct columns *column;
  216. struct tables *table;
  217. struct ast_str *sql = ast_str_thread_get(&findtable_buf, 330);
  218. RAII_VAR(PGresult *, result, NULL, PQclear);
  219. int exec_result;
  220. char *fname, *ftype, *flen, *fnotnull, *fdef;
  221. int i, rows;
  222. AST_LIST_LOCK(&psql_tables);
  223. AST_LIST_TRAVERSE(&psql_tables, table, list) {
  224. if (!strcasecmp(table->name, orig_tablename)) {
  225. ast_debug(1, "Found table in cache; now locking\n");
  226. ast_rwlock_rdlock(&table->lock);
  227. ast_debug(1, "Lock cached table; now returning\n");
  228. AST_LIST_UNLOCK(&psql_tables);
  229. return table;
  230. }
  231. }
  232. if (database == NULL) {
  233. return NULL;
  234. }
  235. ast_debug(1, "Table '%s' not found in cache, querying now\n", orig_tablename);
  236. /* Not found, scan the table */
  237. if (has_schema_support) {
  238. char *schemaname, *tablename;
  239. if (strchr(orig_tablename, '.')) {
  240. schemaname = ast_strdupa(orig_tablename);
  241. tablename = strchr(schemaname, '.');
  242. *tablename++ = '\0';
  243. } else {
  244. schemaname = "";
  245. tablename = ast_strdupa(orig_tablename);
  246. }
  247. /* Escape special characters in schemaname */
  248. if (strchr(schemaname, '\\') || strchr(schemaname, '\'')) {
  249. char *tmp = schemaname, *ptr;
  250. ptr = schemaname = ast_alloca(strlen(tmp) * 2 + 1);
  251. for (; *tmp; tmp++) {
  252. if (strchr("\\'", *tmp)) {
  253. *ptr++ = *tmp;
  254. }
  255. *ptr++ = *tmp;
  256. }
  257. *ptr = '\0';
  258. }
  259. /* Escape special characters in tablename */
  260. if (strchr(tablename, '\\') || strchr(tablename, '\'')) {
  261. char *tmp = tablename, *ptr;
  262. ptr = tablename = ast_alloca(strlen(tmp) * 2 + 1);
  263. for (; *tmp; tmp++) {
  264. if (strchr("\\'", *tmp)) {
  265. *ptr++ = *tmp;
  266. }
  267. *ptr++ = *tmp;
  268. }
  269. *ptr = '\0';
  270. }
  271. ast_str_set(&sql, 0, "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc, a.atttypmod FROM (((pg_catalog.pg_class c INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace AND c.relname = '%s' AND n.nspname = %s%s%s) INNER JOIN pg_catalog.pg_attribute a ON (NOT a.attisdropped) AND a.attnum > 0 AND a.attrelid = c.oid) INNER JOIN pg_catalog.pg_type t ON t.oid = a.atttypid) LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum ORDER BY n.nspname, c.relname, attnum",
  272. tablename,
  273. ast_strlen_zero(schemaname) ? "" : "'", ast_strlen_zero(schemaname) ? "current_schema()" : schemaname, ast_strlen_zero(schemaname) ? "" : "'");
  274. } else {
  275. /* Escape special characters in tablename */
  276. if (strchr(orig_tablename, '\\') || strchr(orig_tablename, '\'')) {
  277. const char *tmp = orig_tablename;
  278. char *ptr;
  279. orig_tablename = ptr = ast_alloca(strlen(tmp) * 2 + 1);
  280. for (; *tmp; tmp++) {
  281. if (strchr("\\'", *tmp)) {
  282. *ptr++ = *tmp;
  283. }
  284. *ptr++ = *tmp;
  285. }
  286. *ptr = '\0';
  287. }
  288. ast_str_set(&sql, 0, "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc, a.atttypmod FROM pg_class c, pg_type t, pg_attribute a LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum WHERE c.oid = a.attrelid AND a.atttypid = t.oid AND (a.attnum > 0) AND c.relname = '%s' ORDER BY c.relname, attnum", orig_tablename);
  289. }
  290. exec_result = pgsql_exec(database, orig_tablename, ast_str_buffer(sql), &result);
  291. ast_debug(1, "Query of table structure complete. Now retrieving results.\n");
  292. if (exec_result != 0) {
  293. ast_log(LOG_ERROR, "Failed to query database columns for table %s\n", orig_tablename);
  294. AST_LIST_UNLOCK(&psql_tables);
  295. return NULL;
  296. }
  297. if (!(table = ast_calloc(1, sizeof(*table) + strlen(orig_tablename) + 1))) {
  298. ast_log(LOG_ERROR, "Unable to allocate memory for new table structure\n");
  299. AST_LIST_UNLOCK(&psql_tables);
  300. return NULL;
  301. }
  302. strcpy(table->name, orig_tablename); /* SAFE */
  303. ast_rwlock_init(&table->lock);
  304. AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
  305. rows = PQntuples(result);
  306. for (i = 0; i < rows; i++) {
  307. fname = PQgetvalue(result, i, 0);
  308. ftype = PQgetvalue(result, i, 1);
  309. flen = PQgetvalue(result, i, 2);
  310. fnotnull = PQgetvalue(result, i, 3);
  311. fdef = PQgetvalue(result, i, 4);
  312. ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
  313. if (!(column = ast_calloc(1, sizeof(*column) + strlen(fname) + strlen(ftype) + 2))) {
  314. ast_log(LOG_ERROR, "Unable to allocate column element for %s, %s\n", orig_tablename, fname);
  315. destroy_table(table);
  316. AST_LIST_UNLOCK(&psql_tables);
  317. return NULL;
  318. }
  319. if (strcmp(flen, "-1") == 0) {
  320. /* Some types, like chars, have the length stored in a different field */
  321. flen = PQgetvalue(result, i, 5);
  322. sscanf(flen, "%30d", &column->len);
  323. column->len -= 4;
  324. } else {
  325. sscanf(flen, "%30d", &column->len);
  326. }
  327. column->name = (char *)column + sizeof(*column);
  328. column->type = (char *)column + sizeof(*column) + strlen(fname) + 1;
  329. strcpy(column->name, fname);
  330. strcpy(column->type, ftype);
  331. if (*fnotnull == 't') {
  332. column->notnull = 1;
  333. } else {
  334. column->notnull = 0;
  335. }
  336. if (!ast_strlen_zero(fdef)) {
  337. column->hasdefault = 1;
  338. } else {
  339. column->hasdefault = 0;
  340. }
  341. AST_LIST_INSERT_TAIL(&table->columns, column, list);
  342. }
  343. AST_LIST_INSERT_TAIL(&psql_tables, table, list);
  344. ast_rwlock_rdlock(&table->lock);
  345. AST_LIST_UNLOCK(&psql_tables);
  346. return table;
  347. }
  348. #define release_table(table) ast_rwlock_unlock(&(table)->lock);
  349. static struct columns *find_column(struct tables *t, const char *colname)
  350. {
  351. struct columns *column;
  352. /* Check that the column exists in the table */
  353. AST_LIST_TRAVERSE(&t->columns, column, list) {
  354. if (strcmp(column->name, colname) == 0) {
  355. return column;
  356. }
  357. }
  358. return NULL;
  359. }
  360. static struct ast_variable *realtime_pgsql(const char *database, const char *tablename, const struct ast_variable *fields)
  361. {
  362. RAII_VAR(PGresult *, result, NULL, PQclear);
  363. int num_rows = 0, pgresult;
  364. struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
  365. struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
  366. char *stringp;
  367. char *chunk;
  368. char *op;
  369. const struct ast_variable *field = fields;
  370. struct ast_variable *var = NULL, *prev = NULL;
  371. /*
  372. * Ignore database from the extconfig.conf since it was
  373. * configured by res_pgsql.conf.
  374. */
  375. database = dbname;
  376. if (!tablename) {
  377. ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
  378. return NULL;
  379. }
  380. /* Get the first parameter and first value in our list of passed paramater/value pairs */
  381. if (!field) {
  382. ast_log(LOG_WARNING,
  383. "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
  384. if (pgsqlConn) {
  385. PQfinish(pgsqlConn);
  386. pgsqlConn = NULL;
  387. }
  388. return NULL;
  389. }
  390. /* Create the first part of the query using the first parameter/value pairs we just extracted
  391. If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
  392. op = strchr(field->name, ' ') ? "" : " =";
  393. ESCAPE_STRING(escapebuf, field->value);
  394. if (pgresult) {
  395. ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
  396. return NULL;
  397. }
  398. ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", tablename, field->name, op, ast_str_buffer(escapebuf));
  399. while ((field = field->next)) {
  400. if (!strchr(field->name, ' '))
  401. op = " =";
  402. else
  403. op = "";
  404. ESCAPE_STRING(escapebuf, field->value);
  405. if (pgresult) {
  406. ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
  407. return NULL;
  408. }
  409. ast_str_append(&sql, 0, " AND %s%s '%s'", field->name, op, ast_str_buffer(escapebuf));
  410. }
  411. /* We now have our complete statement; Lets connect to the server and execute it. */
  412. ast_mutex_lock(&pgsql_lock);
  413. if (pgsql_exec(database, tablename, ast_str_buffer(sql), &result) != 0) {
  414. ast_mutex_unlock(&pgsql_lock);
  415. return NULL;
  416. }
  417. ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, ast_str_buffer(sql));
  418. if ((num_rows = PQntuples(result)) > 0) {
  419. int i = 0;
  420. int rowIndex = 0;
  421. int numFields = PQnfields(result);
  422. char **fieldnames = NULL;
  423. ast_debug(1, "PostgreSQL RealTime: Found %d rows.\n", num_rows);
  424. if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
  425. ast_mutex_unlock(&pgsql_lock);
  426. return NULL;
  427. }
  428. for (i = 0; i < numFields; i++)
  429. fieldnames[i] = PQfname(result, i);
  430. for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
  431. for (i = 0; i < numFields; i++) {
  432. stringp = PQgetvalue(result, rowIndex, i);
  433. while (stringp) {
  434. chunk = strsep(&stringp, ";");
  435. if (chunk && !ast_strlen_zero(ast_realtime_decode_chunk(ast_strip(chunk)))) {
  436. if (prev) {
  437. prev->next = ast_variable_new(fieldnames[i], chunk, "");
  438. if (prev->next) {
  439. prev = prev->next;
  440. }
  441. } else {
  442. prev = var = ast_variable_new(fieldnames[i], chunk, "");
  443. }
  444. }
  445. }
  446. }
  447. }
  448. ast_free(fieldnames);
  449. } else {
  450. ast_debug(1, "Postgresql RealTime: Could not find any rows in table %s@%s.\n", tablename, database);
  451. }
  452. ast_mutex_unlock(&pgsql_lock);
  453. return var;
  454. }
  455. static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, const struct ast_variable *fields)
  456. {
  457. RAII_VAR(PGresult *, result, NULL, PQclear);
  458. int num_rows = 0, pgresult;
  459. struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
  460. struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
  461. const struct ast_variable *field = fields;
  462. const char *initfield = NULL;
  463. char *stringp;
  464. char *chunk;
  465. char *op;
  466. struct ast_variable *var = NULL;
  467. struct ast_config *cfg = NULL;
  468. struct ast_category *cat = NULL;
  469. /*
  470. * Ignore database from the extconfig.conf since it was
  471. * configured by res_pgsql.conf.
  472. */
  473. database = dbname;
  474. if (!table) {
  475. ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
  476. return NULL;
  477. }
  478. if (!(cfg = ast_config_new()))
  479. return NULL;
  480. /* Get the first parameter and first value in our list of passed paramater/value pairs */
  481. if (!field) {
  482. ast_log(LOG_WARNING,
  483. "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
  484. if (pgsqlConn) {
  485. PQfinish(pgsqlConn);
  486. pgsqlConn = NULL;
  487. }
  488. ast_config_destroy(cfg);
  489. return NULL;
  490. }
  491. initfield = ast_strdupa(field->name);
  492. if ((op = strchr(initfield, ' '))) {
  493. *op = '\0';
  494. }
  495. /* Create the first part of the query using the first parameter/value pairs we just extracted
  496. If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
  497. if (!strchr(field->name, ' '))
  498. op = " =";
  499. else
  500. op = "";
  501. ESCAPE_STRING(escapebuf, field->value);
  502. if (pgresult) {
  503. ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
  504. ast_config_destroy(cfg);
  505. return NULL;
  506. }
  507. ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, field->name, op, ast_str_buffer(escapebuf));
  508. while ((field = field->next)) {
  509. if (!strchr(field->name, ' '))
  510. op = " =";
  511. else
  512. op = "";
  513. ESCAPE_STRING(escapebuf, field->value);
  514. if (pgresult) {
  515. ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
  516. ast_config_destroy(cfg);
  517. return NULL;
  518. }
  519. ast_str_append(&sql, 0, " AND %s%s '%s'", field->name, op, ast_str_buffer(escapebuf));
  520. }
  521. if (initfield) {
  522. ast_str_append(&sql, 0, " ORDER BY %s", initfield);
  523. }
  524. /* We now have our complete statement; Lets connect to the server and execute it. */
  525. ast_mutex_lock(&pgsql_lock);
  526. if (pgsql_exec(database, table, ast_str_buffer(sql), &result) != 0) {
  527. ast_mutex_unlock(&pgsql_lock);
  528. ast_config_destroy(cfg);
  529. return NULL;
  530. } else {
  531. ExecStatusType result_status = PQresultStatus(result);
  532. if (result_status != PGRES_COMMAND_OK
  533. && result_status != PGRES_TUPLES_OK
  534. && result_status != PGRES_NONFATAL_ERROR) {
  535. ast_log(LOG_WARNING,
  536. "PostgreSQL RealTime: Failed to query %s@%s. Check debug for more info.\n", table, database);
  537. ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
  538. ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
  539. PQresultErrorMessage(result), PQresStatus(result_status));
  540. ast_mutex_unlock(&pgsql_lock);
  541. ast_config_destroy(cfg);
  542. return NULL;
  543. }
  544. }
  545. ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, ast_str_buffer(sql));
  546. if ((num_rows = PQntuples(result)) > 0) {
  547. int numFields = PQnfields(result);
  548. int i = 0;
  549. int rowIndex = 0;
  550. char **fieldnames = NULL;
  551. ast_debug(1, "PostgreSQL RealTime: Found %d rows.\n", num_rows);
  552. if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
  553. ast_mutex_unlock(&pgsql_lock);
  554. ast_config_destroy(cfg);
  555. return NULL;
  556. }
  557. for (i = 0; i < numFields; i++)
  558. fieldnames[i] = PQfname(result, i);
  559. for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
  560. var = NULL;
  561. if (!(cat = ast_category_new("","",99999)))
  562. continue;
  563. for (i = 0; i < numFields; i++) {
  564. stringp = PQgetvalue(result, rowIndex, i);
  565. while (stringp) {
  566. chunk = strsep(&stringp, ";");
  567. if (chunk && !ast_strlen_zero(ast_realtime_decode_chunk(ast_strip(chunk)))) {
  568. if (initfield && !strcmp(initfield, fieldnames[i])) {
  569. ast_category_rename(cat, chunk);
  570. }
  571. var = ast_variable_new(fieldnames[i], chunk, "");
  572. ast_variable_append(cat, var);
  573. }
  574. }
  575. }
  576. ast_category_append(cfg, cat);
  577. }
  578. ast_free(fieldnames);
  579. } else {
  580. ast_debug(1, "PostgreSQL RealTime: Could not find any rows in table %s.\n", table);
  581. }
  582. ast_mutex_unlock(&pgsql_lock);
  583. return cfg;
  584. }
  585. static int update_pgsql(const char *database, const char *tablename, const char *keyfield,
  586. const char *lookup, const struct ast_variable *fields)
  587. {
  588. RAII_VAR(PGresult *, result, NULL, PQclear);
  589. int numrows = 0, pgresult;
  590. const struct ast_variable *field = fields;
  591. struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
  592. struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
  593. struct tables *table;
  594. struct columns *column = NULL;
  595. /*
  596. * Ignore database from the extconfig.conf since it was
  597. * configured by res_pgsql.conf.
  598. */
  599. database = dbname;
  600. if (!tablename) {
  601. ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
  602. return -1;
  603. }
  604. if (!(table = find_table(database, tablename))) {
  605. ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
  606. return -1;
  607. }
  608. /* Get the first parameter and first value in our list of passed paramater/value pairs */
  609. if (!field) {
  610. ast_log(LOG_WARNING,
  611. "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
  612. if (pgsqlConn) {
  613. PQfinish(pgsqlConn);
  614. pgsqlConn = NULL;
  615. }
  616. release_table(table);
  617. return -1;
  618. }
  619. /* Check that the column exists in the table */
  620. AST_LIST_TRAVERSE(&table->columns, column, list) {
  621. if (strcmp(column->name, field->name) == 0) {
  622. break;
  623. }
  624. }
  625. if (!column) {
  626. ast_log(LOG_ERROR, "PostgreSQL RealTime: Updating on column '%s', but that column does not exist within the table '%s'!\n", field->name, tablename);
  627. release_table(table);
  628. return -1;
  629. }
  630. /* Create the first part of the query using the first parameter/value pairs we just extracted
  631. If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
  632. ESCAPE_STRING(escapebuf, field->value);
  633. if (pgresult) {
  634. ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
  635. release_table(table);
  636. return -1;
  637. }
  638. ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, field->name, ast_str_buffer(escapebuf));
  639. while ((field = field->next)) {
  640. if (!find_column(table, field->name)) {
  641. ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s', but column does not exist!\n", field->name, tablename);
  642. continue;
  643. }
  644. ESCAPE_STRING(escapebuf, field->value);
  645. if (pgresult) {
  646. ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
  647. release_table(table);
  648. return -1;
  649. }
  650. ast_str_append(&sql, 0, ", %s = '%s'", field->name, ast_str_buffer(escapebuf));
  651. }
  652. release_table(table);
  653. ESCAPE_STRING(escapebuf, lookup);
  654. if (pgresult) {
  655. ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", lookup);
  656. return -1;
  657. }
  658. ast_str_append(&sql, 0, " WHERE %s = '%s'", keyfield, ast_str_buffer(escapebuf));
  659. ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
  660. /* We now have our complete statement; Lets connect to the server and execute it. */
  661. ast_mutex_lock(&pgsql_lock);
  662. if (pgsql_exec(database, tablename, ast_str_buffer(sql), &result) != 0) {
  663. ast_mutex_unlock(&pgsql_lock);
  664. return -1;
  665. } else {
  666. ExecStatusType result_status = PQresultStatus(result);
  667. if (result_status != PGRES_COMMAND_OK
  668. && result_status != PGRES_TUPLES_OK
  669. && result_status != PGRES_NONFATAL_ERROR) {
  670. ast_log(LOG_WARNING,
  671. "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
  672. ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
  673. ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
  674. PQresultErrorMessage(result), PQresStatus(result_status));
  675. ast_mutex_unlock(&pgsql_lock);
  676. return -1;
  677. }
  678. }
  679. numrows = atoi(PQcmdTuples(result));
  680. ast_mutex_unlock(&pgsql_lock);
  681. ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, tablename);
  682. /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
  683. * An integer greater than zero indicates the number of rows affected
  684. * Zero indicates that no records were updated
  685. * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
  686. */
  687. if (numrows >= 0)
  688. return (int) numrows;
  689. return -1;
  690. }
  691. static int update2_pgsql(const char *database, const char *tablename, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields)
  692. {
  693. RAII_VAR(PGresult *, result, NULL, PQclear);
  694. int numrows = 0, pgresult, first = 1;
  695. struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 16);
  696. const struct ast_variable *field;
  697. struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
  698. struct ast_str *where = ast_str_thread_get(&where_buf, 100);
  699. struct tables *table;
  700. /*
  701. * Ignore database from the extconfig.conf since it was
  702. * configured by res_pgsql.conf.
  703. */
  704. database = dbname;
  705. if (!tablename) {
  706. ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
  707. return -1;
  708. }
  709. if (!escapebuf || !sql || !where) {
  710. /* Memory error, already handled */
  711. return -1;
  712. }
  713. if (!(table = find_table(database, tablename))) {
  714. ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
  715. return -1;
  716. }
  717. ast_str_set(&sql, 0, "UPDATE %s SET", tablename);
  718. ast_str_set(&where, 0, " WHERE");
  719. for (field = lookup_fields; field; field = field->next) {
  720. if (!find_column(table, field->name)) {
  721. ast_log(LOG_ERROR, "Attempted to update based on criteria column '%s' (%s@%s), but that column does not exist!\n", field->name, tablename, database);
  722. release_table(table);
  723. return -1;
  724. }
  725. ESCAPE_STRING(escapebuf, field->value);
  726. if (pgresult) {
  727. ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
  728. release_table(table);
  729. return -1;
  730. }
  731. ast_str_append(&where, 0, "%s %s='%s'", first ? "" : " AND", field->name, ast_str_buffer(escapebuf));
  732. first = 0;
  733. }
  734. if (first) {
  735. ast_log(LOG_WARNING,
  736. "PostgreSQL RealTime: Realtime update requires at least 1 parameter and 1 value to search on.\n");
  737. if (pgsqlConn) {
  738. PQfinish(pgsqlConn);
  739. pgsqlConn = NULL;
  740. }
  741. release_table(table);
  742. return -1;
  743. }
  744. /* Now retrieve the columns to update */
  745. first = 1;
  746. for (field = update_fields; field; field = field->next) {
  747. /* If the column is not within the table, then skip it */
  748. if (!find_column(table, field->name)) {
  749. ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s@%s', but column does not exist!\n", field->name, tablename, database);
  750. continue;
  751. }
  752. ESCAPE_STRING(escapebuf, field->value);
  753. if (pgresult) {
  754. ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
  755. release_table(table);
  756. return -1;
  757. }
  758. ast_str_append(&sql, 0, "%s %s='%s'", first ? "" : ",", field->name, ast_str_buffer(escapebuf));
  759. first = 0;
  760. }
  761. release_table(table);
  762. ast_str_append(&sql, 0, "%s", ast_str_buffer(where));
  763. ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
  764. /* We now have our complete statement; connect to the server and execute it. */
  765. if (pgsql_exec(database, tablename, ast_str_buffer(sql), &result) != 0) {
  766. ast_mutex_unlock(&pgsql_lock);
  767. return -1;
  768. }
  769. numrows = atoi(PQcmdTuples(result));
  770. ast_mutex_unlock(&pgsql_lock);
  771. ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, tablename);
  772. /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
  773. * An integer greater than zero indicates the number of rows affected
  774. * Zero indicates that no records were updated
  775. * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
  776. */
  777. if (numrows >= 0) {
  778. return (int) numrows;
  779. }
  780. return -1;
  781. }
  782. static int store_pgsql(const char *database, const char *table, const struct ast_variable *fields)
  783. {
  784. RAII_VAR(PGresult *, result, NULL, PQclear);
  785. int numrows;
  786. struct ast_str *buf = ast_str_thread_get(&escapebuf_buf, 256);
  787. struct ast_str *sql1 = ast_str_thread_get(&sql_buf, 256);
  788. struct ast_str *sql2 = ast_str_thread_get(&where_buf, 256);
  789. int pgresult;
  790. const struct ast_variable *field = fields;
  791. /*
  792. * Ignore database from the extconfig.conf since it was
  793. * configured by res_pgsql.conf.
  794. */
  795. database = dbname;
  796. if (!table) {
  797. ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
  798. return -1;
  799. }
  800. /* Get the first parameter and first value in our list of passed paramater/value pairs */
  801. if (!field) {
  802. ast_log(LOG_WARNING,
  803. "PostgreSQL RealTime: Realtime storage requires at least 1 parameter and 1 value to store.\n");
  804. if (pgsqlConn) {
  805. PQfinish(pgsqlConn);
  806. pgsqlConn = NULL;
  807. }
  808. return -1;
  809. }
  810. /* Must connect to the server before anything else, as the escape function requires the connection handle.. */
  811. ast_mutex_lock(&pgsql_lock);
  812. if (!pgsql_reconnect(database)) {
  813. ast_mutex_unlock(&pgsql_lock);
  814. return -1;
  815. }
  816. /* Create the first part of the query using the first parameter/value pairs we just extracted
  817. If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
  818. ESCAPE_STRING(buf, field->name);
  819. ast_str_set(&sql1, 0, "INSERT INTO %s (%s", table, ast_str_buffer(buf));
  820. ESCAPE_STRING(buf, field->value);
  821. ast_str_set(&sql2, 0, ") VALUES ('%s'", ast_str_buffer(buf));
  822. while ((field = field->next)) {
  823. ESCAPE_STRING(buf, field->name);
  824. ast_str_append(&sql1, 0, ", %s", ast_str_buffer(buf));
  825. ESCAPE_STRING(buf, field->value);
  826. ast_str_append(&sql2, 0, ", '%s'", ast_str_buffer(buf));
  827. }
  828. ast_str_append(&sql1, 0, "%s)", ast_str_buffer(sql2));
  829. ast_debug(1, "PostgreSQL RealTime: Insert SQL: %s\n", ast_str_buffer(sql1));
  830. if (pgsql_exec(database, table, ast_str_buffer(sql1), &result) != 0) {
  831. ast_mutex_unlock(&pgsql_lock);
  832. return -1;
  833. }
  834. numrows = atoi(PQcmdTuples(result));
  835. ast_mutex_unlock(&pgsql_lock);
  836. ast_debug(1, "PostgreSQL RealTime: row inserted on table: %s.", table);
  837. /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
  838. * An integer greater than zero indicates the number of rows affected
  839. * Zero indicates that no records were updated
  840. * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
  841. */
  842. if (numrows >= 0) {
  843. return numrows;
  844. }
  845. return -1;
  846. }
  847. static int destroy_pgsql(const char *database, const char *table, const char *keyfield, const char *lookup, const struct ast_variable *fields)
  848. {
  849. RAII_VAR(PGresult *, result, NULL, PQclear);
  850. int numrows = 0;
  851. int pgresult;
  852. struct ast_str *sql = ast_str_thread_get(&sql_buf, 256);
  853. struct ast_str *buf1 = ast_str_thread_get(&where_buf, 60), *buf2 = ast_str_thread_get(&escapebuf_buf, 60);
  854. const struct ast_variable *field;
  855. /*
  856. * Ignore database from the extconfig.conf since it was
  857. * configured by res_pgsql.conf.
  858. */
  859. database = dbname;
  860. if (!table) {
  861. ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
  862. return -1;
  863. }
  864. /* Get the first parameter and first value in our list of passed paramater/value pairs */
  865. /*newparam = va_arg(ap, const char *);
  866. newval = va_arg(ap, const char *);
  867. if (!newparam || !newval) {*/
  868. if (ast_strlen_zero(keyfield) || ast_strlen_zero(lookup)) {
  869. ast_log(LOG_WARNING,
  870. "PostgreSQL RealTime: Realtime destroy requires at least 1 parameter and 1 value to search on.\n");
  871. if (pgsqlConn) {
  872. PQfinish(pgsqlConn);
  873. pgsqlConn = NULL;
  874. };
  875. return -1;
  876. }
  877. /* Must connect to the server before anything else, as the escape function requires the connection handle.. */
  878. ast_mutex_lock(&pgsql_lock);
  879. if (!pgsql_reconnect(database)) {
  880. ast_mutex_unlock(&pgsql_lock);
  881. return -1;
  882. }
  883. /* Create the first part of the query using the first parameter/value pairs we just extracted
  884. If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
  885. ESCAPE_STRING(buf1, keyfield);
  886. ESCAPE_STRING(buf2, lookup);
  887. ast_str_set(&sql, 0, "DELETE FROM %s WHERE %s = '%s'", table, ast_str_buffer(buf1), ast_str_buffer(buf2));
  888. for (field = fields; field; field = field->next) {
  889. ESCAPE_STRING(buf1, field->name);
  890. ESCAPE_STRING(buf2, field->value);
  891. ast_str_append(&sql, 0, " AND %s = '%s'", ast_str_buffer(buf1), ast_str_buffer(buf2));
  892. }
  893. ast_debug(1, "PostgreSQL RealTime: Delete SQL: %s\n", ast_str_buffer(sql));
  894. if (pgsql_exec(database, table, ast_str_buffer(sql), &result) != 0) {
  895. ast_mutex_unlock(&pgsql_lock);
  896. return -1;
  897. }
  898. numrows = atoi(PQcmdTuples(result));
  899. ast_mutex_unlock(&pgsql_lock);
  900. ast_debug(1, "PostgreSQL RealTime: Deleted %d rows on table: %s\n", numrows, table);
  901. /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
  902. * An integer greater than zero indicates the number of rows affected
  903. * Zero indicates that no records were updated
  904. * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
  905. */
  906. if (numrows >= 0)
  907. return (int) numrows;
  908. return -1;
  909. }
  910. static struct ast_config *config_pgsql(const char *database, const char *table,
  911. const char *file, struct ast_config *cfg,
  912. struct ast_flags flags, const char *suggested_incl, const char *who_asked)
  913. {
  914. RAII_VAR(PGresult *, result, NULL, PQclear);
  915. long num_rows;
  916. struct ast_variable *new_v;
  917. struct ast_category *cur_cat = NULL;
  918. struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
  919. char last[80];
  920. int last_cat_metric = 0;
  921. last[0] = '\0';
  922. /*
  923. * Ignore database from the extconfig.conf since it is
  924. * configured by res_pgsql.conf.
  925. */
  926. database = dbname;
  927. if (!file || !strcmp(file, RES_CONFIG_PGSQL_CONF)) {
  928. ast_log(LOG_WARNING, "PostgreSQL RealTime: Cannot configure myself.\n");
  929. return NULL;
  930. }
  931. ast_str_set(&sql, 0, "SELECT category, var_name, var_val, cat_metric FROM %s "
  932. "WHERE filename='%s' and commented=0 "
  933. "ORDER BY cat_metric DESC, var_metric ASC, category, var_name ", table, file);
  934. ast_debug(1, "PostgreSQL RealTime: Static SQL: %s\n", ast_str_buffer(sql));
  935. ast_mutex_lock(&pgsql_lock);
  936. /* We now have our complete statement; Lets connect to the server and execute it. */
  937. if (pgsql_exec(database, table, ast_str_buffer(sql), &result) != 0) {
  938. ast_mutex_unlock(&pgsql_lock);
  939. return NULL;
  940. }
  941. if ((num_rows = PQntuples(result)) > 0) {
  942. int rowIndex = 0;
  943. ast_debug(1, "PostgreSQL RealTime: Found %ld rows.\n", num_rows);
  944. for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
  945. char *field_category = PQgetvalue(result, rowIndex, 0);
  946. char *field_var_name = PQgetvalue(result, rowIndex, 1);
  947. char *field_var_val = PQgetvalue(result, rowIndex, 2);
  948. char *field_cat_metric = PQgetvalue(result, rowIndex, 3);
  949. if (!strcmp(field_var_name, "#include")) {
  950. if (!ast_config_internal_load(field_var_val, cfg, flags, "", who_asked)) {
  951. ast_mutex_unlock(&pgsql_lock);
  952. return NULL;
  953. }
  954. continue;
  955. }
  956. if (strcmp(last, field_category) || last_cat_metric != atoi(field_cat_metric)) {
  957. cur_cat = ast_category_new(field_category, "", 99999);
  958. if (!cur_cat)
  959. break;
  960. ast_copy_string(last, field_category, sizeof(last));
  961. last_cat_metric = atoi(field_cat_metric);
  962. ast_category_append(cfg, cur_cat);
  963. }
  964. new_v = ast_variable_new(field_var_name, field_var_val, "");
  965. ast_variable_append(cur_cat, new_v);
  966. }
  967. } else {
  968. ast_log(LOG_WARNING,
  969. "PostgreSQL RealTime: Could not find config '%s' in database.\n", file);
  970. }
  971. ast_mutex_unlock(&pgsql_lock);
  972. return cfg;
  973. }
  974. static int require_pgsql(const char *database, const char *tablename, va_list ap)
  975. {
  976. struct columns *column;
  977. struct tables *table;
  978. char *elm;
  979. int type, size, res = 0;
  980. /*
  981. * Ignore database from the extconfig.conf since it was
  982. * configured by res_pgsql.conf.
  983. */
  984. database = dbname;
  985. table = find_table(database, tablename);
  986. if (!table) {
  987. ast_log(LOG_WARNING, "Table %s not found in database. This table should exist if you're using realtime.\n", tablename);
  988. return -1;
  989. }
  990. while ((elm = va_arg(ap, char *))) {
  991. type = va_arg(ap, require_type);
  992. size = va_arg(ap, int);
  993. AST_LIST_TRAVERSE(&table->columns, column, list) {
  994. if (strcmp(column->name, elm) == 0) {
  995. /* Char can hold anything, as long as it is large enough */
  996. if ((strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0 || strcmp(column->type, "bpchar") == 0)) {
  997. if ((size > column->len) && column->len != -1) {
  998. ast_log(LOG_WARNING, "Column '%s' should be at least %d long, but is only %d long.\n", column->name, size, column->len);
  999. res = -1;
  1000. }
  1001. } else if (strncmp(column->type, "int", 3) == 0) {
  1002. int typesize = atoi(column->type + 3);
  1003. /* Integers can hold only other integers */
  1004. if ((type == RQ_INTEGER8 || type == RQ_UINTEGER8 ||
  1005. type == RQ_INTEGER4 || type == RQ_UINTEGER4 ||
  1006. type == RQ_INTEGER3 || type == RQ_UINTEGER3 ||
  1007. type == RQ_UINTEGER2) && typesize == 2) {
  1008. ast_log(LOG_WARNING, "Column '%s' may not be large enough for the required data length: %d\n", column->name, size);
  1009. res = -1;
  1010. } else if ((type == RQ_INTEGER8 || type == RQ_UINTEGER8 ||
  1011. type == RQ_UINTEGER4) && typesize == 4) {
  1012. ast_log(LOG_WARNING, "Column '%s' may not be large enough for the required data length: %d\n", column->name, size);
  1013. res = -1;
  1014. } else if (type == RQ_CHAR || type == RQ_DATETIME || type == RQ_FLOAT || type == RQ_DATE) {
  1015. ast_log(LOG_WARNING, "Column '%s' is of the incorrect type: (need %s(%d) but saw %s)\n",
  1016. column->name,
  1017. type == RQ_CHAR ? "char" :
  1018. type == RQ_DATETIME ? "datetime" :
  1019. type == RQ_DATE ? "date" :
  1020. type == RQ_FLOAT ? "float" :
  1021. "a rather stiff drink ",
  1022. size, column->type);
  1023. res = -1;
  1024. }
  1025. } else if (strncmp(column->type, "float", 5) == 0) {
  1026. if (!ast_rq_is_int(type) && type != RQ_FLOAT) {
  1027. ast_log(LOG_WARNING, "Column %s cannot be a %s\n", column->name, column->type);
  1028. res = -1;
  1029. }
  1030. } else if (strncmp(column->type, "timestamp", 9) == 0) {
  1031. if (type != RQ_DATETIME && type != RQ_DATE) {
  1032. ast_log(LOG_WARNING, "Column %s cannot be a %s\n", column->name, column->type);
  1033. res = -1;
  1034. }
  1035. } else { /* There are other types that no module implements yet */
  1036. ast_log(LOG_WARNING, "Possibly unsupported column type '%s' on column '%s'\n", column->type, column->name);
  1037. res = -1;
  1038. }
  1039. break;
  1040. }
  1041. }
  1042. if (!column) {
  1043. if (requirements == RQ_WARN) {
  1044. ast_log(LOG_WARNING, "Table %s requires a column '%s' of size '%d', but no such column exists.\n", tablename, elm, size);
  1045. } else {
  1046. struct ast_str *sql = ast_str_create(100);
  1047. char fieldtype[15];
  1048. PGresult *result;
  1049. if (requirements == RQ_CREATECHAR || type == RQ_CHAR) {
  1050. /* Size is minimum length; make it at least 50% greater,
  1051. * just to be sure, because PostgreSQL doesn't support
  1052. * resizing columns. */
  1053. snprintf(fieldtype, sizeof(fieldtype), "CHAR(%d)",
  1054. size < 15 ? size * 2 :
  1055. (size * 3 / 2 > 255) ? 255 : size * 3 / 2);
  1056. } else if (type == RQ_INTEGER1 || type == RQ_UINTEGER1 || type == RQ_INTEGER2) {
  1057. snprintf(fieldtype, sizeof(fieldtype), "INT2");
  1058. } else if (type == RQ_UINTEGER2 || type == RQ_INTEGER3 || type == RQ_UINTEGER3 || type == RQ_INTEGER4) {
  1059. snprintf(fieldtype, sizeof(fieldtype), "INT4");
  1060. } else if (type == RQ_UINTEGER4 || type == RQ_INTEGER8) {
  1061. snprintf(fieldtype, sizeof(fieldtype), "INT8");
  1062. } else if (type == RQ_UINTEGER8) {
  1063. /* No such type on PostgreSQL */
  1064. snprintf(fieldtype, sizeof(fieldtype), "CHAR(20)");
  1065. } else if (type == RQ_FLOAT) {
  1066. snprintf(fieldtype, sizeof(fieldtype), "FLOAT8");
  1067. } else if (type == RQ_DATE) {
  1068. snprintf(fieldtype, sizeof(fieldtype), "DATE");
  1069. } else if (type == RQ_DATETIME) {
  1070. snprintf(fieldtype, sizeof(fieldtype), "TIMESTAMP");
  1071. } else {
  1072. ast_log(LOG_ERROR, "Unrecognized request type %d\n", type);
  1073. ast_free(sql);
  1074. continue;
  1075. }
  1076. ast_str_set(&sql, 0, "ALTER TABLE %s ADD COLUMN %s %s", tablename, elm, fieldtype);
  1077. ast_debug(1, "About to lock pgsql_lock (running alter on table '%s' to add column '%s')\n", tablename, elm);
  1078. ast_mutex_lock(&pgsql_lock);
  1079. ast_debug(1, "About to run ALTER query on table '%s' to add column '%s'\n", tablename, elm);
  1080. if (pgsql_exec(database, tablename, ast_str_buffer(sql), &result) != 0) {
  1081. ast_mutex_unlock(&pgsql_lock);
  1082. return -1;
  1083. }
  1084. ast_debug(1, "Finished running ALTER query on table '%s'\n", tablename);
  1085. if (PQresultStatus(result) != PGRES_COMMAND_OK) {
  1086. ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
  1087. }
  1088. PQclear(result);
  1089. ast_mutex_unlock(&pgsql_lock);
  1090. ast_free(sql);
  1091. }
  1092. }
  1093. }
  1094. release_table(table);
  1095. return res;
  1096. }
  1097. static int unload_pgsql(const char *database, const char *tablename)
  1098. {
  1099. struct tables *cur;
  1100. /*
  1101. * Ignore database from the extconfig.conf since it was
  1102. * configured by res_pgsql.conf.
  1103. */
  1104. database = dbname;
  1105. ast_debug(2, "About to lock table cache list\n");
  1106. AST_LIST_LOCK(&psql_tables);
  1107. ast_debug(2, "About to traverse table cache list\n");
  1108. AST_LIST_TRAVERSE_SAFE_BEGIN(&psql_tables, cur, list) {
  1109. if (strcmp(cur->name, tablename) == 0) {
  1110. ast_debug(2, "About to remove matching cache entry\n");
  1111. AST_LIST_REMOVE_CURRENT(list);
  1112. ast_debug(2, "About to destroy matching cache entry\n");
  1113. destroy_table(cur);
  1114. ast_debug(1, "Cache entry '%s@%s' destroyed\n", tablename, database);
  1115. break;
  1116. }
  1117. }
  1118. AST_LIST_TRAVERSE_SAFE_END
  1119. AST_LIST_UNLOCK(&psql_tables);
  1120. ast_debug(2, "About to return\n");
  1121. return cur ? 0 : -1;
  1122. }
  1123. static struct ast_config_engine pgsql_engine = {
  1124. .name = "pgsql",
  1125. .load_func = config_pgsql,
  1126. .realtime_func = realtime_pgsql,
  1127. .realtime_multi_func = realtime_multi_pgsql,
  1128. .store_func = store_pgsql,
  1129. .destroy_func = destroy_pgsql,
  1130. .update_func = update_pgsql,
  1131. .update2_func = update2_pgsql,
  1132. .require_func = require_pgsql,
  1133. .unload_func = unload_pgsql,
  1134. };
  1135. static int load_module(void)
  1136. {
  1137. if(!parse_config(0))
  1138. return AST_MODULE_LOAD_DECLINE;
  1139. ast_config_engine_register(&pgsql_engine);
  1140. ast_verb(1, "PostgreSQL RealTime driver loaded.\n");
  1141. ast_cli_register_multiple(cli_realtime, ARRAY_LEN(cli_realtime));
  1142. return 0;
  1143. }
  1144. static int unload_module(void)
  1145. {
  1146. struct tables *table;
  1147. /* Acquire control before doing anything to the module itself. */
  1148. ast_mutex_lock(&pgsql_lock);
  1149. if (pgsqlConn) {
  1150. PQfinish(pgsqlConn);
  1151. pgsqlConn = NULL;
  1152. }
  1153. ast_cli_unregister_multiple(cli_realtime, ARRAY_LEN(cli_realtime));
  1154. ast_config_engine_deregister(&pgsql_engine);
  1155. ast_verb(1, "PostgreSQL RealTime unloaded.\n");
  1156. /* Destroy cached table info */
  1157. AST_LIST_LOCK(&psql_tables);
  1158. while ((table = AST_LIST_REMOVE_HEAD(&psql_tables, list))) {
  1159. destroy_table(table);
  1160. }
  1161. AST_LIST_UNLOCK(&psql_tables);
  1162. /* Unlock so something else can destroy the lock. */
  1163. ast_mutex_unlock(&pgsql_lock);
  1164. return 0;
  1165. }
  1166. static int reload(void)
  1167. {
  1168. parse_config(1);
  1169. return 0;
  1170. }
  1171. static int parse_config(int is_reload)
  1172. {
  1173. struct ast_config *config;
  1174. const char *s;
  1175. struct ast_flags config_flags = { is_reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
  1176. config = ast_config_load(RES_CONFIG_PGSQL_CONF, config_flags);
  1177. if (config == CONFIG_STATUS_FILEUNCHANGED) {
  1178. return 0;
  1179. }
  1180. if (config == CONFIG_STATUS_FILEMISSING || config == CONFIG_STATUS_FILEINVALID) {
  1181. ast_log(LOG_WARNING, "Unable to load config %s\n", RES_CONFIG_PGSQL_CONF);
  1182. return 0;
  1183. }
  1184. ast_mutex_lock(&pgsql_lock);
  1185. if (pgsqlConn) {
  1186. PQfinish(pgsqlConn);
  1187. pgsqlConn = NULL;
  1188. }
  1189. if (!(s = ast_variable_retrieve(config, "general", "dbuser"))) {
  1190. ast_log(LOG_WARNING,
  1191. "PostgreSQL RealTime: No database user found, using 'asterisk' as default.\n");
  1192. strcpy(dbuser, "asterisk");
  1193. } else {
  1194. ast_copy_string(dbuser, s, sizeof(dbuser));
  1195. }
  1196. if (!(s = ast_variable_retrieve(config, "general", "dbpass"))) {
  1197. ast_log(LOG_WARNING,
  1198. "PostgreSQL RealTime: No database password found, using 'asterisk' as default.\n");
  1199. strcpy(dbpass, "asterisk");
  1200. } else {
  1201. ast_copy_string(dbpass, s, sizeof(dbpass));
  1202. }
  1203. if (!(s = ast_variable_retrieve(config, "general", "dbhost"))) {
  1204. ast_log(LOG_WARNING,
  1205. "PostgreSQL RealTime: No database host found, using localhost via socket.\n");
  1206. dbhost[0] = '\0';
  1207. } else {
  1208. ast_copy_string(dbhost, s, sizeof(dbhost));
  1209. }
  1210. if (!(s = ast_variable_retrieve(config, "general", "dbname"))) {
  1211. ast_log(LOG_WARNING,
  1212. "PostgreSQL RealTime: No database name found, using 'asterisk' as default.\n");
  1213. strcpy(dbname, "asterisk");
  1214. } else {
  1215. ast_copy_string(dbname, s, sizeof(dbname));
  1216. }
  1217. if (!(s = ast_variable_retrieve(config, "general", "dbport"))) {
  1218. ast_log(LOG_WARNING,
  1219. "PostgreSQL RealTime: No database port found, using 5432 as default.\n");
  1220. dbport = 5432;
  1221. } else {
  1222. dbport = atoi(s);
  1223. }
  1224. if (!ast_strlen_zero(dbhost)) {
  1225. /* No socket needed */
  1226. } else if (!(s = ast_variable_retrieve(config, "general", "dbsock"))) {
  1227. ast_log(LOG_WARNING,
  1228. "PostgreSQL RealTime: No database socket found, using '/tmp/.s.PGSQL.%d' as default.\n", dbport);
  1229. strcpy(dbsock, "/tmp");
  1230. } else {
  1231. ast_copy_string(dbsock, s, sizeof(dbsock));
  1232. }
  1233. if (!(s = ast_variable_retrieve(config, "general", "requirements"))) {
  1234. ast_log(LOG_WARNING,
  1235. "PostgreSQL RealTime: no requirements setting found, using 'warn' as default.\n");
  1236. requirements = RQ_WARN;
  1237. } else if (!strcasecmp(s, "createclose")) {
  1238. requirements = RQ_CREATECLOSE;
  1239. } else if (!strcasecmp(s, "createchar")) {
  1240. requirements = RQ_CREATECHAR;
  1241. }
  1242. ast_config_destroy(config);
  1243. if (option_debug) {
  1244. if (!ast_strlen_zero(dbhost)) {
  1245. ast_debug(1, "PostgreSQL RealTime Host: %s\n", dbhost);
  1246. ast_debug(1, "PostgreSQL RealTime Port: %i\n", dbport);
  1247. } else {
  1248. ast_debug(1, "PostgreSQL RealTime Socket: %s\n", dbsock);
  1249. }
  1250. ast_debug(1, "PostgreSQL RealTime User: %s\n", dbuser);
  1251. ast_debug(1, "PostgreSQL RealTime Password: %s\n", dbpass);
  1252. ast_debug(1, "PostgreSQL RealTime DBName: %s\n", dbname);
  1253. }
  1254. if (!pgsql_reconnect(NULL)) {
  1255. ast_log(LOG_WARNING,
  1256. "PostgreSQL RealTime: Couldn't establish connection. Check debug.\n");
  1257. ast_debug(1, "PostgreSQL RealTime: Cannot Connect: %s\n", PQerrorMessage(pgsqlConn));
  1258. }
  1259. ast_verb(2, "PostgreSQL RealTime reloaded.\n");
  1260. /* Done reloading. Release lock so others can now use driver. */
  1261. ast_mutex_unlock(&pgsql_lock);
  1262. return 1;
  1263. }
  1264. static int pgsql_reconnect(const char *database)
  1265. {
  1266. char my_database[50];
  1267. ast_copy_string(my_database, S_OR(database, dbname), sizeof(my_database));
  1268. /* mutex lock should have been locked before calling this function. */
  1269. if (pgsqlConn && PQstatus(pgsqlConn) != CONNECTION_OK) {
  1270. PQfinish(pgsqlConn);
  1271. pgsqlConn = NULL;
  1272. }
  1273. /* DB password can legitimately be 0-length */
  1274. if ((!pgsqlConn) && (!ast_strlen_zero(dbhost) || !ast_strlen_zero(dbsock)) && !ast_strlen_zero(dbuser) && !ast_strlen_zero(my_database)) {
  1275. struct ast_str *connInfo = ast_str_create(128);
  1276. ast_str_set(&connInfo, 0, "host=%s port=%d dbname=%s user=%s",
  1277. S_OR(dbhost, dbsock), dbport, my_database, dbuser);
  1278. if (!ast_strlen_zero(dbpass))
  1279. ast_str_append(&connInfo, 0, " password=%s", dbpass);
  1280. ast_debug(1, "%u connInfo=%s\n", (unsigned int)ast_str_size(connInfo), ast_str_buffer(connInfo));
  1281. pgsqlConn = PQconnectdb(ast_str_buffer(connInfo));
  1282. ast_debug(1, "%u connInfo=%s\n", (unsigned int)ast_str_size(connInfo), ast_str_buffer(connInfo));
  1283. ast_free(connInfo);
  1284. connInfo = NULL;
  1285. ast_debug(1, "pgsqlConn=%p\n", pgsqlConn);
  1286. if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) {
  1287. ast_debug(1, "PostgreSQL RealTime: Successfully connected to database.\n");
  1288. connect_time = time(NULL);
  1289. version = PQserverVersion(pgsqlConn);
  1290. return 1;
  1291. } else {
  1292. ast_log(LOG_ERROR,
  1293. "PostgreSQL RealTime: Failed to connect database %s on %s: %s\n",
  1294. my_database, dbhost, PQresultErrorMessage(NULL));
  1295. return 0;
  1296. }
  1297. } else {
  1298. ast_debug(1, "PostgreSQL RealTime: One or more of the parameters in the config does not pass our validity checks.\n");
  1299. return 1;
  1300. }
  1301. }
  1302. static char *handle_cli_realtime_pgsql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  1303. {
  1304. struct tables *cur;
  1305. int l, which;
  1306. char *ret = NULL;
  1307. switch (cmd) {
  1308. case CLI_INIT:
  1309. e->command = "realtime show pgsql cache";
  1310. e->usage =
  1311. "Usage: realtime show pgsql cache [<table>]\n"
  1312. " Shows table cache for the PostgreSQL RealTime driver\n";
  1313. return NULL;
  1314. case CLI_GENERATE:
  1315. if (a->argc != 4) {
  1316. return NULL;
  1317. }
  1318. l = strlen(a->word);
  1319. which = 0;
  1320. AST_LIST_LOCK(&psql_tables);
  1321. AST_LIST_TRAVERSE(&psql_tables, cur, list) {
  1322. if (!strncasecmp(a->word, cur->name, l) && ++which > a->n) {
  1323. ret = ast_strdup(cur->name);
  1324. break;
  1325. }
  1326. }
  1327. AST_LIST_UNLOCK(&psql_tables);
  1328. return ret;
  1329. }
  1330. if (a->argc == 4) {
  1331. /* List of tables */
  1332. AST_LIST_LOCK(&psql_tables);
  1333. AST_LIST_TRAVERSE(&psql_tables, cur, list) {
  1334. ast_cli(a->fd, "%s\n", cur->name);
  1335. }
  1336. AST_LIST_UNLOCK(&psql_tables);
  1337. } else if (a->argc == 5) {
  1338. /* List of columns */
  1339. if ((cur = find_table(NULL, a->argv[4]))) {
  1340. struct columns *col;
  1341. ast_cli(a->fd, "Columns for Table Cache '%s':\n", a->argv[4]);
  1342. ast_cli(a->fd, "%-20.20s %-20.20s %-3.3s %-8.8s\n", "Name", "Type", "Len", "Nullable");
  1343. AST_LIST_TRAVERSE(&cur->columns, col, list) {
  1344. ast_cli(a->fd, "%-20.20s %-20.20s %3d %-8.8s\n", col->name, col->type, col->len, col->notnull ? "NOT NULL" : "");
  1345. }
  1346. release_table(cur);
  1347. } else {
  1348. ast_cli(a->fd, "No such table '%s'\n", a->argv[4]);
  1349. }
  1350. }
  1351. return 0;
  1352. }
  1353. static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  1354. {
  1355. char status[256], credentials[100] = "";
  1356. int ctimesec = time(NULL) - connect_time;
  1357. switch (cmd) {
  1358. case CLI_INIT:
  1359. e->command = "realtime show pgsql status";
  1360. e->usage =
  1361. "Usage: realtime show pgsql status\n"
  1362. " Shows connection information for the PostgreSQL RealTime driver\n";
  1363. return NULL;
  1364. case CLI_GENERATE:
  1365. return NULL;
  1366. }
  1367. if (a->argc != 4)
  1368. return CLI_SHOWUSAGE;
  1369. if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) {
  1370. if (!ast_strlen_zero(dbhost))
  1371. snprintf(status, sizeof(status), "Connected to %s@%s, port %d", dbname, dbhost, dbport);
  1372. else if (!ast_strlen_zero(dbsock))
  1373. snprintf(status, sizeof(status), "Connected to %s on socket file %s", dbname, dbsock);
  1374. else
  1375. snprintf(status, sizeof(status), "Connected to %s@%s", dbname, dbhost);
  1376. if (!ast_strlen_zero(dbuser))
  1377. snprintf(credentials, sizeof(credentials), " with username %s", dbuser);
  1378. if (ctimesec > 31536000)
  1379. ast_cli(a->fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n",
  1380. status, credentials, ctimesec / 31536000, (ctimesec % 31536000) / 86400,
  1381. (ctimesec % 86400) / 3600, (ctimesec % 3600) / 60, ctimesec % 60);
  1382. else if (ctimesec > 86400)
  1383. ast_cli(a->fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n", status,
  1384. credentials, ctimesec / 86400, (ctimesec % 86400) / 3600, (ctimesec % 3600) / 60,
  1385. ctimesec % 60);
  1386. else if (ctimesec > 3600)
  1387. ast_cli(a->fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, credentials,
  1388. ctimesec / 3600, (ctimesec % 3600) / 60, ctimesec % 60);
  1389. else if (ctimesec > 60)
  1390. ast_cli(a->fd, "%s%s for %d minutes, %d seconds.\n", status, credentials, ctimesec / 60,
  1391. ctimesec % 60);
  1392. else
  1393. ast_cli(a->fd, "%s%s for %d seconds.\n", status, credentials, ctimesec);
  1394. return CLI_SUCCESS;
  1395. } else {
  1396. return CLI_FAILURE;
  1397. }
  1398. }
  1399. /* needs usecount semantics defined */
  1400. AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PostgreSQL RealTime Configuration Driver",
  1401. .load = load_module,
  1402. .unload = unload_module,
  1403. .reload = reload,
  1404. .load_pri = AST_MODPRI_REALTIME_DRIVER,
  1405. );