00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038 #include "asterisk.h"
00039
00040 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 307793 $")
00041
00042 #include "asterisk/file.h"
00043 #include "asterisk/channel.h"
00044 #include "asterisk/config.h"
00045 #include "asterisk/pbx.h"
00046 #include "asterisk/module.h"
00047 #include "asterisk/cli.h"
00048 #include "asterisk/lock.h"
00049 #include "asterisk/res_odbc.h"
00050 #include "asterisk/time.h"
00051 #include "asterisk/astobj2.h"
00052 #include "asterisk/app.h"
00053 #include "asterisk/strings.h"
00054 #include "asterisk/threadstorage.h"
00055 #include "asterisk/data.h"
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117 struct odbc_class
00118 {
00119 AST_LIST_ENTRY(odbc_class) list;
00120 char name[80];
00121 char dsn[80];
00122 char *username;
00123 char *password;
00124 char *sanitysql;
00125 SQLHENV env;
00126 unsigned int haspool:1;
00127 unsigned int delme:1;
00128 unsigned int backslash_is_escape:1;
00129 unsigned int forcecommit:1;
00130 unsigned int isolation;
00131 unsigned int limit;
00132 int count;
00133 unsigned int idlecheck;
00134 unsigned int conntimeout;
00135
00136 struct timeval negative_connection_cache;
00137
00138 struct timeval last_negative_connect;
00139
00140 struct ao2_container *obj_container;
00141 };
00142
00143 static struct ao2_container *class_container;
00144
00145 static AST_RWLIST_HEAD_STATIC(odbc_tables, odbc_cache_tables);
00146
00147 static odbc_status odbc_obj_connect(struct odbc_obj *obj);
00148 static odbc_status odbc_obj_disconnect(struct odbc_obj *obj);
00149 static int odbc_register_class(struct odbc_class *class, int connect);
00150 static void odbc_txn_free(void *data);
00151 static void odbc_release_obj2(struct odbc_obj *obj, struct odbc_txn_frame *tx);
00152
00153 AST_THREADSTORAGE(errors_buf);
00154
00155 static struct ast_datastore_info txn_info = {
00156 .type = "ODBC_Transaction",
00157 .destroy = odbc_txn_free,
00158 };
00159
00160 struct odbc_txn_frame {
00161 AST_LIST_ENTRY(odbc_txn_frame) list;
00162 struct ast_channel *owner;
00163 struct odbc_obj *obj;
00164
00165
00166
00167
00168
00169
00170
00171 unsigned int active:1;
00172 unsigned int forcecommit:1;
00173 unsigned int isolation;
00174 char name[0];
00175 };
00176
00177 #define DATA_EXPORT_ODBC_CLASS(MEMBER) \
00178 MEMBER(odbc_class, name, AST_DATA_STRING) \
00179 MEMBER(odbc_class, dsn, AST_DATA_STRING) \
00180 MEMBER(odbc_class, username, AST_DATA_STRING) \
00181 MEMBER(odbc_class, password, AST_DATA_PASSWORD) \
00182 MEMBER(odbc_class, limit, AST_DATA_INTEGER) \
00183 MEMBER(odbc_class, count, AST_DATA_INTEGER) \
00184 MEMBER(odbc_class, forcecommit, AST_DATA_BOOLEAN)
00185
00186 AST_DATA_STRUCTURE(odbc_class, DATA_EXPORT_ODBC_CLASS);
00187
00188 static const char *isolation2text(int iso)
00189 {
00190 if (iso == SQL_TXN_READ_COMMITTED) {
00191 return "read_committed";
00192 } else if (iso == SQL_TXN_READ_UNCOMMITTED) {
00193 return "read_uncommitted";
00194 } else if (iso == SQL_TXN_SERIALIZABLE) {
00195 return "serializable";
00196 } else if (iso == SQL_TXN_REPEATABLE_READ) {
00197 return "repeatable_read";
00198 } else {
00199 return "unknown";
00200 }
00201 }
00202
00203 static int text2isolation(const char *txt)
00204 {
00205 if (strncasecmp(txt, "read_", 5) == 0) {
00206 if (strncasecmp(txt + 5, "c", 1) == 0) {
00207 return SQL_TXN_READ_COMMITTED;
00208 } else if (strncasecmp(txt + 5, "u", 1) == 0) {
00209 return SQL_TXN_READ_UNCOMMITTED;
00210 } else {
00211 return 0;
00212 }
00213 } else if (strncasecmp(txt, "ser", 3) == 0) {
00214 return SQL_TXN_SERIALIZABLE;
00215 } else if (strncasecmp(txt, "rep", 3) == 0) {
00216 return SQL_TXN_REPEATABLE_READ;
00217 } else {
00218 return 0;
00219 }
00220 }
00221
00222 static struct odbc_txn_frame *find_transaction(struct ast_channel *chan, struct odbc_obj *obj, const char *name, int active)
00223 {
00224 struct ast_datastore *txn_store;
00225 AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
00226 struct odbc_txn_frame *txn = NULL;
00227
00228 if (!chan && obj && obj->txf && obj->txf->owner) {
00229 chan = obj->txf->owner;
00230 } else if (!chan) {
00231
00232 return NULL;
00233 }
00234
00235 ast_channel_lock(chan);
00236 if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
00237 oldlist = txn_store->data;
00238 } else {
00239
00240 if (!(txn_store = ast_datastore_alloc(&txn_info, NULL))) {
00241 ast_log(LOG_ERROR, "Unable to allocate a new datastore. Cannot create a new transaction.\n");
00242 ast_channel_unlock(chan);
00243 return NULL;
00244 }
00245
00246 if (!(oldlist = ast_calloc(1, sizeof(*oldlist)))) {
00247 ast_log(LOG_ERROR, "Unable to allocate datastore list head. Cannot create a new transaction.\n");
00248 ast_datastore_free(txn_store);
00249 ast_channel_unlock(chan);
00250 return NULL;
00251 }
00252
00253 txn_store->data = oldlist;
00254 AST_LIST_HEAD_INIT(oldlist);
00255 ast_channel_datastore_add(chan, txn_store);
00256 }
00257
00258 AST_LIST_LOCK(oldlist);
00259 ast_channel_unlock(chan);
00260
00261
00262 if (obj != NULL || active == 1) {
00263 AST_LIST_TRAVERSE(oldlist, txn, list) {
00264 if (txn->obj == obj || txn->active) {
00265 AST_LIST_UNLOCK(oldlist);
00266 return txn;
00267 }
00268 }
00269 }
00270
00271 if (name != NULL) {
00272 AST_LIST_TRAVERSE(oldlist, txn, list) {
00273 if (!strcasecmp(txn->name, name)) {
00274 AST_LIST_UNLOCK(oldlist);
00275 return txn;
00276 }
00277 }
00278 }
00279
00280
00281 if (name && obj && (txn = ast_calloc(1, sizeof(*txn) + strlen(name) + 1))) {
00282 struct odbc_txn_frame *otxn;
00283
00284 strcpy(txn->name, name);
00285 txn->obj = obj;
00286 txn->isolation = obj->parent->isolation;
00287 txn->forcecommit = obj->parent->forcecommit;
00288 txn->owner = chan;
00289 txn->active = 1;
00290
00291
00292 AST_LIST_TRAVERSE(oldlist, otxn, list) {
00293 otxn->active = 0;
00294 }
00295 AST_LIST_INSERT_TAIL(oldlist, txn, list);
00296
00297 obj->txf = txn;
00298 obj->tx = 1;
00299 }
00300 AST_LIST_UNLOCK(oldlist);
00301
00302 return txn;
00303 }
00304
00305 static struct odbc_txn_frame *release_transaction(struct odbc_txn_frame *tx)
00306 {
00307 if (!tx) {
00308 return NULL;
00309 }
00310
00311 ast_debug(2, "release_transaction(%p) called (tx->obj = %p, tx->obj->txf = %p)\n", tx, tx->obj, tx->obj ? tx->obj->txf : NULL);
00312
00313
00314 if (tx->owner) {
00315 struct ast_datastore *txn_store;
00316 AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
00317
00318 ast_channel_lock(tx->owner);
00319 if ((txn_store = ast_channel_datastore_find(tx->owner, &txn_info, NULL))) {
00320 oldlist = txn_store->data;
00321 AST_LIST_LOCK(oldlist);
00322 AST_LIST_REMOVE(oldlist, tx, list);
00323 AST_LIST_UNLOCK(oldlist);
00324 }
00325 ast_channel_unlock(tx->owner);
00326 tx->owner = NULL;
00327 }
00328
00329 if (tx->obj) {
00330
00331 struct odbc_obj *obj = tx->obj;
00332
00333 tx->obj->txf = NULL;
00334 tx->obj = NULL;
00335 odbc_release_obj2(obj, tx);
00336 }
00337 ast_free(tx);
00338 return NULL;
00339 }
00340
00341 static void odbc_txn_free(void *vdata)
00342 {
00343 struct odbc_txn_frame *tx;
00344 AST_LIST_HEAD(, odbc_txn_frame) *oldlist = vdata;
00345
00346 ast_debug(2, "odbc_txn_free(%p) called\n", vdata);
00347
00348 AST_LIST_LOCK(oldlist);
00349 while ((tx = AST_LIST_REMOVE_HEAD(oldlist, list))) {
00350 release_transaction(tx);
00351 }
00352 AST_LIST_UNLOCK(oldlist);
00353 AST_LIST_HEAD_DESTROY(oldlist);
00354 ast_free(oldlist);
00355 }
00356
00357 static int mark_transaction_active(struct ast_channel *chan, struct odbc_txn_frame *tx)
00358 {
00359 struct ast_datastore *txn_store;
00360 AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
00361 struct odbc_txn_frame *active = NULL, *txn;
00362
00363 if (!chan && tx && tx->owner) {
00364 chan = tx->owner;
00365 }
00366
00367 ast_channel_lock(chan);
00368 if (!(txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
00369 ast_channel_unlock(chan);
00370 return -1;
00371 }
00372
00373 oldlist = txn_store->data;
00374 AST_LIST_LOCK(oldlist);
00375 AST_LIST_TRAVERSE(oldlist, txn, list) {
00376 if (txn == tx) {
00377 txn->active = 1;
00378 active = txn;
00379 } else {
00380 txn->active = 0;
00381 }
00382 }
00383 AST_LIST_UNLOCK(oldlist);
00384 ast_channel_unlock(chan);
00385 return active ? 0 : -1;
00386 }
00387
00388 static void odbc_class_destructor(void *data)
00389 {
00390 struct odbc_class *class = data;
00391
00392
00393
00394 if (class->username) {
00395 ast_free(class->username);
00396 }
00397 if (class->password) {
00398 ast_free(class->password);
00399 }
00400 if (class->sanitysql) {
00401 ast_free(class->sanitysql);
00402 }
00403 ao2_ref(class->obj_container, -1);
00404 SQLFreeHandle(SQL_HANDLE_ENV, class->env);
00405 }
00406
00407 static int null_hash_fn(const void *obj, const int flags)
00408 {
00409 return 0;
00410 }
00411
00412 static void odbc_obj_destructor(void *data)
00413 {
00414 struct odbc_obj *obj = data;
00415 struct odbc_class *class = obj->parent;
00416 obj->parent = NULL;
00417 odbc_obj_disconnect(obj);
00418 ast_mutex_destroy(&obj->lock);
00419 ao2_ref(class, -1);
00420 }
00421
00422 static void destroy_table_cache(struct odbc_cache_tables *table) {
00423 struct odbc_cache_columns *col;
00424 ast_debug(1, "Destroying table cache for %s\n", table->table);
00425 AST_RWLIST_WRLOCK(&table->columns);
00426 while ((col = AST_RWLIST_REMOVE_HEAD(&table->columns, list))) {
00427 ast_free(col);
00428 }
00429 AST_RWLIST_UNLOCK(&table->columns);
00430 AST_RWLIST_HEAD_DESTROY(&table->columns);
00431 ast_free(table);
00432 }
00433
00434
00435
00436
00437
00438
00439
00440
00441
00442
00443 struct odbc_cache_tables *ast_odbc_find_table(const char *database, const char *tablename)
00444 {
00445 struct odbc_cache_tables *tableptr;
00446 struct odbc_cache_columns *entry;
00447 char columnname[80];
00448 SQLLEN sqlptr;
00449 SQLHSTMT stmt = NULL;
00450 int res = 0, error = 0, try = 0;
00451 struct odbc_obj *obj = ast_odbc_request_obj(database, 0);
00452
00453 AST_RWLIST_RDLOCK(&odbc_tables);
00454 AST_RWLIST_TRAVERSE(&odbc_tables, tableptr, list) {
00455 if (strcmp(tableptr->connection, database) == 0 && strcmp(tableptr->table, tablename) == 0) {
00456 break;
00457 }
00458 }
00459 if (tableptr) {
00460 AST_RWLIST_RDLOCK(&tableptr->columns);
00461 AST_RWLIST_UNLOCK(&odbc_tables);
00462 if (obj) {
00463 ast_odbc_release_obj(obj);
00464 }
00465 return tableptr;
00466 }
00467
00468 if (!obj) {
00469 ast_log(LOG_WARNING, "Unable to retrieve database handle for table description '%s@%s'\n", tablename, database);
00470 AST_RWLIST_UNLOCK(&odbc_tables);
00471 return NULL;
00472 }
00473
00474
00475 do {
00476 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
00477 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00478 if (try == 0) {
00479 try = 1;
00480 ast_odbc_sanity_check(obj);
00481 continue;
00482 }
00483 ast_log(LOG_WARNING, "SQL Alloc Handle failed on connection '%s'!\n", database);
00484 break;
00485 }
00486
00487 res = SQLColumns(stmt, NULL, 0, NULL, 0, (unsigned char *)tablename, SQL_NTS, (unsigned char *)"%", SQL_NTS);
00488 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00489 if (try == 0) {
00490 try = 1;
00491 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00492 ast_odbc_sanity_check(obj);
00493 continue;
00494 }
00495 ast_log(LOG_ERROR, "Unable to query database columns on connection '%s'.\n", database);
00496 break;
00497 }
00498
00499 if (!(tableptr = ast_calloc(sizeof(char), sizeof(*tableptr) + strlen(database) + 1 + strlen(tablename) + 1))) {
00500 ast_log(LOG_ERROR, "Out of memory creating entry for table '%s' on connection '%s'\n", tablename, database);
00501 break;
00502 }
00503
00504 tableptr->connection = (char *)tableptr + sizeof(*tableptr);
00505 tableptr->table = (char *)tableptr + sizeof(*tableptr) + strlen(database) + 1;
00506 strcpy(tableptr->connection, database);
00507 strcpy(tableptr->table, tablename);
00508 AST_RWLIST_HEAD_INIT(&(tableptr->columns));
00509
00510 while ((res = SQLFetch(stmt)) != SQL_NO_DATA && res != SQL_ERROR) {
00511 SQLGetData(stmt, 4, SQL_C_CHAR, columnname, sizeof(columnname), &sqlptr);
00512
00513 if (!(entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(columnname) + 1))) {
00514 ast_log(LOG_ERROR, "Out of memory creating entry for column '%s' in table '%s' on connection '%s'\n", columnname, tablename, database);
00515 error = 1;
00516 break;
00517 }
00518 entry->name = (char *)entry + sizeof(*entry);
00519 strcpy(entry->name, columnname);
00520
00521 SQLGetData(stmt, 5, SQL_C_SHORT, &entry->type, sizeof(entry->type), NULL);
00522 SQLGetData(stmt, 7, SQL_C_LONG, &entry->size, sizeof(entry->size), NULL);
00523 SQLGetData(stmt, 9, SQL_C_SHORT, &entry->decimals, sizeof(entry->decimals), NULL);
00524 SQLGetData(stmt, 10, SQL_C_SHORT, &entry->radix, sizeof(entry->radix), NULL);
00525 SQLGetData(stmt, 11, SQL_C_SHORT, &entry->nullable, sizeof(entry->nullable), NULL);
00526 SQLGetData(stmt, 16, SQL_C_LONG, &entry->octetlen, sizeof(entry->octetlen), NULL);
00527
00528
00529
00530
00531 if (entry->octetlen == 0) {
00532 entry->octetlen = entry->size;
00533 }
00534
00535 ast_verb(10, "Found %s column with type %hd with len %ld, octetlen %ld, and numlen (%hd,%hd)\n", entry->name, entry->type, (long) entry->size, (long) entry->octetlen, entry->decimals, entry->radix);
00536
00537 AST_LIST_INSERT_TAIL(&(tableptr->columns), entry, list);
00538 }
00539 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00540
00541 AST_RWLIST_INSERT_TAIL(&odbc_tables, tableptr, list);
00542 AST_RWLIST_RDLOCK(&(tableptr->columns));
00543 break;
00544 } while (1);
00545
00546 AST_RWLIST_UNLOCK(&odbc_tables);
00547
00548 if (error) {
00549 destroy_table_cache(tableptr);
00550 tableptr = NULL;
00551 }
00552 if (obj) {
00553 ast_odbc_release_obj(obj);
00554 }
00555 return tableptr;
00556 }
00557
00558 struct odbc_cache_columns *ast_odbc_find_column(struct odbc_cache_tables *table, const char *colname)
00559 {
00560 struct odbc_cache_columns *col;
00561 AST_RWLIST_TRAVERSE(&table->columns, col, list) {
00562 if (strcasecmp(col->name, colname) == 0) {
00563 return col;
00564 }
00565 }
00566 return NULL;
00567 }
00568
00569 int ast_odbc_clear_cache(const char *database, const char *tablename)
00570 {
00571 struct odbc_cache_tables *tableptr;
00572
00573 AST_RWLIST_WRLOCK(&odbc_tables);
00574 AST_RWLIST_TRAVERSE_SAFE_BEGIN(&odbc_tables, tableptr, list) {
00575 if (strcmp(tableptr->connection, database) == 0 && strcmp(tableptr->table, tablename) == 0) {
00576 AST_LIST_REMOVE_CURRENT(list);
00577 destroy_table_cache(tableptr);
00578 break;
00579 }
00580 }
00581 AST_RWLIST_TRAVERSE_SAFE_END
00582 AST_RWLIST_UNLOCK(&odbc_tables);
00583 return tableptr ? 0 : -1;
00584 }
00585
00586 SQLHSTMT ast_odbc_direct_execute(struct odbc_obj *obj, SQLHSTMT (*exec_cb)(struct odbc_obj *obj, void *data), void *data)
00587 {
00588 int attempt;
00589 SQLHSTMT stmt;
00590
00591 for (attempt = 0; attempt < 2; attempt++) {
00592 stmt = exec_cb(obj, data);
00593
00594 if (stmt) {
00595 break;
00596 } else if (obj->tx) {
00597 ast_log(LOG_WARNING, "Failed to execute, but unable to reconnect, as we're transactional.\n");
00598 break;
00599 } else if (attempt == 0) {
00600 ast_log(LOG_WARNING, "SQL Execute error! Verifying connection to %s [%s]...\n", obj->parent->name, obj->parent->dsn);
00601 }
00602 if (!ast_odbc_sanity_check(obj)) {
00603 break;
00604 }
00605 }
00606
00607 return stmt;
00608 }
00609
00610 SQLHSTMT ast_odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT (*prepare_cb)(struct odbc_obj *obj, void *data), void *data)
00611 {
00612 int res = 0, i, attempt;
00613 SQLINTEGER nativeerror=0, numfields=0;
00614 SQLSMALLINT diagbytes=0;
00615 unsigned char state[10], diagnostic[256];
00616 SQLHSTMT stmt;
00617
00618 for (attempt = 0; attempt < 2; attempt++) {
00619
00620
00621
00622
00623
00624 stmt = prepare_cb(obj, data);
00625
00626 if (stmt) {
00627 res = SQLExecute(stmt);
00628 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
00629 if (res == SQL_ERROR) {
00630 SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
00631 for (i = 0; i < numfields; i++) {
00632 SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
00633 ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
00634 if (i > 10) {
00635 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
00636 break;
00637 }
00638 }
00639 }
00640
00641 if (obj->tx) {
00642 ast_log(LOG_WARNING, "SQL Execute error, but unable to reconnect, as we're transactional.\n");
00643 break;
00644 } else {
00645 ast_log(LOG_WARNING, "SQL Execute error %d! Verifying connection to %s [%s]...\n", res, obj->parent->name, obj->parent->dsn);
00646 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00647 stmt = NULL;
00648
00649 obj->up = 0;
00650
00651
00652
00653
00654 if (!ast_odbc_sanity_check(obj)) {
00655 break;
00656 }
00657 continue;
00658 }
00659 } else {
00660 obj->last_used = ast_tvnow();
00661 }
00662 break;
00663 } else if (attempt == 0) {
00664 ast_odbc_sanity_check(obj);
00665 }
00666 }
00667
00668 return stmt;
00669 }
00670
00671 int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt)
00672 {
00673 int res = 0, i;
00674 SQLINTEGER nativeerror=0, numfields=0;
00675 SQLSMALLINT diagbytes=0;
00676 unsigned char state[10], diagnostic[256];
00677
00678 res = SQLExecute(stmt);
00679 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
00680 if (res == SQL_ERROR) {
00681 SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
00682 for (i = 0; i < numfields; i++) {
00683 SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
00684 ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
00685 if (i > 10) {
00686 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
00687 break;
00688 }
00689 }
00690 }
00691 } else {
00692 obj->last_used = ast_tvnow();
00693 }
00694
00695 return res;
00696 }
00697
00698 SQLRETURN ast_odbc_ast_str_SQLGetData(struct ast_str **buf, int pmaxlen, SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, SQLLEN *StrLen_or_Ind)
00699 {
00700 SQLRETURN res;
00701
00702 if (pmaxlen == 0) {
00703 if (SQLGetData(StatementHandle, ColumnNumber, TargetType, ast_str_buffer(*buf), 0, StrLen_or_Ind) == SQL_SUCCESS_WITH_INFO) {
00704 ast_str_make_space(buf, *StrLen_or_Ind + 1);
00705 }
00706 } else if (pmaxlen > 0) {
00707 ast_str_make_space(buf, pmaxlen);
00708 }
00709 res = SQLGetData(StatementHandle, ColumnNumber, TargetType, ast_str_buffer(*buf), ast_str_size(*buf), StrLen_or_Ind);
00710 ast_str_update(*buf);
00711
00712 return res;
00713 }
00714
00715 int ast_odbc_sanity_check(struct odbc_obj *obj)
00716 {
00717 char *test_sql = "select 1";
00718 SQLHSTMT stmt;
00719 int res = 0;
00720
00721 if (!ast_strlen_zero(obj->parent->sanitysql))
00722 test_sql = obj->parent->sanitysql;
00723
00724 if (obj->up) {
00725 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
00726 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00727 obj->up = 0;
00728 } else {
00729 res = SQLPrepare(stmt, (unsigned char *)test_sql, SQL_NTS);
00730 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00731 obj->up = 0;
00732 } else {
00733 res = SQLExecute(stmt);
00734 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00735 obj->up = 0;
00736 }
00737 }
00738 }
00739 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
00740 }
00741
00742 if (!obj->up && !obj->tx) {
00743 ast_log(LOG_WARNING, "Connection is down attempting to reconnect...\n");
00744 odbc_obj_disconnect(obj);
00745 odbc_obj_connect(obj);
00746 }
00747 return obj->up;
00748 }
00749
00750 static int load_odbc_config(void)
00751 {
00752 static char *cfg = "res_odbc.conf";
00753 struct ast_config *config;
00754 struct ast_variable *v;
00755 char *cat;
00756 const char *dsn, *username, *password, *sanitysql;
00757 int enabled, pooling, limit, bse, conntimeout, forcecommit, isolation;
00758 struct timeval ncache = { 0, 0 };
00759 unsigned int idlecheck;
00760 int preconnect = 0, res = 0;
00761 struct ast_flags config_flags = { 0 };
00762
00763 struct odbc_class *new;
00764
00765 config = ast_config_load(cfg, config_flags);
00766 if (config == CONFIG_STATUS_FILEMISSING || config == CONFIG_STATUS_FILEINVALID) {
00767 ast_log(LOG_WARNING, "Unable to load config file res_odbc.conf\n");
00768 return -1;
00769 }
00770 for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) {
00771 if (!strcasecmp(cat, "ENV")) {
00772 for (v = ast_variable_browse(config, cat); v; v = v->next) {
00773 setenv(v->name, v->value, 1);
00774 ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value);
00775 }
00776 } else {
00777
00778 dsn = username = password = sanitysql = NULL;
00779 enabled = 1;
00780 preconnect = idlecheck = 0;
00781 pooling = 0;
00782 limit = 0;
00783 bse = 1;
00784 conntimeout = 10;
00785 forcecommit = 0;
00786 isolation = SQL_TXN_READ_COMMITTED;
00787 for (v = ast_variable_browse(config, cat); v; v = v->next) {
00788 if (!strcasecmp(v->name, "pooling")) {
00789 if (ast_true(v->value))
00790 pooling = 1;
00791 } else if (!strncasecmp(v->name, "share", 5)) {
00792
00793 if (ast_false(v->value))
00794 pooling = 1;
00795 } else if (!strcasecmp(v->name, "limit")) {
00796 sscanf(v->value, "%30d", &limit);
00797 if (ast_true(v->value) && !limit) {
00798 ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Setting limit to 1023 for ODBC class '%s'.\n", v->value, cat);
00799 limit = 1023;
00800 } else if (ast_false(v->value)) {
00801 ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Disabling ODBC class '%s'.\n", v->value, cat);
00802 enabled = 0;
00803 break;
00804 }
00805 } else if (!strcasecmp(v->name, "idlecheck")) {
00806 sscanf(v->value, "%30u", &idlecheck);
00807 } else if (!strcasecmp(v->name, "enabled")) {
00808 enabled = ast_true(v->value);
00809 } else if (!strcasecmp(v->name, "pre-connect")) {
00810 preconnect = ast_true(v->value);
00811 } else if (!strcasecmp(v->name, "dsn")) {
00812 dsn = v->value;
00813 } else if (!strcasecmp(v->name, "username")) {
00814 username = v->value;
00815 } else if (!strcasecmp(v->name, "password")) {
00816 password = v->value;
00817 } else if (!strcasecmp(v->name, "sanitysql")) {
00818 sanitysql = v->value;
00819 } else if (!strcasecmp(v->name, "backslash_is_escape")) {
00820 bse = ast_true(v->value);
00821 } else if (!strcasecmp(v->name, "connect_timeout")) {
00822 if (sscanf(v->value, "%d", &conntimeout) != 1 || conntimeout < 1) {
00823 ast_log(LOG_WARNING, "connect_timeout must be a positive integer\n");
00824 conntimeout = 10;
00825 }
00826 } else if (!strcasecmp(v->name, "negative_connection_cache")) {
00827 double dncache;
00828 if (sscanf(v->value, "%lf", &dncache) != 1 || dncache < 0) {
00829 ast_log(LOG_WARNING, "negative_connection_cache must be a non-negative integer\n");
00830
00831 ncache.tv_sec = 300;
00832 ncache.tv_usec = 0;
00833 } else {
00834 ncache.tv_sec = (int)dncache;
00835 ncache.tv_usec = (dncache - ncache.tv_sec) * 1000000;
00836 }
00837 } else if (!strcasecmp(v->name, "forcecommit")) {
00838 forcecommit = ast_true(v->value);
00839 } else if (!strcasecmp(v->name, "isolation")) {
00840 if ((isolation = text2isolation(v->value)) == 0) {
00841 ast_log(LOG_ERROR, "Unrecognized value for 'isolation': '%s' in section '%s'\n", v->value, cat);
00842 isolation = SQL_TXN_READ_COMMITTED;
00843 }
00844 }
00845 }
00846
00847 if (enabled && !ast_strlen_zero(dsn)) {
00848 new = ao2_alloc(sizeof(*new), odbc_class_destructor);
00849
00850 if (!new) {
00851 res = -1;
00852 break;
00853 }
00854
00855 SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env);
00856 res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
00857
00858 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00859 ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n");
00860 ao2_ref(new, -1);
00861 return res;
00862 }
00863
00864 new->obj_container = ao2_container_alloc(1, null_hash_fn, ao2_match_by_addr);
00865
00866 if (pooling) {
00867 new->haspool = pooling;
00868 if (limit) {
00869 new->limit = limit;
00870 } else {
00871 ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless. Changing limit from 0 to 5.\n");
00872 new->limit = 5;
00873 }
00874 }
00875
00876 new->backslash_is_escape = bse ? 1 : 0;
00877 new->forcecommit = forcecommit ? 1 : 0;
00878 new->isolation = isolation;
00879 new->idlecheck = idlecheck;
00880 new->conntimeout = conntimeout;
00881 new->negative_connection_cache = ncache;
00882
00883 if (cat)
00884 ast_copy_string(new->name, cat, sizeof(new->name));
00885 if (dsn)
00886 ast_copy_string(new->dsn, dsn, sizeof(new->dsn));
00887 if (username && !(new->username = ast_strdup(username))) {
00888 ao2_ref(new, -1);
00889 break;
00890 }
00891 if (password && !(new->password = ast_strdup(password))) {
00892 ao2_ref(new, -1);
00893 break;
00894 }
00895 if (sanitysql && !(new->sanitysql = ast_strdup(sanitysql))) {
00896 ao2_ref(new, -1);
00897 break;
00898 }
00899
00900 odbc_register_class(new, preconnect);
00901 ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn);
00902 ao2_ref(new, -1);
00903 new = NULL;
00904 }
00905 }
00906 }
00907 ast_config_destroy(config);
00908 return res;
00909 }
00910
00911 static char *handle_cli_odbc_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00912 {
00913 struct ao2_iterator aoi = ao2_iterator_init(class_container, 0);
00914 struct odbc_class *class;
00915 struct odbc_obj *current;
00916 int length = 0;
00917 int which = 0;
00918 char *ret = NULL;
00919
00920 switch (cmd) {
00921 case CLI_INIT:
00922 e->command = "odbc show";
00923 e->usage =
00924 "Usage: odbc show [class]\n"
00925 " List settings of a particular ODBC class or,\n"
00926 " if not specified, all classes.\n";
00927 return NULL;
00928 case CLI_GENERATE:
00929 if (a->pos != 2)
00930 return NULL;
00931 length = strlen(a->word);
00932 while ((class = ao2_iterator_next(&aoi))) {
00933 if (!strncasecmp(a->word, class->name, length) && ++which > a->n) {
00934 ret = ast_strdup(class->name);
00935 }
00936 ao2_ref(class, -1);
00937 if (ret) {
00938 break;
00939 }
00940 }
00941 ao2_iterator_destroy(&aoi);
00942 if (!ret && !strncasecmp(a->word, "all", length) && ++which > a->n) {
00943 ret = ast_strdup("all");
00944 }
00945 return ret;
00946 }
00947
00948 ast_cli(a->fd, "\nODBC DSN Settings\n");
00949 ast_cli(a->fd, "-----------------\n\n");
00950 aoi = ao2_iterator_init(class_container, 0);
00951 while ((class = ao2_iterator_next(&aoi))) {
00952 if ((a->argc == 2) || (a->argc == 3 && !strcmp(a->argv[2], "all")) || (!strcmp(a->argv[2], class->name))) {
00953 int count = 0;
00954 char timestr[80];
00955 struct ast_tm tm;
00956
00957 ast_localtime(&class->last_negative_connect, &tm, NULL);
00958 ast_strftime(timestr, sizeof(timestr), "%Y-%m-%d %T", &tm);
00959 ast_cli(a->fd, " Name: %s\n DSN: %s\n", class->name, class->dsn);
00960 ast_cli(a->fd, " Last connection attempt: %s\n", timestr);
00961
00962 if (class->haspool) {
00963 struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
00964
00965 ast_cli(a->fd, " Pooled: Yes\n Limit: %d\n Connections in use: %d\n", class->limit, class->count);
00966
00967 while ((current = ao2_iterator_next(&aoi2))) {
00968 ast_mutex_lock(¤t->lock);
00969 #ifdef DEBUG_THREADS
00970 ast_cli(a->fd, " - Connection %d: %s (%s:%d %s)\n", ++count,
00971 current->used ? "in use" :
00972 current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected",
00973 current->file, current->lineno, current->function);
00974 #else
00975 ast_cli(a->fd, " - Connection %d: %s\n", ++count,
00976 current->used ? "in use" :
00977 current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
00978 #endif
00979 ast_mutex_unlock(¤t->lock);
00980 ao2_ref(current, -1);
00981 }
00982 ao2_iterator_destroy(&aoi2);
00983 } else {
00984
00985 struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
00986 while ((current = ao2_iterator_next(&aoi2))) {
00987 ast_cli(a->fd, " Pooled: No\n Connected: %s\n", current->used ? "In use" :
00988 current->up && ast_odbc_sanity_check(current) ? "Yes" : "No");
00989 ao2_ref(current, -1);
00990 }
00991 ao2_iterator_destroy(&aoi2);
00992 }
00993 ast_cli(a->fd, "\n");
00994 }
00995 ao2_ref(class, -1);
00996 }
00997 ao2_iterator_destroy(&aoi);
00998
00999 return CLI_SUCCESS;
01000 }
01001
01002 static struct ast_cli_entry cli_odbc[] = {
01003 AST_CLI_DEFINE(handle_cli_odbc_show, "List ODBC DSN(s)")
01004 };
01005
01006 static int odbc_register_class(struct odbc_class *class, int preconnect)
01007 {
01008 struct odbc_obj *obj;
01009 if (class) {
01010 ao2_link(class_container, class);
01011
01012
01013 if (preconnect) {
01014
01015 obj = ast_odbc_request_obj(class->name, 0);
01016 if (obj) {
01017 ast_odbc_release_obj(obj);
01018 }
01019 }
01020
01021 return 0;
01022 } else {
01023 ast_log(LOG_WARNING, "Attempted to register a NULL class?\n");
01024 return -1;
01025 }
01026 }
01027
01028 static void odbc_release_obj2(struct odbc_obj *obj, struct odbc_txn_frame *tx)
01029 {
01030 SQLINTEGER nativeerror=0, numfields=0;
01031 SQLSMALLINT diagbytes=0, i;
01032 unsigned char state[10], diagnostic[256];
01033
01034 ast_debug(2, "odbc_release_obj2(%p) called (obj->txf = %p)\n", obj, obj->txf);
01035 if (tx) {
01036 ast_debug(1, "called on a transactional handle with %s\n", tx->forcecommit ? "COMMIT" : "ROLLBACK");
01037 if (SQLEndTran(SQL_HANDLE_DBC, obj->con, tx->forcecommit ? SQL_COMMIT : SQL_ROLLBACK) == SQL_ERROR) {
01038
01039 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01040 for (i = 0; i < numfields; i++) {
01041 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01042 ast_log(LOG_WARNING, "SQLEndTran returned an error: %s: %s\n", state, diagnostic);
01043 if (!strcmp((char *)state, "25S02") || !strcmp((char *)state, "08007")) {
01044
01045
01046
01047 SQLEndTran(SQL_HANDLE_DBC, obj->con, SQL_ROLLBACK);
01048 }
01049 if (i > 10) {
01050 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01051 break;
01052 }
01053 }
01054 }
01055
01056
01057 if (SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_ON, 0) == SQL_ERROR) {
01058 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01059 for (i = 0; i < numfields; i++) {
01060 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01061 ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
01062 if (i > 10) {
01063 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01064 break;
01065 }
01066 }
01067 }
01068 }
01069
01070 #ifdef DEBUG_THREADS
01071 obj->file[0] = '\0';
01072 obj->function[0] = '\0';
01073 obj->lineno = 0;
01074 #endif
01075
01076
01077
01078 obj->used = 0;
01079 if (obj->txf) {
01080
01081 obj->txf->obj = NULL;
01082 obj->txf = release_transaction(obj->txf);
01083 }
01084 ao2_ref(obj, -1);
01085 }
01086
01087 void ast_odbc_release_obj(struct odbc_obj *obj)
01088 {
01089 struct odbc_txn_frame *tx = find_transaction(NULL, obj, NULL, 0);
01090 odbc_release_obj2(obj, tx);
01091 }
01092
01093 int ast_odbc_backslash_is_escape(struct odbc_obj *obj)
01094 {
01095 return obj->parent->backslash_is_escape;
01096 }
01097
01098 static int commit_exec(struct ast_channel *chan, const char *data)
01099 {
01100 struct odbc_txn_frame *tx;
01101 SQLINTEGER nativeerror=0, numfields=0;
01102 SQLSMALLINT diagbytes=0, i;
01103 unsigned char state[10], diagnostic[256];
01104
01105 if (ast_strlen_zero(data)) {
01106 tx = find_transaction(chan, NULL, NULL, 1);
01107 } else {
01108 tx = find_transaction(chan, NULL, data, 0);
01109 }
01110
01111 pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", "OK");
01112
01113 if (tx) {
01114 if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_COMMIT) == SQL_ERROR) {
01115 struct ast_str *errors = ast_str_thread_get(&errors_buf, 16);
01116 ast_str_reset(errors);
01117
01118
01119 SQLGetDiagField(SQL_HANDLE_DBC, tx->obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01120 for (i = 0; i < numfields; i++) {
01121 SQLGetDiagRec(SQL_HANDLE_DBC, tx->obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01122 ast_str_append(&errors, 0, "%s%s", ast_str_strlen(errors) ? "," : "", state);
01123 ast_log(LOG_WARNING, "SQLEndTran returned an error: %s: %s\n", state, diagnostic);
01124 if (i > 10) {
01125 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01126 break;
01127 }
01128 }
01129 pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", ast_str_buffer(errors));
01130 }
01131 }
01132 return 0;
01133 }
01134
01135 static int rollback_exec(struct ast_channel *chan, const char *data)
01136 {
01137 struct odbc_txn_frame *tx;
01138 SQLINTEGER nativeerror=0, numfields=0;
01139 SQLSMALLINT diagbytes=0, i;
01140 unsigned char state[10], diagnostic[256];
01141
01142 if (ast_strlen_zero(data)) {
01143 tx = find_transaction(chan, NULL, NULL, 1);
01144 } else {
01145 tx = find_transaction(chan, NULL, data, 0);
01146 }
01147
01148 pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", "OK");
01149
01150 if (tx) {
01151 if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_ROLLBACK) == SQL_ERROR) {
01152 struct ast_str *errors = ast_str_thread_get(&errors_buf, 16);
01153 ast_str_reset(errors);
01154
01155
01156 SQLGetDiagField(SQL_HANDLE_DBC, tx->obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01157 for (i = 0; i < numfields; i++) {
01158 SQLGetDiagRec(SQL_HANDLE_DBC, tx->obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01159 ast_str_append(&errors, 0, "%s%s", ast_str_strlen(errors) ? "," : "", state);
01160 ast_log(LOG_WARNING, "SQLEndTran returned an error: %s: %s\n", state, diagnostic);
01161 if (i > 10) {
01162 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01163 break;
01164 }
01165 }
01166 pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", ast_str_buffer(errors));
01167 }
01168 }
01169 return 0;
01170 }
01171
01172 static int aoro2_class_cb(void *obj, void *arg, int flags)
01173 {
01174 struct odbc_class *class = obj;
01175 char *name = arg;
01176 if (!strcmp(class->name, name) && !class->delme) {
01177 return CMP_MATCH | CMP_STOP;
01178 }
01179 return 0;
01180 }
01181
01182 #define USE_TX (void *)(long)1
01183 #define NO_TX (void *)(long)2
01184 #define EOR_TX (void *)(long)3
01185
01186 static int aoro2_obj_cb(void *vobj, void *arg, int flags)
01187 {
01188 struct odbc_obj *obj = vobj;
01189 ast_mutex_lock(&obj->lock);
01190 if ((arg == NO_TX && !obj->tx) || (arg == EOR_TX && !obj->used) || (arg == USE_TX && obj->tx && !obj->used)) {
01191 obj->used = 1;
01192 ast_mutex_unlock(&obj->lock);
01193 return CMP_MATCH | CMP_STOP;
01194 }
01195 ast_mutex_unlock(&obj->lock);
01196 return 0;
01197 }
01198
01199 struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags, const char *file, const char *function, int lineno)
01200 {
01201 struct odbc_obj *obj = NULL;
01202 struct odbc_class *class;
01203 SQLINTEGER nativeerror=0, numfields=0;
01204 SQLSMALLINT diagbytes=0, i;
01205 unsigned char state[10], diagnostic[256];
01206
01207 if (!(class = ao2_callback(class_container, 0, aoro2_class_cb, (char *) name))) {
01208 ast_debug(1, "Class '%s' not found!\n", name);
01209 return NULL;
01210 }
01211
01212 ast_assert(ao2_ref(class, 0) > 1);
01213
01214 if (class->haspool) {
01215
01216 obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, EOR_TX);
01217
01218 if (obj) {
01219 ast_assert(ao2_ref(obj, 0) > 1);
01220 }
01221 if (!obj && (ast_atomic_fetchadd_int(&class->count, +1) < class->limit) &&
01222 (time(NULL) > class->last_negative_connect.tv_sec + class->negative_connection_cache.tv_sec)) {
01223 obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor);
01224 if (!obj) {
01225 class->count--;
01226 ao2_ref(class, -1);
01227 ast_debug(3, "Unable to allocate object\n");
01228 ast_atomic_fetchadd_int(&class->count, -1);
01229 return NULL;
01230 }
01231 ast_assert(ao2_ref(obj, 0) == 1);
01232 ast_mutex_init(&obj->lock);
01233
01234 obj->parent = class;
01235 class = NULL;
01236 if (odbc_obj_connect(obj) == ODBC_FAIL) {
01237 ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
01238 ao2_ref(obj, -1);
01239 obj = NULL;
01240 ast_assert(ao2_ref(class, 0) > 0);
01241 ast_atomic_fetchadd_int(&class->count, -1);
01242 } else {
01243 obj->used = 1;
01244 ao2_link(obj->parent->obj_container, obj);
01245 }
01246 } else {
01247
01248 if (!obj) {
01249 ast_atomic_fetchadd_int(&class->count, -1);
01250 }
01251
01252 ao2_ref(class, -1);
01253 class = NULL;
01254 }
01255
01256 if (obj && ast_test_flag(&flags, RES_ODBC_INDEPENDENT_CONNECTION)) {
01257
01258 if (SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
01259 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01260 for (i = 0; i < numfields; i++) {
01261 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01262 ast_log(LOG_WARNING, "SQLSetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
01263 if (i > 10) {
01264 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01265 break;
01266 }
01267 }
01268 }
01269 }
01270 } else if (ast_test_flag(&flags, RES_ODBC_INDEPENDENT_CONNECTION)) {
01271
01272 if (!(obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, USE_TX))) {
01273 ast_debug(1, "Object not found\n");
01274 obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor);
01275 if (!obj) {
01276 ao2_ref(class, -1);
01277 ast_debug(3, "Unable to allocate object\n");
01278 return NULL;
01279 }
01280 ast_mutex_init(&obj->lock);
01281
01282 obj->parent = class;
01283 class = NULL;
01284 if (odbc_obj_connect(obj) == ODBC_FAIL) {
01285 ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
01286 ao2_ref(obj, -1);
01287 obj = NULL;
01288 } else {
01289 obj->used = 1;
01290 ao2_link(obj->parent->obj_container, obj);
01291 ast_atomic_fetchadd_int(&obj->parent->count, +1);
01292 }
01293 }
01294
01295 if (obj && SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
01296 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01297 for (i = 0; i < numfields; i++) {
01298 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01299 ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
01300 if (i > 10) {
01301 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01302 break;
01303 }
01304 }
01305 }
01306 } else {
01307
01308 if ((obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, NO_TX))) {
01309
01310 ast_assert(ao2_ref(class, 0) > 1);
01311 ao2_ref(class, -1);
01312 class = NULL;
01313 } else {
01314
01315 if (!(obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor))) {
01316 ast_assert(ao2_ref(class, 0) > 1);
01317 ao2_ref(class, -1);
01318 ast_debug(3, "Unable to allocate object\n");
01319 return NULL;
01320 }
01321 ast_mutex_init(&obj->lock);
01322
01323 obj->parent = class;
01324 class = NULL;
01325 if (odbc_obj_connect(obj) == ODBC_FAIL) {
01326 ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
01327 ao2_ref(obj, -1);
01328 obj = NULL;
01329 } else {
01330 ao2_link(obj->parent->obj_container, obj);
01331 ast_assert(ao2_ref(obj, 0) > 1);
01332 }
01333 }
01334
01335 if (obj && SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_ON, 0) == SQL_ERROR) {
01336 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01337 for (i = 0; i < numfields; i++) {
01338 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01339 ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
01340 if (i > 10) {
01341 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01342 break;
01343 }
01344 }
01345 }
01346 }
01347
01348
01349 if (obj && SQLSetConnectAttr(obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)obj->parent->isolation, 0) == SQL_ERROR) {
01350 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01351 for (i = 0; i < numfields; i++) {
01352 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01353 ast_log(LOG_WARNING, "SetConnectAttr (Txn isolation) returned an error: %s: %s\n", state, diagnostic);
01354 if (i > 10) {
01355 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01356 break;
01357 }
01358 }
01359 }
01360
01361 if (obj && ast_test_flag(&flags, RES_ODBC_CONNECTED) && !obj->up) {
01362
01363 if (time(NULL) > class->last_negative_connect.tv_sec + class->negative_connection_cache.tv_sec) {
01364 odbc_obj_connect(obj);
01365 }
01366 } else if (obj && ast_test_flag(&flags, RES_ODBC_SANITY_CHECK)) {
01367 ast_odbc_sanity_check(obj);
01368 } else if (obj && obj->parent->idlecheck > 0 && ast_tvdiff_sec(ast_tvnow(), obj->last_used) > obj->parent->idlecheck) {
01369 odbc_obj_connect(obj);
01370 }
01371
01372 #ifdef DEBUG_THREADS
01373 if (obj) {
01374 ast_copy_string(obj->file, file, sizeof(obj->file));
01375 ast_copy_string(obj->function, function, sizeof(obj->function));
01376 obj->lineno = lineno;
01377 }
01378 #endif
01379 ast_assert(class == NULL);
01380
01381 if (obj) {
01382 ast_assert(ao2_ref(obj, 0) > 1);
01383 }
01384 return obj;
01385 }
01386
01387 struct odbc_obj *_ast_odbc_request_obj(const char *name, int check, const char *file, const char *function, int lineno)
01388 {
01389 struct ast_flags flags = { check ? RES_ODBC_SANITY_CHECK : 0 };
01390 return _ast_odbc_request_obj2(name, flags, file, function, lineno);
01391 }
01392
01393 struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname)
01394 {
01395 struct ast_datastore *txn_store;
01396 AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
01397 struct odbc_txn_frame *txn = NULL;
01398
01399 if (!chan) {
01400
01401 return NULL;
01402 }
01403
01404 ast_channel_lock(chan);
01405 if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
01406 oldlist = txn_store->data;
01407 } else {
01408 ast_channel_unlock(chan);
01409 return NULL;
01410 }
01411
01412 AST_LIST_LOCK(oldlist);
01413 ast_channel_unlock(chan);
01414
01415 AST_LIST_TRAVERSE(oldlist, txn, list) {
01416 if (txn->obj && txn->obj->parent && !strcmp(txn->obj->parent->name, objname)) {
01417 AST_LIST_UNLOCK(oldlist);
01418 return txn->obj;
01419 }
01420 }
01421 AST_LIST_UNLOCK(oldlist);
01422 return NULL;
01423 }
01424
01425 static odbc_status odbc_obj_disconnect(struct odbc_obj *obj)
01426 {
01427 int res;
01428 SQLINTEGER err;
01429 short int mlen;
01430 unsigned char msg[200], state[10];
01431
01432
01433 if (!obj->con) {
01434 return ODBC_SUCCESS;
01435 }
01436
01437 ast_mutex_lock(&obj->lock);
01438
01439 res = SQLDisconnect(obj->con);
01440
01441 if (obj->parent) {
01442 if (res == SQL_SUCCESS || res == SQL_SUCCESS_WITH_INFO) {
01443 ast_log(LOG_DEBUG, "Disconnected %d from %s [%s]\n", res, obj->parent->name, obj->parent->dsn);
01444 } else {
01445 ast_log(LOG_DEBUG, "res_odbc: %s [%s] already disconnected\n", obj->parent->name, obj->parent->dsn);
01446 }
01447 }
01448
01449 if ((res = SQLFreeHandle(SQL_HANDLE_DBC, obj->con) == SQL_SUCCESS)) {
01450 obj->con = NULL;
01451 ast_log(LOG_DEBUG, "Database handle deallocated\n");
01452 } else {
01453 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, state, &err, msg, 100, &mlen);
01454 ast_log(LOG_WARNING, "Unable to deallocate database handle? %d errno=%d %s\n", res, (int)err, msg);
01455 }
01456
01457 obj->up = 0;
01458 ast_mutex_unlock(&obj->lock);
01459 return ODBC_SUCCESS;
01460 }
01461
01462 static odbc_status odbc_obj_connect(struct odbc_obj *obj)
01463 {
01464 int res;
01465 SQLINTEGER err;
01466 short int mlen;
01467 unsigned char msg[200], state[10];
01468 #ifdef NEEDTRACE
01469 SQLINTEGER enable = 1;
01470 char *tracefile = "/tmp/odbc.trace";
01471 #endif
01472 ast_mutex_lock(&obj->lock);
01473
01474 if (obj->up) {
01475 odbc_obj_disconnect(obj);
01476 ast_log(LOG_NOTICE, "Re-connecting %s\n", obj->parent->name);
01477 } else {
01478 ast_log(LOG_NOTICE, "Connecting %s\n", obj->parent->name);
01479 }
01480
01481 res = SQLAllocHandle(SQL_HANDLE_DBC, obj->parent->env, &obj->con);
01482
01483 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
01484 ast_log(LOG_WARNING, "res_odbc: Error AllocHDB %d\n", res);
01485 obj->parent->last_negative_connect = ast_tvnow();
01486 ast_mutex_unlock(&obj->lock);
01487 return ODBC_FAIL;
01488 }
01489 SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)(long) obj->parent->conntimeout, 0);
01490 SQLSetConnectAttr(obj->con, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER *)(long) obj->parent->conntimeout, 0);
01491 #ifdef NEEDTRACE
01492 SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER);
01493 SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile));
01494 #endif
01495
01496 res = SQLConnect(obj->con,
01497 (SQLCHAR *) obj->parent->dsn, SQL_NTS,
01498 (SQLCHAR *) obj->parent->username, SQL_NTS,
01499 (SQLCHAR *) obj->parent->password, SQL_NTS);
01500
01501 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
01502 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, state, &err, msg, 100, &mlen);
01503 obj->parent->last_negative_connect = ast_tvnow();
01504 ast_mutex_unlock(&obj->lock);
01505 ast_log(LOG_WARNING, "res_odbc: Error SQLConnect=%d errno=%d %s\n", res, (int)err, msg);
01506 return ODBC_FAIL;
01507 } else {
01508 ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->parent->name, obj->parent->dsn);
01509 obj->up = 1;
01510 obj->last_used = ast_tvnow();
01511 }
01512
01513 ast_mutex_unlock(&obj->lock);
01514 return ODBC_SUCCESS;
01515 }
01516
01517 static int acf_transaction_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
01518 {
01519 AST_DECLARE_APP_ARGS(args,
01520 AST_APP_ARG(property);
01521 AST_APP_ARG(opt);
01522 );
01523 struct odbc_txn_frame *tx;
01524
01525 AST_STANDARD_APP_ARGS(args, data);
01526 if (strcasecmp(args.property, "transaction") == 0) {
01527 if ((tx = find_transaction(chan, NULL, NULL, 1))) {
01528 ast_copy_string(buf, tx->name, len);
01529 return 0;
01530 }
01531 } else if (strcasecmp(args.property, "isolation") == 0) {
01532 if (!ast_strlen_zero(args.opt)) {
01533 tx = find_transaction(chan, NULL, args.opt, 0);
01534 } else {
01535 tx = find_transaction(chan, NULL, NULL, 1);
01536 }
01537 if (tx) {
01538 ast_copy_string(buf, isolation2text(tx->isolation), len);
01539 return 0;
01540 }
01541 } else if (strcasecmp(args.property, "forcecommit") == 0) {
01542 if (!ast_strlen_zero(args.opt)) {
01543 tx = find_transaction(chan, NULL, args.opt, 0);
01544 } else {
01545 tx = find_transaction(chan, NULL, NULL, 1);
01546 }
01547 if (tx) {
01548 ast_copy_string(buf, tx->forcecommit ? "1" : "0", len);
01549 return 0;
01550 }
01551 }
01552 return -1;
01553 }
01554
01555 static int acf_transaction_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
01556 {
01557 AST_DECLARE_APP_ARGS(args,
01558 AST_APP_ARG(property);
01559 AST_APP_ARG(opt);
01560 );
01561 struct odbc_txn_frame *tx;
01562 SQLINTEGER nativeerror=0, numfields=0;
01563 SQLSMALLINT diagbytes=0, i;
01564 unsigned char state[10], diagnostic[256];
01565
01566 AST_STANDARD_APP_ARGS(args, s);
01567 if (strcasecmp(args.property, "transaction") == 0) {
01568
01569 struct odbc_obj *obj;
01570 if ((tx = find_transaction(chan, NULL, value, 0))) {
01571 mark_transaction_active(chan, tx);
01572 } else {
01573
01574 struct ast_flags flags = { RES_ODBC_INDEPENDENT_CONNECTION };
01575 if (ast_strlen_zero(args.opt) || !(obj = ast_odbc_request_obj2(args.opt, flags))) {
01576 ast_log(LOG_ERROR, "Could not create transaction: invalid database specification '%s'\n", S_OR(args.opt, ""));
01577 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_DB");
01578 return -1;
01579 }
01580 if (!(tx = find_transaction(chan, obj, value, 0))) {
01581 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
01582 return -1;
01583 }
01584 obj->tx = 1;
01585 }
01586 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
01587 return 0;
01588 } else if (strcasecmp(args.property, "forcecommit") == 0) {
01589
01590 if (ast_strlen_zero(args.opt)) {
01591 tx = find_transaction(chan, NULL, NULL, 1);
01592 } else {
01593 tx = find_transaction(chan, NULL, args.opt, 0);
01594 }
01595 if (!tx) {
01596 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
01597 return -1;
01598 }
01599 if (ast_true(value)) {
01600 tx->forcecommit = 1;
01601 } else if (ast_false(value)) {
01602 tx->forcecommit = 0;
01603 } else {
01604 ast_log(LOG_ERROR, "Invalid value for forcecommit: '%s'\n", S_OR(value, ""));
01605 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
01606 return -1;
01607 }
01608
01609 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
01610 return 0;
01611 } else if (strcasecmp(args.property, "isolation") == 0) {
01612
01613 int isolation = text2isolation(value);
01614 if (ast_strlen_zero(args.opt)) {
01615 tx = find_transaction(chan, NULL, NULL, 1);
01616 } else {
01617 tx = find_transaction(chan, NULL, args.opt, 0);
01618 }
01619 if (!tx) {
01620 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
01621 return -1;
01622 }
01623 if (isolation == 0) {
01624 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
01625 ast_log(LOG_ERROR, "Invalid isolation specification: '%s'\n", S_OR(value, ""));
01626 } else if (SQLSetConnectAttr(tx->obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)isolation, 0) == SQL_ERROR) {
01627 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "SQL_ERROR");
01628 SQLGetDiagField(SQL_HANDLE_DBC, tx->obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01629 for (i = 0; i < numfields; i++) {
01630 SQLGetDiagRec(SQL_HANDLE_DBC, tx->obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01631 ast_log(LOG_WARNING, "SetConnectAttr (Txn isolation) returned an error: %s: %s\n", state, diagnostic);
01632 if (i > 10) {
01633 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01634 break;
01635 }
01636 }
01637 } else {
01638 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
01639 tx->isolation = isolation;
01640 }
01641 return 0;
01642 } else {
01643 ast_log(LOG_ERROR, "Unknown property: '%s'\n", args.property);
01644 return -1;
01645 }
01646 }
01647
01648 static struct ast_custom_function odbc_function = {
01649 .name = "ODBC",
01650 .read = acf_transaction_read,
01651 .write = acf_transaction_write,
01652 };
01653
01654 static const char * const app_commit = "ODBC_Commit";
01655 static const char * const app_rollback = "ODBC_Rollback";
01656
01657
01658
01659
01660
01661 static int data_odbc_provider_handler(const struct ast_data_search *search,
01662 struct ast_data *root)
01663 {
01664 struct ao2_iterator aoi, aoi2;
01665 struct odbc_class *class;
01666 struct odbc_obj *current;
01667 struct ast_data *data_odbc_class, *data_odbc_connections, *data_odbc_connection;
01668 struct ast_data *enum_node;
01669 int count;
01670
01671 aoi = ao2_iterator_init(class_container, 0);
01672 while ((class = ao2_iterator_next(&aoi))) {
01673 data_odbc_class = ast_data_add_node(root, "class");
01674 if (!data_odbc_class) {
01675 ao2_ref(class, -1);
01676 continue;
01677 }
01678
01679 ast_data_add_structure(odbc_class, data_odbc_class, class);
01680
01681 if (!ao2_container_count(class->obj_container)) {
01682 ao2_ref(class, -1);
01683 continue;
01684 }
01685
01686 data_odbc_connections = ast_data_add_node(data_odbc_class, "connections");
01687 if (!data_odbc_connections) {
01688 ao2_ref(class, -1);
01689 continue;
01690 }
01691
01692 ast_data_add_bool(data_odbc_class, "shared", !class->haspool);
01693
01694 enum_node = ast_data_add_node(data_odbc_class, "isolation");
01695 if (!enum_node) {
01696 ao2_ref(class, -1);
01697 continue;
01698 }
01699 ast_data_add_int(enum_node, "value", class->isolation);
01700 ast_data_add_str(enum_node, "text", isolation2text(class->isolation));
01701
01702 count = 0;
01703 aoi2 = ao2_iterator_init(class->obj_container, 0);
01704 while ((current = ao2_iterator_next(&aoi2))) {
01705 data_odbc_connection = ast_data_add_node(data_odbc_connections, "connection");
01706 if (!data_odbc_connection) {
01707 ao2_ref(current, -1);
01708 continue;
01709 }
01710
01711 ast_mutex_lock(¤t->lock);
01712 ast_data_add_str(data_odbc_connection, "status", current->used ? "in use" :
01713 current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
01714 ast_data_add_bool(data_odbc_connection, "transactional", current->tx);
01715 ast_mutex_unlock(¤t->lock);
01716
01717 if (class->haspool) {
01718 ast_data_add_int(data_odbc_connection, "number", ++count);
01719 }
01720
01721 ao2_ref(current, -1);
01722 }
01723 ao2_ref(class, -1);
01724
01725 if (!ast_data_search_match(search, data_odbc_class)) {
01726 ast_data_remove_node(root, data_odbc_class);
01727 }
01728 }
01729 return 0;
01730 }
01731
01732
01733
01734
01735
01736 static const struct ast_data_handler odbc_provider = {
01737 .version = AST_DATA_HANDLER_VERSION,
01738 .get = data_odbc_provider_handler
01739 };
01740
01741 static const struct ast_data_entry odbc_providers[] = {
01742 AST_DATA_ENTRY("/asterisk/res/odbc", &odbc_provider),
01743 };
01744
01745 static int reload(void)
01746 {
01747 struct odbc_cache_tables *table;
01748 struct odbc_class *class;
01749 struct odbc_obj *current;
01750 struct ao2_iterator aoi = ao2_iterator_init(class_container, 0);
01751
01752
01753 while ((class = ao2_iterator_next(&aoi))) {
01754 class->delme = 1;
01755 ao2_ref(class, -1);
01756 }
01757 ao2_iterator_destroy(&aoi);
01758
01759 load_odbc_config();
01760
01761
01762
01763
01764
01765
01766
01767
01768
01769
01770
01771
01772
01773
01774
01775
01776
01777
01778
01779
01780
01781
01782
01783
01784
01785 aoi = ao2_iterator_init(class_container, 0);
01786 while ((class = ao2_iterator_next(&aoi))) {
01787 if (class->delme) {
01788 struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
01789 while ((current = ao2_iterator_next(&aoi2))) {
01790 ao2_unlink(class->obj_container, current);
01791 ao2_ref(current, -1);
01792
01793
01794
01795
01796 }
01797 ao2_iterator_destroy(&aoi2);
01798 ao2_unlink(class_container, class);
01799
01800
01801
01802
01803
01804 }
01805 ao2_ref(class, -1);
01806 }
01807 ao2_iterator_destroy(&aoi);
01808
01809
01810 AST_RWLIST_WRLOCK(&odbc_tables);
01811 while ((table = AST_RWLIST_REMOVE_HEAD(&odbc_tables, list))) {
01812 destroy_table_cache(table);
01813 }
01814 AST_RWLIST_UNLOCK(&odbc_tables);
01815
01816 return 0;
01817 }
01818
01819 static int unload_module(void)
01820 {
01821
01822 return -1;
01823 }
01824
01825 static int load_module(void)
01826 {
01827 if (!(class_container = ao2_container_alloc(1, null_hash_fn, ao2_match_by_addr)))
01828 return AST_MODULE_LOAD_DECLINE;
01829 if (load_odbc_config() == -1)
01830 return AST_MODULE_LOAD_DECLINE;
01831 ast_cli_register_multiple(cli_odbc, ARRAY_LEN(cli_odbc));
01832 ast_data_register_multiple(odbc_providers, ARRAY_LEN(odbc_providers));
01833 ast_register_application_xml(app_commit, commit_exec);
01834 ast_register_application_xml(app_rollback, rollback_exec);
01835 ast_custom_function_register(&odbc_function);
01836 ast_log(LOG_NOTICE, "res_odbc loaded.\n");
01837 return 0;
01838 }
01839
01840 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "ODBC resource",
01841 .load = load_module,
01842 .unload = unload_module,
01843 .reload = reload,
01844 .load_pri = AST_MODPRI_REALTIME_DEPEND,
01845 );