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: 300623 $")
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 && (class->count < 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 return NULL;
01229 }
01230 ast_assert(ao2_ref(obj, 0) == 1);
01231 ast_mutex_init(&obj->lock);
01232
01233 obj->parent = class;
01234 class = NULL;
01235 if (odbc_obj_connect(obj) == ODBC_FAIL) {
01236 ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
01237 ao2_ref(obj, -1);
01238 ast_assert(ao2_ref(class, 0) > 0);
01239 obj = NULL;
01240 } else {
01241 obj->used = 1;
01242 ao2_link(obj->parent->obj_container, obj);
01243 ast_atomic_fetchadd_int(&obj->parent->count, +1);
01244 }
01245 } else {
01246
01247 ao2_ref(class, -1);
01248 class = NULL;
01249 }
01250
01251 if (obj && ast_test_flag(&flags, RES_ODBC_INDEPENDENT_CONNECTION)) {
01252
01253 if (SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
01254 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01255 for (i = 0; i < numfields; i++) {
01256 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01257 ast_log(LOG_WARNING, "SQLSetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
01258 if (i > 10) {
01259 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01260 break;
01261 }
01262 }
01263 }
01264 }
01265 } else if (ast_test_flag(&flags, RES_ODBC_INDEPENDENT_CONNECTION)) {
01266
01267 if (!(obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, USE_TX))) {
01268 ast_debug(1, "Object not found\n");
01269 obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor);
01270 if (!obj) {
01271 ao2_ref(class, -1);
01272 ast_debug(3, "Unable to allocate object\n");
01273 return NULL;
01274 }
01275 ast_mutex_init(&obj->lock);
01276
01277 obj->parent = class;
01278 class = NULL;
01279 if (odbc_obj_connect(obj) == ODBC_FAIL) {
01280 ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
01281 ao2_ref(obj, -1);
01282 obj = NULL;
01283 } else {
01284 obj->used = 1;
01285 ao2_link(obj->parent->obj_container, obj);
01286 ast_atomic_fetchadd_int(&obj->parent->count, +1);
01287 }
01288 }
01289
01290 if (obj && SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
01291 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01292 for (i = 0; i < numfields; i++) {
01293 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01294 ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
01295 if (i > 10) {
01296 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01297 break;
01298 }
01299 }
01300 }
01301 } else {
01302
01303 if ((obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, NO_TX))) {
01304
01305 ast_assert(ao2_ref(class, 0) > 1);
01306 ao2_ref(class, -1);
01307 class = NULL;
01308 } else {
01309
01310 if (!(obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor))) {
01311 ast_assert(ao2_ref(class, 0) > 1);
01312 ao2_ref(class, -1);
01313 ast_debug(3, "Unable to allocate object\n");
01314 return NULL;
01315 }
01316 ast_mutex_init(&obj->lock);
01317
01318 obj->parent = class;
01319 class = NULL;
01320 if (odbc_obj_connect(obj) == ODBC_FAIL) {
01321 ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
01322 ao2_ref(obj, -1);
01323 obj = NULL;
01324 } else {
01325 ao2_link(obj->parent->obj_container, obj);
01326 ast_assert(ao2_ref(obj, 0) > 1);
01327 }
01328 }
01329
01330 if (obj && SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_ON, 0) == SQL_ERROR) {
01331 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01332 for (i = 0; i < numfields; i++) {
01333 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01334 ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
01335 if (i > 10) {
01336 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01337 break;
01338 }
01339 }
01340 }
01341 }
01342
01343
01344 if (obj && SQLSetConnectAttr(obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)obj->parent->isolation, 0) == SQL_ERROR) {
01345 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01346 for (i = 0; i < numfields; i++) {
01347 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01348 ast_log(LOG_WARNING, "SetConnectAttr (Txn isolation) returned an error: %s: %s\n", state, diagnostic);
01349 if (i > 10) {
01350 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01351 break;
01352 }
01353 }
01354 }
01355
01356 if (obj && ast_test_flag(&flags, RES_ODBC_CONNECTED) && !obj->up) {
01357
01358 if (time(NULL) > class->last_negative_connect.tv_sec + class->negative_connection_cache.tv_sec) {
01359 odbc_obj_connect(obj);
01360 }
01361 } else if (obj && ast_test_flag(&flags, RES_ODBC_SANITY_CHECK)) {
01362 ast_odbc_sanity_check(obj);
01363 } else if (obj && obj->parent->idlecheck > 0 && ast_tvdiff_sec(ast_tvnow(), obj->last_used) > obj->parent->idlecheck) {
01364 odbc_obj_connect(obj);
01365 }
01366
01367 #ifdef DEBUG_THREADS
01368 if (obj) {
01369 ast_copy_string(obj->file, file, sizeof(obj->file));
01370 ast_copy_string(obj->function, function, sizeof(obj->function));
01371 obj->lineno = lineno;
01372 }
01373 #endif
01374 ast_assert(class == NULL);
01375
01376 if (obj) {
01377 ast_assert(ao2_ref(obj, 0) > 1);
01378 }
01379 return obj;
01380 }
01381
01382 struct odbc_obj *_ast_odbc_request_obj(const char *name, int check, const char *file, const char *function, int lineno)
01383 {
01384 struct ast_flags flags = { check ? RES_ODBC_SANITY_CHECK : 0 };
01385 return _ast_odbc_request_obj2(name, flags, file, function, lineno);
01386 }
01387
01388 struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname)
01389 {
01390 struct ast_datastore *txn_store;
01391 AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
01392 struct odbc_txn_frame *txn = NULL;
01393
01394 if (!chan) {
01395
01396 return NULL;
01397 }
01398
01399 ast_channel_lock(chan);
01400 if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
01401 oldlist = txn_store->data;
01402 } else {
01403 ast_channel_unlock(chan);
01404 return NULL;
01405 }
01406
01407 AST_LIST_LOCK(oldlist);
01408 ast_channel_unlock(chan);
01409
01410 AST_LIST_TRAVERSE(oldlist, txn, list) {
01411 if (txn->obj && txn->obj->parent && !strcmp(txn->obj->parent->name, objname)) {
01412 AST_LIST_UNLOCK(oldlist);
01413 return txn->obj;
01414 }
01415 }
01416 AST_LIST_UNLOCK(oldlist);
01417 return NULL;
01418 }
01419
01420 static odbc_status odbc_obj_disconnect(struct odbc_obj *obj)
01421 {
01422 int res;
01423 SQLINTEGER err;
01424 short int mlen;
01425 unsigned char msg[200], state[10];
01426
01427
01428 if (!obj->con) {
01429 return ODBC_SUCCESS;
01430 }
01431
01432 ast_mutex_lock(&obj->lock);
01433
01434 res = SQLDisconnect(obj->con);
01435
01436 if (obj->parent) {
01437 if (res == SQL_SUCCESS || res == SQL_SUCCESS_WITH_INFO) {
01438 ast_log(LOG_DEBUG, "Disconnected %d from %s [%s]\n", res, obj->parent->name, obj->parent->dsn);
01439 } else {
01440 ast_log(LOG_DEBUG, "res_odbc: %s [%s] already disconnected\n", obj->parent->name, obj->parent->dsn);
01441 }
01442 }
01443
01444 if ((res = SQLFreeHandle(SQL_HANDLE_DBC, obj->con) == SQL_SUCCESS)) {
01445 obj->con = NULL;
01446 ast_log(LOG_DEBUG, "Database handle deallocated\n");
01447 } else {
01448 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, state, &err, msg, 100, &mlen);
01449 ast_log(LOG_WARNING, "Unable to deallocate database handle? %d errno=%d %s\n", res, (int)err, msg);
01450 }
01451
01452 obj->up = 0;
01453 ast_mutex_unlock(&obj->lock);
01454 return ODBC_SUCCESS;
01455 }
01456
01457 static odbc_status odbc_obj_connect(struct odbc_obj *obj)
01458 {
01459 int res;
01460 SQLINTEGER err;
01461 short int mlen;
01462 unsigned char msg[200], state[10];
01463 #ifdef NEEDTRACE
01464 SQLINTEGER enable = 1;
01465 char *tracefile = "/tmp/odbc.trace";
01466 #endif
01467 ast_mutex_lock(&obj->lock);
01468
01469 if (obj->up) {
01470 odbc_obj_disconnect(obj);
01471 ast_log(LOG_NOTICE, "Re-connecting %s\n", obj->parent->name);
01472 } else {
01473 ast_log(LOG_NOTICE, "Connecting %s\n", obj->parent->name);
01474 }
01475
01476 res = SQLAllocHandle(SQL_HANDLE_DBC, obj->parent->env, &obj->con);
01477
01478 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
01479 ast_log(LOG_WARNING, "res_odbc: Error AllocHDB %d\n", res);
01480 obj->parent->last_negative_connect = ast_tvnow();
01481 ast_mutex_unlock(&obj->lock);
01482 return ODBC_FAIL;
01483 }
01484 SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)(long) obj->parent->conntimeout, 0);
01485 SQLSetConnectAttr(obj->con, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER *)(long) obj->parent->conntimeout, 0);
01486 #ifdef NEEDTRACE
01487 SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER);
01488 SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile));
01489 #endif
01490
01491 res = SQLConnect(obj->con,
01492 (SQLCHAR *) obj->parent->dsn, SQL_NTS,
01493 (SQLCHAR *) obj->parent->username, SQL_NTS,
01494 (SQLCHAR *) obj->parent->password, SQL_NTS);
01495
01496 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
01497 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, state, &err, msg, 100, &mlen);
01498 obj->parent->last_negative_connect = ast_tvnow();
01499 ast_mutex_unlock(&obj->lock);
01500 ast_log(LOG_WARNING, "res_odbc: Error SQLConnect=%d errno=%d %s\n", res, (int)err, msg);
01501 return ODBC_FAIL;
01502 } else {
01503 ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->parent->name, obj->parent->dsn);
01504 obj->up = 1;
01505 obj->last_used = ast_tvnow();
01506 }
01507
01508 ast_mutex_unlock(&obj->lock);
01509 return ODBC_SUCCESS;
01510 }
01511
01512 static int acf_transaction_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
01513 {
01514 AST_DECLARE_APP_ARGS(args,
01515 AST_APP_ARG(property);
01516 AST_APP_ARG(opt);
01517 );
01518 struct odbc_txn_frame *tx;
01519
01520 AST_STANDARD_APP_ARGS(args, data);
01521 if (strcasecmp(args.property, "transaction") == 0) {
01522 if ((tx = find_transaction(chan, NULL, NULL, 1))) {
01523 ast_copy_string(buf, tx->name, len);
01524 return 0;
01525 }
01526 } else if (strcasecmp(args.property, "isolation") == 0) {
01527 if (!ast_strlen_zero(args.opt)) {
01528 tx = find_transaction(chan, NULL, args.opt, 0);
01529 } else {
01530 tx = find_transaction(chan, NULL, NULL, 1);
01531 }
01532 if (tx) {
01533 ast_copy_string(buf, isolation2text(tx->isolation), len);
01534 return 0;
01535 }
01536 } else if (strcasecmp(args.property, "forcecommit") == 0) {
01537 if (!ast_strlen_zero(args.opt)) {
01538 tx = find_transaction(chan, NULL, args.opt, 0);
01539 } else {
01540 tx = find_transaction(chan, NULL, NULL, 1);
01541 }
01542 if (tx) {
01543 ast_copy_string(buf, tx->forcecommit ? "1" : "0", len);
01544 return 0;
01545 }
01546 }
01547 return -1;
01548 }
01549
01550 static int acf_transaction_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
01551 {
01552 AST_DECLARE_APP_ARGS(args,
01553 AST_APP_ARG(property);
01554 AST_APP_ARG(opt);
01555 );
01556 struct odbc_txn_frame *tx;
01557 SQLINTEGER nativeerror=0, numfields=0;
01558 SQLSMALLINT diagbytes=0, i;
01559 unsigned char state[10], diagnostic[256];
01560
01561 AST_STANDARD_APP_ARGS(args, s);
01562 if (strcasecmp(args.property, "transaction") == 0) {
01563
01564 struct odbc_obj *obj;
01565 if ((tx = find_transaction(chan, NULL, value, 0))) {
01566 mark_transaction_active(chan, tx);
01567 } else {
01568
01569 struct ast_flags flags = { RES_ODBC_INDEPENDENT_CONNECTION };
01570 if (ast_strlen_zero(args.opt) || !(obj = ast_odbc_request_obj2(args.opt, flags))) {
01571 ast_log(LOG_ERROR, "Could not create transaction: invalid database specification '%s'\n", S_OR(args.opt, ""));
01572 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_DB");
01573 return -1;
01574 }
01575 if (!(tx = find_transaction(chan, obj, value, 0))) {
01576 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
01577 return -1;
01578 }
01579 obj->tx = 1;
01580 }
01581 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
01582 return 0;
01583 } else if (strcasecmp(args.property, "forcecommit") == 0) {
01584
01585 if (ast_strlen_zero(args.opt)) {
01586 tx = find_transaction(chan, NULL, NULL, 1);
01587 } else {
01588 tx = find_transaction(chan, NULL, args.opt, 0);
01589 }
01590 if (!tx) {
01591 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
01592 return -1;
01593 }
01594 if (ast_true(value)) {
01595 tx->forcecommit = 1;
01596 } else if (ast_false(value)) {
01597 tx->forcecommit = 0;
01598 } else {
01599 ast_log(LOG_ERROR, "Invalid value for forcecommit: '%s'\n", S_OR(value, ""));
01600 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
01601 return -1;
01602 }
01603
01604 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
01605 return 0;
01606 } else if (strcasecmp(args.property, "isolation") == 0) {
01607
01608 int isolation = text2isolation(value);
01609 if (ast_strlen_zero(args.opt)) {
01610 tx = find_transaction(chan, NULL, NULL, 1);
01611 } else {
01612 tx = find_transaction(chan, NULL, args.opt, 0);
01613 }
01614 if (!tx) {
01615 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
01616 return -1;
01617 }
01618 if (isolation == 0) {
01619 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
01620 ast_log(LOG_ERROR, "Invalid isolation specification: '%s'\n", S_OR(value, ""));
01621 } else if (SQLSetConnectAttr(tx->obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)isolation, 0) == SQL_ERROR) {
01622 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "SQL_ERROR");
01623 SQLGetDiagField(SQL_HANDLE_DBC, tx->obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01624 for (i = 0; i < numfields; i++) {
01625 SQLGetDiagRec(SQL_HANDLE_DBC, tx->obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01626 ast_log(LOG_WARNING, "SetConnectAttr (Txn isolation) returned an error: %s: %s\n", state, diagnostic);
01627 if (i > 10) {
01628 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01629 break;
01630 }
01631 }
01632 } else {
01633 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
01634 tx->isolation = isolation;
01635 }
01636 return 0;
01637 } else {
01638 ast_log(LOG_ERROR, "Unknown property: '%s'\n", args.property);
01639 return -1;
01640 }
01641 }
01642
01643 static struct ast_custom_function odbc_function = {
01644 .name = "ODBC",
01645 .read = acf_transaction_read,
01646 .write = acf_transaction_write,
01647 };
01648
01649 static const char * const app_commit = "ODBC_Commit";
01650 static const char * const app_rollback = "ODBC_Rollback";
01651
01652
01653
01654
01655
01656 static int data_odbc_provider_handler(const struct ast_data_search *search,
01657 struct ast_data *root)
01658 {
01659 struct ao2_iterator aoi, aoi2;
01660 struct odbc_class *class;
01661 struct odbc_obj *current;
01662 struct ast_data *data_odbc_class, *data_odbc_connections, *data_odbc_connection;
01663 struct ast_data *enum_node;
01664 int count;
01665
01666 aoi = ao2_iterator_init(class_container, 0);
01667 while ((class = ao2_iterator_next(&aoi))) {
01668 data_odbc_class = ast_data_add_node(root, "class");
01669 if (!data_odbc_class) {
01670 ao2_ref(class, -1);
01671 continue;
01672 }
01673
01674 ast_data_add_structure(odbc_class, data_odbc_class, class);
01675
01676 if (!ao2_container_count(class->obj_container)) {
01677 ao2_ref(class, -1);
01678 continue;
01679 }
01680
01681 data_odbc_connections = ast_data_add_node(data_odbc_class, "connections");
01682 if (!data_odbc_connections) {
01683 ao2_ref(class, -1);
01684 continue;
01685 }
01686
01687 ast_data_add_bool(data_odbc_class, "shared", !class->haspool);
01688
01689 enum_node = ast_data_add_node(data_odbc_class, "isolation");
01690 if (!enum_node) {
01691 ao2_ref(class, -1);
01692 continue;
01693 }
01694 ast_data_add_int(enum_node, "value", class->isolation);
01695 ast_data_add_str(enum_node, "text", isolation2text(class->isolation));
01696
01697 count = 0;
01698 aoi2 = ao2_iterator_init(class->obj_container, 0);
01699 while ((current = ao2_iterator_next(&aoi2))) {
01700 data_odbc_connection = ast_data_add_node(data_odbc_connections, "connection");
01701 if (!data_odbc_connection) {
01702 ao2_ref(current, -1);
01703 continue;
01704 }
01705
01706 ast_mutex_lock(¤t->lock);
01707 ast_data_add_str(data_odbc_connection, "status", current->used ? "in use" :
01708 current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
01709 ast_data_add_bool(data_odbc_connection, "transactional", current->tx);
01710 ast_mutex_unlock(¤t->lock);
01711
01712 if (class->haspool) {
01713 ast_data_add_int(data_odbc_connection, "number", ++count);
01714 }
01715
01716 ao2_ref(current, -1);
01717 }
01718 ao2_ref(class, -1);
01719
01720 if (!ast_data_search_match(search, data_odbc_class)) {
01721 ast_data_remove_node(root, data_odbc_class);
01722 }
01723 }
01724 return 0;
01725 }
01726
01727
01728
01729
01730
01731 static const struct ast_data_handler odbc_provider = {
01732 .version = AST_DATA_HANDLER_VERSION,
01733 .get = data_odbc_provider_handler
01734 };
01735
01736 static const struct ast_data_entry odbc_providers[] = {
01737 AST_DATA_ENTRY("/asterisk/res/odbc", &odbc_provider),
01738 };
01739
01740 static int reload(void)
01741 {
01742 struct odbc_cache_tables *table;
01743 struct odbc_class *class;
01744 struct odbc_obj *current;
01745 struct ao2_iterator aoi = ao2_iterator_init(class_container, 0);
01746
01747
01748 while ((class = ao2_iterator_next(&aoi))) {
01749 class->delme = 1;
01750 ao2_ref(class, -1);
01751 }
01752 ao2_iterator_destroy(&aoi);
01753
01754 load_odbc_config();
01755
01756
01757
01758
01759
01760
01761
01762
01763
01764
01765
01766
01767
01768
01769
01770
01771
01772
01773
01774
01775
01776
01777
01778
01779
01780 aoi = ao2_iterator_init(class_container, 0);
01781 while ((class = ao2_iterator_next(&aoi))) {
01782 if (class->delme) {
01783 struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
01784 while ((current = ao2_iterator_next(&aoi2))) {
01785 ao2_unlink(class->obj_container, current);
01786 ao2_ref(current, -1);
01787
01788
01789
01790
01791 }
01792 ao2_iterator_destroy(&aoi2);
01793 ao2_unlink(class_container, class);
01794
01795
01796
01797
01798
01799 }
01800 ao2_ref(class, -1);
01801 }
01802 ao2_iterator_destroy(&aoi);
01803
01804
01805 AST_RWLIST_WRLOCK(&odbc_tables);
01806 while ((table = AST_RWLIST_REMOVE_HEAD(&odbc_tables, list))) {
01807 destroy_table_cache(table);
01808 }
01809 AST_RWLIST_UNLOCK(&odbc_tables);
01810
01811 return 0;
01812 }
01813
01814 static int unload_module(void)
01815 {
01816
01817 return -1;
01818 }
01819
01820 static int load_module(void)
01821 {
01822 if (!(class_container = ao2_container_alloc(1, null_hash_fn, ao2_match_by_addr)))
01823 return AST_MODULE_LOAD_DECLINE;
01824 if (load_odbc_config() == -1)
01825 return AST_MODULE_LOAD_DECLINE;
01826 ast_cli_register_multiple(cli_odbc, ARRAY_LEN(cli_odbc));
01827 ast_data_register_multiple(odbc_providers, ARRAY_LEN(odbc_providers));
01828 ast_register_application_xml(app_commit, commit_exec);
01829 ast_register_application_xml(app_rollback, rollback_exec);
01830 ast_custom_function_register(&odbc_function);
01831 ast_log(LOG_NOTICE, "res_odbc loaded.\n");
01832 return 0;
01833 }
01834
01835 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "ODBC resource",
01836 .load = load_module,
01837 .unload = unload_module,
01838 .reload = reload,
01839 .load_pri = AST_MODPRI_REALTIME_DEPEND,
01840 );