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