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

Generated on Thu Apr 16 06:27:17 2015 for Asterisk - The Open Source Telephony Project by  doxygen 1.5.6