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: 352955 $")
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;
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 aoi = ao2_iterator_init(class_container, 0);
00934 while ((class = ao2_iterator_next(&aoi))) {
00935 if (!strncasecmp(a->word, class->name, length) && ++which > a->n) {
00936 ret = ast_strdup(class->name);
00937 }
00938 ao2_ref(class, -1);
00939 if (ret) {
00940 break;
00941 }
00942 }
00943 ao2_iterator_destroy(&aoi);
00944 if (!ret && !strncasecmp(a->word, "all", length) && ++which > a->n) {
00945 ret = ast_strdup("all");
00946 }
00947 return ret;
00948 }
00949
00950 ast_cli(a->fd, "\nODBC DSN Settings\n");
00951 ast_cli(a->fd, "-----------------\n\n");
00952 aoi = ao2_iterator_init(class_container, 0);
00953 while ((class = ao2_iterator_next(&aoi))) {
00954 if ((a->argc == 2) || (a->argc == 3 && !strcmp(a->argv[2], "all")) || (!strcmp(a->argv[2], class->name))) {
00955 int count = 0;
00956 char timestr[80];
00957 struct ast_tm tm;
00958
00959 ast_localtime(&class->last_negative_connect, &tm, NULL);
00960 ast_strftime(timestr, sizeof(timestr), "%Y-%m-%d %T", &tm);
00961 ast_cli(a->fd, " Name: %s\n DSN: %s\n", class->name, class->dsn);
00962 ast_cli(a->fd, " Last connection attempt: %s\n", timestr);
00963
00964 if (class->haspool) {
00965 struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
00966
00967 ast_cli(a->fd, " Pooled: Yes\n Limit: %d\n Connections in use: %d\n", class->limit, class->count);
00968
00969 while ((current = ao2_iterator_next(&aoi2))) {
00970 ast_mutex_lock(¤t->lock);
00971 #ifdef DEBUG_THREADS
00972 ast_cli(a->fd, " - Connection %d: %s (%s:%d %s)\n", ++count,
00973 current->used ? "in use" :
00974 current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected",
00975 current->file, current->lineno, current->function);
00976 #else
00977 ast_cli(a->fd, " - Connection %d: %s\n", ++count,
00978 current->used ? "in use" :
00979 current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
00980 #endif
00981 ast_mutex_unlock(¤t->lock);
00982 ao2_ref(current, -1);
00983 }
00984 ao2_iterator_destroy(&aoi2);
00985 } else {
00986
00987 struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
00988 while ((current = ao2_iterator_next(&aoi2))) {
00989 ast_cli(a->fd, " Pooled: No\n Connected: %s\n", current->used ? "In use" :
00990 current->up && ast_odbc_sanity_check(current) ? "Yes" : "No");
00991 ao2_ref(current, -1);
00992 }
00993 ao2_iterator_destroy(&aoi2);
00994 }
00995 ast_cli(a->fd, "\n");
00996 }
00997 ao2_ref(class, -1);
00998 }
00999 ao2_iterator_destroy(&aoi);
01000
01001 return CLI_SUCCESS;
01002 }
01003
01004 static struct ast_cli_entry cli_odbc[] = {
01005 AST_CLI_DEFINE(handle_cli_odbc_show, "List ODBC DSN(s)")
01006 };
01007
01008 static int odbc_register_class(struct odbc_class *class, int preconnect)
01009 {
01010 struct odbc_obj *obj;
01011 if (class) {
01012 ao2_link(class_container, class);
01013
01014
01015 if (preconnect) {
01016
01017 obj = ast_odbc_request_obj(class->name, 0);
01018 if (obj) {
01019 ast_odbc_release_obj(obj);
01020 }
01021 }
01022
01023 return 0;
01024 } else {
01025 ast_log(LOG_WARNING, "Attempted to register a NULL class?\n");
01026 return -1;
01027 }
01028 }
01029
01030 static void odbc_release_obj2(struct odbc_obj *obj, struct odbc_txn_frame *tx)
01031 {
01032 SQLINTEGER nativeerror=0, numfields=0;
01033 SQLSMALLINT diagbytes=0, i;
01034 unsigned char state[10], diagnostic[256];
01035
01036 ast_debug(2, "odbc_release_obj2(%p) called (obj->txf = %p)\n", obj, obj->txf);
01037 if (tx) {
01038 ast_debug(1, "called on a transactional handle with %s\n", tx->forcecommit ? "COMMIT" : "ROLLBACK");
01039 if (SQLEndTran(SQL_HANDLE_DBC, obj->con, tx->forcecommit ? SQL_COMMIT : SQL_ROLLBACK) == SQL_ERROR) {
01040
01041 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01042 for (i = 0; i < numfields; i++) {
01043 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01044 ast_log(LOG_WARNING, "SQLEndTran returned an error: %s: %s\n", state, diagnostic);
01045 if (!strcmp((char *)state, "25S02") || !strcmp((char *)state, "08007")) {
01046
01047
01048
01049 SQLEndTran(SQL_HANDLE_DBC, obj->con, SQL_ROLLBACK);
01050 }
01051 if (i > 10) {
01052 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01053 break;
01054 }
01055 }
01056 }
01057
01058
01059 if (SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_ON, 0) == SQL_ERROR) {
01060 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01061 for (i = 0; i < numfields; i++) {
01062 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01063 ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
01064 if (i > 10) {
01065 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01066 break;
01067 }
01068 }
01069 }
01070 }
01071
01072 #ifdef DEBUG_THREADS
01073 obj->file[0] = '\0';
01074 obj->function[0] = '\0';
01075 obj->lineno = 0;
01076 #endif
01077
01078
01079
01080 obj->used = 0;
01081 if (obj->txf) {
01082
01083 obj->txf->obj = NULL;
01084 obj->txf = release_transaction(obj->txf);
01085 }
01086 ao2_ref(obj, -1);
01087 }
01088
01089 void ast_odbc_release_obj(struct odbc_obj *obj)
01090 {
01091 struct odbc_txn_frame *tx = find_transaction(NULL, obj, NULL, 0);
01092 odbc_release_obj2(obj, tx);
01093 }
01094
01095 int ast_odbc_backslash_is_escape(struct odbc_obj *obj)
01096 {
01097 return obj->parent->backslash_is_escape;
01098 }
01099
01100 static int commit_exec(struct ast_channel *chan, const char *data)
01101 {
01102 struct odbc_txn_frame *tx;
01103 SQLINTEGER nativeerror=0, numfields=0;
01104 SQLSMALLINT diagbytes=0, i;
01105 unsigned char state[10], diagnostic[256];
01106
01107 if (ast_strlen_zero(data)) {
01108 tx = find_transaction(chan, NULL, NULL, 1);
01109 } else {
01110 tx = find_transaction(chan, NULL, data, 0);
01111 }
01112
01113 pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", "OK");
01114
01115 if (tx) {
01116 if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_COMMIT) == SQL_ERROR) {
01117 struct ast_str *errors = ast_str_thread_get(&errors_buf, 16);
01118 ast_str_reset(errors);
01119
01120
01121 SQLGetDiagField(SQL_HANDLE_DBC, tx->obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01122 for (i = 0; i < numfields; i++) {
01123 SQLGetDiagRec(SQL_HANDLE_DBC, tx->obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01124 ast_str_append(&errors, 0, "%s%s", ast_str_strlen(errors) ? "," : "", state);
01125 ast_log(LOG_WARNING, "SQLEndTran returned an error: %s: %s\n", state, diagnostic);
01126 if (i > 10) {
01127 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01128 break;
01129 }
01130 }
01131 pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", ast_str_buffer(errors));
01132 }
01133 }
01134 return 0;
01135 }
01136
01137 static int rollback_exec(struct ast_channel *chan, const char *data)
01138 {
01139 struct odbc_txn_frame *tx;
01140 SQLINTEGER nativeerror=0, numfields=0;
01141 SQLSMALLINT diagbytes=0, i;
01142 unsigned char state[10], diagnostic[256];
01143
01144 if (ast_strlen_zero(data)) {
01145 tx = find_transaction(chan, NULL, NULL, 1);
01146 } else {
01147 tx = find_transaction(chan, NULL, data, 0);
01148 }
01149
01150 pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", "OK");
01151
01152 if (tx) {
01153 if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_ROLLBACK) == SQL_ERROR) {
01154 struct ast_str *errors = ast_str_thread_get(&errors_buf, 16);
01155 ast_str_reset(errors);
01156
01157
01158 SQLGetDiagField(SQL_HANDLE_DBC, tx->obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01159 for (i = 0; i < numfields; i++) {
01160 SQLGetDiagRec(SQL_HANDLE_DBC, tx->obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01161 ast_str_append(&errors, 0, "%s%s", ast_str_strlen(errors) ? "," : "", state);
01162 ast_log(LOG_WARNING, "SQLEndTran returned an error: %s: %s\n", state, diagnostic);
01163 if (i > 10) {
01164 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01165 break;
01166 }
01167 }
01168 pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", ast_str_buffer(errors));
01169 }
01170 }
01171 return 0;
01172 }
01173
01174 static int aoro2_class_cb(void *obj, void *arg, int flags)
01175 {
01176 struct odbc_class *class = obj;
01177 char *name = arg;
01178 if (!strcmp(class->name, name) && !class->delme) {
01179 return CMP_MATCH | CMP_STOP;
01180 }
01181 return 0;
01182 }
01183
01184 #define USE_TX (void *)(long)1
01185 #define NO_TX (void *)(long)2
01186 #define EOR_TX (void *)(long)3
01187
01188 static int aoro2_obj_cb(void *vobj, void *arg, int flags)
01189 {
01190 struct odbc_obj *obj = vobj;
01191 ast_mutex_lock(&obj->lock);
01192 if ((arg == NO_TX && !obj->tx) || (arg == EOR_TX && !obj->used) || (arg == USE_TX && obj->tx && !obj->used)) {
01193 obj->used = 1;
01194 ast_mutex_unlock(&obj->lock);
01195 return CMP_MATCH | CMP_STOP;
01196 }
01197 ast_mutex_unlock(&obj->lock);
01198 return 0;
01199 }
01200
01201 struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags, const char *file, const char *function, int lineno)
01202 {
01203 struct odbc_obj *obj = NULL;
01204 struct odbc_class *class;
01205 SQLINTEGER nativeerror=0, numfields=0;
01206 SQLSMALLINT diagbytes=0, i;
01207 unsigned char state[10], diagnostic[256];
01208
01209 if (!(class = ao2_callback(class_container, 0, aoro2_class_cb, (char *) name))) {
01210 ast_debug(1, "Class '%s' not found!\n", name);
01211 return NULL;
01212 }
01213
01214 ast_assert(ao2_ref(class, 0) > 1);
01215
01216 if (class->haspool) {
01217
01218 obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, EOR_TX);
01219
01220 if (obj) {
01221 ast_assert(ao2_ref(obj, 0) > 1);
01222 }
01223 if (!obj && (ast_atomic_fetchadd_int(&class->count, +1) < class->limit) &&
01224 (time(NULL) > class->last_negative_connect.tv_sec + class->negative_connection_cache.tv_sec)) {
01225 obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor);
01226 if (!obj) {
01227 class->count--;
01228 ao2_ref(class, -1);
01229 ast_debug(3, "Unable to allocate object\n");
01230 ast_atomic_fetchadd_int(&class->count, -1);
01231 return NULL;
01232 }
01233 ast_assert(ao2_ref(obj, 0) == 1);
01234 ast_mutex_init(&obj->lock);
01235
01236 obj->parent = class;
01237 class = NULL;
01238 if (odbc_obj_connect(obj) == ODBC_FAIL) {
01239 ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
01240 ast_assert(ao2_ref(obj->parent, 0) > 0);
01241
01242 ast_atomic_fetchadd_int(&obj->parent->count, -1);
01243 ao2_ref(obj, -1);
01244 obj = NULL;
01245 } else {
01246 obj->used = 1;
01247 ao2_link(obj->parent->obj_container, obj);
01248 }
01249 } else {
01250
01251 if (!obj) {
01252 ast_atomic_fetchadd_int(&class->count, -1);
01253 }
01254
01255 ao2_ref(class, -1);
01256 class = NULL;
01257 }
01258
01259 if (obj && ast_test_flag(&flags, RES_ODBC_INDEPENDENT_CONNECTION)) {
01260
01261 if (SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
01262 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01263 for (i = 0; i < numfields; i++) {
01264 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01265 ast_log(LOG_WARNING, "SQLSetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
01266 if (i > 10) {
01267 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01268 break;
01269 }
01270 }
01271 }
01272 }
01273 } else if (ast_test_flag(&flags, RES_ODBC_INDEPENDENT_CONNECTION)) {
01274
01275 if (!(obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, USE_TX))) {
01276 ast_debug(1, "Object not found\n");
01277 obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor);
01278 if (!obj) {
01279 ao2_ref(class, -1);
01280 ast_debug(3, "Unable to allocate object\n");
01281 return NULL;
01282 }
01283 ast_mutex_init(&obj->lock);
01284
01285 obj->parent = class;
01286 class = NULL;
01287 if (odbc_obj_connect(obj) == ODBC_FAIL) {
01288 ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
01289 ao2_ref(obj, -1);
01290 obj = NULL;
01291 } else {
01292 obj->used = 1;
01293 ao2_link(obj->parent->obj_container, obj);
01294 ast_atomic_fetchadd_int(&obj->parent->count, +1);
01295 }
01296 }
01297
01298 if (obj && SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
01299 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01300 for (i = 0; i < numfields; i++) {
01301 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01302 ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
01303 if (i > 10) {
01304 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01305 break;
01306 }
01307 }
01308 }
01309 } else {
01310
01311 if ((obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, NO_TX))) {
01312
01313 ast_assert(ao2_ref(class, 0) > 1);
01314 ao2_ref(class, -1);
01315 class = NULL;
01316 } else {
01317
01318 if (!(obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor))) {
01319 ast_assert(ao2_ref(class, 0) > 1);
01320 ao2_ref(class, -1);
01321 ast_debug(3, "Unable to allocate object\n");
01322 return NULL;
01323 }
01324 ast_mutex_init(&obj->lock);
01325
01326 obj->parent = class;
01327 class = NULL;
01328 if (odbc_obj_connect(obj) == ODBC_FAIL) {
01329 ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
01330 ao2_ref(obj, -1);
01331 obj = NULL;
01332 } else {
01333 ao2_link(obj->parent->obj_container, obj);
01334 ast_assert(ao2_ref(obj, 0) > 1);
01335 }
01336 }
01337
01338 if (obj && SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_ON, 0) == SQL_ERROR) {
01339 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01340 for (i = 0; i < numfields; i++) {
01341 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01342 ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
01343 if (i > 10) {
01344 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01345 break;
01346 }
01347 }
01348 }
01349 }
01350
01351
01352 if (obj && SQLSetConnectAttr(obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)obj->parent->isolation, 0) == SQL_ERROR) {
01353 SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01354 for (i = 0; i < numfields; i++) {
01355 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01356 ast_log(LOG_WARNING, "SetConnectAttr (Txn isolation) returned an error: %s: %s\n", state, diagnostic);
01357 if (i > 10) {
01358 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01359 break;
01360 }
01361 }
01362 }
01363
01364 if (obj && ast_test_flag(&flags, RES_ODBC_CONNECTED) && !obj->up) {
01365
01366 if (time(NULL) > obj->parent->last_negative_connect.tv_sec + obj->parent->negative_connection_cache.tv_sec) {
01367 odbc_obj_connect(obj);
01368 }
01369 } else if (obj && ast_test_flag(&flags, RES_ODBC_SANITY_CHECK)) {
01370 ast_odbc_sanity_check(obj);
01371 } else if (obj && obj->parent->idlecheck > 0 && ast_tvdiff_sec(ast_tvnow(), obj->last_used) > obj->parent->idlecheck) {
01372 odbc_obj_connect(obj);
01373 }
01374
01375 #ifdef DEBUG_THREADS
01376 if (obj) {
01377 ast_copy_string(obj->file, file, sizeof(obj->file));
01378 ast_copy_string(obj->function, function, sizeof(obj->function));
01379 obj->lineno = lineno;
01380 }
01381 #endif
01382 ast_assert(class == NULL);
01383
01384 if (obj) {
01385 ast_assert(ao2_ref(obj, 0) > 1);
01386 }
01387 return obj;
01388 }
01389
01390 struct odbc_obj *_ast_odbc_request_obj(const char *name, int check, const char *file, const char *function, int lineno)
01391 {
01392 struct ast_flags flags = { check ? RES_ODBC_SANITY_CHECK : 0 };
01393 return _ast_odbc_request_obj2(name, flags, file, function, lineno);
01394 }
01395
01396 struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname)
01397 {
01398 struct ast_datastore *txn_store;
01399 AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
01400 struct odbc_txn_frame *txn = NULL;
01401
01402 if (!chan) {
01403
01404 return NULL;
01405 }
01406
01407 ast_channel_lock(chan);
01408 if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
01409 oldlist = txn_store->data;
01410 } else {
01411 ast_channel_unlock(chan);
01412 return NULL;
01413 }
01414
01415 AST_LIST_LOCK(oldlist);
01416 ast_channel_unlock(chan);
01417
01418 AST_LIST_TRAVERSE(oldlist, txn, list) {
01419 if (txn->obj && txn->obj->parent && !strcmp(txn->obj->parent->name, objname)) {
01420 AST_LIST_UNLOCK(oldlist);
01421 return txn->obj;
01422 }
01423 }
01424 AST_LIST_UNLOCK(oldlist);
01425 return NULL;
01426 }
01427
01428 static odbc_status odbc_obj_disconnect(struct odbc_obj *obj)
01429 {
01430 int res;
01431 SQLINTEGER err;
01432 short int mlen;
01433 unsigned char msg[200], state[10];
01434
01435
01436 if (!obj->con) {
01437 return ODBC_SUCCESS;
01438 }
01439
01440 ast_mutex_lock(&obj->lock);
01441
01442 res = SQLDisconnect(obj->con);
01443
01444 if (obj->parent) {
01445 if (res == SQL_SUCCESS || res == SQL_SUCCESS_WITH_INFO) {
01446 ast_log(LOG_DEBUG, "Disconnected %d from %s [%s]\n", res, obj->parent->name, obj->parent->dsn);
01447 } else {
01448 ast_log(LOG_DEBUG, "res_odbc: %s [%s] already disconnected\n", obj->parent->name, obj->parent->dsn);
01449 }
01450 }
01451
01452 if ((res = SQLFreeHandle(SQL_HANDLE_DBC, obj->con) == SQL_SUCCESS)) {
01453 obj->con = NULL;
01454 ast_log(LOG_DEBUG, "Database handle deallocated\n");
01455 } else {
01456 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, state, &err, msg, 100, &mlen);
01457 ast_log(LOG_WARNING, "Unable to deallocate database handle? %d errno=%d %s\n", res, (int)err, msg);
01458 }
01459
01460 obj->up = 0;
01461 ast_mutex_unlock(&obj->lock);
01462 return ODBC_SUCCESS;
01463 }
01464
01465 static odbc_status odbc_obj_connect(struct odbc_obj *obj)
01466 {
01467 int res;
01468 SQLINTEGER err;
01469 short int mlen;
01470 unsigned char msg[200], state[10];
01471 #ifdef NEEDTRACE
01472 SQLINTEGER enable = 1;
01473 char *tracefile = "/tmp/odbc.trace";
01474 #endif
01475 ast_mutex_lock(&obj->lock);
01476
01477 if (obj->up) {
01478 odbc_obj_disconnect(obj);
01479 ast_log(LOG_NOTICE, "Re-connecting %s\n", obj->parent->name);
01480 } else {
01481 ast_log(LOG_NOTICE, "Connecting %s\n", obj->parent->name);
01482 }
01483
01484 res = SQLAllocHandle(SQL_HANDLE_DBC, obj->parent->env, &obj->con);
01485
01486 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
01487 ast_log(LOG_WARNING, "res_odbc: Error AllocHDB %d\n", res);
01488 obj->parent->last_negative_connect = ast_tvnow();
01489 ast_mutex_unlock(&obj->lock);
01490 return ODBC_FAIL;
01491 }
01492 SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)(long) obj->parent->conntimeout, 0);
01493 SQLSetConnectAttr(obj->con, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER *)(long) obj->parent->conntimeout, 0);
01494 #ifdef NEEDTRACE
01495 SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER);
01496 SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile));
01497 #endif
01498
01499 res = SQLConnect(obj->con,
01500 (SQLCHAR *) obj->parent->dsn, SQL_NTS,
01501 (SQLCHAR *) obj->parent->username, SQL_NTS,
01502 (SQLCHAR *) obj->parent->password, SQL_NTS);
01503
01504 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
01505 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, state, &err, msg, 100, &mlen);
01506 obj->parent->last_negative_connect = ast_tvnow();
01507 ast_mutex_unlock(&obj->lock);
01508 ast_log(LOG_WARNING, "res_odbc: Error SQLConnect=%d errno=%d %s\n", res, (int)err, msg);
01509 return ODBC_FAIL;
01510 } else {
01511 ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->parent->name, obj->parent->dsn);
01512 obj->up = 1;
01513 obj->last_used = ast_tvnow();
01514 }
01515
01516 ast_mutex_unlock(&obj->lock);
01517 return ODBC_SUCCESS;
01518 }
01519
01520 static int acf_transaction_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
01521 {
01522 AST_DECLARE_APP_ARGS(args,
01523 AST_APP_ARG(property);
01524 AST_APP_ARG(opt);
01525 );
01526 struct odbc_txn_frame *tx;
01527
01528 AST_STANDARD_APP_ARGS(args, data);
01529 if (strcasecmp(args.property, "transaction") == 0) {
01530 if ((tx = find_transaction(chan, NULL, NULL, 1))) {
01531 ast_copy_string(buf, tx->name, len);
01532 return 0;
01533 }
01534 } else if (strcasecmp(args.property, "isolation") == 0) {
01535 if (!ast_strlen_zero(args.opt)) {
01536 tx = find_transaction(chan, NULL, args.opt, 0);
01537 } else {
01538 tx = find_transaction(chan, NULL, NULL, 1);
01539 }
01540 if (tx) {
01541 ast_copy_string(buf, isolation2text(tx->isolation), len);
01542 return 0;
01543 }
01544 } else if (strcasecmp(args.property, "forcecommit") == 0) {
01545 if (!ast_strlen_zero(args.opt)) {
01546 tx = find_transaction(chan, NULL, args.opt, 0);
01547 } else {
01548 tx = find_transaction(chan, NULL, NULL, 1);
01549 }
01550 if (tx) {
01551 ast_copy_string(buf, tx->forcecommit ? "1" : "0", len);
01552 return 0;
01553 }
01554 }
01555 return -1;
01556 }
01557
01558 static int acf_transaction_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
01559 {
01560 AST_DECLARE_APP_ARGS(args,
01561 AST_APP_ARG(property);
01562 AST_APP_ARG(opt);
01563 );
01564 struct odbc_txn_frame *tx;
01565 SQLINTEGER nativeerror=0, numfields=0;
01566 SQLSMALLINT diagbytes=0, i;
01567 unsigned char state[10], diagnostic[256];
01568
01569 AST_STANDARD_APP_ARGS(args, s);
01570 if (strcasecmp(args.property, "transaction") == 0) {
01571
01572 struct odbc_obj *obj;
01573 if ((tx = find_transaction(chan, NULL, value, 0))) {
01574 mark_transaction_active(chan, tx);
01575 } else {
01576
01577 struct ast_flags flags = { RES_ODBC_INDEPENDENT_CONNECTION };
01578 if (ast_strlen_zero(args.opt) || !(obj = ast_odbc_request_obj2(args.opt, flags))) {
01579 ast_log(LOG_ERROR, "Could not create transaction: invalid database specification '%s'\n", S_OR(args.opt, ""));
01580 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_DB");
01581 return -1;
01582 }
01583 if (!(tx = find_transaction(chan, obj, value, 0))) {
01584 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
01585 return -1;
01586 }
01587 obj->tx = 1;
01588 }
01589 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
01590 return 0;
01591 } else if (strcasecmp(args.property, "forcecommit") == 0) {
01592
01593 if (ast_strlen_zero(args.opt)) {
01594 tx = find_transaction(chan, NULL, NULL, 1);
01595 } else {
01596 tx = find_transaction(chan, NULL, args.opt, 0);
01597 }
01598 if (!tx) {
01599 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
01600 return -1;
01601 }
01602 if (ast_true(value)) {
01603 tx->forcecommit = 1;
01604 } else if (ast_false(value)) {
01605 tx->forcecommit = 0;
01606 } else {
01607 ast_log(LOG_ERROR, "Invalid value for forcecommit: '%s'\n", S_OR(value, ""));
01608 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
01609 return -1;
01610 }
01611
01612 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
01613 return 0;
01614 } else if (strcasecmp(args.property, "isolation") == 0) {
01615
01616 int isolation = text2isolation(value);
01617 if (ast_strlen_zero(args.opt)) {
01618 tx = find_transaction(chan, NULL, NULL, 1);
01619 } else {
01620 tx = find_transaction(chan, NULL, args.opt, 0);
01621 }
01622 if (!tx) {
01623 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
01624 return -1;
01625 }
01626 if (isolation == 0) {
01627 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
01628 ast_log(LOG_ERROR, "Invalid isolation specification: '%s'\n", S_OR(value, ""));
01629 } else if (SQLSetConnectAttr(tx->obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)isolation, 0) == SQL_ERROR) {
01630 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "SQL_ERROR");
01631 SQLGetDiagField(SQL_HANDLE_DBC, tx->obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
01632 for (i = 0; i < numfields; i++) {
01633 SQLGetDiagRec(SQL_HANDLE_DBC, tx->obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
01634 ast_log(LOG_WARNING, "SetConnectAttr (Txn isolation) returned an error: %s: %s\n", state, diagnostic);
01635 if (i > 10) {
01636 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
01637 break;
01638 }
01639 }
01640 } else {
01641 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
01642 tx->isolation = isolation;
01643 }
01644 return 0;
01645 } else {
01646 ast_log(LOG_ERROR, "Unknown property: '%s'\n", args.property);
01647 return -1;
01648 }
01649 }
01650
01651 static struct ast_custom_function odbc_function = {
01652 .name = "ODBC",
01653 .read = acf_transaction_read,
01654 .write = acf_transaction_write,
01655 };
01656
01657 static const char * const app_commit = "ODBC_Commit";
01658 static const char * const app_rollback = "ODBC_Rollback";
01659
01660
01661
01662
01663
01664 static int data_odbc_provider_handler(const struct ast_data_search *search,
01665 struct ast_data *root)
01666 {
01667 struct ao2_iterator aoi, aoi2;
01668 struct odbc_class *class;
01669 struct odbc_obj *current;
01670 struct ast_data *data_odbc_class, *data_odbc_connections, *data_odbc_connection;
01671 struct ast_data *enum_node;
01672 int count;
01673
01674 aoi = ao2_iterator_init(class_container, 0);
01675 while ((class = ao2_iterator_next(&aoi))) {
01676 data_odbc_class = ast_data_add_node(root, "class");
01677 if (!data_odbc_class) {
01678 ao2_ref(class, -1);
01679 continue;
01680 }
01681
01682 ast_data_add_structure(odbc_class, data_odbc_class, class);
01683
01684 if (!ao2_container_count(class->obj_container)) {
01685 ao2_ref(class, -1);
01686 continue;
01687 }
01688
01689 data_odbc_connections = ast_data_add_node(data_odbc_class, "connections");
01690 if (!data_odbc_connections) {
01691 ao2_ref(class, -1);
01692 continue;
01693 }
01694
01695 ast_data_add_bool(data_odbc_class, "shared", !class->haspool);
01696
01697 enum_node = ast_data_add_node(data_odbc_class, "isolation");
01698 if (!enum_node) {
01699 ao2_ref(class, -1);
01700 continue;
01701 }
01702 ast_data_add_int(enum_node, "value", class->isolation);
01703 ast_data_add_str(enum_node, "text", isolation2text(class->isolation));
01704
01705 count = 0;
01706 aoi2 = ao2_iterator_init(class->obj_container, 0);
01707 while ((current = ao2_iterator_next(&aoi2))) {
01708 data_odbc_connection = ast_data_add_node(data_odbc_connections, "connection");
01709 if (!data_odbc_connection) {
01710 ao2_ref(current, -1);
01711 continue;
01712 }
01713
01714 ast_mutex_lock(¤t->lock);
01715 ast_data_add_str(data_odbc_connection, "status", current->used ? "in use" :
01716 current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
01717 ast_data_add_bool(data_odbc_connection, "transactional", current->tx);
01718 ast_mutex_unlock(¤t->lock);
01719
01720 if (class->haspool) {
01721 ast_data_add_int(data_odbc_connection, "number", ++count);
01722 }
01723
01724 ao2_ref(current, -1);
01725 }
01726 ao2_iterator_destroy(&aoi2);
01727 ao2_ref(class, -1);
01728
01729 if (!ast_data_search_match(search, data_odbc_class)) {
01730 ast_data_remove_node(root, data_odbc_class);
01731 }
01732 }
01733 ao2_iterator_destroy(&aoi);
01734 return 0;
01735 }
01736
01737
01738
01739
01740
01741 static const struct ast_data_handler odbc_provider = {
01742 .version = AST_DATA_HANDLER_VERSION,
01743 .get = data_odbc_provider_handler
01744 };
01745
01746 static const struct ast_data_entry odbc_providers[] = {
01747 AST_DATA_ENTRY("/asterisk/res/odbc", &odbc_provider),
01748 };
01749
01750 static int reload(void)
01751 {
01752 struct odbc_cache_tables *table;
01753 struct odbc_class *class;
01754 struct odbc_obj *current;
01755 struct ao2_iterator aoi = ao2_iterator_init(class_container, 0);
01756
01757
01758 while ((class = ao2_iterator_next(&aoi))) {
01759 class->delme = 1;
01760 ao2_ref(class, -1);
01761 }
01762 ao2_iterator_destroy(&aoi);
01763
01764 load_odbc_config();
01765
01766
01767
01768
01769
01770
01771
01772
01773
01774
01775
01776
01777
01778
01779
01780
01781
01782
01783
01784
01785
01786
01787
01788
01789
01790 aoi = ao2_iterator_init(class_container, 0);
01791 while ((class = ao2_iterator_next(&aoi))) {
01792 if (class->delme) {
01793 struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
01794 while ((current = ao2_iterator_next(&aoi2))) {
01795 ao2_unlink(class->obj_container, current);
01796 ao2_ref(current, -1);
01797
01798
01799
01800
01801 }
01802 ao2_iterator_destroy(&aoi2);
01803 ao2_unlink(class_container, class);
01804
01805
01806
01807
01808
01809 }
01810 ao2_ref(class, -1);
01811 }
01812 ao2_iterator_destroy(&aoi);
01813
01814
01815 AST_RWLIST_WRLOCK(&odbc_tables);
01816 while ((table = AST_RWLIST_REMOVE_HEAD(&odbc_tables, list))) {
01817 destroy_table_cache(table);
01818 }
01819 AST_RWLIST_UNLOCK(&odbc_tables);
01820
01821 return 0;
01822 }
01823
01824 static int unload_module(void)
01825 {
01826
01827 return -1;
01828 }
01829
01830 static int load_module(void)
01831 {
01832 if (!(class_container = ao2_container_alloc(1, null_hash_fn, ao2_match_by_addr)))
01833 return AST_MODULE_LOAD_DECLINE;
01834 if (load_odbc_config() == -1)
01835 return AST_MODULE_LOAD_DECLINE;
01836 ast_cli_register_multiple(cli_odbc, ARRAY_LEN(cli_odbc));
01837 ast_data_register_multiple(odbc_providers, ARRAY_LEN(odbc_providers));
01838 ast_register_application_xml(app_commit, commit_exec);
01839 ast_register_application_xml(app_rollback, rollback_exec);
01840 ast_custom_function_register(&odbc_function);
01841 ast_log(LOG_NOTICE, "res_odbc loaded.\n");
01842 return 0;
01843 }
01844
01845 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "ODBC resource",
01846 .load = load_module,
01847 .unload = unload_module,
01848 .reload = reload,
01849 .load_pri = AST_MODPRI_REALTIME_DEPEND,
01850 );