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 #include "asterisk.h"
00037
00038 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
00039
00040 #include <stdio.h>
00041 #include <stdlib.h>
00042 #include <unistd.h>
00043 #include <string.h>
00044
00045 #include "asterisk/file.h"
00046 #include "asterisk/logger.h"
00047 #include "asterisk/channel.h"
00048 #include "asterisk/config.h"
00049 #include "asterisk/options.h"
00050 #include "asterisk/pbx.h"
00051 #include "asterisk/module.h"
00052 #include "asterisk/cli.h"
00053 #include "asterisk/lock.h"
00054 #include "asterisk/res_odbc.h"
00055
00056 struct odbc_class
00057 {
00058 AST_LIST_ENTRY(odbc_class) list;
00059 char name[80];
00060 char dsn[80];
00061 char username[80];
00062 char password[80];
00063 SQLHENV env;
00064 unsigned int haspool:1;
00065 unsigned int limit:10;
00066 unsigned int count:10;
00067 unsigned int delme:1;
00068 AST_LIST_HEAD(, odbc_obj) odbc_obj;
00069 };
00070
00071 AST_LIST_HEAD_STATIC(odbc_list, odbc_class);
00072
00073 static odbc_status odbc_obj_connect(struct odbc_obj *obj);
00074 static odbc_status odbc_obj_disconnect(struct odbc_obj *obj);
00075 static int odbc_register_class(struct odbc_class *class, int connect);
00076
00077
00078 SQLHSTMT ast_odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT (*prepare_cb)(struct odbc_obj *obj, void *data), void *data)
00079 {
00080 int res = 0, i, attempt;
00081 SQLINTEGER nativeerror=0, numfields=0;
00082 SQLSMALLINT diagbytes=0;
00083 unsigned char state[10], diagnostic[256];
00084 SQLHSTMT stmt;
00085
00086 for (attempt = 0; attempt < 2; attempt++) {
00087
00088
00089
00090
00091
00092 stmt = prepare_cb(obj, data);
00093
00094 if (stmt) {
00095 res = SQLExecute(stmt);
00096 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
00097 if (res == SQL_ERROR) {
00098 SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
00099 for (i = 0; i < numfields; i++) {
00100 SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
00101 ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
00102 if (i > 10) {
00103 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
00104 break;
00105 }
00106 }
00107 }
00108
00109 ast_log(LOG_WARNING, "SQL Execute error %d! Attempting a reconnect...\n", res);
00110 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00111 stmt = NULL;
00112
00113 obj->up = 0;
00114
00115
00116
00117
00118
00119 odbc_obj_disconnect(obj);
00120 odbc_obj_connect(obj);
00121 continue;
00122 }
00123 break;
00124 } else {
00125 ast_log(LOG_WARNING, "SQL Prepare failed. Attempting a reconnect...\n");
00126 odbc_obj_disconnect(obj);
00127 odbc_obj_connect(obj);
00128 }
00129 }
00130
00131 return stmt;
00132 }
00133
00134 int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt)
00135 {
00136 int res = 0, i;
00137 SQLINTEGER nativeerror=0, numfields=0;
00138 SQLSMALLINT diagbytes=0;
00139 unsigned char state[10], diagnostic[256];
00140
00141 res = SQLExecute(stmt);
00142 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
00143 if (res == SQL_ERROR) {
00144 SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
00145 for (i = 0; i < numfields; i++) {
00146 SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
00147 ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
00148 if (i > 10) {
00149 ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
00150 break;
00151 }
00152 }
00153 }
00154 #if 0
00155
00156
00157
00158
00159
00160
00161
00162 ast_log(LOG_WARNING, "SQL Execute error %d! Attempting a reconnect...\n", res);
00163 ast_mutex_lock(&obj->lock);
00164 obj->up = 0;
00165 ast_mutex_unlock(&obj->lock);
00166 odbc_obj_disconnect(obj);
00167 odbc_obj_connect(obj);
00168 res = SQLExecute(stmt);
00169 #endif
00170 }
00171
00172 return res;
00173 }
00174
00175
00176 int ast_odbc_sanity_check(struct odbc_obj *obj)
00177 {
00178 char *test_sql = "select 1";
00179 SQLHSTMT stmt;
00180 int res = 0;
00181
00182 if (obj->up) {
00183 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
00184 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00185 obj->up = 0;
00186 } else {
00187 res = SQLPrepare(stmt, (unsigned char *)test_sql, SQL_NTS);
00188 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00189 obj->up = 0;
00190 } else {
00191 res = SQLExecute(stmt);
00192 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00193 obj->up = 0;
00194 }
00195 }
00196 }
00197 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
00198 }
00199
00200 if (!obj->up) {
00201 ast_log(LOG_WARNING, "Connection is down attempting to reconnect...\n");
00202 odbc_obj_disconnect(obj);
00203 odbc_obj_connect(obj);
00204 }
00205 return obj->up;
00206 }
00207
00208 static int load_odbc_config(void)
00209 {
00210 static char *cfg = "res_odbc.conf";
00211 struct ast_config *config;
00212 struct ast_variable *v;
00213 char *cat, *dsn, *username, *password;
00214 int enabled, pooling, limit;
00215 int connect = 0, res = 0;
00216
00217 struct odbc_class *new;
00218
00219 config = ast_config_load(cfg);
00220 if (!config) {
00221 ast_log(LOG_WARNING, "Unable to load config file res_odbc.conf\n");
00222 return -1;
00223 }
00224 for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) {
00225 if (!strcasecmp(cat, "ENV")) {
00226 for (v = ast_variable_browse(config, cat); v; v = v->next) {
00227 setenv(v->name, v->value, 1);
00228 ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value);
00229 }
00230 } else {
00231
00232 dsn = username = password = NULL;
00233 enabled = 1;
00234 connect = 0;
00235 pooling = 0;
00236 limit = 0;
00237 for (v = ast_variable_browse(config, cat); v; v = v->next) {
00238 if (!strcasecmp(v->name, "pooling")) {
00239 if (ast_true(v->value))
00240 pooling = 1;
00241 } else if (!strcasecmp(v->name, "limit")) {
00242 sscanf(v->value, "%d", &limit);
00243 if (ast_true(v->value) && !limit) {
00244 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);
00245 limit = 1023;
00246 } else if (ast_false(v->value)) {
00247 ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Disabling ODBC class '%s'.\n", v->value, cat);
00248 enabled = 0;
00249 break;
00250 }
00251 } else if (!strcasecmp(v->name, "enabled")) {
00252 enabled = ast_true(v->value);
00253 } else if (!strcasecmp(v->name, "pre-connect")) {
00254 connect = ast_true(v->value);
00255 } else if (!strcasecmp(v->name, "dsn")) {
00256 dsn = v->value;
00257 } else if (!strcasecmp(v->name, "username")) {
00258 username = v->value;
00259 } else if (!strcasecmp(v->name, "password")) {
00260 password = v->value;
00261 }
00262 }
00263
00264 if (enabled && !ast_strlen_zero(dsn)) {
00265 new = ast_calloc(1, sizeof(*new));
00266
00267 if (!new) {
00268 res = -1;
00269 break;
00270 }
00271
00272 if (cat)
00273 ast_copy_string(new->name, cat, sizeof(new->name));
00274 if (dsn)
00275 ast_copy_string(new->dsn, dsn, sizeof(new->dsn));
00276 if (username)
00277 ast_copy_string(new->username, username, sizeof(new->username));
00278 if (password)
00279 ast_copy_string(new->password, password, sizeof(new->password));
00280
00281 SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env);
00282 res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
00283
00284 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00285 ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n");
00286 SQLFreeHandle(SQL_HANDLE_ENV, new->env);
00287 return res;
00288 }
00289
00290 if (pooling) {
00291 new->haspool = pooling;
00292 if (limit) {
00293 new->limit = limit;
00294 } else {
00295 ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless. Changing limit from 0 to 5.\n");
00296 new->limit = 5;
00297 }
00298 }
00299
00300 odbc_register_class(new, connect);
00301 ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn);
00302 }
00303 }
00304 }
00305 ast_config_destroy(config);
00306 return res;
00307 }
00308
00309 static int odbc_show_command(int fd, int argc, char **argv)
00310 {
00311 struct odbc_class *class;
00312 struct odbc_obj *current;
00313
00314 AST_LIST_LOCK(&odbc_list);
00315 AST_LIST_TRAVERSE(&odbc_list, class, list) {
00316 if ((argc == 2) || (argc == 3 && !strcmp(argv[2], "all")) || (!strcmp(argv[2], class->name))) {
00317 int count = 0;
00318 ast_cli(fd, "Name: %s\nDSN: %s\n", class->name, class->dsn);
00319
00320 if (class->haspool) {
00321 ast_cli(fd, "Pooled: yes\nLimit: %d\nConnections in use: %d\n", class->limit, class->count);
00322
00323 AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) {
00324 ast_cli(fd, " Connection %d: %s", ++count, current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
00325 }
00326 } else {
00327
00328 AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) {
00329 ast_cli(fd, "Pooled: no\nConnected: %s\n", current->up && ast_odbc_sanity_check(current) ? "yes" : "no");
00330 }
00331 }
00332
00333 ast_cli(fd, "\n");
00334 }
00335 }
00336 AST_LIST_UNLOCK(&odbc_list);
00337
00338 return 0;
00339 }
00340
00341 static char show_usage[] =
00342 "Usage: odbc show [<class>]\n"
00343 " List settings of a particular ODBC class.\n"
00344 " or, if not specified, all classes.\n";
00345
00346 static struct ast_cli_entry cli_odbc[] = {
00347 { { "odbc", "show", NULL },
00348 odbc_show_command, "List ODBC DSN(s)",
00349 show_usage },
00350 };
00351
00352 static int odbc_register_class(struct odbc_class *class, int connect)
00353 {
00354 struct odbc_obj *obj;
00355 if (class) {
00356 AST_LIST_LOCK(&odbc_list);
00357 AST_LIST_INSERT_HEAD(&odbc_list, class, list);
00358 AST_LIST_UNLOCK(&odbc_list);
00359
00360 if (connect) {
00361
00362 obj = ast_odbc_request_obj(class->name, 0);
00363 if (obj)
00364 ast_odbc_release_obj(obj);
00365 }
00366
00367 return 0;
00368 } else {
00369 ast_log(LOG_WARNING, "Attempted to register a NULL class?\n");
00370 return -1;
00371 }
00372 }
00373
00374 void ast_odbc_release_obj(struct odbc_obj *obj)
00375 {
00376
00377
00378 obj->used = 0;
00379 }
00380
00381 struct odbc_obj *ast_odbc_request_obj(const char *name, int check)
00382 {
00383 struct odbc_obj *obj = NULL;
00384 struct odbc_class *class;
00385
00386 AST_LIST_LOCK(&odbc_list);
00387 AST_LIST_TRAVERSE(&odbc_list, class, list) {
00388 if (!strcmp(class->name, name))
00389 break;
00390 }
00391 AST_LIST_UNLOCK(&odbc_list);
00392
00393 if (!class)
00394 return NULL;
00395
00396 AST_LIST_LOCK(&class->odbc_obj);
00397 if (class->haspool) {
00398
00399 AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) {
00400 if (! obj->used) {
00401 obj->used = 1;
00402 break;
00403 }
00404 }
00405
00406 if (!obj && (class->count < class->limit)) {
00407 class->count++;
00408 obj = ast_calloc(1, sizeof(*obj));
00409 if (!obj) {
00410 AST_LIST_UNLOCK(&class->odbc_obj);
00411 return NULL;
00412 }
00413 ast_mutex_init(&obj->lock);
00414 obj->parent = class;
00415 odbc_obj_connect(obj);
00416 AST_LIST_INSERT_TAIL(&class->odbc_obj, obj, list);
00417 }
00418 } else {
00419
00420 AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) {
00421
00422 break;
00423 }
00424
00425 if (!obj) {
00426
00427 obj = ast_calloc(1, sizeof(*obj));
00428 if (!obj) {
00429 AST_LIST_UNLOCK(&class->odbc_obj);
00430 return NULL;
00431 }
00432 ast_mutex_init(&obj->lock);
00433 obj->parent = class;
00434 if (odbc_obj_connect(obj) == ODBC_FAIL) {
00435 ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
00436 ast_mutex_destroy(&obj->lock);
00437 free(obj);
00438 obj = NULL;
00439 } else {
00440 AST_LIST_INSERT_HEAD(&class->odbc_obj, obj, list);
00441 }
00442 }
00443 }
00444 AST_LIST_UNLOCK(&class->odbc_obj);
00445
00446 if (obj && check) {
00447 ast_odbc_sanity_check(obj);
00448 }
00449 return obj;
00450 }
00451
00452 static odbc_status odbc_obj_disconnect(struct odbc_obj *obj)
00453 {
00454 int res;
00455 ast_mutex_lock(&obj->lock);
00456
00457 res = SQLDisconnect(obj->con);
00458
00459 if (res == ODBC_SUCCESS) {
00460 ast_log(LOG_WARNING, "res_odbc: disconnected %d from %s [%s]\n", res, obj->parent->name, obj->parent->dsn);
00461 } else {
00462 ast_log(LOG_WARNING, "res_odbc: %s [%s] already disconnected\n",
00463 obj->parent->name, obj->parent->dsn);
00464 }
00465 obj->up = 0;
00466 ast_mutex_unlock(&obj->lock);
00467 return ODBC_SUCCESS;
00468 }
00469
00470 static odbc_status odbc_obj_connect(struct odbc_obj *obj)
00471 {
00472 int res;
00473 SQLINTEGER err;
00474 short int mlen;
00475 unsigned char msg[200], stat[10];
00476 #ifdef NEEDTRACE
00477 SQLINTEGER enable = 1;
00478 char *tracefile = "/tmp/odbc.trace";
00479 #endif
00480 ast_mutex_lock(&obj->lock);
00481
00482 res = SQLAllocHandle(SQL_HANDLE_DBC, obj->parent->env, &obj->con);
00483
00484 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00485
00486 ast_log(LOG_WARNING, "res_odbc: Error AllocHDB %d\n", res);
00487 SQLFreeHandle(SQL_HANDLE_ENV, obj->parent->env);
00488
00489 ast_mutex_unlock(&obj->lock);
00490 return ODBC_FAIL;
00491 }
00492 SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *) 10, 0);
00493 #ifdef NEEDTRACE
00494 SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER);
00495 SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile));
00496 #endif
00497
00498 if (obj->up) {
00499 odbc_obj_disconnect(obj);
00500 ast_log(LOG_NOTICE, "Re-connecting %s\n", obj->parent->name);
00501 } else {
00502 ast_log(LOG_NOTICE, "Connecting %s\n", obj->parent->name);
00503 }
00504
00505 res = SQLConnect(obj->con,
00506 (SQLCHAR *) obj->parent->dsn, SQL_NTS,
00507 (SQLCHAR *) obj->parent->username, SQL_NTS,
00508 (SQLCHAR *) obj->parent->password, SQL_NTS);
00509
00510 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00511 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, stat, &err, msg, 100, &mlen);
00512 ast_mutex_unlock(&obj->lock);
00513 ast_log(LOG_WARNING, "res_odbc: Error SQLConnect=%d errno=%d %s\n", res, (int)err, msg);
00514 return ODBC_FAIL;
00515 } else {
00516 ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->parent->name, obj->parent->dsn);
00517 obj->up = 1;
00518 }
00519
00520 ast_mutex_unlock(&obj->lock);
00521 return ODBC_SUCCESS;
00522 }
00523
00524 static int reload(void)
00525 {
00526 static char *cfg = "res_odbc.conf";
00527 struct ast_config *config;
00528 struct ast_variable *v;
00529 char *cat, *dsn, *username, *password;
00530 int enabled, pooling, limit;
00531 int connect = 0, res = 0;
00532
00533 struct odbc_class *new, *class;
00534 struct odbc_obj *current;
00535
00536
00537 AST_LIST_LOCK(&odbc_list);
00538 AST_LIST_TRAVERSE(&odbc_list, class, list) {
00539 class->delme = 1;
00540 }
00541
00542 config = ast_config_load(cfg);
00543 if (config) {
00544 for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) {
00545 if (!strcasecmp(cat, "ENV")) {
00546 for (v = ast_variable_browse(config, cat); v; v = v->next) {
00547 setenv(v->name, v->value, 1);
00548 ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value);
00549 }
00550 } else {
00551
00552 dsn = username = password = NULL;
00553 enabled = 1;
00554 connect = 0;
00555 pooling = 0;
00556 limit = 0;
00557 for (v = ast_variable_browse(config, cat); v; v = v->next) {
00558 if (!strcasecmp(v->name, "pooling")) {
00559 pooling = 1;
00560 } else if (!strcasecmp(v->name, "limit")) {
00561 sscanf(v->value, "%d", &limit);
00562 if (ast_true(v->value) && !limit) {
00563 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);
00564 limit = 1023;
00565 } else if (ast_false(v->value)) {
00566 ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Disabling ODBC class '%s'.\n", v->value, cat);
00567 enabled = 0;
00568 break;
00569 }
00570 } else if (!strcasecmp(v->name, "enabled")) {
00571 enabled = ast_true(v->value);
00572 } else if (!strcasecmp(v->name, "pre-connect")) {
00573 connect = ast_true(v->value);
00574 } else if (!strcasecmp(v->name, "dsn")) {
00575 dsn = v->value;
00576 } else if (!strcasecmp(v->name, "username")) {
00577 username = v->value;
00578 } else if (!strcasecmp(v->name, "password")) {
00579 password = v->value;
00580 }
00581 }
00582
00583 if (enabled && !ast_strlen_zero(dsn)) {
00584
00585 AST_LIST_TRAVERSE(&odbc_list, class, list) {
00586 if (!strcmp(class->name, cat)) {
00587 class->delme = 0;
00588 break;
00589 }
00590 }
00591
00592 if (class) {
00593 new = class;
00594 } else {
00595 new = ast_calloc(1, sizeof(*new));
00596 }
00597
00598 if (!new) {
00599 res = -1;
00600 break;
00601 }
00602
00603 if (cat)
00604 ast_copy_string(new->name, cat, sizeof(new->name));
00605 if (dsn)
00606 ast_copy_string(new->dsn, dsn, sizeof(new->dsn));
00607 if (username)
00608 ast_copy_string(new->username, username, sizeof(new->username));
00609 if (password)
00610 ast_copy_string(new->password, password, sizeof(new->password));
00611
00612 if (!class) {
00613 SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env);
00614 res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
00615
00616 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00617 ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n");
00618 SQLFreeHandle(SQL_HANDLE_ENV, new->env);
00619 AST_LIST_UNLOCK(&odbc_list);
00620 return res;
00621 }
00622 }
00623
00624 if (pooling) {
00625 new->haspool = pooling;
00626 if (limit) {
00627 new->limit = limit;
00628 } else {
00629 ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless. Changing limit from 0 to 5.\n");
00630 new->limit = 5;
00631 }
00632 }
00633
00634 if (class) {
00635 ast_log(LOG_NOTICE, "Refreshing ODBC class '%s' dsn->[%s]\n", cat, dsn);
00636 } else {
00637 odbc_register_class(new, connect);
00638 ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn);
00639 }
00640 }
00641 }
00642 }
00643 ast_config_destroy(config);
00644 }
00645
00646
00647 AST_LIST_TRAVERSE_SAFE_BEGIN(&odbc_list, class, list) {
00648 if (class->delme && class->haspool && class->count == 0) {
00649 AST_LIST_TRAVERSE_SAFE_BEGIN(&(class->odbc_obj), current, list) {
00650 AST_LIST_REMOVE_CURRENT(&(class->odbc_obj), list);
00651 odbc_obj_disconnect(current);
00652 ast_mutex_destroy(¤t->lock);
00653 free(current);
00654 }
00655 AST_LIST_TRAVERSE_SAFE_END;
00656
00657 AST_LIST_REMOVE_CURRENT(&odbc_list, list);
00658 free(class);
00659 }
00660 }
00661 AST_LIST_TRAVERSE_SAFE_END;
00662 AST_LIST_UNLOCK(&odbc_list);
00663
00664 return 0;
00665 }
00666
00667 static int unload_module(void)
00668 {
00669
00670 return -1;
00671 }
00672
00673 static int load_module(void)
00674 {
00675 if(load_odbc_config() == -1)
00676 return AST_MODULE_LOAD_DECLINE;
00677 ast_cli_register_multiple(cli_odbc, sizeof(cli_odbc) / sizeof(struct ast_cli_entry));
00678 ast_log(LOG_NOTICE, "res_odbc loaded.\n");
00679 return 0;
00680 }
00681
00682 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "ODBC Resource",
00683 .load = load_module,
00684 .unload = unload_module,
00685 .reload = reload,
00686 );