Mon Jun 27 16:50:55 2011

Asterisk developer's documentation


res_config_pgsql.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- A telephony toolkit for Linux.
00003  *
00004  * Copyright (C) 1999-2010, Digium, Inc.
00005  *
00006  * Manuel Guesdon <mguesdon@oxymium.net> - PostgreSQL RealTime Driver Author/Adaptor
00007  * Mark Spencer <markster@digium.com>  - Asterisk Author
00008  * Matthew Boehm <mboehm@cytelcom.com> - MySQL RealTime Driver Author
00009  *
00010  * res_config_pgsql.c <PostgreSQL plugin for RealTime configuration engine>
00011  *
00012  * v1.0   - (07-11-05) - Initial version based on res_config_mysql v2.0
00013  */
00014 
00015 /*! \file
00016  *
00017  * \brief PostgreSQL plugin for Asterisk RealTime Architecture
00018  *
00019  * \author Mark Spencer <markster@digium.com>
00020  * \author Manuel Guesdon <mguesdon@oxymium.net> - PostgreSQL RealTime Driver Author/Adaptor
00021  *
00022  * \extref PostgreSQL http://www.postgresql.org
00023  */
00024 
00025 /*** MODULEINFO
00026    <depend>pgsql</depend>
00027  ***/
00028 
00029 #include "asterisk.h"
00030 
00031 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 284473 $")
00032 
00033 #include <libpq-fe.h>         /* PostgreSQL */
00034 
00035 #include "asterisk/file.h"
00036 #include "asterisk/channel.h"
00037 #include "asterisk/pbx.h"
00038 #include "asterisk/config.h"
00039 #include "asterisk/module.h"
00040 #include "asterisk/lock.h"
00041 #include "asterisk/utils.h"
00042 #include "asterisk/cli.h"
00043 
00044 AST_MUTEX_DEFINE_STATIC(pgsql_lock);
00045 AST_THREADSTORAGE(sql_buf);
00046 AST_THREADSTORAGE(findtable_buf);
00047 AST_THREADSTORAGE(where_buf);
00048 AST_THREADSTORAGE(escapebuf_buf);
00049 AST_THREADSTORAGE(semibuf_buf);
00050 
00051 #define RES_CONFIG_PGSQL_CONF "res_pgsql.conf"
00052 
00053 static PGconn *pgsqlConn = NULL;
00054 static int version;
00055 #define has_schema_support (version > 70300 ? 1 : 0)
00056 
00057 #define MAX_DB_OPTION_SIZE 64
00058 
00059 struct columns {
00060    char *name;
00061    char *type;
00062    int len;
00063    unsigned int notnull:1;
00064    unsigned int hasdefault:1;
00065    AST_LIST_ENTRY(columns) list;
00066 };
00067 
00068 struct tables {
00069    ast_rwlock_t lock;
00070    AST_LIST_HEAD_NOLOCK(psql_columns, columns) columns;
00071    AST_LIST_ENTRY(tables) list;
00072    char name[0];
00073 };
00074 
00075 static AST_LIST_HEAD_STATIC(psql_tables, tables);
00076 
00077 static char dbhost[MAX_DB_OPTION_SIZE] = "";
00078 static char dbuser[MAX_DB_OPTION_SIZE] = "";
00079 static char dbpass[MAX_DB_OPTION_SIZE] = "";
00080 static char dbname[MAX_DB_OPTION_SIZE] = "";
00081 static char dbsock[MAX_DB_OPTION_SIZE] = "";
00082 static int dbport = 5432;
00083 static time_t connect_time = 0;
00084 
00085 static int parse_config(int reload);
00086 static int pgsql_reconnect(const char *database);
00087 static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
00088 static char *handle_cli_realtime_pgsql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
00089 
00090 static enum { RQ_WARN, RQ_CREATECLOSE, RQ_CREATECHAR } requirements;
00091 
00092 static struct ast_cli_entry cli_realtime[] = {
00093    AST_CLI_DEFINE(handle_cli_realtime_pgsql_status, "Shows connection information for the PostgreSQL RealTime driver"),
00094    AST_CLI_DEFINE(handle_cli_realtime_pgsql_cache, "Shows cached tables within the PostgreSQL realtime driver"),
00095 };
00096 
00097 #define ESCAPE_STRING(buffer, stringname) \
00098    do { \
00099       int len = strlen(stringname); \
00100       struct ast_str *semi = ast_str_thread_get(&semibuf_buf, len * 3 + 1); \
00101       const char *chunk = stringname; \
00102       ast_str_reset(semi); \
00103       for (; *chunk; chunk++) { \
00104          if (strchr(";^", *chunk)) { \
00105             ast_str_append(&semi, 0, "^%02hhX", *chunk); \
00106          } else { \
00107             ast_str_append(&semi, 0, "%c", *chunk); \
00108          } \
00109       } \
00110       if (ast_str_strlen(semi) > (ast_str_size(buffer) - 1) / 2) { \
00111          ast_str_make_space(&buffer, ast_str_strlen(semi) * 2 + 1); \
00112       } \
00113       PQescapeStringConn(pgsqlConn, ast_str_buffer(buffer), ast_str_buffer(semi), ast_str_size(buffer), &pgresult); \
00114    } while (0)
00115 
00116 static void destroy_table(struct tables *table)
00117 {
00118    struct columns *column;
00119    ast_rwlock_wrlock(&table->lock);
00120    while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
00121       ast_free(column);
00122    }
00123    ast_rwlock_unlock(&table->lock);
00124    ast_rwlock_destroy(&table->lock);
00125    ast_free(table);
00126 }
00127 
00128 static struct tables *find_table(const char *orig_tablename)
00129 {
00130    struct columns *column;
00131    struct tables *table;
00132    struct ast_str *sql = ast_str_thread_get(&findtable_buf, 330);
00133    char *pgerror;
00134    PGresult *result;
00135    char *fname, *ftype, *flen, *fnotnull, *fdef;
00136    int i, rows;
00137 
00138    AST_LIST_LOCK(&psql_tables);
00139    AST_LIST_TRAVERSE(&psql_tables, table, list) {
00140       if (!strcasecmp(table->name, orig_tablename)) {
00141          ast_debug(1, "Found table in cache; now locking\n");
00142          ast_rwlock_rdlock(&table->lock);
00143          ast_debug(1, "Lock cached table; now returning\n");
00144          AST_LIST_UNLOCK(&psql_tables);
00145          return table;
00146       }
00147    }
00148 
00149    ast_debug(1, "Table '%s' not found in cache, querying now\n", orig_tablename);
00150 
00151    /* Not found, scan the table */
00152    if (has_schema_support) {
00153       char *schemaname, *tablename;
00154       if (strchr(orig_tablename, '.')) {
00155          schemaname = ast_strdupa(orig_tablename);
00156          tablename = strchr(schemaname, '.');
00157          *tablename++ = '\0';
00158       } else {
00159          schemaname = "";
00160          tablename = ast_strdupa(orig_tablename);
00161       }
00162 
00163       /* Escape special characters in schemaname */
00164       if (strchr(schemaname, '\\') || strchr(schemaname, '\'')) {
00165          char *tmp = schemaname, *ptr;
00166 
00167          ptr = schemaname = alloca(strlen(tmp) * 2 + 1);
00168          for (; *tmp; tmp++) {
00169             if (strchr("\\'", *tmp)) {
00170                *ptr++ = *tmp;
00171             }
00172             *ptr++ = *tmp;
00173          }
00174          *ptr = '\0';
00175       }
00176       /* Escape special characters in tablename */
00177       if (strchr(tablename, '\\') || strchr(tablename, '\'')) {
00178          char *tmp = tablename, *ptr;
00179 
00180          ptr = tablename = alloca(strlen(tmp) * 2 + 1);
00181          for (; *tmp; tmp++) {
00182             if (strchr("\\'", *tmp)) {
00183                *ptr++ = *tmp;
00184             }
00185             *ptr++ = *tmp;
00186          }
00187          *ptr = '\0';
00188       }
00189 
00190       ast_str_set(&sql, 0, "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc, a.atttypmod FROM (((pg_catalog.pg_class c INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace AND c.relname = '%s' AND n.nspname = %s%s%s) INNER JOIN pg_catalog.pg_attribute a ON (NOT a.attisdropped) AND a.attnum > 0 AND a.attrelid = c.oid) INNER JOIN pg_catalog.pg_type t ON t.oid = a.atttypid) LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum ORDER BY n.nspname, c.relname, attnum",
00191          tablename,
00192          ast_strlen_zero(schemaname) ? "" : "'", ast_strlen_zero(schemaname) ? "current_schema()" : schemaname, ast_strlen_zero(schemaname) ? "" : "'");
00193    } else {
00194       /* Escape special characters in tablename */
00195       if (strchr(orig_tablename, '\\') || strchr(orig_tablename, '\'')) {
00196          const char *tmp = orig_tablename;
00197          char *ptr;
00198 
00199          orig_tablename = ptr = alloca(strlen(tmp) * 2 + 1);
00200          for (; *tmp; tmp++) {
00201             if (strchr("\\'", *tmp)) {
00202                *ptr++ = *tmp;
00203             }
00204             *ptr++ = *tmp;
00205          }
00206          *ptr = '\0';
00207       }
00208 
00209       ast_str_set(&sql, 0, "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc, a.atttypmod FROM pg_class c, pg_type t, pg_attribute a LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum WHERE c.oid = a.attrelid AND a.atttypid = t.oid AND (a.attnum > 0) AND c.relname = '%s' ORDER BY c.relname, attnum", orig_tablename);
00210    }
00211 
00212    result = PQexec(pgsqlConn, ast_str_buffer(sql));
00213    ast_debug(1, "Query of table structure complete.  Now retrieving results.\n");
00214    if (PQresultStatus(result) != PGRES_TUPLES_OK) {
00215       pgerror = PQresultErrorMessage(result);
00216       ast_log(LOG_ERROR, "Failed to query database columns: %s\n", pgerror);
00217       PQclear(result);
00218       AST_LIST_UNLOCK(&psql_tables);
00219       return NULL;
00220    }
00221 
00222    if (!(table = ast_calloc(1, sizeof(*table) + strlen(orig_tablename) + 1))) {
00223       ast_log(LOG_ERROR, "Unable to allocate memory for new table structure\n");
00224       AST_LIST_UNLOCK(&psql_tables);
00225       return NULL;
00226    }
00227    strcpy(table->name, orig_tablename); /* SAFE */
00228    ast_rwlock_init(&table->lock);
00229    AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
00230 
00231    rows = PQntuples(result);
00232    for (i = 0; i < rows; i++) {
00233       fname = PQgetvalue(result, i, 0);
00234       ftype = PQgetvalue(result, i, 1);
00235       flen = PQgetvalue(result, i, 2);
00236       fnotnull = PQgetvalue(result, i, 3);
00237       fdef = PQgetvalue(result, i, 4);
00238       ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
00239 
00240       if (!(column = ast_calloc(1, sizeof(*column) + strlen(fname) + strlen(ftype) + 2))) {
00241          ast_log(LOG_ERROR, "Unable to allocate column element for %s, %s\n", orig_tablename, fname);
00242          destroy_table(table);
00243          AST_LIST_UNLOCK(&psql_tables);
00244          return NULL;
00245       }
00246 
00247       if (strcmp(flen, "-1") == 0) {
00248          /* Some types, like chars, have the length stored in a different field */
00249          flen = PQgetvalue(result, i, 5);
00250          sscanf(flen, "%30d", &column->len);
00251          column->len -= 4;
00252       } else {
00253          sscanf(flen, "%30d", &column->len);
00254       }
00255       column->name = (char *)column + sizeof(*column);
00256       column->type = (char *)column + sizeof(*column) + strlen(fname) + 1;
00257       strcpy(column->name, fname);
00258       strcpy(column->type, ftype);
00259       if (*fnotnull == 't') {
00260          column->notnull = 1;
00261       } else {
00262          column->notnull = 0;
00263       }
00264       if (!ast_strlen_zero(fdef)) {
00265          column->hasdefault = 1;
00266       } else {
00267          column->hasdefault = 0;
00268       }
00269       AST_LIST_INSERT_TAIL(&table->columns, column, list);
00270    }
00271    PQclear(result);
00272 
00273    AST_LIST_INSERT_TAIL(&psql_tables, table, list);
00274    ast_rwlock_rdlock(&table->lock);
00275    AST_LIST_UNLOCK(&psql_tables);
00276    return table;
00277 }
00278 
00279 #define release_table(table) ast_rwlock_unlock(&(table)->lock);
00280 
00281 static struct columns *find_column(struct tables *t, const char *colname)
00282 {
00283    struct columns *column;
00284 
00285    /* Check that the column exists in the table */
00286    AST_LIST_TRAVERSE(&t->columns, column, list) {
00287       if (strcmp(column->name, colname) == 0) {
00288          return column;
00289       }
00290    }
00291    return NULL;
00292 }
00293 
00294 static struct ast_variable *realtime_pgsql(const char *database, const char *tablename, va_list ap)
00295 {
00296    PGresult *result = NULL;
00297    int num_rows = 0, pgresult;
00298    struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
00299    struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
00300    char *stringp;
00301    char *chunk;
00302    char *op;
00303    const char *newparam, *newval;
00304    struct ast_variable *var = NULL, *prev = NULL;
00305 
00306    if (!tablename) {
00307       ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
00308       return NULL;
00309    }
00310 
00311    /* Get the first parameter and first value in our list of passed paramater/value pairs */
00312    newparam = va_arg(ap, const char *);
00313    newval = va_arg(ap, const char *);
00314    if (!newparam || !newval) {
00315       ast_log(LOG_WARNING,
00316             "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
00317       if (pgsqlConn) {
00318          PQfinish(pgsqlConn);
00319          pgsqlConn = NULL;
00320       }
00321       return NULL;
00322    }
00323 
00324    /* Create the first part of the query using the first parameter/value pairs we just extracted
00325       If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
00326    op = strchr(newparam, ' ') ? "" : " =";
00327 
00328    ESCAPE_STRING(escapebuf, newval);
00329    if (pgresult) {
00330       ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00331       va_end(ap);
00332       return NULL;
00333    }
00334 
00335    ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", tablename, newparam, op, ast_str_buffer(escapebuf));
00336    while ((newparam = va_arg(ap, const char *))) {
00337       newval = va_arg(ap, const char *);
00338       if (!strchr(newparam, ' '))
00339          op = " =";
00340       else
00341          op = "";
00342 
00343       ESCAPE_STRING(escapebuf, newval);
00344       if (pgresult) {
00345          ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00346          va_end(ap);
00347          return NULL;
00348       }
00349 
00350       ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(escapebuf));
00351    }
00352    va_end(ap);
00353 
00354    /* We now have our complete statement; Lets connect to the server and execute it. */
00355    ast_mutex_lock(&pgsql_lock);
00356    if (!pgsql_reconnect(database)) {
00357       ast_mutex_unlock(&pgsql_lock);
00358       return NULL;
00359    }
00360 
00361    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
00362       ast_log(LOG_WARNING,
00363             "PostgreSQL RealTime: Failed to query '%s@%s'. Check debug for more info.\n", tablename, database);
00364       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00365       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
00366       ast_mutex_unlock(&pgsql_lock);
00367       return NULL;
00368    } else {
00369       ExecStatusType result_status = PQresultStatus(result);
00370       if (result_status != PGRES_COMMAND_OK
00371          && result_status != PGRES_TUPLES_OK
00372          && result_status != PGRES_NONFATAL_ERROR) {
00373          ast_log(LOG_WARNING,
00374                "PostgreSQL RealTime: Failed to query '%s@%s'. Check debug for more info.\n", tablename, database);
00375          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00376          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
00377                   PQresultErrorMessage(result), PQresStatus(result_status));
00378          ast_mutex_unlock(&pgsql_lock);
00379          return NULL;
00380       }
00381    }
00382 
00383    ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, ast_str_buffer(sql));
00384 
00385    if ((num_rows = PQntuples(result)) > 0) {
00386       int i = 0;
00387       int rowIndex = 0;
00388       int numFields = PQnfields(result);
00389       char **fieldnames = NULL;
00390 
00391       ast_debug(1, "PostgreSQL RealTime: Found %d rows.\n", num_rows);
00392 
00393       if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
00394          ast_mutex_unlock(&pgsql_lock);
00395          PQclear(result);
00396          return NULL;
00397       }
00398       for (i = 0; i < numFields; i++)
00399          fieldnames[i] = PQfname(result, i);
00400       for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
00401          for (i = 0; i < numFields; i++) {
00402             stringp = PQgetvalue(result, rowIndex, i);
00403             while (stringp) {
00404                chunk = strsep(&stringp, ";");
00405                if (chunk && !ast_strlen_zero(ast_realtime_decode_chunk(ast_strip(chunk)))) {
00406                   if (prev) {
00407                      prev->next = ast_variable_new(fieldnames[i], chunk, "");
00408                      if (prev->next) {
00409                         prev = prev->next;
00410                      }
00411                   } else {
00412                      prev = var = ast_variable_new(fieldnames[i], chunk, "");
00413                   }
00414                }
00415             }
00416          }
00417       }
00418       ast_free(fieldnames);
00419    } else {
00420       ast_debug(1, "Postgresql RealTime: Could not find any rows in table %s@%s.\n", tablename, database);
00421    }
00422 
00423    ast_mutex_unlock(&pgsql_lock);
00424    PQclear(result);
00425 
00426    return var;
00427 }
00428 
00429 static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, va_list ap)
00430 {
00431    PGresult *result = NULL;
00432    int num_rows = 0, pgresult;
00433    struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
00434    struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
00435    const char *initfield = NULL;
00436    char *stringp;
00437    char *chunk;
00438    char *op;
00439    const char *newparam, *newval;
00440    struct ast_variable *var = NULL;
00441    struct ast_config *cfg = NULL;
00442    struct ast_category *cat = NULL;
00443 
00444    if (!table) {
00445       ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
00446       return NULL;
00447    }
00448 
00449    if (!(cfg = ast_config_new()))
00450       return NULL;
00451 
00452    /* Get the first parameter and first value in our list of passed paramater/value pairs */
00453    newparam = va_arg(ap, const char *);
00454    newval = va_arg(ap, const char *);
00455    if (!newparam || !newval) {
00456       ast_log(LOG_WARNING,
00457             "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
00458       if (pgsqlConn) {
00459          PQfinish(pgsqlConn);
00460          pgsqlConn = NULL;
00461       }
00462       return NULL;
00463    }
00464 
00465    initfield = ast_strdupa(newparam);
00466    if ((op = strchr(initfield, ' '))) {
00467       *op = '\0';
00468    }
00469 
00470    /* Create the first part of the query using the first parameter/value pairs we just extracted
00471       If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
00472 
00473    if (!strchr(newparam, ' '))
00474       op = " =";
00475    else
00476       op = "";
00477 
00478    ESCAPE_STRING(escapebuf, newval);
00479    if (pgresult) {
00480       ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00481       va_end(ap);
00482       return NULL;
00483    }
00484 
00485    ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(escapebuf));
00486    while ((newparam = va_arg(ap, const char *))) {
00487       newval = va_arg(ap, const char *);
00488       if (!strchr(newparam, ' '))
00489          op = " =";
00490       else
00491          op = "";
00492 
00493       ESCAPE_STRING(escapebuf, newval);
00494       if (pgresult) {
00495          ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00496          va_end(ap);
00497          return NULL;
00498       }
00499 
00500       ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(escapebuf));
00501    }
00502 
00503    if (initfield) {
00504       ast_str_append(&sql, 0, " ORDER BY %s", initfield);
00505    }
00506 
00507    va_end(ap);
00508 
00509    /* We now have our complete statement; Lets connect to the server and execute it. */
00510    ast_mutex_lock(&pgsql_lock);
00511    if (!pgsql_reconnect(database)) {
00512       ast_mutex_unlock(&pgsql_lock);
00513       return NULL;
00514    }
00515 
00516    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
00517       ast_log(LOG_WARNING,
00518             "PostgreSQL RealTime: Failed to query %s@%s. Check debug for more info.\n", table, database);
00519       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00520       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
00521       ast_mutex_unlock(&pgsql_lock);
00522       return NULL;
00523    } else {
00524       ExecStatusType result_status = PQresultStatus(result);
00525       if (result_status != PGRES_COMMAND_OK
00526          && result_status != PGRES_TUPLES_OK
00527          && result_status != PGRES_NONFATAL_ERROR) {
00528          ast_log(LOG_WARNING,
00529                "PostgreSQL RealTime: Failed to query %s@%s. Check debug for more info.\n", table, database);
00530          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00531          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
00532                   PQresultErrorMessage(result), PQresStatus(result_status));
00533          ast_mutex_unlock(&pgsql_lock);
00534          return NULL;
00535       }
00536    }
00537 
00538    ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, ast_str_buffer(sql));
00539 
00540    if ((num_rows = PQntuples(result)) > 0) {
00541       int numFields = PQnfields(result);
00542       int i = 0;
00543       int rowIndex = 0;
00544       char **fieldnames = NULL;
00545 
00546       ast_debug(1, "PostgreSQL RealTime: Found %d rows.\n", num_rows);
00547 
00548       if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
00549          ast_mutex_unlock(&pgsql_lock);
00550          PQclear(result);
00551          return NULL;
00552       }
00553       for (i = 0; i < numFields; i++)
00554          fieldnames[i] = PQfname(result, i);
00555 
00556       for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
00557          var = NULL;
00558          if (!(cat = ast_category_new("","",99999)))
00559             continue;
00560          for (i = 0; i < numFields; i++) {
00561             stringp = PQgetvalue(result, rowIndex, i);
00562             while (stringp) {
00563                chunk = strsep(&stringp, ";");
00564                if (chunk && !ast_strlen_zero(ast_realtime_decode_chunk(ast_strip(chunk)))) {
00565                   if (initfield && !strcmp(initfield, fieldnames[i])) {
00566                      ast_category_rename(cat, chunk);
00567                   }
00568                   var = ast_variable_new(fieldnames[i], chunk, "");
00569                   ast_variable_append(cat, var);
00570                }
00571             }
00572          }
00573          ast_category_append(cfg, cat);
00574       }
00575       ast_free(fieldnames);
00576    } else {
00577       ast_debug(1, "PostgreSQL RealTime: Could not find any rows in table %s.\n", table);
00578    }
00579 
00580    ast_mutex_unlock(&pgsql_lock);
00581    PQclear(result);
00582 
00583    return cfg;
00584 }
00585 
00586 static int update_pgsql(const char *database, const char *tablename, const char *keyfield,
00587                   const char *lookup, va_list ap)
00588 {
00589    PGresult *result = NULL;
00590    int numrows = 0, pgresult;
00591    const char *newparam, *newval;
00592    struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
00593    struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
00594    struct tables *table;
00595    struct columns *column = NULL;
00596 
00597    if (!tablename) {
00598       ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
00599       return -1;
00600    }
00601 
00602    if (!(table = find_table(tablename))) {
00603       ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
00604       return -1;
00605    }
00606 
00607    /* Get the first parameter and first value in our list of passed paramater/value pairs */
00608    newparam = va_arg(ap, const char *);
00609    newval = va_arg(ap, const char *);
00610    if (!newparam || !newval) {
00611       ast_log(LOG_WARNING,
00612             "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
00613       if (pgsqlConn) {
00614          PQfinish(pgsqlConn);
00615          pgsqlConn = NULL;
00616       }
00617       release_table(table);
00618       return -1;
00619    }
00620 
00621    /* Check that the column exists in the table */
00622    AST_LIST_TRAVERSE(&table->columns, column, list) {
00623       if (strcmp(column->name, newparam) == 0) {
00624          break;
00625       }
00626    }
00627 
00628    if (!column) {
00629       ast_log(LOG_ERROR, "PostgreSQL RealTime: Updating on column '%s', but that column does not exist within the table '%s'!\n", newparam, tablename);
00630       release_table(table);
00631       return -1;
00632    }
00633 
00634    /* Create the first part of the query using the first parameter/value pairs we just extracted
00635       If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
00636 
00637    ESCAPE_STRING(escapebuf, newval);
00638    if (pgresult) {
00639       ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00640       va_end(ap);
00641       release_table(table);
00642       return -1;
00643    }
00644    ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, newparam, ast_str_buffer(escapebuf));
00645 
00646    while ((newparam = va_arg(ap, const char *))) {
00647       newval = va_arg(ap, const char *);
00648 
00649       if (!find_column(table, newparam)) {
00650          ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename);
00651          continue;
00652       }
00653 
00654       ESCAPE_STRING(escapebuf, newval);
00655       if (pgresult) {
00656          ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00657          va_end(ap);
00658          release_table(table);
00659          return -1;
00660       }
00661 
00662       ast_str_append(&sql, 0, ", %s = '%s'", newparam, ast_str_buffer(escapebuf));
00663    }
00664    va_end(ap);
00665    release_table(table);
00666 
00667    ESCAPE_STRING(escapebuf, lookup);
00668    if (pgresult) {
00669       ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", lookup);
00670       va_end(ap);
00671       return -1;
00672    }
00673 
00674    ast_str_append(&sql, 0, " WHERE %s = '%s'", keyfield, ast_str_buffer(escapebuf));
00675 
00676    ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
00677 
00678    /* We now have our complete statement; Lets connect to the server and execute it. */
00679    ast_mutex_lock(&pgsql_lock);
00680    if (!pgsql_reconnect(database)) {
00681       ast_mutex_unlock(&pgsql_lock);
00682       return -1;
00683    }
00684 
00685    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
00686       ast_log(LOG_WARNING,
00687             "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
00688       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00689       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
00690       ast_mutex_unlock(&pgsql_lock);
00691       return -1;
00692    } else {
00693       ExecStatusType result_status = PQresultStatus(result);
00694       if (result_status != PGRES_COMMAND_OK
00695          && result_status != PGRES_TUPLES_OK
00696          && result_status != PGRES_NONFATAL_ERROR) {
00697          ast_log(LOG_WARNING,
00698                "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
00699          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00700          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
00701                   PQresultErrorMessage(result), PQresStatus(result_status));
00702          ast_mutex_unlock(&pgsql_lock);
00703          return -1;
00704       }
00705    }
00706 
00707    numrows = atoi(PQcmdTuples(result));
00708    ast_mutex_unlock(&pgsql_lock);
00709 
00710    ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, tablename);
00711 
00712    /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
00713     * An integer greater than zero indicates the number of rows affected
00714     * Zero indicates that no records were updated
00715     * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
00716     */
00717 
00718    if (numrows >= 0)
00719       return (int) numrows;
00720 
00721    return -1;
00722 }
00723 
00724 static int update2_pgsql(const char *database, const char *tablename, va_list ap)
00725 {
00726    PGresult *result = NULL;
00727    int numrows = 0, pgresult, first = 1;
00728    struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 16);
00729    const char *newparam, *newval;
00730    struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
00731    struct ast_str *where = ast_str_thread_get(&where_buf, 100);
00732    struct tables *table;
00733 
00734    if (!tablename) {
00735       ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
00736       return -1;
00737    }
00738 
00739    if (!escapebuf || !sql || !where) {
00740       /* Memory error, already handled */
00741       return -1;
00742    }
00743 
00744    if (!(table = find_table(tablename))) {
00745       ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
00746       return -1;
00747    }
00748 
00749    ast_str_set(&sql, 0, "UPDATE %s SET ", tablename);
00750    ast_str_set(&where, 0, "WHERE");
00751 
00752    while ((newparam = va_arg(ap, const char *))) {
00753       if (!find_column(table, newparam)) {
00754          ast_log(LOG_ERROR, "Attempted to update based on criteria column '%s' (%s@%s), but that column does not exist!\n", newparam, tablename, database);
00755          release_table(table);
00756          return -1;
00757       }
00758 
00759       newval = va_arg(ap, const char *);
00760       ESCAPE_STRING(escapebuf, newval);
00761       if (pgresult) {
00762          ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00763          release_table(table);
00764          ast_free(sql);
00765          return -1;
00766       }
00767       ast_str_append(&where, 0, "%s %s='%s'", first ? "" : " AND", newparam, ast_str_buffer(escapebuf));
00768       first = 0;
00769    }
00770 
00771    if (first) {
00772       ast_log(LOG_WARNING,
00773             "PostgreSQL RealTime: Realtime update requires at least 1 parameter and 1 value to search on.\n");
00774       if (pgsqlConn) {
00775          PQfinish(pgsqlConn);
00776          pgsqlConn = NULL;
00777       }
00778       release_table(table);
00779       return -1;
00780    }
00781 
00782    /* Now retrieve the columns to update */
00783    first = 1;
00784    while ((newparam = va_arg(ap, const char *))) {
00785       newval = va_arg(ap, const char *);
00786 
00787       /* If the column is not within the table, then skip it */
00788       if (!find_column(table, newparam)) {
00789          ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s@%s', but column does not exist!\n", newparam, tablename, database);
00790          continue;
00791       }
00792 
00793       ESCAPE_STRING(escapebuf, newval);
00794       if (pgresult) {
00795          ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00796          release_table(table);
00797          ast_free(sql);
00798          return -1;
00799       }
00800 
00801       ast_str_append(&sql, 0, "%s %s='%s'", first ? "" : ",", newparam, ast_str_buffer(escapebuf));
00802    }
00803    release_table(table);
00804 
00805    ast_str_append(&sql, 0, " %s", ast_str_buffer(where));
00806 
00807    ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
00808 
00809    /* We now have our complete statement; connect to the server and execute it. */
00810    ast_mutex_lock(&pgsql_lock);
00811    if (!pgsql_reconnect(database)) {
00812       ast_mutex_unlock(&pgsql_lock);
00813       return -1;
00814    }
00815 
00816    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
00817       ast_log(LOG_WARNING,
00818             "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
00819       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00820       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
00821       ast_mutex_unlock(&pgsql_lock);
00822       return -1;
00823    } else {
00824       ExecStatusType result_status = PQresultStatus(result);
00825       if (result_status != PGRES_COMMAND_OK
00826          && result_status != PGRES_TUPLES_OK
00827          && result_status != PGRES_NONFATAL_ERROR) {
00828          ast_log(LOG_WARNING,
00829                "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
00830          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00831          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
00832                   PQresultErrorMessage(result), PQresStatus(result_status));
00833          ast_mutex_unlock(&pgsql_lock);
00834          return -1;
00835       }
00836    }
00837 
00838    numrows = atoi(PQcmdTuples(result));
00839    ast_mutex_unlock(&pgsql_lock);
00840 
00841    ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, tablename);
00842 
00843    /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
00844     * An integer greater than zero indicates the number of rows affected
00845     * Zero indicates that no records were updated
00846     * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
00847     */
00848 
00849    if (numrows >= 0) {
00850       return (int) numrows;
00851    }
00852 
00853    return -1;
00854 }
00855 
00856 static int store_pgsql(const char *database, const char *table, va_list ap)
00857 {
00858    PGresult *result = NULL;
00859    Oid insertid;
00860    struct ast_str *buf = ast_str_thread_get(&escapebuf_buf, 256);
00861    struct ast_str *sql1 = ast_str_thread_get(&sql_buf, 256);
00862    struct ast_str *sql2 = ast_str_thread_get(&where_buf, 256);
00863    int pgresult;
00864    const char *newparam, *newval;
00865 
00866    if (!table) {
00867       ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
00868       return -1;
00869    }
00870 
00871    /* Get the first parameter and first value in our list of passed paramater/value pairs */
00872    newparam = va_arg(ap, const char *);
00873    newval = va_arg(ap, const char *);
00874    if (!newparam || !newval) {
00875       ast_log(LOG_WARNING,
00876             "PostgreSQL RealTime: Realtime storage requires at least 1 parameter and 1 value to store.\n");
00877       if (pgsqlConn) {
00878          PQfinish(pgsqlConn);
00879          pgsqlConn = NULL;
00880       }
00881       return -1;
00882    }
00883 
00884    /* Must connect to the server before anything else, as the escape function requires the connection handle.. */
00885    ast_mutex_lock(&pgsql_lock);
00886    if (!pgsql_reconnect(database)) {
00887       ast_mutex_unlock(&pgsql_lock);
00888       return -1;
00889    }
00890 
00891    /* Create the first part of the query using the first parameter/value pairs we just extracted
00892       If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
00893    ESCAPE_STRING(buf, newparam);
00894    ast_str_set(&sql1, 0, "INSERT INTO %s (%s", table, ast_str_buffer(buf));
00895    ESCAPE_STRING(buf, newval);
00896    ast_str_set(&sql2, 0, ") VALUES ('%s'", ast_str_buffer(buf));
00897    while ((newparam = va_arg(ap, const char *))) {
00898       newval = va_arg(ap, const char *);
00899       ESCAPE_STRING(buf, newparam);
00900       ast_str_append(&sql1, 0, ", %s", ast_str_buffer(buf));
00901       ESCAPE_STRING(buf, newval);
00902       ast_str_append(&sql2, 0, ", '%s'", ast_str_buffer(buf));
00903    }
00904    va_end(ap);
00905    ast_str_append(&sql1, 0, "%s)", ast_str_buffer(sql2));
00906 
00907    ast_debug(1, "PostgreSQL RealTime: Insert SQL: %s\n", ast_str_buffer(sql1));
00908 
00909    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql1)))) {
00910       ast_log(LOG_WARNING,
00911             "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
00912       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql1));
00913       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
00914       ast_mutex_unlock(&pgsql_lock);
00915       return -1;
00916    } else {
00917       ExecStatusType result_status = PQresultStatus(result);
00918       if (result_status != PGRES_COMMAND_OK
00919          && result_status != PGRES_TUPLES_OK
00920          && result_status != PGRES_NONFATAL_ERROR) {
00921          ast_log(LOG_WARNING,
00922                "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
00923          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql1));
00924          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
00925                   PQresultErrorMessage(result), PQresStatus(result_status));
00926          ast_mutex_unlock(&pgsql_lock);
00927          return -1;
00928       }
00929    }
00930 
00931    insertid = PQoidValue(result);
00932    ast_mutex_unlock(&pgsql_lock);
00933 
00934    ast_debug(1, "PostgreSQL RealTime: row inserted on table: %s, id: %u\n", table, insertid);
00935 
00936    /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
00937     * An integer greater than zero indicates the number of rows affected
00938     * Zero indicates that no records were updated
00939     * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
00940     */
00941 
00942    if (insertid >= 0)
00943       return (int) insertid;
00944 
00945    return -1;
00946 }
00947 
00948 static int destroy_pgsql(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
00949 {
00950    PGresult *result = NULL;
00951    int numrows = 0;
00952    int pgresult;
00953    struct ast_str *sql = ast_str_thread_get(&sql_buf, 256);
00954    struct ast_str *buf1 = ast_str_thread_get(&where_buf, 60), *buf2 = ast_str_thread_get(&escapebuf_buf, 60);
00955    const char *newparam, *newval;
00956 
00957    if (!table) {
00958       ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
00959       return -1;
00960    }
00961 
00962    /* Get the first parameter and first value in our list of passed paramater/value pairs */
00963    /*newparam = va_arg(ap, const char *);
00964    newval = va_arg(ap, const char *);
00965    if (!newparam || !newval) {*/
00966    if (ast_strlen_zero(keyfield) || ast_strlen_zero(lookup))  {
00967       ast_log(LOG_WARNING,
00968             "PostgreSQL RealTime: Realtime destroy requires at least 1 parameter and 1 value to search on.\n");
00969       if (pgsqlConn) {
00970          PQfinish(pgsqlConn);
00971          pgsqlConn = NULL;
00972       };
00973       return -1;
00974    }
00975 
00976    /* Must connect to the server before anything else, as the escape function requires the connection handle.. */
00977    ast_mutex_lock(&pgsql_lock);
00978    if (!pgsql_reconnect(database)) {
00979       ast_mutex_unlock(&pgsql_lock);
00980       return -1;
00981    }
00982 
00983 
00984    /* Create the first part of the query using the first parameter/value pairs we just extracted
00985       If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
00986 
00987    ESCAPE_STRING(buf1, keyfield);
00988    ESCAPE_STRING(buf2, lookup);
00989    ast_str_set(&sql, 0, "DELETE FROM %s WHERE %s = '%s'", table, ast_str_buffer(buf1), ast_str_buffer(buf2));
00990    while ((newparam = va_arg(ap, const char *))) {
00991       newval = va_arg(ap, const char *);
00992       ESCAPE_STRING(buf1, newparam);
00993       ESCAPE_STRING(buf2, newval);
00994       ast_str_append(&sql, 0, " AND %s = '%s'", ast_str_buffer(buf1), ast_str_buffer(buf2));
00995    }
00996    va_end(ap);
00997 
00998    ast_debug(1, "PostgreSQL RealTime: Delete SQL: %s\n", ast_str_buffer(sql));
00999 
01000    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
01001       ast_log(LOG_WARNING,
01002             "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
01003       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
01004       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
01005       ast_mutex_unlock(&pgsql_lock);
01006       return -1;
01007    } else {
01008       ExecStatusType result_status = PQresultStatus(result);
01009       if (result_status != PGRES_COMMAND_OK
01010          && result_status != PGRES_TUPLES_OK
01011          && result_status != PGRES_NONFATAL_ERROR) {
01012          ast_log(LOG_WARNING,
01013                "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
01014          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
01015          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
01016                   PQresultErrorMessage(result), PQresStatus(result_status));
01017          ast_mutex_unlock(&pgsql_lock);
01018          return -1;
01019       }
01020    }
01021 
01022    numrows = atoi(PQcmdTuples(result));
01023    ast_mutex_unlock(&pgsql_lock);
01024 
01025    ast_debug(1, "PostgreSQL RealTime: Deleted %d rows on table: %s\n", numrows, table);
01026 
01027    /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
01028     * An integer greater than zero indicates the number of rows affected
01029     * Zero indicates that no records were updated
01030     * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
01031     */
01032 
01033    if (numrows >= 0)
01034       return (int) numrows;
01035 
01036    return -1;
01037 }
01038 
01039 
01040 static struct ast_config *config_pgsql(const char *database, const char *table,
01041                               const char *file, struct ast_config *cfg,
01042                               struct ast_flags flags, const char *suggested_incl, const char *who_asked)
01043 {
01044    PGresult *result = NULL;
01045    long num_rows;
01046    struct ast_variable *new_v;
01047    struct ast_category *cur_cat = NULL;
01048    struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
01049    char last[80] = "";
01050    int last_cat_metric = 0;
01051 
01052    last[0] = '\0';
01053 
01054    if (!file || !strcmp(file, RES_CONFIG_PGSQL_CONF)) {
01055       ast_log(LOG_WARNING, "PostgreSQL RealTime: Cannot configure myself.\n");
01056       return NULL;
01057    }
01058 
01059    ast_str_set(&sql, 0, "SELECT category, var_name, var_val, cat_metric FROM %s "
01060          "WHERE filename='%s' and commented=0"
01061          "ORDER BY cat_metric DESC, var_metric ASC, category, var_name ", table, file);
01062 
01063    ast_debug(1, "PostgreSQL RealTime: Static SQL: %s\n", ast_str_buffer(sql));
01064 
01065    /* We now have our complete statement; Lets connect to the server and execute it. */
01066    ast_mutex_lock(&pgsql_lock);
01067    if (!pgsql_reconnect(database)) {
01068       ast_mutex_unlock(&pgsql_lock);
01069       return NULL;
01070    }
01071 
01072    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
01073       ast_log(LOG_WARNING,
01074             "PostgreSQL RealTime: Failed to query '%s@%s'. Check debug for more info.\n", table, database);
01075       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
01076       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
01077       ast_mutex_unlock(&pgsql_lock);
01078       return NULL;
01079    } else {
01080       ExecStatusType result_status = PQresultStatus(result);
01081       if (result_status != PGRES_COMMAND_OK
01082          && result_status != PGRES_TUPLES_OK
01083          && result_status != PGRES_NONFATAL_ERROR) {
01084          ast_log(LOG_WARNING,
01085                "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
01086          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
01087          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
01088                   PQresultErrorMessage(result), PQresStatus(result_status));
01089          ast_mutex_unlock(&pgsql_lock);
01090          return NULL;
01091       }
01092    }
01093 
01094    if ((num_rows = PQntuples(result)) > 0) {
01095       int rowIndex = 0;
01096 
01097       ast_debug(1, "PostgreSQL RealTime: Found %ld rows.\n", num_rows);
01098 
01099       for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
01100          char *field_category = PQgetvalue(result, rowIndex, 0);
01101          char *field_var_name = PQgetvalue(result, rowIndex, 1);
01102          char *field_var_val = PQgetvalue(result, rowIndex, 2);
01103          char *field_cat_metric = PQgetvalue(result, rowIndex, 3);
01104          if (!strcmp(field_var_name, "#include")) {
01105             if (!ast_config_internal_load(field_var_val, cfg, flags, "", who_asked)) {
01106                PQclear(result);
01107                ast_mutex_unlock(&pgsql_lock);
01108                return NULL;
01109             }
01110             continue;
01111          }
01112 
01113          if (strcmp(last, field_category) || last_cat_metric != atoi(field_cat_metric)) {
01114             cur_cat = ast_category_new(field_category, "", 99999);
01115             if (!cur_cat)
01116                break;
01117             strcpy(last, field_category);
01118             last_cat_metric = atoi(field_cat_metric);
01119             ast_category_append(cfg, cur_cat);
01120          }
01121          new_v = ast_variable_new(field_var_name, field_var_val, "");
01122          ast_variable_append(cur_cat, new_v);
01123       }
01124    } else {
01125       ast_log(LOG_WARNING,
01126             "PostgreSQL RealTime: Could not find config '%s' in database.\n", file);
01127    }
01128 
01129    PQclear(result);
01130    ast_mutex_unlock(&pgsql_lock);
01131 
01132    return cfg;
01133 }
01134 
01135 static int require_pgsql(const char *database, const char *tablename, va_list ap)
01136 {
01137    struct columns *column;
01138    struct tables *table = find_table(tablename);
01139    char *elm;
01140    int type, size, res = 0;
01141 
01142    if (!table) {
01143       ast_log(LOG_WARNING, "Table %s not found in database.  This table should exist if you're using realtime.\n", tablename);
01144       return -1;
01145    }
01146 
01147    while ((elm = va_arg(ap, char *))) {
01148       type = va_arg(ap, require_type);
01149       size = va_arg(ap, int);
01150       AST_LIST_TRAVERSE(&table->columns, column, list) {
01151          if (strcmp(column->name, elm) == 0) {
01152             /* Char can hold anything, as long as it is large enough */
01153             if ((strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0 || strcmp(column->type, "bpchar") == 0)) {
01154                if ((size > column->len) && column->len != -1) {
01155                   ast_log(LOG_WARNING, "Column '%s' should be at least %d long, but is only %d long.\n", column->name, size, column->len);
01156                   res = -1;
01157                }
01158             } else if (strncmp(column->type, "int", 3) == 0) {
01159                int typesize = atoi(column->type + 3);
01160                /* Integers can hold only other integers */
01161                if ((type == RQ_INTEGER8 || type == RQ_UINTEGER8 ||
01162                   type == RQ_INTEGER4 || type == RQ_UINTEGER4 ||
01163                   type == RQ_INTEGER3 || type == RQ_UINTEGER3 ||
01164                   type == RQ_UINTEGER2) && typesize == 2) {
01165                   ast_log(LOG_WARNING, "Column '%s' may not be large enough for the required data length: %d\n", column->name, size);
01166                   res = -1;
01167                } else if ((type == RQ_INTEGER8 || type == RQ_UINTEGER8 ||
01168                   type == RQ_UINTEGER4) && typesize == 4) {
01169                   ast_log(LOG_WARNING, "Column '%s' may not be large enough for the required data length: %d\n", column->name, size);
01170                   res = -1;
01171                } else if (type == RQ_CHAR || type == RQ_DATETIME || type == RQ_FLOAT || type == RQ_DATE) {
01172                   ast_log(LOG_WARNING, "Column '%s' is of the incorrect type: (need %s(%d) but saw %s)\n",
01173                      column->name,
01174                         type == RQ_CHAR ? "char" :
01175                         type == RQ_DATETIME ? "datetime" :
01176                         type == RQ_DATE ? "date" :
01177                         type == RQ_FLOAT ? "float" :
01178                         "a rather stiff drink ",
01179                      size, column->type);
01180                   res = -1;
01181                }
01182             } else if (strncmp(column->type, "float", 5) == 0) {
01183                if (!ast_rq_is_int(type) && type != RQ_FLOAT) {
01184                   ast_log(LOG_WARNING, "Column %s cannot be a %s\n", column->name, column->type);
01185                   res = -1;
01186                }
01187             } else if (strncmp(column->type, "timestamp", 9) == 0) {
01188                if (type != RQ_DATETIME && type != RQ_DATE) {
01189                   ast_log(LOG_WARNING, "Column %s cannot be a %s\n", column->name, column->type);
01190                   res = -1;
01191                }
01192             } else { /* There are other types that no module implements yet */
01193                ast_log(LOG_WARNING, "Possibly unsupported column type '%s' on column '%s'\n", column->type, column->name);
01194                res = -1;
01195             }
01196             break;
01197          }
01198       }
01199 
01200       if (!column) {
01201          if (requirements == RQ_WARN) {
01202             ast_log(LOG_WARNING, "Table %s requires a column '%s' of size '%d', but no such column exists.\n", tablename, elm, size);
01203          } else {
01204             struct ast_str *sql = ast_str_create(100);
01205             char fieldtype[15];
01206             PGresult *result;
01207 
01208             if (requirements == RQ_CREATECHAR || type == RQ_CHAR) {
01209                /* Size is minimum length; make it at least 50% greater,
01210                 * just to be sure, because PostgreSQL doesn't support
01211                 * resizing columns. */
01212                snprintf(fieldtype, sizeof(fieldtype), "CHAR(%d)",
01213                   size < 15 ? size * 2 :
01214                   (size * 3 / 2 > 255) ? 255 : size * 3 / 2);
01215             } else if (type == RQ_INTEGER1 || type == RQ_UINTEGER1 || type == RQ_INTEGER2) {
01216                snprintf(fieldtype, sizeof(fieldtype), "INT2");
01217             } else if (type == RQ_UINTEGER2 || type == RQ_INTEGER3 || type == RQ_UINTEGER3 || type == RQ_INTEGER4) {
01218                snprintf(fieldtype, sizeof(fieldtype), "INT4");
01219             } else if (type == RQ_UINTEGER4 || type == RQ_INTEGER8) {
01220                snprintf(fieldtype, sizeof(fieldtype), "INT8");
01221             } else if (type == RQ_UINTEGER8) {
01222                /* No such type on PostgreSQL */
01223                snprintf(fieldtype, sizeof(fieldtype), "CHAR(20)");
01224             } else if (type == RQ_FLOAT) {
01225                snprintf(fieldtype, sizeof(fieldtype), "FLOAT8");
01226             } else if (type == RQ_DATE) {
01227                snprintf(fieldtype, sizeof(fieldtype), "DATE");
01228             } else if (type == RQ_DATETIME) {
01229                snprintf(fieldtype, sizeof(fieldtype), "TIMESTAMP");
01230             } else {
01231                ast_log(LOG_ERROR, "Unrecognized request type %d\n", type);
01232                ast_free(sql);
01233                continue;
01234             }
01235             ast_str_set(&sql, 0, "ALTER TABLE %s ADD COLUMN %s %s", tablename, elm, fieldtype);
01236             ast_debug(1, "About to lock pgsql_lock (running alter on table '%s' to add column '%s')\n", tablename, elm);
01237 
01238             ast_mutex_lock(&pgsql_lock);
01239             if (!pgsql_reconnect(database)) {
01240                ast_mutex_unlock(&pgsql_lock);
01241                ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
01242                ast_free(sql);
01243                continue;
01244             }
01245 
01246             ast_debug(1, "About to run ALTER query on table '%s' to add column '%s'\n", tablename, elm);
01247             result = PQexec(pgsqlConn, ast_str_buffer(sql));
01248             ast_debug(1, "Finished running ALTER query on table '%s'\n", tablename);
01249             if (PQresultStatus(result) != PGRES_COMMAND_OK) {
01250                ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
01251             }
01252             PQclear(result);
01253             ast_mutex_unlock(&pgsql_lock);
01254 
01255             ast_free(sql);
01256          }
01257       }
01258    }
01259    release_table(table);
01260    return res;
01261 }
01262 
01263 static int unload_pgsql(const char *database, const char *tablename)
01264 {
01265    struct tables *cur;
01266    ast_debug(2, "About to lock table cache list\n");
01267    AST_LIST_LOCK(&psql_tables);
01268    ast_debug(2, "About to traverse table cache list\n");
01269    AST_LIST_TRAVERSE_SAFE_BEGIN(&psql_tables, cur, list) {
01270       if (strcmp(cur->name, tablename) == 0) {
01271          ast_debug(2, "About to remove matching cache entry\n");
01272          AST_LIST_REMOVE_CURRENT(list);
01273          ast_debug(2, "About to destroy matching cache entry\n");
01274          destroy_table(cur);
01275          ast_debug(1, "Cache entry '%s@%s' destroyed\n", tablename, database);
01276          break;
01277       }
01278    }
01279    AST_LIST_TRAVERSE_SAFE_END
01280    AST_LIST_UNLOCK(&psql_tables);
01281    ast_debug(2, "About to return\n");
01282    return cur ? 0 : -1;
01283 }
01284 
01285 static struct ast_config_engine pgsql_engine = {
01286    .name = "pgsql",
01287    .load_func = config_pgsql,
01288    .realtime_func = realtime_pgsql,
01289    .realtime_multi_func = realtime_multi_pgsql,
01290    .store_func = store_pgsql,
01291    .destroy_func = destroy_pgsql,
01292    .update_func = update_pgsql,
01293    .update2_func = update2_pgsql,
01294    .require_func = require_pgsql,
01295    .unload_func = unload_pgsql,
01296 };
01297 
01298 static int load_module(void)
01299 {
01300    if(!parse_config(0))
01301       return AST_MODULE_LOAD_DECLINE;
01302 
01303    ast_config_engine_register(&pgsql_engine);
01304    ast_verb(1, "PostgreSQL RealTime driver loaded.\n");
01305    ast_cli_register_multiple(cli_realtime, ARRAY_LEN(cli_realtime));
01306 
01307    return 0;
01308 }
01309 
01310 static int unload_module(void)
01311 {
01312    struct tables *table;
01313    /* Acquire control before doing anything to the module itself. */
01314    ast_mutex_lock(&pgsql_lock);
01315 
01316    if (pgsqlConn) {
01317       PQfinish(pgsqlConn);
01318       pgsqlConn = NULL;
01319    }
01320    ast_cli_unregister_multiple(cli_realtime, ARRAY_LEN(cli_realtime));
01321    ast_config_engine_deregister(&pgsql_engine);
01322    ast_verb(1, "PostgreSQL RealTime unloaded.\n");
01323 
01324    /* Destroy cached table info */
01325    AST_LIST_LOCK(&psql_tables);
01326    while ((table = AST_LIST_REMOVE_HEAD(&psql_tables, list))) {
01327       destroy_table(table);
01328    }
01329    AST_LIST_UNLOCK(&psql_tables);
01330 
01331    /* Unlock so something else can destroy the lock. */
01332    ast_mutex_unlock(&pgsql_lock);
01333 
01334    return 0;
01335 }
01336 
01337 static int reload(void)
01338 {
01339    parse_config(1);
01340 
01341    return 0;
01342 }
01343 
01344 static int parse_config(int is_reload)
01345 {
01346    struct ast_config *config;
01347    const char *s;
01348    struct ast_flags config_flags = { is_reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
01349 
01350    config = ast_config_load(RES_CONFIG_PGSQL_CONF, config_flags);
01351    if (config == CONFIG_STATUS_FILEUNCHANGED) {
01352       return 0;
01353    }
01354 
01355    if (config == CONFIG_STATUS_FILEMISSING || config == CONFIG_STATUS_FILEINVALID) {
01356       ast_log(LOG_WARNING, "Unable to load config %s\n", RES_CONFIG_PGSQL_CONF);
01357       return 0;
01358    }
01359 
01360    ast_mutex_lock(&pgsql_lock);
01361 
01362    if (pgsqlConn) {
01363       PQfinish(pgsqlConn);
01364       pgsqlConn = NULL;
01365    }
01366 
01367    if (!(s = ast_variable_retrieve(config, "general", "dbuser"))) {
01368       ast_log(LOG_WARNING,
01369             "PostgreSQL RealTime: No database user found, using 'asterisk' as default.\n");
01370       strcpy(dbuser, "asterisk");
01371    } else {
01372       ast_copy_string(dbuser, s, sizeof(dbuser));
01373    }
01374 
01375    if (!(s = ast_variable_retrieve(config, "general", "dbpass"))) {
01376       ast_log(LOG_WARNING,
01377             "PostgreSQL RealTime: No database password found, using 'asterisk' as default.\n");
01378       strcpy(dbpass, "asterisk");
01379    } else {
01380       ast_copy_string(dbpass, s, sizeof(dbpass));
01381    }
01382 
01383    if (!(s = ast_variable_retrieve(config, "general", "dbhost"))) {
01384       ast_log(LOG_WARNING,
01385             "PostgreSQL RealTime: No database host found, using localhost via socket.\n");
01386       dbhost[0] = '\0';
01387    } else {
01388       ast_copy_string(dbhost, s, sizeof(dbhost));
01389    }
01390 
01391    if (!(s = ast_variable_retrieve(config, "general", "dbname"))) {
01392       ast_log(LOG_WARNING,
01393             "PostgreSQL RealTime: No database name found, using 'asterisk' as default.\n");
01394       strcpy(dbname, "asterisk");
01395    } else {
01396       ast_copy_string(dbname, s, sizeof(dbname));
01397    }
01398 
01399    if (!(s = ast_variable_retrieve(config, "general", "dbport"))) {
01400       ast_log(LOG_WARNING,
01401             "PostgreSQL RealTime: No database port found, using 5432 as default.\n");
01402       dbport = 5432;
01403    } else {
01404       dbport = atoi(s);
01405    }
01406 
01407    if (!ast_strlen_zero(dbhost)) {
01408       /* No socket needed */
01409    } else if (!(s = ast_variable_retrieve(config, "general", "dbsock"))) {
01410       ast_log(LOG_WARNING,
01411             "PostgreSQL RealTime: No database socket found, using '/tmp/.s.PGSQL.%d' as default.\n", dbport);
01412       strcpy(dbsock, "/tmp");
01413    } else {
01414       ast_copy_string(dbsock, s, sizeof(dbsock));
01415    }
01416 
01417    if (!(s = ast_variable_retrieve(config, "general", "requirements"))) {
01418       ast_log(LOG_WARNING,
01419             "PostgreSQL RealTime: no requirements setting found, using 'warn' as default.\n");
01420       requirements = RQ_WARN;
01421    } else if (!strcasecmp(s, "createclose")) {
01422       requirements = RQ_CREATECLOSE;
01423    } else if (!strcasecmp(s, "createchar")) {
01424       requirements = RQ_CREATECHAR;
01425    }
01426 
01427    ast_config_destroy(config);
01428 
01429    if (option_debug) {
01430       if (!ast_strlen_zero(dbhost)) {
01431          ast_debug(1, "PostgreSQL RealTime Host: %s\n", dbhost);
01432          ast_debug(1, "PostgreSQL RealTime Port: %i\n", dbport);
01433       } else {
01434          ast_debug(1, "PostgreSQL RealTime Socket: %s\n", dbsock);
01435       }
01436       ast_debug(1, "PostgreSQL RealTime User: %s\n", dbuser);
01437       ast_debug(1, "PostgreSQL RealTime Password: %s\n", dbpass);
01438       ast_debug(1, "PostgreSQL RealTime DBName: %s\n", dbname);
01439    }
01440 
01441    if (!pgsql_reconnect(NULL)) {
01442       ast_log(LOG_WARNING,
01443             "PostgreSQL RealTime: Couldn't establish connection. Check debug.\n");
01444       ast_debug(1, "PostgreSQL RealTime: Cannot Connect: %s\n", PQerrorMessage(pgsqlConn));
01445    }
01446 
01447    ast_verb(2, "PostgreSQL RealTime reloaded.\n");
01448 
01449    /* Done reloading. Release lock so others can now use driver. */
01450    ast_mutex_unlock(&pgsql_lock);
01451 
01452    return 1;
01453 }
01454 
01455 static int pgsql_reconnect(const char *database)
01456 {
01457    char my_database[50];
01458 
01459    ast_copy_string(my_database, S_OR(database, dbname), sizeof(my_database));
01460 
01461    /* mutex lock should have been locked before calling this function. */
01462 
01463    if (pgsqlConn && PQstatus(pgsqlConn) != CONNECTION_OK) {
01464       PQfinish(pgsqlConn);
01465       pgsqlConn = NULL;
01466    }
01467 
01468    /* DB password can legitimately be 0-length */
01469    if ((!pgsqlConn) && (!ast_strlen_zero(dbhost) || !ast_strlen_zero(dbsock)) && !ast_strlen_zero(dbuser) && !ast_strlen_zero(my_database)) {
01470       struct ast_str *connInfo = ast_str_create(32);
01471 
01472       ast_str_set(&connInfo, 0, "host=%s port=%d dbname=%s user=%s",
01473          S_OR(dbhost, dbsock), dbport, my_database, dbuser);
01474       if (!ast_strlen_zero(dbpass))
01475          ast_str_append(&connInfo, 0, " password=%s", dbpass);
01476 
01477       ast_debug(1, "%u connInfo=%s\n", (unsigned int)ast_str_size(connInfo), ast_str_buffer(connInfo));
01478       pgsqlConn = PQconnectdb(ast_str_buffer(connInfo));
01479       ast_debug(1, "%u connInfo=%s\n", (unsigned int)ast_str_size(connInfo), ast_str_buffer(connInfo));
01480       ast_free(connInfo);
01481       connInfo = NULL;
01482 
01483       ast_debug(1, "pgsqlConn=%p\n", pgsqlConn);
01484       if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) {
01485          ast_debug(1, "PostgreSQL RealTime: Successfully connected to database.\n");
01486          connect_time = time(NULL);
01487          version = PQserverVersion(pgsqlConn);
01488          return 1;
01489       } else {
01490          ast_log(LOG_ERROR,
01491                "PostgreSQL RealTime: Failed to connect database %s on %s: %s\n",
01492                dbname, dbhost, PQresultErrorMessage(NULL));
01493          return 0;
01494       }
01495    } else {
01496       ast_debug(1, "PostgreSQL RealTime: One or more of the parameters in the config does not pass our validity checks.\n");
01497       return 1;
01498    }
01499 }
01500 
01501 static char *handle_cli_realtime_pgsql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01502 {
01503    struct tables *cur;
01504    int l, which;
01505    char *ret = NULL;
01506 
01507    switch (cmd) {
01508    case CLI_INIT:
01509       e->command = "realtime show pgsql cache";
01510       e->usage =
01511          "Usage: realtime show pgsql cache [<table>]\n"
01512          "       Shows table cache for the PostgreSQL RealTime driver\n";
01513       return NULL;
01514    case CLI_GENERATE:
01515       if (a->argc != 4) {
01516          return NULL;
01517       }
01518       l = strlen(a->word);
01519       which = 0;
01520       AST_LIST_LOCK(&psql_tables);
01521       AST_LIST_TRAVERSE(&psql_tables, cur, list) {
01522          if (!strncasecmp(a->word, cur->name, l) && ++which > a->n) {
01523             ret = ast_strdup(cur->name);
01524             break;
01525          }
01526       }
01527       AST_LIST_UNLOCK(&psql_tables);
01528       return ret;
01529    }
01530 
01531    if (a->argc == 4) {
01532       /* List of tables */
01533       AST_LIST_LOCK(&psql_tables);
01534       AST_LIST_TRAVERSE(&psql_tables, cur, list) {
01535          ast_cli(a->fd, "%s\n", cur->name);
01536       }
01537       AST_LIST_UNLOCK(&psql_tables);
01538    } else if (a->argc == 5) {
01539       /* List of columns */
01540       if ((cur = find_table(a->argv[4]))) {
01541          struct columns *col;
01542          ast_cli(a->fd, "Columns for Table Cache '%s':\n", a->argv[4]);
01543          ast_cli(a->fd, "%-20.20s %-20.20s %-3.3s %-8.8s\n", "Name", "Type", "Len", "Nullable");
01544          AST_LIST_TRAVERSE(&cur->columns, col, list) {
01545             ast_cli(a->fd, "%-20.20s %-20.20s %3d %-8.8s\n", col->name, col->type, col->len, col->notnull ? "NOT NULL" : "");
01546          }
01547          release_table(cur);
01548       } else {
01549          ast_cli(a->fd, "No such table '%s'\n", a->argv[4]);
01550       }
01551    }
01552    return 0;
01553 }
01554 
01555 static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01556 {
01557    char status[256], credentials[100] = "";
01558    int ctimesec = time(NULL) - connect_time;
01559 
01560    switch (cmd) {
01561    case CLI_INIT:
01562       e->command = "realtime show pgsql status";
01563       e->usage =
01564          "Usage: realtime show pgsql status\n"
01565          "       Shows connection information for the PostgreSQL RealTime driver\n";
01566       return NULL;
01567    case CLI_GENERATE:
01568       return NULL;
01569    }
01570 
01571    if (a->argc != 4)
01572       return CLI_SHOWUSAGE;
01573 
01574    if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) {
01575       if (!ast_strlen_zero(dbhost))
01576          snprintf(status, sizeof(status), "Connected to %s@%s, port %d", dbname, dbhost, dbport);
01577       else if (!ast_strlen_zero(dbsock))
01578          snprintf(status, sizeof(status), "Connected to %s on socket file %s", dbname, dbsock);
01579       else
01580          snprintf(status, sizeof(status), "Connected to %s@%s", dbname, dbhost);
01581 
01582       if (!ast_strlen_zero(dbuser))
01583          snprintf(credentials, sizeof(credentials), " with username %s", dbuser);
01584 
01585       if (ctimesec > 31536000)
01586          ast_cli(a->fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n",
01587                status, credentials, ctimesec / 31536000, (ctimesec % 31536000) / 86400,
01588                (ctimesec % 86400) / 3600, (ctimesec % 3600) / 60, ctimesec % 60);
01589       else if (ctimesec > 86400)
01590          ast_cli(a->fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n", status,
01591                credentials, ctimesec / 86400, (ctimesec % 86400) / 3600, (ctimesec % 3600) / 60,
01592                ctimesec % 60);
01593       else if (ctimesec > 3600)
01594          ast_cli(a->fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, credentials,
01595                ctimesec / 3600, (ctimesec % 3600) / 60, ctimesec % 60);
01596       else if (ctimesec > 60)
01597          ast_cli(a->fd, "%s%s for %d minutes, %d seconds.\n", status, credentials, ctimesec / 60,
01598                ctimesec % 60);
01599       else
01600          ast_cli(a->fd, "%s%s for %d seconds.\n", status, credentials, ctimesec);
01601 
01602       return CLI_SUCCESS;
01603    } else {
01604       return CLI_FAILURE;
01605    }
01606 }
01607 
01608 /* needs usecount semantics defined */
01609 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PostgreSQL RealTime Configuration Driver",
01610       .load = load_module,
01611       .unload = unload_module,
01612       .reload = reload,
01613       .load_pri = AST_MODPRI_REALTIME_DRIVER,
01614           );

Generated on Mon Jun 27 16:50:55 2011 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7