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