Sat Apr 26 2014 22:01:29

Asterisk developer's documentation


cdr_pgsql.c
Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2003 - 2012
00005  *
00006  * Matthew D. Hardeman <mhardemn@papersoft.com>
00007  * Adapted from the MySQL CDR logger originally by James Sharp
00008  *
00009  * Modified September 2003
00010  * Matthew D. Hardeman <mhardemn@papersoft.com>
00011  *
00012  * See http://www.asterisk.org for more information about
00013  * the Asterisk project. Please do not directly contact
00014  * any of the maintainers of this project for assistance;
00015  * the project provides a web site, mailing lists and IRC
00016  * channels for your use.
00017  *
00018  * This program is free software, distributed under the terms of
00019  * the GNU General Public License Version 2. See the LICENSE file
00020  * at the top of the source tree.
00021  */
00022 
00023 /*!
00024  * \file
00025  * \brief PostgreSQL CDR logger
00026  *
00027  * \author Matthew D. Hardeman <mhardemn@papersoft.com>
00028  * \extref PostgreSQL http://www.postgresql.org/
00029  *
00030  * See also
00031  * \arg \ref Config_cdr
00032  * \extref PostgreSQL http://www.postgresql.org/
00033  * \ingroup cdr_drivers
00034  */
00035 
00036 /*** MODULEINFO
00037    <depend>pgsql</depend>
00038    <support_level>extended</support_level>
00039  ***/
00040 
00041 #include "asterisk.h"
00042 
00043 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 370655 $")
00044 
00045 #include <libpq-fe.h>
00046 
00047 #include "asterisk/config.h"
00048 #include "asterisk/channel.h"
00049 #include "asterisk/cdr.h"
00050 #include "asterisk/cli.h"
00051 #include "asterisk/module.h"
00052 
00053 #define DATE_FORMAT "'%Y-%m-%d %T'"
00054 
00055 static const char name[] = "pgsql";
00056 static const char config[] = "cdr_pgsql.conf";
00057 static char *pghostname = NULL, *pgdbname = NULL, *pgdbuser = NULL, *pgpassword = NULL, *pgdbport = NULL, *table = NULL, *encoding = NULL, *tz = NULL;
00058 static int connected = 0;
00059 static int maxsize = 512, maxsize2 = 512;
00060 static time_t connect_time = 0;
00061 static int totalrecords = 0;
00062 static int records;
00063 
00064 static char *handle_cdr_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
00065 static struct ast_cli_entry cdr_pgsql_status_cli[] = {
00066         AST_CLI_DEFINE(handle_cdr_pgsql_status, "Show connection status of the PostgreSQL CDR driver (cdr_pgsql)"),
00067 };
00068 
00069 AST_MUTEX_DEFINE_STATIC(pgsql_lock);
00070 
00071 static PGconn  *conn = NULL;
00072 
00073 struct columns {
00074    char *name;
00075    char *type;
00076    int len;
00077    unsigned int notnull:1;
00078    unsigned int hasdefault:1;
00079    AST_RWLIST_ENTRY(columns) list;
00080 };
00081 
00082 static AST_RWLIST_HEAD_STATIC(psql_columns, columns);
00083 
00084 #define LENGTHEN_BUF1(size)                                               \
00085          do {                                                          \
00086             /* Lengthen buffer, if necessary */                       \
00087             if (ast_str_strlen(sql) + size + 1 > ast_str_size(sql)) { \
00088                if (ast_str_make_space(&sql, ((ast_str_size(sql) + size + 3) / 512 + 1) * 512) != 0) { \
00089                   ast_log(LOG_ERROR, "Unable to allocate sufficient memory.  Insert CDR failed.\n"); \
00090                   ast_free(sql);                                    \
00091                   ast_free(sql2);                                   \
00092                   AST_RWLIST_UNLOCK(&psql_columns);                 \
00093                   ast_mutex_unlock(&pgsql_lock);                    \
00094                   return -1;                                        \
00095                }                                                     \
00096             }                                                         \
00097          } while (0)
00098 
00099 #define LENGTHEN_BUF2(size)                               \
00100          do {                                          \
00101             if (ast_str_strlen(sql2) + size + 1 > ast_str_size(sql2)) {  \
00102                if (ast_str_make_space(&sql2, ((ast_str_size(sql2) + size + 3) / 512 + 1) * 512) != 0) {  \
00103                   ast_log(LOG_ERROR, "Unable to allocate sufficient memory.  Insert CDR failed.\n");  \
00104                   ast_free(sql);                    \
00105                   ast_free(sql2);                   \
00106                   AST_RWLIST_UNLOCK(&psql_columns); \
00107                   ast_mutex_unlock(&pgsql_lock);    \
00108                   return -1;                        \
00109                }                                     \
00110             }                                         \
00111          } while (0)
00112 
00113 /*! \brief Handle the CLI command cdr show pgsql status */
00114 static char *handle_cdr_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00115 {
00116    switch (cmd) {
00117    case CLI_INIT:
00118       e->command = "cdr show pgsql status";
00119       e->usage =
00120          "Usage: cdr show pgsql status\n"
00121          "       Shows current connection status for cdr_pgsql\n";
00122       return NULL;
00123    case CLI_GENERATE:
00124       return NULL;
00125    }
00126 
00127    if (a->argc != 3)
00128       return CLI_SHOWUSAGE;
00129 
00130    if (connected) {
00131       char status[256], status2[100] = "";
00132       int ctime = time(NULL) - connect_time;
00133 
00134       if (pgdbport) {
00135          snprintf(status, 255, "Connected to %s@%s, port %s", pgdbname, pghostname, pgdbport);
00136       } else {
00137          snprintf(status, 255, "Connected to %s@%s", pgdbname, pghostname);
00138       }
00139 
00140       if (pgdbuser && *pgdbuser) {
00141          snprintf(status2, 99, " with username %s", pgdbuser);
00142       }
00143       if (table && *table) {
00144          snprintf(status2, 99, " using table %s", table);
00145       }
00146       if (ctime > 31536000) {
00147          ast_cli(a->fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 31536000, (ctime % 31536000) / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60);
00148       } else if (ctime > 86400) {
00149          ast_cli(a->fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60);
00150       } else if (ctime > 3600) {
00151          ast_cli(a->fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 3600, (ctime % 3600) / 60, ctime % 60);
00152       } else if (ctime > 60) {
00153          ast_cli(a->fd, "%s%s for %d minutes, %d seconds.\n", status, status2, ctime / 60, ctime % 60);
00154       } else {
00155          ast_cli(a->fd, "%s%s for %d seconds.\n", status, status2, ctime);
00156       }
00157       if (records == totalrecords) {
00158          ast_cli(a->fd, "  Wrote %d records since last restart.\n", totalrecords);
00159       } else {
00160          ast_cli(a->fd, "  Wrote %d records since last restart and %d records since last reconnect.\n", totalrecords, records);
00161       }
00162    } else {
00163       ast_cli(a->fd, "Not currently connected to a PgSQL server.\n");
00164    }
00165    return CLI_SUCCESS;
00166 }
00167 
00168 static int pgsql_log(struct ast_cdr *cdr)
00169 {
00170    struct ast_tm tm;
00171    char *pgerror;
00172    PGresult *result;
00173 
00174    ast_mutex_lock(&pgsql_lock);
00175 
00176    if ((!connected) && pghostname && pgdbuser && pgpassword && pgdbname) {
00177       conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
00178       if (PQstatus(conn) != CONNECTION_BAD) {
00179          connected = 1;
00180          connect_time = time(NULL);
00181          records = 0;
00182          if (PQsetClientEncoding(conn, encoding)) {
00183 #ifdef HAVE_PGSQL_pg_encoding_to_char
00184             ast_log(LOG_WARNING, "Failed to set encoding to '%s'.  Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
00185 #else
00186             ast_log(LOG_WARNING, "Failed to set encoding to '%s'.  Encoding set to default.\n", encoding);
00187 #endif
00188          }
00189       } else {
00190          pgerror = PQerrorMessage(conn);
00191          ast_log(LOG_ERROR, "Unable to connect to database server %s.  Calls will not be logged!\n", pghostname);
00192          ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
00193          PQfinish(conn);
00194          conn = NULL;
00195       }
00196    }
00197 
00198    if (connected) {
00199       struct columns *cur;
00200       struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
00201       char buf[257], escapebuf[513], *value;
00202       int first = 1;
00203 
00204       if (!sql || !sql2) {
00205          ast_free(sql);
00206          ast_free(sql2);
00207          return -1;
00208       }
00209 
00210       ast_str_set(&sql, 0, "INSERT INTO %s (", table);
00211       ast_str_set(&sql2, 0, " VALUES (");
00212 
00213       AST_RWLIST_RDLOCK(&psql_columns);
00214       AST_RWLIST_TRAVERSE(&psql_columns, cur, list) {
00215          /* For fields not set, simply skip them */
00216          ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
00217          if (strcmp(cur->name, "calldate") == 0 && !value) {
00218             ast_cdr_getvar(cdr, "start", &value, buf, sizeof(buf), 0, 0);
00219          }
00220          if (!value) {
00221             if (cur->notnull && !cur->hasdefault) {
00222                /* Field is NOT NULL (but no default), must include it anyway */
00223                LENGTHEN_BUF1(strlen(cur->name) + 2);
00224                ast_str_append(&sql, 0, "%s\"%s\"", first ? "" : ",", cur->name);
00225                LENGTHEN_BUF2(3);
00226                ast_str_append(&sql2, 0, "%s''", first ? "" : ",");
00227                first = 0;
00228             }
00229             continue;
00230          }
00231 
00232          LENGTHEN_BUF1(strlen(cur->name) + 2);
00233          ast_str_append(&sql, 0, "%s\"%s\"", first ? "" : ",", cur->name);
00234 
00235          if (strcmp(cur->name, "start") == 0 || strcmp(cur->name, "calldate") == 0) {
00236             if (strncmp(cur->type, "int", 3) == 0) {
00237                LENGTHEN_BUF2(13);
00238                ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->start.tv_sec);
00239             } else if (strncmp(cur->type, "float", 5) == 0) {
00240                LENGTHEN_BUF2(31);
00241                ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->start.tv_sec + (double)cdr->start.tv_usec / 1000000.0);
00242             } else {
00243                /* char, hopefully */
00244                LENGTHEN_BUF2(31);
00245                ast_localtime(&cdr->start, &tm, tz);
00246                ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
00247                ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
00248             }
00249          } else if (strcmp(cur->name, "answer") == 0) {
00250             if (strncmp(cur->type, "int", 3) == 0) {
00251                LENGTHEN_BUF2(13);
00252                ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->answer.tv_sec);
00253             } else if (strncmp(cur->type, "float", 5) == 0) {
00254                LENGTHEN_BUF2(31);
00255                ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->answer.tv_sec + (double)cdr->answer.tv_usec / 1000000.0);
00256             } else {
00257                /* char, hopefully */
00258                LENGTHEN_BUF2(31);
00259                ast_localtime(&cdr->answer, &tm, tz);
00260                ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
00261                ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
00262             }
00263          } else if (strcmp(cur->name, "end") == 0) {
00264             if (strncmp(cur->type, "int", 3) == 0) {
00265                LENGTHEN_BUF2(13);
00266                ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->end.tv_sec);
00267             } else if (strncmp(cur->type, "float", 5) == 0) {
00268                LENGTHEN_BUF2(31);
00269                ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->end.tv_sec + (double)cdr->end.tv_usec / 1000000.0);
00270             } else {
00271                /* char, hopefully */
00272                LENGTHEN_BUF2(31);
00273                ast_localtime(&cdr->end, &tm, tz);
00274                ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
00275                ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
00276             }
00277          } else if (strcmp(cur->name, "duration") == 0 || strcmp(cur->name, "billsec") == 0) {
00278             if (cur->type[0] == 'i') {
00279                /* Get integer, no need to escape anything */
00280                ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
00281                LENGTHEN_BUF2(13);
00282                ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
00283             } else if (strncmp(cur->type, "float", 5) == 0) {
00284                struct timeval *when = cur->name[0] == 'd' ? &cdr->start : ast_tvzero(cdr->answer) ? &cdr->end : &cdr->answer;
00285                LENGTHEN_BUF2(31);
00286                ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0));
00287             } else {
00288                /* Char field, probably */
00289                struct timeval *when = cur->name[0] == 'd' ? &cdr->start : ast_tvzero(cdr->answer) ? &cdr->end : &cdr->answer;
00290                LENGTHEN_BUF2(31);
00291                ast_str_append(&sql2, 0, "%s'%f'", first ? "" : ",", (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0));
00292             }
00293          } else if (strcmp(cur->name, "disposition") == 0 || strcmp(cur->name, "amaflags") == 0) {
00294             if (strncmp(cur->type, "int", 3) == 0) {
00295                /* Integer, no need to escape anything */
00296                ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 1);
00297                LENGTHEN_BUF2(13);
00298                ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
00299             } else {
00300                /* Although this is a char field, there are no special characters in the values for these fields */
00301                ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
00302                LENGTHEN_BUF2(31);
00303                ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", value);
00304             }
00305          } else {
00306             /* Arbitrary field, could be anything */
00307             ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
00308             if (strncmp(cur->type, "int", 3) == 0) {
00309                long long whatever;
00310                if (value && sscanf(value, "%30lld", &whatever) == 1) {
00311                   LENGTHEN_BUF2(26);
00312                   ast_str_append(&sql2, 0, "%s%lld", first ? "" : ",", whatever);
00313                } else {
00314                   LENGTHEN_BUF2(2);
00315                   ast_str_append(&sql2, 0, "%s0", first ? "" : ",");
00316                }
00317             } else if (strncmp(cur->type, "float", 5) == 0) {
00318                long double whatever;
00319                if (value && sscanf(value, "%30Lf", &whatever) == 1) {
00320                   LENGTHEN_BUF2(51);
00321                   ast_str_append(&sql2, 0, "%s%30Lf", first ? "" : ",", whatever);
00322                } else {
00323                   LENGTHEN_BUF2(2);
00324                   ast_str_append(&sql2, 0, "%s0", first ? "" : ",");
00325                }
00326             /* XXX Might want to handle dates, times, and other misc fields here XXX */
00327             } else {
00328                if (value)
00329                   PQescapeStringConn(conn, escapebuf, value, strlen(value), NULL);
00330                else
00331                   escapebuf[0] = '\0';
00332                LENGTHEN_BUF2(strlen(escapebuf) + 3);
00333                ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", escapebuf);
00334             }
00335          }
00336          first = 0;
00337       }
00338 
00339       LENGTHEN_BUF1(ast_str_strlen(sql2) + 2);
00340       AST_RWLIST_UNLOCK(&psql_columns);
00341       ast_str_append(&sql, 0, ")%s)", ast_str_buffer(sql2));
00342       ast_verb(11, "[%s]\n", ast_str_buffer(sql));
00343 
00344       ast_debug(2, "inserting a CDR record.\n");
00345 
00346       /* Test to be sure we're still connected... */
00347       /* If we're connected, and connection is working, good. */
00348       /* Otherwise, attempt reconnect.  If it fails... sorry... */
00349       if (PQstatus(conn) == CONNECTION_OK) {
00350          connected = 1;
00351       } else {
00352          ast_log(LOG_ERROR, "Connection was lost... attempting to reconnect.\n");
00353          PQreset(conn);
00354          if (PQstatus(conn) == CONNECTION_OK) {
00355             ast_log(LOG_ERROR, "Connection reestablished.\n");
00356             connected = 1;
00357             connect_time = time(NULL);
00358             records = 0;
00359          } else {
00360             pgerror = PQerrorMessage(conn);
00361             ast_log(LOG_ERROR, "Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname);
00362             ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
00363             PQfinish(conn);
00364             conn = NULL;
00365             connected = 0;
00366             ast_mutex_unlock(&pgsql_lock);
00367             ast_free(sql);
00368             ast_free(sql2);
00369             return -1;
00370          }
00371       }
00372       result = PQexec(conn, ast_str_buffer(sql));
00373       if (PQresultStatus(result) != PGRES_COMMAND_OK) {
00374          pgerror = PQresultErrorMessage(result);
00375          ast_log(LOG_ERROR, "Failed to insert call detail record into database!\n");
00376          ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
00377          ast_log(LOG_ERROR, "Connection may have been lost... attempting to reconnect.\n");
00378          PQreset(conn);
00379          if (PQstatus(conn) == CONNECTION_OK) {
00380             ast_log(LOG_ERROR, "Connection reestablished.\n");
00381             connected = 1;
00382             connect_time = time(NULL);
00383             records = 0;
00384             PQclear(result);
00385             result = PQexec(conn, ast_str_buffer(sql));
00386             if (PQresultStatus(result) != PGRES_COMMAND_OK) {
00387                pgerror = PQresultErrorMessage(result);
00388                ast_log(LOG_ERROR, "HARD ERROR!  Attempted reconnection failed.  DROPPING CALL RECORD!\n");
00389                ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
00390             }  else {
00391                /* Second try worked out ok */
00392                totalrecords++;
00393                records++;
00394                ast_mutex_unlock(&pgsql_lock);
00395                PQclear(result);
00396                return 0;
00397             }
00398          }
00399          ast_mutex_unlock(&pgsql_lock);
00400          PQclear(result);
00401          ast_free(sql);
00402          ast_free(sql2);
00403          return -1;
00404       } else {
00405          totalrecords++;
00406          records++;
00407       }
00408       PQclear(result);
00409       ast_free(sql);
00410       ast_free(sql2);
00411    }
00412    ast_mutex_unlock(&pgsql_lock);
00413    return 0;
00414 }
00415 
00416 /* This function should be called without holding the pgsql_columns lock */
00417 static void empty_columns(void)
00418 {
00419    struct columns *current;
00420    AST_RWLIST_WRLOCK(&psql_columns);
00421    while ((current = AST_RWLIST_REMOVE_HEAD(&psql_columns, list))) {
00422       ast_free(current);
00423    }
00424    AST_RWLIST_UNLOCK(&psql_columns);
00425 
00426 }
00427 
00428 static int unload_module(void)
00429 {
00430    ast_cdr_unregister(name);
00431    ast_cli_unregister_multiple(cdr_pgsql_status_cli, ARRAY_LEN(cdr_pgsql_status_cli));
00432 
00433    PQfinish(conn);
00434 
00435    ast_free(pghostname);
00436    ast_free(pgdbname);
00437    ast_free(pgdbuser);
00438    ast_free(pgpassword);
00439    ast_free(pgdbport);
00440    ast_free(table);
00441    ast_free(encoding);
00442    ast_free(tz);
00443 
00444    empty_columns();
00445 
00446    return 0;
00447 }
00448 
00449 static int config_module(int reload)
00450 {
00451    char *pgerror;
00452    struct columns *cur;
00453    PGresult *result;
00454    const char *tmp;
00455    struct ast_config *cfg;
00456    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
00457 
00458    if ((cfg = ast_config_load(config, config_flags)) == NULL || cfg == CONFIG_STATUS_FILEINVALID) {
00459       ast_log(LOG_WARNING, "Unable to load config for PostgreSQL CDR's: %s\n", config);
00460       return -1;
00461    } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
00462       return 0;
00463    }
00464 
00465    ast_mutex_lock(&pgsql_lock);
00466 
00467    if (!ast_variable_browse(cfg, "global")) {
00468       ast_config_destroy(cfg);
00469       ast_mutex_unlock(&pgsql_lock);
00470       ast_log(LOG_NOTICE, "cdr_pgsql configuration contains no global section, skipping module %s.\n",
00471          reload ? "reload" : "load");
00472       return -1;
00473    }
00474 
00475    if (!(tmp = ast_variable_retrieve(cfg, "global", "hostname"))) {
00476       ast_log(LOG_WARNING, "PostgreSQL server hostname not specified.  Assuming unix socket connection\n");
00477       tmp = "";   /* connect via UNIX-socket by default */
00478    }
00479 
00480    ast_free(pghostname);
00481    if (!(pghostname = ast_strdup(tmp))) {
00482       ast_config_destroy(cfg);
00483       ast_mutex_unlock(&pgsql_lock);
00484       return -1;
00485    }
00486 
00487    if (!(tmp = ast_variable_retrieve(cfg, "global", "dbname"))) {
00488       ast_log(LOG_WARNING, "PostgreSQL database not specified.  Assuming asterisk\n");
00489       tmp = "asteriskcdrdb";
00490    }
00491 
00492    ast_free(pgdbname);
00493    if (!(pgdbname = ast_strdup(tmp))) {
00494       ast_config_destroy(cfg);
00495       ast_mutex_unlock(&pgsql_lock);
00496       return -1;
00497    }
00498 
00499    if (!(tmp = ast_variable_retrieve(cfg, "global", "user"))) {
00500       ast_log(LOG_WARNING, "PostgreSQL database user not specified.  Assuming asterisk\n");
00501       tmp = "asterisk";
00502    }
00503 
00504    ast_free(pgdbuser);
00505    if (!(pgdbuser = ast_strdup(tmp))) {
00506       ast_config_destroy(cfg);
00507       ast_mutex_unlock(&pgsql_lock);
00508       return -1;
00509    }
00510 
00511    if (!(tmp = ast_variable_retrieve(cfg, "global", "password"))) {
00512       ast_log(LOG_WARNING, "PostgreSQL database password not specified.  Assuming blank\n");
00513       tmp = "";
00514    }
00515 
00516    ast_free(pgpassword);
00517    if (!(pgpassword = ast_strdup(tmp))) {
00518       ast_config_destroy(cfg);
00519       ast_mutex_unlock(&pgsql_lock);
00520       return -1;
00521    }
00522 
00523    if (!(tmp = ast_variable_retrieve(cfg, "global", "port"))) {
00524       ast_log(LOG_WARNING, "PostgreSQL database port not specified.  Using default 5432.\n");
00525       tmp = "5432";
00526    }
00527 
00528    ast_free(pgdbport);
00529    if (!(pgdbport = ast_strdup(tmp))) {
00530       ast_config_destroy(cfg);
00531       ast_mutex_unlock(&pgsql_lock);
00532       return -1;
00533    }
00534 
00535    if (!(tmp = ast_variable_retrieve(cfg, "global", "table"))) {
00536       ast_log(LOG_WARNING, "CDR table not specified.  Assuming cdr\n");
00537       tmp = "cdr";
00538    }
00539 
00540    ast_free(table);
00541    if (!(table = ast_strdup(tmp))) {
00542       ast_config_destroy(cfg);
00543       ast_mutex_unlock(&pgsql_lock);
00544       return -1;
00545    }
00546 
00547    if (!(tmp = ast_variable_retrieve(cfg, "global", "encoding"))) {
00548       ast_log(LOG_WARNING, "Encoding not specified.  Assuming LATIN9\n");
00549       tmp = "LATIN9";
00550    }
00551 
00552    ast_free(encoding);
00553    if (!(encoding = ast_strdup(tmp))) {
00554       ast_config_destroy(cfg);
00555       ast_mutex_unlock(&pgsql_lock);
00556       return -1;
00557    }
00558 
00559    if (!(tmp = ast_variable_retrieve(cfg, "global", "timezone"))) {
00560       tmp = "";
00561    }
00562 
00563    ast_free(tz);
00564    tz = NULL;
00565 
00566    if (!ast_strlen_zero(tmp) && !(tz = ast_strdup(tmp))) {
00567       ast_config_destroy(cfg);
00568       ast_mutex_unlock(&pgsql_lock);
00569       return -1;
00570    }
00571 
00572    if (option_debug) {
00573       if (ast_strlen_zero(pghostname)) {
00574          ast_debug(1, "using default unix socket\n");
00575       } else {
00576          ast_debug(1, "got hostname of %s\n", pghostname);
00577       }
00578       ast_debug(1, "got port of %s\n", pgdbport);
00579       ast_debug(1, "got user of %s\n", pgdbuser);
00580       ast_debug(1, "got dbname of %s\n", pgdbname);
00581       ast_debug(1, "got password of %s\n", pgpassword);
00582       ast_debug(1, "got sql table name of %s\n", table);
00583       ast_debug(1, "got encoding of %s\n", encoding);
00584       ast_debug(1, "got timezone of %s\n", tz);
00585    }
00586 
00587    conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
00588    if (PQstatus(conn) != CONNECTION_BAD) {
00589       char sqlcmd[768];
00590       char *fname, *ftype, *flen, *fnotnull, *fdef;
00591       int i, rows, version;
00592       ast_debug(1, "Successfully connected to PostgreSQL database.\n");
00593       connected = 1;
00594       connect_time = time(NULL);
00595       records = 0;
00596       if (PQsetClientEncoding(conn, encoding)) {
00597 #ifdef HAVE_PGSQL_pg_encoding_to_char
00598          ast_log(LOG_WARNING, "Failed to set encoding to '%s'.  Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
00599 #else
00600          ast_log(LOG_WARNING, "Failed to set encoding to '%s'.  Encoding set to default.\n", encoding);
00601 #endif
00602       }
00603       version = PQserverVersion(conn);
00604 
00605       if (version >= 70300) {
00606          char *schemaname, *tablename;
00607          if (strchr(table, '.')) {
00608             schemaname = ast_strdupa(table);
00609             tablename = strchr(schemaname, '.');
00610             *tablename++ = '\0';
00611          } else {
00612             schemaname = "";
00613             tablename = table;
00614          }
00615 
00616          /* Escape special characters in schemaname */
00617          if (strchr(schemaname, '\\') || strchr(schemaname, '\'')) {
00618             char *tmp = schemaname, *ptr;
00619 
00620             ptr = schemaname = ast_alloca(strlen(tmp) * 2 + 1);
00621             for (; *tmp; tmp++) {
00622                if (strchr("\\'", *tmp)) {
00623                   *ptr++ = *tmp;
00624                }
00625                *ptr++ = *tmp;
00626             }
00627             *ptr = '\0';
00628          }
00629          /* Escape special characters in tablename */
00630          if (strchr(tablename, '\\') || strchr(tablename, '\'')) {
00631             char *tmp = tablename, *ptr;
00632 
00633             ptr = tablename = ast_alloca(strlen(tmp) * 2 + 1);
00634             for (; *tmp; tmp++) {
00635                if (strchr("\\'", *tmp)) {
00636                   *ptr++ = *tmp;
00637                }
00638                *ptr++ = *tmp;
00639             }
00640             *ptr = '\0';
00641          }
00642 
00643          snprintf(sqlcmd, sizeof(sqlcmd), "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",
00644             tablename,
00645             ast_strlen_zero(schemaname) ? "" : "'", ast_strlen_zero(schemaname) ? "current_schema()" : schemaname, ast_strlen_zero(schemaname) ? "" : "'");
00646       } else {
00647          snprintf(sqlcmd, sizeof(sqlcmd), "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", table);
00648       }
00649       /* Query the columns */
00650       result = PQexec(conn, sqlcmd);
00651       if (PQresultStatus(result) != PGRES_TUPLES_OK) {
00652          pgerror = PQresultErrorMessage(result);
00653          ast_log(LOG_ERROR, "Failed to query database columns: %s\n", pgerror);
00654          PQclear(result);
00655          unload_module();
00656          ast_mutex_unlock(&pgsql_lock);
00657          return AST_MODULE_LOAD_DECLINE;
00658       }
00659 
00660       rows = PQntuples(result);
00661       if (rows == 0) {
00662          ast_log(LOG_ERROR, "cdr_pgsql: Failed to query database columns. No columns found, does the table exist?\n");
00663          PQclear(result);
00664          unload_module();
00665          ast_mutex_unlock(&pgsql_lock);
00666          return AST_MODULE_LOAD_DECLINE;
00667       }
00668 
00669       /* Clear out the columns list. */
00670       empty_columns();
00671 
00672       for (i = 0; i < rows; i++) {
00673          fname = PQgetvalue(result, i, 0);
00674          ftype = PQgetvalue(result, i, 1);
00675          flen = PQgetvalue(result, i, 2);
00676          fnotnull = PQgetvalue(result, i, 3);
00677          fdef = PQgetvalue(result, i, 4);
00678          if (atoi(flen) == -1) {
00679             /* For varchar columns, the maximum length is encoded in a different field */
00680             flen = PQgetvalue(result, i, 5);
00681          }
00682          ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
00683          cur = ast_calloc(1, sizeof(*cur) + strlen(fname) + strlen(ftype) + 2);
00684          if (cur) {
00685             sscanf(flen, "%30d", &cur->len);
00686             cur->name = (char *)cur + sizeof(*cur);
00687             cur->type = (char *)cur + sizeof(*cur) + strlen(fname) + 1;
00688             strcpy(cur->name, fname);
00689             strcpy(cur->type, ftype);
00690             if (*fnotnull == 't') {
00691                cur->notnull = 1;
00692             } else {
00693                cur->notnull = 0;
00694             }
00695             if (!ast_strlen_zero(fdef)) {
00696                cur->hasdefault = 1;
00697             } else {
00698                cur->hasdefault = 0;
00699             }
00700             AST_RWLIST_WRLOCK(&psql_columns);
00701             AST_RWLIST_INSERT_TAIL(&psql_columns, cur, list);
00702             AST_RWLIST_UNLOCK(&psql_columns);
00703          }
00704       }
00705       PQclear(result);
00706    } else {
00707       pgerror = PQerrorMessage(conn);
00708       ast_log(LOG_ERROR, "Unable to connect to database server %s.  CALLS WILL NOT BE LOGGED!!\n", pghostname);
00709       ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
00710       connected = 0;
00711    }
00712 
00713    ast_config_destroy(cfg);
00714 
00715    ast_mutex_unlock(&pgsql_lock);
00716    return 0;
00717 }
00718 
00719 static int load_module(void)
00720 {
00721    ast_cli_register_multiple(cdr_pgsql_status_cli, sizeof(cdr_pgsql_status_cli) / sizeof(struct ast_cli_entry));
00722    if (config_module(0)) {
00723       return AST_MODULE_LOAD_DECLINE;
00724    }
00725    return ast_cdr_register(name, ast_module_info->description, pgsql_log)
00726       ? AST_MODULE_LOAD_DECLINE : 0;
00727 }
00728 
00729 static int reload(void)
00730 {
00731    return config_module(1);
00732 }
00733 
00734 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PostgreSQL CDR Backend",
00735       .load = load_module,
00736       .unload = unload_module,
00737       .reload = reload,
00738       .load_pri = AST_MODPRI_CDR_DRIVER,
00739           );