remove almost all of the checks of the result from ast_strdupa() or alloca().
[asterisk/asterisk.git] / res / res_config_pgsql.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Copyright (C) 1999-2005, 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
15 /*! \file
16  *
17  * \brief Postgresql plugin for Asterisk RealTime Architecture
18  *
19  * \author Mark Spencer <markster@digium.com>
20  * \author Manuel Guesdon <mguesdon@oxymium.net> - Postgresql RealTime Driver Author/Adaptor
21  *
22  * \arg http://www.postgresql.org
23  */
24
25 /*** MODULEINFO
26         <depend>pgsql</depend>
27  ***/
28
29 #include <stdlib.h>
30 #include <string.h>
31 #include <libpq-fe.h>                   /* PostgreSQL */
32
33 #include "asterisk.h"
34
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36
37 #include "asterisk/file.h"
38 #include "asterisk/logger.h"
39 #include "asterisk/channel.h"
40 #include "asterisk/pbx.h"
41 #include "asterisk/config.h"
42 #include "asterisk/module.h"
43 #include "asterisk/lock.h"
44 #include "asterisk/options.h"
45 #include "asterisk/utils.h"
46 #include "asterisk/cli.h"
47
48 AST_MUTEX_DEFINE_STATIC(pgsql_lock);
49
50 #define RES_CONFIG_PGSQL_CONF "res_pgsql.conf"
51
52 PGconn *pgsqlConn = NULL;
53
54 #define MAX_DB_OPTION_SIZE 64
55
56 static char dbhost[MAX_DB_OPTION_SIZE] = "";
57 static char dbuser[MAX_DB_OPTION_SIZE] = "";
58 static char dbpass[MAX_DB_OPTION_SIZE] = "";
59 static char dbname[MAX_DB_OPTION_SIZE] = "";
60 static char dbsock[MAX_DB_OPTION_SIZE] = "";
61 static int dbport = 5432;
62 static time_t connect_time = 0;
63
64 static int parse_config(void);
65 static int pgsql_reconnect(const char *database);
66 static int realtime_pgsql_status(int fd, int argc, char **argv);
67
68 LOCAL_USER_DECL;
69
70 static char cli_realtime_pgsql_status_usage[] =
71         "Usage: realtime pgsql status\n"
72         "       Shows connection information for the Postgresql RealTime driver\n";
73
74 static struct ast_cli_entry cli_realtime_pgsql_status = {
75          { "realtime", "pgsql", "status", NULL }, realtime_pgsql_status,
76          "Shows connection information for the Postgresql RealTime driver",
77          cli_realtime_pgsql_status_usage, NULL
78  };
79
80 static struct ast_variable *realtime_pgsql(const char *database, const char *table, va_list ap)
81 {
82         PGresult *result = NULL;
83         int num_rows = 0;
84         char sql[256];
85         char *stringp;
86         char *chunk;
87         char *op;
88         const char *newparam, *newval;
89         struct ast_variable *var = NULL, *prev = NULL;
90
91         if (!table) {
92                 ast_log(LOG_WARNING, "Postgresql RealTime: No table specified.\n");
93                 return NULL;
94         }
95
96         /* Get the first parameter and first value in our list of passed paramater/value pairs */
97         newparam = va_arg(ap, const char *);
98         newval = va_arg(ap, const char *);
99         if (!newparam || !newval) {
100                 ast_log(LOG_WARNING,
101                                 "Postgresql RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
102                 if (pgsqlConn) {
103                         PQfinish(pgsqlConn);
104                         pgsqlConn = NULL;
105                 };
106                 return NULL;
107         }
108
109         /* Create the first part of the query using the first parameter/value pairs we just extracted
110            If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
111         op = strchr(newparam, ' ') ? "" : " =";
112
113         snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op,
114                          newval);
115         while ((newparam = va_arg(ap, const char *))) {
116                 newval = va_arg(ap, const char *);
117                 if (!strchr(newparam, ' '))
118                         op = " =";
119                 else
120                         op = "";
121                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam,
122                                  op, newval);
123         }
124         va_end(ap);
125
126         /* We now have our complete statement; Lets connect to the server and execute it. */
127         ast_mutex_lock(&pgsql_lock);
128         if (!pgsql_reconnect(database)) {
129                 ast_mutex_unlock(&pgsql_lock);
130                 return NULL;
131         }
132
133         if (!(result = PQexec(pgsqlConn, sql))) {
134                 ast_log(LOG_WARNING,
135                                 "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
136                 ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
137                 ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s\n",
138                                 PQerrorMessage(pgsqlConn));
139                 ast_mutex_unlock(&pgsql_lock);
140                 return NULL;
141         } else {
142                 ExecStatusType result_status = PQresultStatus(result);
143                 if (result_status != PGRES_COMMAND_OK
144                         && result_status != PGRES_TUPLES_OK
145                         && result_status != PGRES_NONFATAL_ERROR) {
146                         ast_log(LOG_WARNING,
147                                         "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
148                         ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
149                         ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s (%s)\n",
150                                         PQresultErrorMessage(result), PQresStatus(result_status));
151                         ast_mutex_unlock(&pgsql_lock);
152                         return NULL;
153                 }
154         }
155
156         ast_log(LOG_DEBUG, "1Postgresql RealTime: Result=%p Query: %s\n", result, sql);
157
158         if ((num_rows = PQntuples(result)) > 0) {
159                 int i = 0;
160                 int rowIndex = 0;
161                 int numFields = PQnfields(result);
162                 char **fieldnames = NULL;
163
164                 ast_log(LOG_DEBUG, "Postgresql RealTime: Found %d rows.\n", num_rows);
165
166                 if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
167                         ast_mutex_unlock(&pgsql_lock);
168                         PQclear(result);
169                         return NULL;
170                 }
171                 for (i = 0; i < numFields; i++)
172                         fieldnames[i] = PQfname(result, i);
173                 for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
174                         for (i = 0; i < numFields; i++) {
175                                 stringp = PQgetvalue(result, rowIndex, i);
176                                 while (stringp) {
177                                         chunk = strsep(&stringp, ";");
178                                         if (chunk && !ast_strlen_zero(ast_strip(chunk))) {
179                                                 if (prev) {
180                                                         prev->next = ast_variable_new(fieldnames[i], chunk);
181                                                         if (prev->next) {
182                                                                 prev = prev->next;
183                                                         }
184                                                 } else {
185                                                         prev = var = ast_variable_new(fieldnames[i], chunk);
186                                                 }
187                                         }
188                                 }
189                         }
190                 }
191                 free(fieldnames);
192         } else {
193                 ast_log(LOG_WARNING,
194                                 "Postgresql RealTime: Could not find any rows in table %s.\n", table);
195         }
196
197         ast_mutex_unlock(&pgsql_lock);
198         PQclear(result);
199
200         return var;
201 }
202
203 static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, va_list ap)
204 {
205         PGresult *result = NULL;
206         int num_rows = 0;
207         char sql[256];
208         const char *initfield = NULL;
209         char *stringp;
210         char *chunk;
211         char *op;
212         const char *newparam, *newval;
213         struct ast_realloca ra;
214         struct ast_variable *var = NULL;
215         struct ast_config *cfg = NULL;
216         struct ast_category *cat = NULL;
217
218         if (!table) {
219                 ast_log(LOG_WARNING, "Postgresql RealTime: No table specified.\n");
220                 return NULL;
221         }
222
223         memset(&ra, 0, sizeof(ra));
224
225         if (!(cfg = ast_config_new()))
226                 return NULL;
227
228         /* Get the first parameter and first value in our list of passed paramater/value pairs */
229         newparam = va_arg(ap, const char *);
230         newval = va_arg(ap, const char *);
231         if (!newparam || !newval) {
232                 ast_log(LOG_WARNING,
233                                 "Postgresql RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
234                 if (pgsqlConn) {
235                         PQfinish(pgsqlConn);
236                         pgsqlConn = NULL;
237                 };
238                 return NULL;
239         }
240
241         initfield = ast_strdupa(newparam);
242         if ((op = strchr(initfield, ' '))) {
243                 *op = '\0';
244         }
245
246         /* Create the first part of the query using the first parameter/value pairs we just extracted
247            If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
248
249         if (!strchr(newparam, ' '))
250                 op = " =";
251         else
252                 op = "";
253
254         snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op,
255                          newval);
256         while ((newparam = va_arg(ap, const char *))) {
257                 newval = va_arg(ap, const char *);
258                 if (!strchr(newparam, ' '))
259                         op = " =";
260                 else
261                         op = "";
262                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam,
263                                  op, newval);
264         }
265
266         if (initfield) {
267                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield);
268         }
269
270         va_end(ap);
271
272         /* We now have our complete statement; Lets connect to the server and execute it. */
273         ast_mutex_lock(&pgsql_lock);
274         if (!pgsql_reconnect(database)) {
275                 ast_mutex_unlock(&pgsql_lock);
276                 return NULL;
277         }
278
279         if (!(result = PQexec(pgsqlConn, sql))) {
280                 ast_log(LOG_WARNING,
281                                 "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
282                 ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
283                 ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s\n",
284                                 PQerrorMessage(pgsqlConn));
285                 ast_mutex_unlock(&pgsql_lock);
286                 return NULL;
287         } else {
288                 ExecStatusType result_status = PQresultStatus(result);
289                 if (result_status != PGRES_COMMAND_OK
290                         && result_status != PGRES_TUPLES_OK
291                         && result_status != PGRES_NONFATAL_ERROR) {
292                         ast_log(LOG_WARNING,
293                                         "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
294                         ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
295                         ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s (%s)\n",
296                                         PQresultErrorMessage(result), PQresStatus(result_status));
297                         ast_mutex_unlock(&pgsql_lock);
298                         return NULL;
299                 }
300         }
301
302         ast_log(LOG_DEBUG, "2Postgresql RealTime: Result=%p Query: %s\n", result, sql);
303
304         if ((num_rows = PQntuples(result)) > 0) {
305                 int numFields = PQnfields(result);
306                 int i = 0;
307                 int rowIndex = 0;
308                 char **fieldnames = NULL;
309
310                 ast_log(LOG_DEBUG, "Postgresql RealTime: Found %d rows.\n", num_rows);
311
312                 if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
313                         ast_mutex_unlock(&pgsql_lock);
314                         PQclear(result);
315                         return NULL;
316                 }
317                 for (i = 0; i < numFields; i++)
318                         fieldnames[i] = PQfname(result, i);
319
320                 for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
321                         var = NULL;
322                         if (!(cat = ast_category_new("")))
323                                 continue;
324                         for (i = 0; i < numFields; i++) {
325                                 stringp = PQgetvalue(result, rowIndex, i);
326                                 while (stringp) {
327                                         chunk = strsep(&stringp, ";");
328                                         if (chunk && !ast_strlen_zero(ast_strip(chunk))) {
329                                                 if (initfield && !strcmp(initfield, fieldnames[i])) {
330                                                         ast_category_rename(cat, chunk);
331                                                 }
332                                                 var = ast_variable_new(fieldnames[i], chunk);
333                                                 ast_variable_append(cat, var);
334                                         }
335                                 }
336                         }
337                         ast_category_append(cfg, cat);
338                 }
339                 free(fieldnames);
340         } else {
341                 ast_log(LOG_WARNING,
342                                 "Postgresql RealTime: Could not find any rows in table %s.\n", table);
343         }
344
345         ast_mutex_unlock(&pgsql_lock);
346         PQclear(result);
347
348         return cfg;
349 }
350
351 static int update_pgsql(const char *database, const char *table, const char *keyfield,
352                                                 const char *lookup, va_list ap)
353 {
354         PGresult *result = NULL;
355         int numrows = 0;
356         char sql[256];
357         const char *newparam, *newval;
358
359         if (!table) {
360                 ast_log(LOG_WARNING, "Postgresql RealTime: No table specified.\n");
361                 return -1;
362         }
363
364         /* Get the first parameter and first value in our list of passed paramater/value pairs */
365         newparam = va_arg(ap, const char *);
366         newval = va_arg(ap, const char *);
367         if (!newparam || !newval) {
368                 ast_log(LOG_WARNING,
369                                 "Postgresql RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
370                 if (pgsqlConn) {
371                         PQfinish(pgsqlConn);
372                         pgsqlConn = NULL;
373                 };
374                 return -1;
375         }
376
377         /* Create the first part of the query using the first parameter/value pairs we just extracted
378            If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
379
380         snprintf(sql, sizeof(sql), "UPDATE %s SET %s = '%s'", table, newparam, newval);
381         while ((newparam = va_arg(ap, const char *))) {
382                 newval = va_arg(ap, const char *);
383                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s = '%s'", newparam,
384                                  newval);
385         }
386         va_end(ap);
387         snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s = '%s'", keyfield,
388                          lookup);
389
390         ast_log(LOG_DEBUG, "Postgresql RealTime: Update SQL: %s\n", sql);
391
392         /* We now have our complete statement; Lets connect to the server and execute it. */
393         ast_mutex_lock(&pgsql_lock);
394         if (!pgsql_reconnect(database)) {
395                 ast_mutex_unlock(&pgsql_lock);
396                 return -1;
397         }
398
399         if (!(result = PQexec(pgsqlConn, sql))) {
400                 ast_log(LOG_WARNING,
401                                 "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
402                 ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
403                 ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s\n",
404                                 PQerrorMessage(pgsqlConn));
405                 ast_mutex_unlock(&pgsql_lock);
406                 return -1;
407         } else {
408                 ExecStatusType result_status = PQresultStatus(result);
409                 if (result_status != PGRES_COMMAND_OK
410                         && result_status != PGRES_TUPLES_OK
411                         && result_status != PGRES_NONFATAL_ERROR) {
412                         ast_log(LOG_WARNING,
413                                         "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
414                         ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
415                         ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s (%s)\n",
416                                         PQresultErrorMessage(result), PQresStatus(result_status));
417                         ast_mutex_unlock(&pgsql_lock);
418                         return -1;
419                 }
420         }
421
422         numrows = atoi(PQcmdTuples(result));
423         ast_mutex_unlock(&pgsql_lock);
424
425         ast_log(LOG_DEBUG, "Postgresql RealTime: Updated %d rows on table: %s\n", numrows,
426                         table);
427
428         /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
429          * An integer greater than zero indicates the number of rows affected
430          * Zero indicates that no records were updated
431          * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
432          */
433
434         if (numrows >= 0)
435                 return (int) numrows;
436
437         return -1;
438 }
439
440 static struct ast_config *config_pgsql(const char *database, const char *table,
441                                            const char *file, struct ast_config *cfg)
442 {
443         PGresult *result = NULL;
444         long num_rows;
445         struct ast_variable *new_v;
446         struct ast_category *cur_cat = NULL;
447         char sql[250] = "";
448         char last[80] = "";
449         int last_cat_metric = 0;
450
451         last[0] = '\0';
452
453         if (!file || !strcmp(file, RES_CONFIG_PGSQL_CONF)) {
454                 ast_log(LOG_WARNING, "Postgresql RealTime: Cannot configure myself.\n");
455                 return NULL;
456         }
457
458         snprintf(sql, sizeof(sql),
459                          "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                          table, file);
461
462         ast_log(LOG_DEBUG, "Postgresql RealTime: Static SQL: %s\n", sql);
463
464         /* We now have our complete statement; Lets connect to the server and execute it. */
465         ast_mutex_lock(&pgsql_lock);
466         if (!pgsql_reconnect(database)) {
467                 ast_mutex_unlock(&pgsql_lock);
468                 return NULL;
469         }
470
471         if (!(result = PQexec(pgsqlConn, sql))) {
472                 ast_log(LOG_WARNING,
473                                 "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
474                 ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
475                 ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s\n",
476                                 PQerrorMessage(pgsqlConn));
477                 ast_mutex_unlock(&pgsql_lock);
478                 return NULL;
479         } else {
480                 ExecStatusType result_status = PQresultStatus(result);
481                 if (result_status != PGRES_COMMAND_OK
482                         && result_status != PGRES_TUPLES_OK
483                         && result_status != PGRES_NONFATAL_ERROR) {
484                         ast_log(LOG_WARNING,
485                                         "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
486                         ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
487                         ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s (%s)\n",
488                                         PQresultErrorMessage(result), PQresStatus(result_status));
489                         ast_mutex_unlock(&pgsql_lock);
490                         return NULL;
491                 }
492         }
493
494         if ((num_rows = PQntuples(result)) > 0) {
495                 int numFields = PQnfields(result);
496                 int i = 0;
497                 int rowIndex = 0;
498                 char **fieldnames = NULL;
499
500                 ast_log(LOG_DEBUG, "Postgresql RealTime: Found %ld rows.\n", num_rows);
501
502                 if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
503                         ast_mutex_unlock(&pgsql_lock);
504                         PQclear(result);
505                         return NULL;
506                 }
507                 for (i = 0; i < numFields; i++)
508                         fieldnames[i] = PQfname(result, i);
509
510                 for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
511                         char *field_category = PQgetvalue(result, rowIndex, 0);
512                         char *field_var_name = PQgetvalue(result, rowIndex, 1);
513                         char *field_var_val = PQgetvalue(result, rowIndex, 2);
514                         char *field_cat_metric = PQgetvalue(result, rowIndex, 3);
515                         if (!strcmp(field_var_name, "#include")) {
516                                 if (!ast_config_internal_load(field_var_val, cfg)) {
517                                         PQclear(result);
518                                         ast_mutex_unlock(&pgsql_lock);
519                                         return NULL;
520                                 }
521                                 continue;
522                         }
523
524                         if (strcmp(last, field_category) || last_cat_metric != atoi(field_cat_metric)) {
525                                 cur_cat = ast_category_new(field_category);
526                                 if (!cur_cat)
527                                         break;
528                                 strcpy(last, field_category);
529                                 last_cat_metric = atoi(field_cat_metric);
530                                 ast_category_append(cfg, cur_cat);
531                         }
532                         new_v = ast_variable_new(field_var_name, field_var_val);
533                         ast_variable_append(cur_cat, new_v);
534                 }
535         } else {
536                 ast_log(LOG_WARNING,
537                                 "Postgresql RealTime: Could not find config '%s' in database.\n", file);
538         }
539
540         PQclear(result);
541         ast_mutex_unlock(&pgsql_lock);
542
543         return cfg;
544 }
545
546 static struct ast_config_engine pgsql_engine = {
547         .name = "pgsql",
548         .load_func = config_pgsql,
549         .realtime_func = realtime_pgsql,
550         .realtime_multi_func = realtime_multi_pgsql,
551         .update_func = update_pgsql
552 };
553
554 static int load_module(void *mod)
555 {
556         parse_config();
557
558         ast_mutex_lock(&pgsql_lock);
559
560         if (!pgsql_reconnect(NULL)) {
561                 ast_log(LOG_WARNING,
562                                 "Postgresql RealTime: Couldn't establish connection. Check debug.\n");
563                 ast_log(LOG_DEBUG, "Postgresql RealTime: Cannot Connect: %s\n",
564                                 PQerrorMessage(pgsqlConn));
565         }
566
567         ast_config_engine_register(&pgsql_engine);
568         if (option_verbose) {
569                 ast_verbose("Postgresql RealTime driver loaded.\n");
570         }
571         ast_cli_register(&cli_realtime_pgsql_status);
572
573         ast_mutex_unlock(&pgsql_lock);
574
575         return 0;
576 }
577
578 static int unload_module(void *mod)
579 {
580         /* Aquire control before doing anything to the module itself. */
581         ast_mutex_lock(&pgsql_lock);
582
583         if (pgsqlConn) {
584                 PQfinish(pgsqlConn);
585                 pgsqlConn = NULL;
586         };
587         ast_cli_unregister(&cli_realtime_pgsql_status);
588         ast_config_engine_deregister(&pgsql_engine);
589         if (option_verbose) {
590                 ast_verbose("Postgresql RealTime unloaded.\n");
591         }
592
593         STANDARD_HANGUP_LOCALUSERS;
594
595         /* Unlock so something else can destroy the lock. */
596         ast_mutex_unlock(&pgsql_lock);
597
598         return 0;
599 }
600
601 static int reload(void *mod)
602 {
603         /* Aquire control before doing anything to the module itself. */
604         ast_mutex_lock(&pgsql_lock);
605
606         if (pgsqlConn) {
607                 PQfinish(pgsqlConn);
608                 pgsqlConn = NULL;
609         };
610         parse_config();
611
612         if (!pgsql_reconnect(NULL)) {
613                 ast_log(LOG_WARNING,
614                                 "Postgresql RealTime: Couldn't establish connection. Check debug.\n");
615                 ast_log(LOG_DEBUG, "Postgresql RealTime: Cannot Connect: %s\n",
616                                 PQerrorMessage(pgsqlConn));
617         }
618
619         ast_verbose(VERBOSE_PREFIX_2 "Postgresql RealTime reloaded.\n");
620
621         /* Done reloading. Release lock so others can now use driver. */
622         ast_mutex_unlock(&pgsql_lock);
623
624         return 0;
625 }
626
627 int parse_config(void)
628 {
629         struct ast_config *config;
630         char *s;
631
632         config = ast_config_load(RES_CONFIG_PGSQL_CONF);
633
634         if (config) {
635                 if (!(s = ast_variable_retrieve(config, "general", "dbuser"))) {
636                         ast_log(LOG_WARNING,
637                                         "Postgresql RealTime: No database user found, using 'asterisk' as default.\n");
638                         strcpy(dbuser, "asterisk");
639                 } else {
640                         ast_copy_string(dbuser, s, sizeof(dbuser));
641                 }
642
643                 if (!(s = ast_variable_retrieve(config, "general", "dbpass"))) {
644                         ast_log(LOG_WARNING,
645                                         "Postgresql RealTime: No database password found, using 'asterisk' as default.\n");
646                         strcpy(dbpass, "asterisk");
647                 } else {
648                         ast_copy_string(dbpass, s, sizeof(dbpass));
649                 }
650
651                 if (!(s = ast_variable_retrieve(config, "general", "dbhost"))) {
652                         ast_log(LOG_WARNING,
653                                         "Postgresql RealTime: No database host found, using localhost via socket.\n");
654                         dbhost[0] = '\0';
655                 } else {
656                         ast_copy_string(dbhost, s, sizeof(dbhost));
657                 }
658
659                 if (!(s = ast_variable_retrieve(config, "general", "dbname"))) {
660                         ast_log(LOG_WARNING,
661                                         "Postgresql RealTime: No database name found, using 'asterisk' as default.\n");
662                         strcpy(dbname, "asterisk");
663                 } else {
664                         ast_copy_string(dbname, s, sizeof(dbname));
665                 }
666
667                 if (!(s = ast_variable_retrieve(config, "general", "dbport"))) {
668                         ast_log(LOG_WARNING,
669                                         "Postgresql RealTime: No database port found, using 5432 as default.\n");
670                         dbport = 5432;
671                 } else {
672                         dbport = atoi(s);
673                 }
674
675                 if (dbhost && !(s = ast_variable_retrieve(config, "general", "dbsock"))) {
676                         ast_log(LOG_WARNING,
677                                         "Postgresql RealTime: No database socket found, using '/tmp/pgsql.sock' as default.\n");
678                         strcpy(dbsock, "/tmp/pgsql.sock");
679                 } else {
680                         ast_copy_string(dbsock, s, sizeof(dbsock));
681                 }
682         }
683         ast_config_destroy(config);
684
685         if (dbhost) {
686                 ast_log(LOG_DEBUG, "Postgresql RealTime Host: %s\n", dbhost);
687                 ast_log(LOG_DEBUG, "Postgresql RealTime Port: %i\n", dbport);
688         } else {
689                 ast_log(LOG_DEBUG, "Postgresql RealTime Socket: %s\n", dbsock);
690         }
691         ast_log(LOG_DEBUG, "Postgresql RealTime User: %s\n", dbuser);
692         ast_log(LOG_DEBUG, "Postgresql RealTime Password: %s\n", dbpass);
693         ast_log(LOG_DEBUG, "Postgresql RealTime DBName: %s\n", dbname);
694
695         return 1;
696 }
697
698 static const char *description(void)
699 {
700         return "Postgresql RealTime Configuration Driver";
701
702 }
703
704 static int usecount(void)
705 {
706         /* XXX check this... */
707         /* Try and get a lock. If unsuccessful, than that means another thread is using the pgsql object. */
708         if (ast_mutex_trylock(&pgsql_lock)) {
709                 ast_log(LOG_DEBUG, "Postgresql RealTime: Module usage count is 1.\n");
710                 return 1;
711         }
712         ast_mutex_unlock(&pgsql_lock);
713         return 0;
714 }
715
716 static const char *key(void)
717 {
718         return ASTERISK_GPL_KEY;
719 }
720
721 STD_MOD(MOD_0, reload, NULL, NULL);
722
723 static int pgsql_reconnect(const char *database)
724 {
725         char my_database[50];
726
727         ast_copy_string(my_database, S_OR(database, dbname), sizeof(my_database));
728
729         /* mutex lock should have been locked before calling this function. */
730
731         if (pgsqlConn && PQstatus(pgsqlConn) != CONNECTION_OK) {
732                 PQfinish(pgsqlConn);
733                 pgsqlConn = NULL;
734         }
735
736         if ((!pgsqlConn) && (dbhost || dbsock) && dbuser && dbpass && my_database) {
737                 char *connInfo = NULL;
738                 unsigned int size = 100 + strlen(dbhost)
739                         + strlen(dbuser)
740                         + strlen(dbpass)
741                         + strlen(my_database);
742                 
743                 if (!(connInfo = ast_malloc(size)))
744                         return 0;
745                 
746                 sprintf(connInfo, "host=%s port=%d dbname=%s user=%s password=%s",
747                                         dbhost, dbport, my_database, dbuser, dbpass);
748                 ast_log(LOG_DEBUG, "%u connInfo=%s\n", size, connInfo);
749                 pgsqlConn = PQconnectdb(connInfo);
750                 ast_log(LOG_DEBUG, "%u connInfo=%s\n", size, connInfo);
751                 free(connInfo);
752                 connInfo = NULL;
753                 ast_log(LOG_DEBUG, "pgsqlConn=%p\n", pgsqlConn);
754                 if (pgsqlConn) {
755                         ast_log(LOG_DEBUG, "Postgresql RealTime: Successfully connected to database.\n");
756                         connect_time = time(NULL);
757                         return 1;
758                 } else {
759                         ast_log(LOG_ERROR,
760                                         "Postgresql RealTime: Failed to connect database server %s on %s. Check debug for more info.\n",
761                                         dbname, dbhost);
762                         ast_log(LOG_DEBUG, "Postgresql RealTime: Cannot Connect: %s\n",
763                                         PQresultErrorMessage(NULL));
764                         return 0;
765                 }
766         } else {
767                 ast_log(LOG_DEBUG, "Postgresql RealTime: Everything is fine.\n");
768                 return 1;
769         }
770 }
771
772 static int realtime_pgsql_status(int fd, int argc, char **argv)
773 {
774         char status[256], status2[100] = "";
775         int ctime = time(NULL) - connect_time;
776
777         if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) {
778                 if (dbhost) {
779                         snprintf(status, 255, "Connected to %s@%s, port %d", dbname, dbhost, dbport);
780                 } else if (dbsock) {
781                         snprintf(status, 255, "Connected to %s on socket file %s", dbname, dbsock);
782                 } else {
783                         snprintf(status, 255, "Connected to %s@%s", dbname, dbhost);
784                 }
785
786                 if (dbuser && *dbuser) {
787                         snprintf(status2, 99, " with username %s", dbuser);
788                 }
789
790                 if (ctime > 31536000) {
791                         ast_cli(fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n",
792                                         status, status2, ctime / 31536000, (ctime % 31536000) / 86400,
793                                         (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60);
794                 } else if (ctime > 86400) {
795                         ast_cli(fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n", status,
796                                         status2, ctime / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60,
797                                         ctime % 60);
798                 } else if (ctime > 3600) {
799                         ast_cli(fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, status2,
800                                         ctime / 3600, (ctime % 3600) / 60, ctime % 60);
801                 } else if (ctime > 60) {
802                         ast_cli(fd, "%s%s for %d minutes, %d seconds.\n", status, status2, ctime / 60,
803                                         ctime % 60);
804                 } else {
805                         ast_cli(fd, "%s%s for %d seconds.\n", status, status2, ctime);
806                 }
807
808                 return RESULT_SUCCESS;
809         } else {
810                 return RESULT_FAILURE;
811         }
812 }