Wed Aug 18 22:33:52 2010

Asterisk developer's documentation


func_odbc.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (c) 2005, 2006 Tilghman Lesher
00005  *
00006  * Tilghman Lesher <func_odbc__200508@the-tilghman.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*!
00020  * \file
00021  *
00022  * \brief ODBC lookups
00023  *
00024  * \author Tilghman Lesher <func_odbc__200508@the-tilghman.com>
00025  *
00026  * \ingroup functions
00027  */
00028 
00029 /*** MODULEINFO
00030    <depend>unixodbc</depend>
00031    <depend>ltdl</depend>
00032    <depend>res_odbc</depend>
00033  ***/
00034 
00035 #include "asterisk.h"
00036 
00037 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 251875 $")
00038 
00039 #include "asterisk/module.h"
00040 #include "asterisk/file.h"
00041 #include "asterisk/channel.h"
00042 #include "asterisk/pbx.h"
00043 #include "asterisk/config.h"
00044 #include "asterisk/res_odbc.h"
00045 #include "asterisk/app.h"
00046 
00047 static char *config = "func_odbc.conf";
00048 
00049 enum {
00050    OPT_ESCAPECOMMAS =   (1 << 0),
00051    OPT_MULTIROW     =   (1 << 1),
00052 } odbc_option_flags;
00053 
00054 struct acf_odbc_query {
00055    AST_RWLIST_ENTRY(acf_odbc_query) list;
00056    char readhandle[5][30];
00057    char writehandle[5][30];
00058    char sql_read[2048];
00059    char sql_write[2048];
00060    unsigned int flags;
00061    int rowlimit;
00062    struct ast_custom_function *acf;
00063 };
00064 
00065 static void odbc_datastore_free(void *data);
00066 
00067 struct ast_datastore_info odbc_info = {
00068    .type = "FUNC_ODBC",
00069    .destroy = odbc_datastore_free,
00070 };
00071 
00072 /* For storing each result row */
00073 struct odbc_datastore_row {
00074    AST_LIST_ENTRY(odbc_datastore_row) list;
00075    char data[0];
00076 };
00077 
00078 /* For storing each result set */
00079 struct odbc_datastore {
00080    AST_LIST_HEAD(, odbc_datastore_row);
00081    char names[0];
00082 };
00083 
00084 AST_RWLIST_HEAD_STATIC(queries, acf_odbc_query);
00085 
00086 static int resultcount = 0;
00087 
00088 AST_THREADSTORAGE(coldata_buf);
00089 AST_THREADSTORAGE(colnames_buf);
00090 
00091 static void odbc_datastore_free(void *data)
00092 {
00093    struct odbc_datastore *result = data;
00094    struct odbc_datastore_row *row;
00095    AST_LIST_LOCK(result);
00096    while ((row = AST_LIST_REMOVE_HEAD(result, list))) {
00097       ast_free(row);
00098    }
00099    AST_LIST_UNLOCK(result);
00100    AST_LIST_HEAD_DESTROY(result);
00101    ast_free(result);
00102 }
00103 
00104 static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data)
00105 {
00106    int res;
00107    char *sql = data;
00108    SQLHSTMT stmt;
00109 
00110    res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
00111    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00112       ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
00113       return NULL;
00114    }
00115 
00116    res = SQLExecDirect(stmt, (unsigned char *)sql, SQL_NTS);
00117    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00118       ast_log(LOG_WARNING, "SQL Exec Direct failed![%s]\n", sql);
00119       SQLCloseCursor(stmt);
00120       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
00121       return NULL;
00122    }
00123 
00124    return stmt;
00125 }
00126 
00127 /*
00128  * Master control routine
00129  */
00130 static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
00131 {
00132    struct odbc_obj *obj = NULL;
00133    struct acf_odbc_query *query;
00134    char *t, varname[15];
00135    int i, dsn, bogus_chan = 0;
00136    AST_DECLARE_APP_ARGS(values,
00137       AST_APP_ARG(field)[100];
00138    );
00139    AST_DECLARE_APP_ARGS(args,
00140       AST_APP_ARG(field)[100];
00141    );
00142    SQLHSTMT stmt = NULL;
00143    SQLLEN rows=0;
00144    struct ast_str *buf = ast_str_create(16);
00145 
00146    if (!buf) {
00147       return -1;
00148    }
00149 
00150    AST_RWLIST_RDLOCK(&queries);
00151    AST_RWLIST_TRAVERSE(&queries, query, list) {
00152       if (!strcmp(query->acf->name, cmd)) {
00153          break;
00154       }
00155    }
00156 
00157    if (!query) {
00158       ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
00159       AST_RWLIST_UNLOCK(&queries);
00160       ast_free(buf);
00161       return -1;
00162    }
00163 
00164    if (!chan) {
00165       if ((chan = ast_channel_alloc(0, 0, "", "", "", "", "", 0, "Bogus/func_odbc")))
00166          bogus_chan = 1;
00167    }
00168 
00169    if (chan)
00170       ast_autoservice_start(chan);
00171 
00172    /* Parse our arguments */
00173    t = value ? ast_strdupa(value) : "";
00174 
00175    if (!s || !t) {
00176       ast_log(LOG_ERROR, "Out of memory\n");
00177       AST_RWLIST_UNLOCK(&queries);
00178       if (chan)
00179          ast_autoservice_stop(chan);
00180       if (bogus_chan)
00181          ast_channel_free(chan);
00182       ast_free(buf);
00183       return -1;
00184    }
00185 
00186    AST_STANDARD_APP_ARGS(args, s);
00187    for (i = 0; i < args.argc; i++) {
00188       snprintf(varname, sizeof(varname), "ARG%d", i + 1);
00189       pbx_builtin_pushvar_helper(chan, varname, args.field[i]);
00190    }
00191 
00192    /* Parse values, just like arguments */
00193    AST_STANDARD_APP_ARGS(values, t);
00194    for (i = 0; i < values.argc; i++) {
00195       snprintf(varname, sizeof(varname), "VAL%d", i + 1);
00196       pbx_builtin_pushvar_helper(chan, varname, values.field[i]);
00197    }
00198 
00199    /* Additionally set the value as a whole (but push an empty string if value is NULL) */
00200    pbx_builtin_pushvar_helper(chan, "VALUE", value ? value : "");
00201 
00202    do {
00203       ast_str_make_space(&buf, 2 * (strlen(query->sql_write) > ast_str_size(buf) ? strlen(query->sql_write) : ast_str_size(buf)));
00204       pbx_substitute_variables_helper(chan, query->sql_write, ast_str_buffer(buf), ast_str_size(buf) - 1);
00205       ast_str_update(buf);
00206    } while (ast_str_strlen(buf) > ast_str_size(buf) - 5);
00207 
00208    /* Restore prior values */
00209    for (i = 0; i < args.argc; i++) {
00210       snprintf(varname, sizeof(varname), "ARG%d", i + 1);
00211       pbx_builtin_setvar_helper(chan, varname, NULL);
00212    }
00213 
00214    for (i = 0; i < values.argc; i++) {
00215       snprintf(varname, sizeof(varname), "VAL%d", i + 1);
00216       pbx_builtin_setvar_helper(chan, varname, NULL);
00217    }
00218    pbx_builtin_setvar_helper(chan, "VALUE", NULL);
00219 
00220    AST_RWLIST_UNLOCK(&queries);
00221 
00222    for (dsn = 0; dsn < 5; dsn++) {
00223       if (!ast_strlen_zero(query->writehandle[dsn])) {
00224          obj = ast_odbc_request_obj(query->writehandle[dsn], 0);
00225          if (obj)
00226             stmt = ast_odbc_direct_execute(obj, generic_execute, buf->str);
00227       }
00228       if (stmt)
00229          break;
00230    }
00231 
00232    if (stmt) {
00233       /* Rows affected */
00234       SQLRowCount(stmt, &rows);
00235    }
00236 
00237    /* Output the affected rows, for all cases.  In the event of failure, we
00238     * flag this as -1 rows.  Note that this is different from 0 affected rows
00239     * which would be the case if we succeeded in our query, but the values did
00240     * not change. */
00241    snprintf(varname, sizeof(varname), "%d", (int)rows);
00242    pbx_builtin_setvar_helper(chan, "ODBCROWS", varname);
00243 
00244    if (stmt) {
00245       SQLCloseCursor(stmt);
00246       SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00247    }
00248    if (obj)
00249       ast_odbc_release_obj(obj);
00250 
00251    if (chan)
00252       ast_autoservice_stop(chan);
00253    if (bogus_chan)
00254       ast_channel_free(chan);
00255    ast_free(buf);
00256 
00257    return 0;
00258 }
00259 
00260 static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, char *buf, size_t len)
00261 {
00262    struct odbc_obj *obj = NULL;
00263    struct acf_odbc_query *query;
00264    char varname[15], rowcount[12] = "-1";
00265    struct ast_str *colnames = ast_str_thread_get(&colnames_buf, 16);
00266    int res, x, y, buflen = 0, escapecommas, rowlimit = 1, dsn, bogus_chan = 0;
00267    AST_DECLARE_APP_ARGS(args,
00268       AST_APP_ARG(field)[100];
00269    );
00270    SQLHSTMT stmt = NULL;
00271    SQLSMALLINT colcount=0;
00272    SQLLEN indicator;
00273    SQLSMALLINT collength;
00274    struct odbc_datastore *resultset = NULL;
00275    struct odbc_datastore_row *row = NULL;
00276    struct ast_str *sql = ast_str_create(16);
00277 
00278    if (!sql) {
00279       return -1;
00280    }
00281 
00282    ast_str_reset(colnames);
00283 
00284    AST_RWLIST_RDLOCK(&queries);
00285    AST_RWLIST_TRAVERSE(&queries, query, list) {
00286       if (!strcmp(query->acf->name, cmd)) {
00287          break;
00288       }
00289    }
00290 
00291    if (!query) {
00292       ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
00293       AST_RWLIST_UNLOCK(&queries);
00294       pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
00295       ast_free(sql);
00296       return -1;
00297    }
00298 
00299    if (!chan) {
00300       if ((chan = ast_channel_alloc(0, 0, "", "", "", "", "", 0, "Bogus/func_odbc")))
00301          bogus_chan = 1;
00302    }
00303 
00304    if (chan)
00305       ast_autoservice_start(chan);
00306 
00307    AST_STANDARD_APP_ARGS(args, s);
00308    for (x = 0; x < args.argc; x++) {
00309       snprintf(varname, sizeof(varname), "ARG%d", x + 1);
00310       pbx_builtin_pushvar_helper(chan, varname, args.field[x]);
00311    }
00312 
00313    do {
00314       ast_str_make_space(&sql, 2 * (strlen(query->sql_read) > ast_str_size(sql) ? strlen(query->sql_read) : ast_str_size(sql)));
00315       pbx_substitute_variables_helper(chan, query->sql_read, ast_str_buffer(sql), ast_str_size(sql) - 1);
00316       ast_str_update(sql);
00317    } while (ast_str_strlen(sql) > ast_str_size(sql) - 5);
00318 
00319    /* Restore prior values */
00320    for (x = 0; x < args.argc; x++) {
00321       snprintf(varname, sizeof(varname), "ARG%d", x + 1);
00322       pbx_builtin_setvar_helper(chan, varname, NULL);
00323    }
00324 
00325    /* Save these flags, so we can release the lock */
00326    escapecommas = ast_test_flag(query, OPT_ESCAPECOMMAS);
00327    if (ast_test_flag(query, OPT_MULTIROW)) {
00328       resultset = ast_calloc(1, sizeof(*resultset));
00329       AST_LIST_HEAD_INIT(resultset);
00330       if (query->rowlimit)
00331          rowlimit = query->rowlimit;
00332       else
00333          rowlimit = INT_MAX;
00334    }
00335    AST_RWLIST_UNLOCK(&queries);
00336 
00337    for (dsn = 0; dsn < 5; dsn++) {
00338       if (!ast_strlen_zero(query->readhandle[dsn])) {
00339          obj = ast_odbc_request_obj(query->readhandle[dsn], 0);
00340          if (obj)
00341             stmt = ast_odbc_direct_execute(obj, generic_execute, sql->str);
00342       }
00343       if (stmt)
00344          break;
00345    }
00346 
00347    if (!stmt) {
00348       ast_log(LOG_ERROR, "Unable to execute query [%s]\n", sql->str);
00349       if (obj)
00350          ast_odbc_release_obj(obj);
00351       pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
00352       if (chan)
00353          ast_autoservice_stop(chan);
00354       if (bogus_chan)
00355          ast_channel_free(chan);
00356       ast_free(sql);
00357       return -1;
00358    }
00359 
00360    res = SQLNumResultCols(stmt, &colcount);
00361    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00362       ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql->str);
00363       SQLCloseCursor(stmt);
00364       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
00365       ast_odbc_release_obj(obj);
00366       pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
00367       if (chan)
00368          ast_autoservice_stop(chan);
00369       if (bogus_chan)
00370          ast_channel_free(chan);
00371       ast_free(sql);
00372       return -1;
00373    }
00374 
00375    res = SQLFetch(stmt);
00376    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00377       int res1 = -1;
00378       if (res == SQL_NO_DATA) {
00379          ast_verb(4, "Found no rows [%s]\n", sql->str);
00380          res1 = 0;
00381          buf[0] = '\0';
00382          ast_copy_string(rowcount, "0", sizeof(rowcount));
00383       } else {
00384          ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, sql->str);
00385       }
00386       SQLCloseCursor(stmt);
00387       SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00388       ast_odbc_release_obj(obj);
00389       pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
00390       if (chan)
00391          ast_autoservice_stop(chan);
00392       if (bogus_chan)
00393          ast_channel_free(chan);
00394       ast_free(sql);
00395       return res1;
00396    }
00397 
00398    for (y = 0; y < rowlimit; y++) {
00399       buf[0] = '\0';
00400       for (x = 0; x < colcount; x++) {
00401          int i;
00402          struct ast_str *coldata = ast_str_thread_get(&coldata_buf, 16);
00403 
00404          if (y == 0) {
00405             char colname[256];
00406             SQLULEN maxcol;
00407 
00408             res = SQLDescribeCol(stmt, x + 1, (unsigned char *)colname, sizeof(colname), &collength, NULL, &maxcol, NULL, NULL);
00409             ast_debug(3, "Got collength of %d and maxcol of %d for column '%s' (offset %d)\n", (int)collength, (int)maxcol, colname, x);
00410             if (((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) || collength == 0) {
00411                snprintf(colname, sizeof(colname), "field%d", x);
00412             }
00413 
00414             if (coldata->len < maxcol + 1) {
00415                ast_str_make_space(&coldata, maxcol + 1);
00416             }
00417 
00418             if (colnames->used) {
00419                ast_str_append(&colnames, 0, ",");
00420             }
00421             ast_str_make_space(&colnames, strlen(colname) * 2 + 1 + colnames->used);
00422 
00423             /* Copy data, encoding '\' and ',' for the argument parser */
00424             for (i = 0; i < sizeof(colname); i++) {
00425                if (escapecommas && (colname[i] == '\\' || colname[i] == ',')) {
00426                   colnames->str[colnames->used++] = '\\';
00427                }
00428                colnames->str[colnames->used++] = colname[i];
00429 
00430                if (colname[i] == '\0') {
00431                   colnames->used--;
00432                   break;
00433                }
00434             }
00435 
00436             if (resultset) {
00437                void *tmp = ast_realloc(resultset, sizeof(*resultset) + colnames->used + 1);
00438                if (!tmp) {
00439                   ast_log(LOG_ERROR, "No space for a new resultset?\n");
00440                   ast_free(resultset);
00441                   SQLCloseCursor(stmt);
00442                   SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00443                   ast_odbc_release_obj(obj);
00444                   pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
00445                   if (chan)
00446                      ast_autoservice_stop(chan);
00447                   if (bogus_chan)
00448                      ast_channel_free(chan);
00449                   ast_free(sql);
00450                   return -1;
00451                }
00452                resultset = tmp;
00453                strcpy((char *)resultset + sizeof(*resultset), colnames->str);
00454             }
00455          }
00456 
00457          buflen = strlen(buf);
00458          res = SQLGetData(stmt, x + 1, SQL_CHAR, coldata->str, coldata->len, &indicator);
00459          if (indicator == SQL_NULL_DATA) {
00460             ast_debug(3, "Got NULL data\n");
00461             ast_str_reset(coldata);
00462             res = SQL_SUCCESS;
00463          }
00464 
00465          if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00466             ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql->str);
00467             y = -1;
00468             buf[0] = '\0';
00469             goto end_acf_read;
00470          }
00471 
00472          ast_debug(2, "Got coldata of '%s'\n", coldata->str);
00473          coldata->used = strlen(coldata->str);
00474 
00475          /* Copy data, encoding '\' and ',' for the argument parser */
00476          for (i = 0; i < coldata->used; i++) {
00477             if (escapecommas && (coldata->str[i] == '\\' || coldata->str[i] == ',')) {
00478                buf[buflen++] = '\\';
00479             }
00480             buf[buflen++] = coldata->str[i];
00481 
00482             if (buflen >= len - 2)
00483                break;
00484 
00485             if (coldata->str[i] == '\0')
00486                break;
00487          }
00488 
00489          buf[buflen++] = ',';
00490          buf[buflen] = '\0';
00491          ast_debug(2, "buf is now set to '%s'\n", buf);
00492       }
00493       /* Trim trailing comma */
00494       buf[buflen - 1] = '\0';
00495       ast_debug(2, "buf is now set to '%s'\n", buf);
00496 
00497       if (resultset) {
00498          row = ast_calloc(1, sizeof(*row) + buflen);
00499          if (!row) {
00500             ast_log(LOG_ERROR, "Unable to allocate space for more rows in this resultset.\n");
00501             goto end_acf_read;
00502          }
00503          strcpy((char *)row + sizeof(*row), buf);
00504          AST_LIST_INSERT_TAIL(resultset, row, list);
00505 
00506          /* Get next row */
00507          res = SQLFetch(stmt);
00508          if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00509             if (res != SQL_NO_DATA)
00510                ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, sql->str);
00511             y++;
00512             break;
00513          }
00514       }
00515    }
00516 
00517 end_acf_read:
00518    snprintf(rowcount, sizeof(rowcount), "%d", y);
00519    pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
00520    pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames->str);
00521    if (resultset) {
00522       int uid;
00523       struct ast_datastore *odbc_store;
00524       uid = ast_atomic_fetchadd_int(&resultcount, +1) + 1;
00525       snprintf(buf, len, "%d", uid);
00526       odbc_store = ast_datastore_alloc(&odbc_info, buf);
00527       if (!odbc_store) {
00528          ast_log(LOG_ERROR, "Rows retrieved, but unable to store it in the channel.  Results fail.\n");
00529          odbc_datastore_free(resultset);
00530          SQLCloseCursor(stmt);
00531          SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00532          ast_odbc_release_obj(obj);
00533          if (chan)
00534             ast_autoservice_stop(chan);
00535          if (bogus_chan)
00536             ast_channel_free(chan);
00537          ast_free(sql);
00538          return -1;
00539       }
00540       odbc_store->data = resultset;
00541       ast_channel_datastore_add(chan, odbc_store);
00542    }
00543    SQLCloseCursor(stmt);
00544    SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00545    ast_odbc_release_obj(obj);
00546    if (chan)
00547       ast_autoservice_stop(chan);
00548    if (bogus_chan)
00549       ast_channel_free(chan);
00550    ast_free(sql);
00551    return 0;
00552 }
00553 
00554 static int acf_escape(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00555 {
00556    char *out = buf;
00557 
00558    for (; *data && out - buf < len; data++) {
00559       if (*data == '\'') {
00560          *out = '\'';
00561          out++;
00562       }
00563       *out++ = *data;
00564    }
00565    *out = '\0';
00566 
00567    return 0;
00568 }
00569 
00570 static struct ast_custom_function escape_function = {
00571    .name = "SQL_ESC",
00572    .synopsis = "Escapes single ticks for use in SQL statements",
00573    .syntax = "SQL_ESC(<string>)",
00574    .desc =
00575 "Used in SQL templates to escape data which may contain single ticks (') which\n"
00576 "are otherwise used to delimit data.  For example:\n"
00577 "SELECT foo FROM bar WHERE baz='${SQL_ESC(${ARG1})}'\n",
00578    .read = acf_escape,
00579    .write = NULL,
00580 };
00581 
00582 static int acf_fetch(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00583 {
00584    struct ast_datastore *store;
00585    struct odbc_datastore *resultset;
00586    struct odbc_datastore_row *row;
00587    store = ast_channel_datastore_find(chan, &odbc_info, data);
00588    if (!store) {
00589       return -1;
00590    }
00591    resultset = store->data;
00592    AST_LIST_LOCK(resultset);
00593    row = AST_LIST_REMOVE_HEAD(resultset, list);
00594    AST_LIST_UNLOCK(resultset);
00595    if (!row) {
00596       /* Cleanup datastore */
00597       ast_channel_datastore_remove(chan, store);
00598       ast_datastore_free(store);
00599       return -1;
00600    }
00601    pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", resultset->names);
00602    ast_copy_string(buf, row->data, len);
00603    ast_free(row);
00604    return 0;
00605 }
00606 
00607 static struct ast_custom_function fetch_function = {
00608    .name = "ODBC_FETCH",
00609    .synopsis = "Fetch a row from a multirow query",
00610    .syntax = "ODBC_FETCH(<result-id>)",
00611    .desc =
00612 "For queries which are marked as mode=multirow, the original query returns a\n"
00613 "result-id from which results may be fetched.  This function implements the\n"
00614 "actual fetch of the results.\n",
00615    .read = acf_fetch,
00616    .write = NULL,
00617 };
00618 
00619 static char *app_odbcfinish = "ODBCFinish";
00620 static char *syn_odbcfinish = "Clear the resultset of a successful multirow query";
00621 static char *desc_odbcfinish =
00622 "ODBCFinish(<result-id>)\n"
00623 "  Clears any remaining rows of the specified resultset\n";
00624 
00625 
00626 static int exec_odbcfinish(struct ast_channel *chan, void *data)
00627 {
00628    struct ast_datastore *store = ast_channel_datastore_find(chan, &odbc_info, data);
00629    if (!store) /* Already freed; no big deal. */
00630       return 0;
00631    ast_channel_datastore_remove(chan, store);
00632    ast_datastore_free(store);
00633    return 0;
00634 }
00635 
00636 static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_query **query)
00637 {
00638    const char *tmp;
00639    int i;
00640    int res;
00641 
00642    if (!cfg || !catg) {
00643       return EINVAL;
00644    }
00645 
00646    *query = ast_calloc(1, sizeof(struct acf_odbc_query));
00647    if (! (*query))
00648       return ENOMEM;
00649 
00650    if (((tmp = ast_variable_retrieve(cfg, catg, "writehandle"))) || ((tmp = ast_variable_retrieve(cfg, catg, "dsn")))) {
00651       char *tmp2 = ast_strdupa(tmp);
00652       AST_DECLARE_APP_ARGS(writeconf,
00653          AST_APP_ARG(dsn)[5];
00654       );
00655       AST_STANDARD_APP_ARGS(writeconf, tmp2);
00656       for (i = 0; i < 5; i++) {
00657          if (!ast_strlen_zero(writeconf.dsn[i]))
00658             ast_copy_string((*query)->writehandle[i], writeconf.dsn[i], sizeof((*query)->writehandle[i]));
00659       }
00660    }
00661 
00662    if ((tmp = ast_variable_retrieve(cfg, catg, "readhandle"))) {
00663       char *tmp2 = ast_strdupa(tmp);
00664       AST_DECLARE_APP_ARGS(readconf,
00665          AST_APP_ARG(dsn)[5];
00666       );
00667       AST_STANDARD_APP_ARGS(readconf, tmp2);
00668       for (i = 0; i < 5; i++) {
00669          if (!ast_strlen_zero(readconf.dsn[i]))
00670             ast_copy_string((*query)->readhandle[i], readconf.dsn[i], sizeof((*query)->readhandle[i]));
00671       }
00672    } else {
00673       /* If no separate readhandle, then use the writehandle for reading */
00674       for (i = 0; i < 5; i++) {
00675          if (!ast_strlen_zero((*query)->writehandle[i]))
00676             ast_copy_string((*query)->readhandle[i], (*query)->writehandle[i], sizeof((*query)->readhandle[i]));
00677       }
00678    }
00679 
00680    if ((tmp = ast_variable_retrieve(cfg, catg, "readsql")))
00681       ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
00682    else if ((tmp = ast_variable_retrieve(cfg, catg, "read"))) {
00683       ast_log(LOG_WARNING, "Parameter 'read' is deprecated for category %s.  Please use 'readsql' instead.\n", catg);
00684       ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
00685    }
00686 
00687    if (!ast_strlen_zero((*query)->sql_read) && ast_strlen_zero((*query)->readhandle[0])) {
00688       ast_free(*query);
00689       *query = NULL;
00690       ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for reading: %s\n", catg);
00691       return EINVAL;
00692    }
00693 
00694    if ((tmp = ast_variable_retrieve(cfg, catg, "writesql")))
00695       ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
00696    else if ((tmp = ast_variable_retrieve(cfg, catg, "write"))) {
00697       ast_log(LOG_WARNING, "Parameter 'write' is deprecated for category %s.  Please use 'writesql' instead.\n", catg);
00698       ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
00699    }
00700 
00701    if (!ast_strlen_zero((*query)->sql_write) && ast_strlen_zero((*query)->writehandle[0])) {
00702       ast_free(*query);
00703       *query = NULL;
00704       ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for writing: %s\n", catg);
00705       return EINVAL;
00706    }
00707 
00708    /* Allow escaping of embedded commas in fields to be turned off */
00709    ast_set_flag((*query), OPT_ESCAPECOMMAS);
00710    if ((tmp = ast_variable_retrieve(cfg, catg, "escapecommas"))) {
00711       if (ast_false(tmp))
00712          ast_clear_flag((*query), OPT_ESCAPECOMMAS);
00713    }
00714 
00715    if ((tmp = ast_variable_retrieve(cfg, catg, "mode"))) {
00716       if (strcasecmp(tmp, "multirow") == 0)
00717          ast_set_flag((*query), OPT_MULTIROW);
00718       if ((tmp = ast_variable_retrieve(cfg, catg, "rowlimit")))
00719          sscanf(tmp, "%30d", &((*query)->rowlimit));
00720    }
00721 
00722    (*query)->acf = ast_calloc(1, sizeof(struct ast_custom_function));
00723    if (! (*query)->acf) {
00724       ast_free(*query);
00725       *query = NULL;
00726       return ENOMEM;
00727    }
00728 
00729    if ((tmp = ast_variable_retrieve(cfg, catg, "prefix")) && !ast_strlen_zero(tmp)) {
00730       if (asprintf((char **)&((*query)->acf->name), "%s_%s", tmp, catg) < 0) {
00731          ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno));
00732       }
00733    } else {
00734       if (asprintf((char **)&((*query)->acf->name), "ODBC_%s", catg) < 0) {
00735          ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno));
00736       }
00737    }
00738 
00739    if (!((*query)->acf->name)) {
00740       ast_free((*query)->acf);
00741       ast_free(*query);
00742       *query = NULL;
00743       return ENOMEM;
00744    }
00745 
00746    if (asprintf((char **)&((*query)->acf->syntax), "%s(<arg1>[...[,<argN>]])", (*query)->acf->name) < 0) {
00747       ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno));
00748       (*query)->acf->syntax = NULL;
00749    }
00750 
00751    if (!((*query)->acf->syntax)) {
00752       ast_free((char *)(*query)->acf->name);
00753       ast_free((*query)->acf);
00754       ast_free(*query);
00755       *query = NULL;
00756       return ENOMEM;
00757    }
00758 
00759    (*query)->acf->synopsis = "Runs the referenced query with the specified arguments";
00760 
00761    res = 0;
00762    if (!ast_strlen_zero((*query)->sql_read) && !ast_strlen_zero((*query)->sql_write)) {
00763       res = asprintf((char **)&((*query)->acf->desc),
00764                 "Runs the following query, as defined in func_odbc.conf, performing\n"
00765                 "substitution of the arguments into the query as specified by ${ARG1},\n"
00766                 "${ARG2}, ... ${ARGn}.  When setting the function, the values are provided\n"
00767                 "either in whole as ${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
00768                 "\nRead:\n%s\n\nWrite:\n%s\n",
00769                 (*query)->sql_read,
00770                 (*query)->sql_write);
00771    } else if (!ast_strlen_zero((*query)->sql_read)) {
00772       res = asprintf((char **)&((*query)->acf->desc),
00773                 "Runs the following query, as defined in func_odbc.conf, performing\n"
00774                 "substitution of the arguments into the query as specified by ${ARG1},\n"
00775                 "${ARG2}, ... ${ARGn}.  This function may only be read, not set.\n\nSQL:\n%s\n",
00776                 (*query)->sql_read);
00777    } else if (!ast_strlen_zero((*query)->sql_write)) {
00778       res = asprintf((char **)&((*query)->acf->desc),
00779                 "Runs the following query, as defined in func_odbc.conf, performing\n"
00780                 "substitution of the arguments into the query as specified by ${ARG1},\n"
00781                 "${ARG2}, ... ${ARGn}.  The values are provided either in whole as\n"
00782                 "${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
00783                 "This function may only be set.\nSQL:\n%s\n",
00784                 (*query)->sql_write);
00785    } else {
00786       ast_free((char *)(*query)->acf->syntax);
00787       ast_free((char *)(*query)->acf->name);
00788       ast_free((*query)->acf);
00789       ast_free(*query);
00790       ast_log(LOG_WARNING, "Section %s was found, but there was no SQL to execute.  Ignoring.\n", catg);
00791       return EINVAL;
00792    }
00793 
00794    if (res < 0) {
00795       ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno));
00796       (*query)->acf->desc = NULL;
00797    }
00798 
00799 
00800    if (!((*query)->acf->desc)) {
00801       ast_free((char *)(*query)->acf->syntax);
00802       ast_free((char *)(*query)->acf->name);
00803       ast_free((*query)->acf);
00804       ast_free(*query);
00805       *query = NULL;
00806       return ENOMEM;
00807    }
00808 
00809    if (ast_strlen_zero((*query)->sql_read)) {
00810       (*query)->acf->read = NULL;
00811    } else {
00812       (*query)->acf->read = acf_odbc_read;
00813    }
00814 
00815    if (ast_strlen_zero((*query)->sql_write)) {
00816       (*query)->acf->write = NULL;
00817    } else {
00818       (*query)->acf->write = acf_odbc_write;
00819    }
00820 
00821    return 0;
00822 }
00823 
00824 static int free_acf_query(struct acf_odbc_query *query)
00825 {
00826    if (query) {
00827       if (query->acf) {
00828          if (query->acf->name)
00829             ast_free((char *)query->acf->name);
00830          if (query->acf->syntax)
00831             ast_free((char *)query->acf->syntax);
00832          if (query->acf->desc)
00833             ast_free((char *)query->acf->desc);
00834          ast_free(query->acf);
00835       }
00836       ast_free(query);
00837    }
00838    return 0;
00839 }
00840 
00841 static int load_module(void)
00842 {
00843    int res = 0;
00844    struct ast_config *cfg;
00845    char *catg;
00846    struct ast_flags config_flags = { 0 };
00847 
00848    res |= ast_custom_function_register(&fetch_function);
00849    res |= ast_register_application(app_odbcfinish, exec_odbcfinish, syn_odbcfinish, desc_odbcfinish);
00850    AST_RWLIST_WRLOCK(&queries);
00851 
00852    cfg = ast_config_load(config, config_flags);
00853    if (!cfg) {
00854       ast_log(LOG_NOTICE, "Unable to load config for func_odbc: %s\n", config);
00855       AST_RWLIST_UNLOCK(&queries);
00856       return AST_MODULE_LOAD_DECLINE;
00857    }
00858 
00859    for (catg = ast_category_browse(cfg, NULL);
00860         catg;
00861         catg = ast_category_browse(cfg, catg)) {
00862       struct acf_odbc_query *query = NULL;
00863       int err;
00864 
00865       if ((err = init_acf_query(cfg, catg, &query))) {
00866          if (err == ENOMEM)
00867             ast_log(LOG_ERROR, "Out of memory\n");
00868          else if (err == EINVAL)
00869             ast_log(LOG_ERROR, "Invalid parameters for category %s\n", catg);
00870          else
00871             ast_log(LOG_ERROR, "%s (%d)\n", strerror(err), err);
00872       } else {
00873          AST_RWLIST_INSERT_HEAD(&queries, query, list);
00874          ast_custom_function_register(query->acf);
00875       }
00876    }
00877 
00878    ast_config_destroy(cfg);
00879    res |= ast_custom_function_register(&escape_function);
00880 
00881    AST_RWLIST_UNLOCK(&queries);
00882    return res;
00883 }
00884 
00885 static int unload_module(void)
00886 {
00887    struct acf_odbc_query *query;
00888    int res = 0;
00889 
00890    AST_RWLIST_WRLOCK(&queries);
00891    while (!AST_RWLIST_EMPTY(&queries)) {
00892       query = AST_RWLIST_REMOVE_HEAD(&queries, list);
00893       ast_custom_function_unregister(query->acf);
00894       free_acf_query(query);
00895    }
00896 
00897    res |= ast_custom_function_unregister(&escape_function);
00898    res |= ast_custom_function_unregister(&fetch_function);
00899    res |= ast_unregister_application(app_odbcfinish);
00900 
00901    /* Allow any threads waiting for this lock to pass (avoids a race) */
00902    AST_RWLIST_UNLOCK(&queries);
00903    usleep(1);
00904    AST_RWLIST_WRLOCK(&queries);
00905 
00906    AST_RWLIST_UNLOCK(&queries);
00907    return 0;
00908 }
00909 
00910 static int reload(void)
00911 {
00912    int res = 0;
00913    struct ast_config *cfg;
00914    struct acf_odbc_query *oldquery;
00915    char *catg;
00916    struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED };
00917 
00918    cfg = ast_config_load(config, config_flags);
00919    if (cfg == CONFIG_STATUS_FILEUNCHANGED)
00920       return 0;
00921 
00922    AST_RWLIST_WRLOCK(&queries);
00923 
00924    while (!AST_RWLIST_EMPTY(&queries)) {
00925       oldquery = AST_RWLIST_REMOVE_HEAD(&queries, list);
00926       ast_custom_function_unregister(oldquery->acf);
00927       free_acf_query(oldquery);
00928    }
00929 
00930    if (!cfg) {
00931       ast_log(LOG_WARNING, "Unable to load config for func_odbc: %s\n", config);
00932       goto reload_out;
00933    }
00934 
00935    for (catg = ast_category_browse(cfg, NULL);
00936         catg;
00937         catg = ast_category_browse(cfg, catg)) {
00938       struct acf_odbc_query *query = NULL;
00939 
00940       if (init_acf_query(cfg, catg, &query)) {
00941          ast_log(LOG_ERROR, "Cannot initialize query %s\n", catg);
00942       } else {
00943          AST_RWLIST_INSERT_HEAD(&queries, query, list);
00944          ast_custom_function_register(query->acf);
00945       }
00946    }
00947 
00948    ast_config_destroy(cfg);
00949 reload_out:
00950    AST_RWLIST_UNLOCK(&queries);
00951    return res;
00952 }
00953 
00954 /* XXX need to revise usecount - set if query_lock is set */
00955 
00956 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "ODBC lookups",
00957       .load = load_module,
00958       .unload = unload_module,
00959       .reload = reload,
00960           );
00961 

Generated on Wed Aug 18 22:33:52 2010 for Asterisk - the Open Source PBX by  doxygen 1.4.7