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