From 501ac5189ccdbc4c51d15a33d3a7e84d6a8a0e7b Mon Sep 17 00:00:00 2001 From: Russell Bryant <russell@russellbryant.com> Date: Tue, 18 Apr 2006 18:16:32 +0000 Subject: [PATCH] update res_odbc to support pooled connections (from tilghman's developer branch, res_odbc_rewrite) git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@21181 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- funcs/func_odbc.c | 18 +- include/asterisk/res_odbc.h | 80 ++-- res/res_config_odbc.c | 162 +++++--- res/res_odbc.c | 724 ++++++++++++++++++++---------------- 4 files changed, 576 insertions(+), 408 deletions(-) diff --git a/funcs/func_odbc.c b/funcs/func_odbc.c index 63c58dff4e..b9751edeec 100644 --- a/funcs/func_odbc.c +++ b/funcs/func_odbc.c @@ -77,7 +77,7 @@ static void acf_odbc_error(SQLHSTMT stmt, int res) */ static int acf_odbc_write(struct ast_channel *chan, char *cmd, char *s, const char *value) { - odbc_obj *obj; + struct odbc_obj *obj; struct acf_odbc_query *query; char *t, *arg, buf[2048]="", varname[15]; int res, argcount=0, valcount=0, i, retry=0; @@ -104,10 +104,10 @@ static int acf_odbc_write(struct ast_channel *chan, char *cmd, char *s, const ch return -1; } - obj = fetch_odbc_obj(query->dsn, 0); + obj = odbc_request_obj(query->dsn, 0); if (!obj) { - ast_log(LOG_ERROR, "No such DSN registered: %s (check res_odbc.conf)\n", query->dsn); + ast_log(LOG_ERROR, "No such DSN registered (or out of connections): %s (check res_odbc.conf)\n", query->dsn); ast_mutex_unlock(&query_lock); return -1; } @@ -204,9 +204,9 @@ retry_write: } } SQLFreeHandle(SQL_HANDLE_STMT, stmt); - odbc_obj_disconnect(obj); + odbc_release_obj(obj); /* All handles are now invalid (after a disconnect), so we gotta redo all handles */ - odbc_obj_connect(obj); + obj = odbc_request_obj("asterisk", 1); if (!retry) { retry = 1; goto retry_write; @@ -235,7 +235,7 @@ retry_write: static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf, size_t len) { - odbc_obj *obj; + struct odbc_obj *obj; struct acf_odbc_query *query; char *arg, sql[2048] = "", varname[15]; int count=0, res, x; @@ -260,10 +260,10 @@ static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf return -1; } - obj = fetch_odbc_obj(query->dsn, 0); + obj = odbc_request_obj(query->dsn, 0); if (!obj) { - ast_log(LOG_ERROR, "No such DSN registered: %s (check res_odbc.conf)\n", query->dsn); + ast_log(LOG_ERROR, "No such DSN registered (or out of connections): %s (check res_odbc.conf)\n", query->dsn); ast_mutex_unlock(&query_lock); return -1; } @@ -331,7 +331,7 @@ static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf goto acf_out; } - for (x=0; x<colcount; x++) { + for (x = 0; x < colcount; x++) { int buflen, coldatalen; char coldata[256]; diff --git a/include/asterisk/res_odbc.h b/include/asterisk/res_odbc.h index 9e4070bebd..bbf0996f8f 100644 --- a/include/asterisk/res_odbc.h +++ b/include/asterisk/res_odbc.h @@ -3,9 +3,11 @@ * * Copyright (C) 1999 - 2005, Digium, Inc. * Copyright (C) 2004 - 2005, Anthony Minessale II + * Copyright (C) 2006, Tilghman Lesher * * Mark Spencer <markster@digium.com> * Anthony Minessale <anthmct@yahoo.com> + * Tilghman Lesher <res_odbc_200603@the-tilghman.com> * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact @@ -29,34 +31,66 @@ #include <sqlext.h> #include <sqltypes.h> -typedef struct odbc_obj odbc_obj; - -typedef enum { ODBC_SUCCESS=0,ODBC_FAIL=-1} odbc_status; +typedef enum { ODBC_SUCCESS=0, ODBC_FAIL=-1} odbc_status; struct odbc_obj { - char *name; - char *dsn; - char *username; - char *password; - SQLHENV env; /* ODBC Environment */ - SQLHDBC con; /* ODBC Connection Handle */ - SQLHSTMT stmt; /* ODBC Statement Handle */ ast_mutex_t lock; - int up; - + SQLHDBC con; /* ODBC Connection Handle */ + struct odbc_class *parent; /* Information about the connection is protected */ + unsigned int used:1; + unsigned int up:1; + AST_LIST_ENTRY(odbc_obj) list; }; /* functions */ -odbc_obj *new_odbc_obj(char *name,char *dsn,char *username, char *password); -odbc_status odbc_obj_connect(odbc_obj *obj); -odbc_status odbc_obj_disconnect(odbc_obj *obj); -void destroy_odbc_obj(odbc_obj **obj); -int register_odbc_obj(char *name,odbc_obj *obj); -odbc_obj *fetch_odbc_obj(const char *name, int check); -int odbc_dump_fd(int fd,odbc_obj *obj); -int odbc_sanity_check(odbc_obj *obj); -SQLHSTMT odbc_prepare_and_execute(odbc_obj *obj, SQLHSTMT (*prepare_cb)(odbc_obj *obj, void *data), void *data); -int odbc_smart_execute(odbc_obj *obj, SQLHSTMT stmt); -int odbc_smart_direct_execute(odbc_obj *obj, SQLHSTMT stmt, char *sql); + +/*! \brief Executes a prepared statement handle + * \param obj The non-NULL result of odbc_request_obj() + * \param stmt The prepared statement handle + * \return Returns 0 on success or -1 on failure + * + * This function was originally designed simply to execute a prepared + * statement handle and to retry if the initial execution failed. + * Unfortunately, it did this by disconnecting and reconnecting the database + * handle which on most databases causes the statement handle to become + * invalid. Therefore, this method has been deprecated in favor of + * odbc_prepare_and_execute() which allows the statement to be prepared + * multiple times, if necessary, in case of a loss of connection. + * + * This function really only ever worked with MySQL, where the statement handle is + * not prepared on the server. If you are not using MySQL, you should avoid it. + */ +int odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt); /* DEPRECATED */ + +/*! \brief Retrieves a connected ODBC object + * \param name The name of the ODBC class for which a connection is needed. + * \param check Whether to ensure that a connection is valid before returning the handle. Usually unnecessary. + * \return Returns an ODBC object or NULL if there is no connection available with the requested name. + * + * Connection classes may, in fact, contain multiple connection handles. If + * the connection is pooled, then each connection will be dedicated to the + * thread which requests it. Note that all connections should be released + * when the thread is done by calling odbc_release_obj(), below. + */ +struct odbc_obj *odbc_request_obj(const char *name, int check); + +/*! \brief Releases an ODBC object previously allocated by odbc_request_obj() + * \param obj The ODBC object + */ +void odbc_release_obj(struct odbc_obj *obj); + +/*! \brief Checks an ODBC object to ensure it is still connected + * \param obj The ODBC object + * \return Returns 0 if connected, -1 otherwise. + */ +int odbc_sanity_check(struct odbc_obj *obj); + +/*! \brief Prepares, executes, and returns the resulting statement handle. + * \param obj The ODBC object + * \param prepare_cb A function callback, which, when called, should return a statement handle prepared, with any necessary parameters or result columns bound. + * \param data A parameter to be passed to the prepare_cb parameter function, indicating which statement handle is to be prepared. + * \return Returns a statement handle or NULL on error. + */ +SQLHSTMT odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT (*prepare_cb)(struct odbc_obj *obj, void *data), void *data); #endif /* _ASTERISK_RES_ODBC_H */ diff --git a/res/res_config_odbc.c b/res/res_config_odbc.c index f2634a93c2..e48b01c02e 100644 --- a/res/res_config_odbc.c +++ b/res/res_config_odbc.c @@ -52,7 +52,7 @@ LOCAL_USER_DECL; static struct ast_variable *realtime_odbc(const char *database, const char *table, va_list ap) { - odbc_obj *obj; + struct odbc_obj *obj; SQLHSTMT stmt; char sql[1024]; char coltitle[256]; @@ -79,19 +79,21 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl if (!table) return NULL; - obj = fetch_odbc_obj(database, 0); + obj = odbc_request_obj(database, 0); if (!obj) return NULL; res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n"); + odbc_release_obj(obj); return NULL; } newparam = va_arg(aq, const char *); if (!newparam) { SQLFreeHandle (SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return NULL; } newval = va_arg(aq, const char *); @@ -107,6 +109,7 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql); SQLFreeHandle (SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return NULL; } @@ -123,6 +126,7 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql); SQLFreeHandle (SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return NULL; } @@ -130,20 +134,23 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql); SQLFreeHandle (SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return NULL; } res = SQLFetch(stmt); if (res == SQL_NO_DATA) { SQLFreeHandle (SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return NULL; } if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql); SQLFreeHandle (SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return NULL; } - for (x=0;x<colcount;x++) { + for (x = 0; x < colcount; x++) { rowdata[0] = '\0'; collen = sizeof(coltitle); res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen, @@ -152,6 +159,7 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql); if (var) ast_variables_destroy(var); + odbc_release_obj(obj); return NULL; } @@ -164,6 +172,7 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql); if (var) ast_variables_destroy(var); + odbc_release_obj(obj); return NULL; } stringp = rowdata; @@ -183,12 +192,13 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl SQLFreeHandle (SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return var; } static struct ast_config *realtime_multi_odbc(const char *database, const char *table, va_list ap) { - odbc_obj *obj; + struct odbc_obj *obj; SQLHSTMT stmt; char sql[1024]; char coltitle[256]; @@ -220,19 +230,21 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char * return NULL; memset(&ra, 0, sizeof(ra)); - obj = fetch_odbc_obj(database, 0); + obj = odbc_request_obj(database, 0); if (!obj) return NULL; res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n"); + odbc_release_obj(obj); return NULL; } newparam = va_arg(aq, const char *); if (!newparam) { - SQLFreeHandle (SQL_HANDLE_STMT, stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return NULL; } initfield = ast_strdupa(newparam); @@ -252,7 +264,8 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char * res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql); - SQLFreeHandle (SQL_HANDLE_STMT, stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return NULL; } @@ -268,21 +281,24 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char * if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql); - SQLFreeHandle (SQL_HANDLE_STMT, stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return NULL; } res = SQLNumResultCols(stmt, &colcount); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql); - SQLFreeHandle (SQL_HANDLE_STMT, stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return NULL; } cfg = ast_config_new(); if (!cfg) { ast_log(LOG_WARNING, "Out of memory!\n"); - SQLFreeHandle (SQL_HANDLE_STMT, stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return NULL; } @@ -332,13 +348,14 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char * ast_category_append(cfg, cat); } - SQLFreeHandle (SQL_HANDLE_STMT, stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return cfg; } static int update_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap) { - odbc_obj *obj; + struct odbc_obj *obj; SQLHSTMT stmt; char sql[256]; SQLLEN rowcount=0; @@ -352,19 +369,21 @@ static int update_odbc(const char *database, const char *table, const char *keyf if (!table) return -1; - obj = fetch_odbc_obj (database, 0); + obj = odbc_request_obj(database, 0); if (!obj) return -1; res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n"); + odbc_release_obj(obj); return -1; } newparam = va_arg(aq, const char *); if (!newparam) { SQLFreeHandle (SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return -1; } newval = va_arg(aq, const char *); @@ -380,6 +399,7 @@ static int update_odbc(const char *database, const char *table, const char *keyf if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql); SQLFreeHandle (SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return -1; } @@ -398,103 +418,147 @@ static int update_odbc(const char *database, const char *table, const char *keyf if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql); SQLFreeHandle (SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return -1; } res = SQLRowCount(stmt, &rowcount); SQLFreeHandle (SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql); return -1; } - if (rowcount >= 0) - return (int)rowcount; + if (rowcount >= 0) + return (int)rowcount; return -1; } +struct config_odbc_obj { + char *sql; + unsigned long id; + unsigned long cat_metric; + unsigned long var_metric; + unsigned long commented; + char filename[128]; + char category[128]; + char var_name[128]; + char var_val[128]; + SQLINTEGER err; +}; + +static SQLHSTMT config_odbc_prepare(struct odbc_obj *obj, void *data) +{ + struct config_odbc_obj *q = data; + SQLHSTMT sth; + int res; + + res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &sth); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + if (option_verbose > 3) + ast_verbose( VERBOSE_PREFIX_4 "Failure in AllocStatement %d\n", res); + return NULL; + } + + res = SQLPrepare(sth, (unsigned char *)q->sql, SQL_NTS); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + if (option_verbose > 3) + ast_verbose( VERBOSE_PREFIX_4 "Error in PREPARE %d\n", res); + SQLFreeHandle(SQL_HANDLE_STMT, sth); + return NULL; + } + + SQLBindCol(sth, 1, SQL_C_ULONG, &q->id, sizeof(q->id), &q->err); + SQLBindCol(sth, 2, SQL_C_ULONG, &q->cat_metric, sizeof(q->cat_metric), &q->err); + SQLBindCol(sth, 3, SQL_C_ULONG, &q->var_metric, sizeof(q->var_metric), &q->err); + SQLBindCol(sth, 4, SQL_C_ULONG, &q->commented, sizeof(q->commented), &q->err); + SQLBindCol(sth, 5, SQL_C_CHAR, q->filename, sizeof(q->filename), &q->err); + SQLBindCol(sth, 6, SQL_C_CHAR, q->category, sizeof(q->category), &q->err); + SQLBindCol(sth, 7, SQL_C_CHAR, q->var_name, sizeof(q->var_name), &q->err); + SQLBindCol(sth, 8, SQL_C_CHAR, q->var_val, sizeof(q->var_val), &q->err); + + return sth; +} + static struct ast_config *config_odbc(const char *database, const char *table, const char *file, struct ast_config *cfg) { struct ast_variable *new_v; struct ast_category *cur_cat; int res = 0; - odbc_obj *obj; - SQLINTEGER err=0, commented=0, cat_metric=0, var_metric=0, last_cat_metric=0; - SQLBIGINT id; - char sql[255] = "", filename[128], category[128], var_name[128], var_val[512]; + struct odbc_obj *obj; + char sql[255] = ""; + unsigned int last_cat_metric = 0; SQLSMALLINT rowcount=0; SQLHSTMT stmt; char last[128] = ""; + struct config_odbc_obj q; + + memset(&q, 0, sizeof(q)); if (!file || !strcmp (file, "res_config_odbc.conf")) return NULL; /* cant configure myself with myself ! */ - obj = fetch_odbc_obj(database, 0); + obj = odbc_request_obj(database, 0); if (!obj) return NULL; - res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt); - - SQLBindCol (stmt, 1, SQL_C_ULONG, &id, sizeof (id), &err); - SQLBindCol (stmt, 2, SQL_C_ULONG, &cat_metric, sizeof (cat_metric), &err); - SQLBindCol (stmt, 3, SQL_C_ULONG, &var_metric, sizeof (var_metric), &err); - SQLBindCol (stmt, 4, SQL_C_ULONG, &commented, sizeof (commented), &err); - SQLBindCol (stmt, 5, SQL_C_CHAR, &filename, sizeof (filename), &err); - SQLBindCol (stmt, 6, SQL_C_CHAR, &category, sizeof (category), &err); - SQLBindCol (stmt, 7, SQL_C_CHAR, &var_name, sizeof (var_name), &err); - SQLBindCol (stmt, 8, SQL_C_CHAR, &var_val, sizeof (var_val), &err); - snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE filename='%s' and commented=0 ORDER BY filename,cat_metric desc,var_metric asc,category,var_name,var_val,id", table, file); + q.sql = sql; - res = odbc_smart_direct_execute(obj, stmt, sql); - - if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { - ast_log (LOG_WARNING, "SQL select error!\n[%s]\n\n", sql); - SQLFreeHandle (SQL_HANDLE_STMT, stmt); + stmt = odbc_prepare_and_execute(obj, config_odbc_prepare, &q); + + if (!stmt) { + ast_log(LOG_WARNING, "SQL select error!\n[%s]\n\n", sql); + odbc_release_obj(obj); return NULL; } - res = SQLNumResultCols (stmt, &rowcount); + res = SQLNumResultCols(stmt, &rowcount); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { - ast_log (LOG_WARNING, "SQL NumResultCols error!\n[%s]\n\n", sql); - SQLFreeHandle (SQL_HANDLE_STMT, stmt); + ast_log(LOG_WARNING, "SQL NumResultCols error!\n[%s]\n\n", sql); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return NULL; } if (!rowcount) { - ast_log (LOG_NOTICE, "found nothing\n"); + ast_log(LOG_NOTICE, "found nothing\n"); + odbc_release_obj(obj); return cfg; } cur_cat = ast_config_get_current_category(cfg); while ((res = SQLFetch(stmt)) != SQL_NO_DATA) { - if (!strcmp (var_name, "#include")) { - if (!ast_config_internal_load(var_val, cfg)) { - SQLFreeHandle (SQL_HANDLE_STMT, stmt); + if (!strcmp (q.var_name, "#include")) { + if (!ast_config_internal_load(q.var_val, cfg)) { + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return NULL; } continue; } - if (strcmp(last, category) || last_cat_metric != cat_metric) { - cur_cat = ast_category_new(category); + if (strcmp(last, q.category) || last_cat_metric != q.cat_metric) { + cur_cat = ast_category_new(q.category); if (!cur_cat) { ast_log(LOG_WARNING, "Out of memory!\n"); break; } - strcpy(last, category); - last_cat_metric = cat_metric; + strcpy(last, q.category); + last_cat_metric = q.cat_metric; ast_category_append(cfg, cur_cat); } - new_v = ast_variable_new(var_name, var_val); + new_v = ast_variable_new(q.var_name, q.var_val); ast_variable_append(cur_cat, new_v); } - SQLFreeHandle (SQL_HANDLE_STMT, stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + odbc_release_obj(obj); return cfg; } diff --git a/res/res_odbc.c b/res/res_odbc.c index cc255844dc..76fd217acf 100644 --- a/res/res_odbc.c +++ b/res/res_odbc.c @@ -49,66 +49,30 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cli.h" #include "asterisk/lock.h" #include "asterisk/res_odbc.h" -#define MAX_ODBC_HANDLES 25 -struct odbc_list +struct odbc_class { + AST_LIST_ENTRY(odbc_class) list; char name[80]; - odbc_obj *obj; - int used; + char dsn[80]; + char username[80]; + char password[80]; + SQLHENV env; + unsigned int haspool:1; /* Boolean - TDS databases need this */ + unsigned int limit:10; /* Gives a limit of 1023 maximum */ + unsigned int count:10; /* Running count of pooled connections */ + unsigned int delme:1; /* Purge the class */ + AST_LIST_HEAD(, odbc_obj) odbc_obj; }; -static struct odbc_list ODBC_REGISTRY[MAX_ODBC_HANDLES]; +AST_LIST_HEAD_STATIC(odbc_list, odbc_class); +static odbc_status odbc_obj_connect(struct odbc_obj *obj); +static odbc_status odbc_obj_disconnect(struct odbc_obj *obj); +static int odbc_register_class(struct odbc_class *class, int connect); -static void odbc_destroy(void) -{ - int x = 0; - - for (x = 0; x < MAX_ODBC_HANDLES; x++) { - if (ODBC_REGISTRY[x].obj) { - destroy_odbc_obj(&ODBC_REGISTRY[x].obj); - ODBC_REGISTRY[x].obj = NULL; - } - } -} - -static odbc_obj *odbc_read(struct odbc_list *registry, const char *name) -{ - int x = 0; - for (x = 0; x < MAX_ODBC_HANDLES; x++) { - if (registry[x].used && !strcmp(registry[x].name, name)) { - return registry[x].obj; - } - } - return NULL; -} - -static int odbc_write(struct odbc_list *registry, char *name, odbc_obj *obj) -{ - int x = 0; - for (x = 0; x < MAX_ODBC_HANDLES; x++) { - if (!registry[x].used) { - ast_copy_string(registry[x].name, name, sizeof(registry[x].name)); - registry[x].obj = obj; - registry[x].used = 1; - return 1; - } - } - return 0; -} - -static void odbc_init(void) -{ - int x = 0; - for (x = 0; x < MAX_ODBC_HANDLES; x++) { - memset(&ODBC_REGISTRY[x], 0, sizeof(struct odbc_list)); - } -} - -/* internal stuff */ -SQLHSTMT odbc_prepare_and_execute(odbc_obj *obj, SQLHSTMT (*prepare_cb)(odbc_obj *obj, void *data), void *data) +SQLHSTMT odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT (*prepare_cb)(struct odbc_obj *obj, void *data), void *data) { int res = 0, i, attempt; SQLINTEGER nativeerror=0, numfields=0; @@ -142,9 +106,12 @@ SQLHSTMT odbc_prepare_and_execute(odbc_obj *obj, SQLHSTMT (*prepare_cb)(odbc_obj ast_log(LOG_WARNING, "SQL Execute error %d! Attempting a reconnect...\n", res); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_mutex_lock(&obj->lock); obj->up = 0; - ast_mutex_unlock(&obj->lock); + /* + * While this isn't the best way to try to correct an error, this won't automatically + * fail when the statement handle invalidates. + */ + /* XXX Actually, it might, if we're using a non-pooled connection. Possible race here. XXX */ odbc_obj_disconnect(obj); odbc_obj_connect(obj); continue; @@ -156,7 +123,7 @@ SQLHSTMT odbc_prepare_and_execute(odbc_obj *obj, SQLHSTMT (*prepare_cb)(odbc_obj return stmt; } -int odbc_smart_execute(odbc_obj *obj, SQLHSTMT stmt) +int odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt) { int res = 0, i; SQLINTEGER nativeerror=0, numfields=0; @@ -176,7 +143,14 @@ int odbc_smart_execute(odbc_obj *obj, SQLHSTMT stmt) } } } -/* +#if 0 + /* This is a really bad method of trying to correct a dead connection. It + * only ever really worked with MySQL. It will not work with any other + * database, since most databases prepare their statements on the server, + * and if you disconnect, you invalidate the statement handle. Hence, if + * you disconnect, you're going to fail anyway, whether you try to execute + * a second time or not. + */ ast_log(LOG_WARNING, "SQL Execute error %d! Attempting a reconnect...\n", res); ast_mutex_lock(&obj->lock); obj->up = 0; @@ -184,58 +158,38 @@ int odbc_smart_execute(odbc_obj *obj, SQLHSTMT stmt) odbc_obj_disconnect(obj); odbc_obj_connect(obj); res = SQLExecute(stmt); -*/ +#endif } return res; } -int odbc_smart_direct_execute(odbc_obj *obj, SQLHSTMT stmt, char *sql) -{ - int res = 0; - - res = SQLExecDirect (stmt, (unsigned char *)sql, SQL_NTS); - if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { - ast_log(LOG_WARNING, "SQL Execute error! Attempting a reconnect...\n"); - ast_mutex_lock(&obj->lock); - obj->up = 0; - ast_mutex_unlock(&obj->lock); - odbc_obj_disconnect(obj); - odbc_obj_connect(obj); - res = SQLExecDirect (stmt, (unsigned char *)sql, SQL_NTS); - } - - return res; -} - -int odbc_sanity_check(odbc_obj *obj) +int odbc_sanity_check(struct odbc_obj *obj) { char *test_sql = "select 1"; SQLHSTMT stmt; int res = 0; - ast_mutex_lock(&obj->lock); - if(obj->up) { /* so you say... let's make sure */ - res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt); + if (obj->up) { + res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { - obj->up = 0; /* Liar!*/ + obj->up = 0; } else { res = SQLPrepare(stmt, (unsigned char *)test_sql, SQL_NTS); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { - obj->up = 0; /* Liar!*/ + obj->up = 0; } else { res = SQLExecute(stmt); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { - obj->up = 0; /* Liar!*/ + obj->up = 0; } } } SQLFreeHandle (SQL_HANDLE_STMT, stmt); } - ast_mutex_unlock(&obj->lock); - if(!obj->up) { /* Try to reconnect! */ + if (!obj->up) { /* Try to reconnect! */ ast_log(LOG_WARNING, "Connection is down attempting to reconnect...\n"); odbc_obj_disconnect(obj); odbc_obj_connect(obj); @@ -249,325 +203,305 @@ static int load_odbc_config(void) struct ast_config *config; struct ast_variable *v; char *cat, *dsn, *username, *password; - int enabled; - int connect = 0; - char *env_var; + int enabled, pooling, limit; + int connect = 0, res = 0; - odbc_obj *obj; + struct odbc_class *new; config = ast_config_load(cfg); if (config) { for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) { - if (!strcmp(cat, "ENV")) { + if (!strcasecmp(cat, "ENV")) { + for (v = ast_variable_browse(config, cat); v; v = v->next) { + setenv(v->name, v->value, 1); + ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value); + } + } else { + /* Reset all to defaults for each class of odbc connections */ + dsn = username = password = NULL; + enabled = 1; + connect = 0; + pooling = 0; + limit = 0; for (v = ast_variable_browse(config, cat); v; v = v->next) { - env_var = malloc(strlen(v->name) + strlen(v->value) + 2); - if (env_var) { - sprintf(env_var, "%s=%s", v->name, v->value); - ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value); - putenv(env_var); - free(env_var); + if (!strcasecmp(v->name, "pooling")) { + pooling = 1; + } else if (!strcasecmp(v->name, "limit")) { + sscanf(v->value, "%d", &limit); + if (ast_true(v->value) && !limit) { + 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); + limit = 1023; + } else if (ast_false(v->value)) { + ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Disabling ODBC class '%s'.\n", v->value, cat); + enabled = 0; + break; + } + } else if (!strcasecmp(v->name, "enabled")) { + enabled = ast_true(v->value); + } else if (!strcasecmp(v->name, "pre-connect")) { + connect = ast_true(v->value); + } else if (!strcasecmp(v->name, "dsn")) { + dsn = v->value; + } else if (!strcasecmp(v->name, "username")) { + username = v->value; + } else if (!strcasecmp(v->name, "password")) { + password = v->value; } } - cat = ast_category_browse(config, cat); - } + if (enabled && !ast_strlen_zero(dsn)) { + new = ast_calloc(1, sizeof(*new)); - dsn = username = password = NULL; - enabled = 1; - connect = 0; - for (v = ast_variable_browse(config, cat); v; v = v->next) { - if (!strcmp(v->name, "enabled")) - enabled = ast_true(v->value); - if (!strcmp(v->name, "pre-connect")) - connect = ast_true(v->value); - if (!strcmp(v->name, "dsn")) - dsn = v->value; - if (!strcmp(v->name, "username")) - username = v->value; - if (!strcmp(v->name, "password")) - password = v->value; - } + if (!new) { + ast_log(LOG_ERROR, "Memory error while loading configuration.\n"); + res = -1; + break; + } - if (enabled && dsn) { - obj = new_odbc_obj(cat, dsn, username, password); - if (obj) { - register_odbc_obj(cat, obj); - ast_log(LOG_NOTICE, "registered database handle '%s' dsn->[%s]\n", cat, obj->dsn); - if (connect) { - odbc_obj_connect(obj); + if (cat) + ast_copy_string(new->name, cat, sizeof(new->name)); + if (dsn) + ast_copy_string(new->dsn, dsn, sizeof(new->dsn)); + if (username) + ast_copy_string(new->username, username, sizeof(new->username)); + if (password) + ast_copy_string(new->password, password, sizeof(new->password)); + + SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env); + res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0); + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n"); + SQLFreeHandle(SQL_HANDLE_ENV, new->env); + return res; + } + + if (pooling) { + new->haspool = pooling; + if (limit) { + new->limit = limit; + } else { + ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless. Changing limit from 0 to 5.\n"); + new->limit = 5; + } } - } else { - ast_log(LOG_WARNING, "Addition of obj %s failed.\n", cat); - } + odbc_register_class(new, connect); + ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn); + } } } ast_config_destroy(config); } - return 0; -} - -int odbc_dump_fd(int fd, odbc_obj *obj) -{ - /* make sure the connection is up before we lie to our master.*/ - odbc_sanity_check(obj); - ast_cli(fd, "Name: %s\nDSN: %s\nConnected: %s\n\n", obj->name, obj->dsn, obj->up ? "yes" : "no"); - return 0; -} - -static int odbc_connect_usage(int fd) -{ - ast_cli(fd, "usage odbc connect <DSN>\n"); - return 0; -} - -static int odbc_disconnect_usage(int fd) -{ - ast_cli(fd, "usage odbc disconnect <DSN>\n"); - return 0; + return res; } static int odbc_show_command(int fd, int argc, char **argv) { - odbc_obj *obj; - int x = 0; - - if (!strcmp(argv[1], "show")) { - if (!argv[2] || (argv[2] && !strcmp(argv[2], "all"))) { - for (x = 0; x < MAX_ODBC_HANDLES; x++) { - if (!ODBC_REGISTRY[x].used) - break; - if (ODBC_REGISTRY[x].obj) - odbc_dump_fd(fd, ODBC_REGISTRY[x].obj); - } - } else { - obj = odbc_read(ODBC_REGISTRY, argv[2]); - if (obj) - odbc_dump_fd(fd, obj); - } - } - return 0; -} + struct odbc_class *class; + struct odbc_obj *current; -static int odbc_disconnect_command(int fd, int argc, char **argv) -{ - odbc_obj *obj; - if (!strcmp(argv[1], "disconnect")) { - if (!argv[2]) - return odbc_disconnect_usage(fd); - - obj = odbc_read(ODBC_REGISTRY, argv[2]); - if (obj) { - odbc_obj_disconnect(obj); - } - } - return 0; -} + if (!strcmp(argv[1], "show")) { + AST_LIST_LOCK(&odbc_list); + AST_LIST_TRAVERSE(&odbc_list, class, list) { + if ((argc == 2) || (argc == 3 && !strcmp(argv[2], "all")) || (!strcmp(argv[2], class->name))) { + int count = 0; + ast_cli(fd, "Name: %s\nDSN: %s\n", class->name, class->dsn); -static int odbc_connect_command(int fd, int argc, char **argv) -{ - odbc_obj *obj; - if (!argv[1]) - return odbc_connect_usage(fd); + if (class->haspool) { + ast_cli(fd, "Pooled: yes\nLimit: %d\nConnections in use: %d\n", class->limit, class->count); - if (!strcmp(argv[1], "connect") || !strcmp(argv[1], "disconnect")) { - if (!argv[2]) - return odbc_connect_usage(fd); + AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) { + ast_cli(fd, " Connection %d: %s", ++count, current->up && odbc_sanity_check(current) ? "connected" : "disconnected"); + } + } else { + /* Should only ever be one of these */ + AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) { + ast_cli(fd, "Pooled: no\nConnected: %s\n", current->up && odbc_sanity_check(current) ? "yes" : "no"); + } + } - obj = odbc_read(ODBC_REGISTRY, argv[2]); - if (obj) { - odbc_obj_connect(obj); + ast_cli(fd, "\n"); + } } + AST_LIST_UNLOCK(&odbc_list); } return 0; } - -static char connect_usage[] = -"Usage: odbc connect <DSN>\n" -" Connect to ODBC DSN\n"; - -static char disconnect_usage[] = -"Usage: odbc connect <DSN>\n" -" Disconnect from ODBC DSN\n"; - static char show_usage[] = -"Usage: odbc show {DSN}\n" -" Show ODBC {DSN}\n" -" Specifying DSN will show that DSN else, all DSNs are shown\n"; - -static struct ast_cli_entry odbc_connect_struct = - { { "odbc", "connect", NULL }, odbc_connect_command, "Connect to ODBC DSN", connect_usage }; - - -static struct ast_cli_entry odbc_disconnect_struct = - { { "odbc", "disconnect", NULL }, odbc_disconnect_command, "Disconnect from ODBC DSN", disconnect_usage }; +"Usage: odbc show [<class>]\n" +" List settings of a particular ODBC class.\n" +" or, if not specified, all classes.\n"; static struct ast_cli_entry odbc_show_struct = { { "odbc", "show", NULL }, odbc_show_command, "Show ODBC DSN(s)", show_usage }; -/* api calls */ - -int register_odbc_obj(char *name, odbc_obj *obj) +static int odbc_register_class(struct odbc_class *class, int connect) { - if (obj != NULL) - return odbc_write(ODBC_REGISTRY, name, obj); - return 0; -} + struct odbc_obj *obj; + if (class) { + AST_LIST_LOCK(&odbc_list); + AST_LIST_INSERT_HEAD(&odbc_list, class, list); + AST_LIST_UNLOCK(&odbc_list); + + if (connect) { + /* Request and release builds a connection */ + obj = odbc_request_obj(class->name, 0); + odbc_release_obj(obj); + } -odbc_obj *fetch_odbc_obj(const char *name, int check) -{ - odbc_obj *obj = NULL; - if((obj = (odbc_obj *) odbc_read(ODBC_REGISTRY, name))) { - if(check) - odbc_sanity_check(obj); + return 0; + } else { + ast_log(LOG_WARNING, "Attempted to register a NULL class?\n"); + return -1; } - return obj; } -odbc_obj *new_odbc_obj(char *name, char *dsn, char *username, char *password) +void odbc_release_obj(struct odbc_obj *obj) { - static odbc_obj *new; + /* For pooled connections, this frees the connection to be + * reused. For non-pooled connections, it does nothing. */ + obj->used = 0; +} - if (!(new = calloc(1, sizeof(*new))) || - !(new->name = malloc(strlen(name) + 1)) || - !(new->dsn = malloc(strlen(dsn) + 1))) - goto cleanup; +struct odbc_obj *odbc_request_obj(const char *name, int check) +{ + struct odbc_obj *obj = NULL; + struct odbc_class *class; - if (username) { - if (!(new->username = malloc(strlen(username) + 1))) - goto cleanup; - strcpy(new->username, username); + AST_LIST_LOCK(&odbc_list); + AST_LIST_TRAVERSE(&odbc_list, class, list) { + if (!strcmp(class->name, name)) + break; } + AST_LIST_UNLOCK(&odbc_list); + + if (!class) + return NULL; + + AST_LIST_LOCK(&class->odbc_obj); + if (class->haspool) { + /* Recycle connections before building another */ + AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) { + if (! obj->used) { + obj->used = 1; + break; + } + } - if (password) { - if (!(new->password = malloc(strlen(password) + 1))) - goto cleanup; - strcpy(new->password, password); - } + if (!obj && (class->count < class->limit)) { + class->count++; + obj = ast_calloc(1, sizeof(*obj)); + if (!obj) { + ast_log(LOG_ERROR, "Out of memory\n"); + AST_LIST_UNLOCK(&class->odbc_obj); + return NULL; + } + ast_mutex_init(&obj->lock); + obj->parent = class; + odbc_obj_connect(obj); + AST_LIST_INSERT_TAIL(&class->odbc_obj, obj, list); + } + } else { + /* Non-pooled connection: multiple modules can use the same connection. */ + AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) { + /* Non-pooled connection: if there is an entry, return it */ + break; + } - strcpy(new->name, name); - strcpy(new->dsn, dsn); - new->env = SQL_NULL_HANDLE; - new->up = 0; - ast_mutex_init(&new->lock); - return new; - -cleanup: - if (new) { - free(new->name); - free(new->dsn); - free(new->username); - free(new->password); - - free(new); + if (!obj) { + /* No entry: build one */ + obj = ast_calloc(1, sizeof(*obj)); + if (!obj) { + ast_log(LOG_ERROR, "Out of memory\n"); + AST_LIST_UNLOCK(&class->odbc_obj); + return NULL; + } + ast_mutex_init(&obj->lock); + obj->parent = class; + if (odbc_obj_connect(obj) == ODBC_FAIL) { + ast_log(LOG_WARNING, "Failed to connect\n"); + ast_mutex_destroy(&obj->lock); + free(obj); + } else { + AST_LIST_INSERT_HEAD(&class->odbc_obj, obj, list); + } + } } + AST_LIST_UNLOCK(&class->odbc_obj); - return NULL; -} - -void destroy_odbc_obj(odbc_obj **obj) -{ - odbc_obj_disconnect(*obj); - - ast_mutex_lock(&(*obj)->lock); - SQLFreeHandle(SQL_HANDLE_STMT, (*obj)->stmt); - SQLFreeHandle(SQL_HANDLE_DBC, (*obj)->con); - SQLFreeHandle(SQL_HANDLE_ENV, (*obj)->env); - - free((*obj)->name); - free((*obj)->dsn); - if ((*obj)->username) - free((*obj)->username); - if ((*obj)->password) - free((*obj)->password); - ast_mutex_unlock(&(*obj)->lock); - ast_mutex_destroy(&(*obj)->lock); - free(*obj); + if (obj && check) { + odbc_sanity_check(obj); + } + return obj; } -odbc_status odbc_obj_disconnect(odbc_obj *obj) +static odbc_status odbc_obj_disconnect(struct odbc_obj *obj) { int res; ast_mutex_lock(&obj->lock); res = SQLDisconnect(obj->con); - if (res == ODBC_SUCCESS) { - ast_log(LOG_WARNING, "res_odbc: disconnected %d from %s [%s]\n", res, obj->name, obj->dsn); + ast_log(LOG_WARNING, "res_odbc: disconnected %d from %s [%s]\n", res, obj->parent->name, obj->parent->dsn); } else { ast_log(LOG_WARNING, "res_odbc: %s [%s] already disconnected\n", - obj->name, obj->dsn); + obj->parent->name, obj->parent->dsn); } obj->up = 0; ast_mutex_unlock(&obj->lock); return ODBC_SUCCESS; } -odbc_status odbc_obj_connect(odbc_obj *obj) +static odbc_status odbc_obj_connect(struct odbc_obj *obj) { int res; SQLINTEGER err; short int mlen; unsigned char msg[200], stat[10]; - +#ifdef NEEDTRACE + SQLINTEGER enable = 1; + char *tracefile = "/tmp/odbc.trace"; +#endif ast_mutex_lock(&obj->lock); - if (obj->env == SQL_NULL_HANDLE) { - res = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &obj->env); - - if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { - if (option_verbose > 3) - ast_log(LOG_WARNING, "res_odbc: Error AllocHandle\n"); - ast_mutex_unlock(&obj->lock); - return ODBC_FAIL; - } - - res = SQLSetEnvAttr(obj->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0); - - if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { - if (option_verbose > 3) - ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n"); - SQLFreeHandle(SQL_HANDLE_ENV, obj->env); - ast_mutex_unlock(&obj->lock); - return ODBC_FAIL; - } - - res = SQLAllocHandle(SQL_HANDLE_DBC, obj->env, &obj->con); + res = SQLAllocHandle(SQL_HANDLE_DBC, obj->parent->env, &obj->con); - if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { - if (option_verbose > 3) - ast_log(LOG_WARNING, "res_odbc: Error AllocHDB %d\n", res); - SQLFreeHandle(SQL_HANDLE_ENV, obj->env); + ast_log(LOG_WARNING, "res_odbc: Error AllocHDB %d\n", res); + SQLFreeHandle(SQL_HANDLE_ENV, obj->parent->env); - ast_mutex_unlock(&obj->lock); - return ODBC_FAIL; - } - SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *) 10, 0); + ast_mutex_unlock(&obj->lock); + return ODBC_FAIL; } - if(obj->up) { + SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *) 10, 0); +#ifdef NEEDTRACE + SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER); + SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile)); +#endif + + if (obj->up) { odbc_obj_disconnect(obj); - ast_log(LOG_NOTICE,"Re-connecting %s\n", obj->name); + ast_log(LOG_NOTICE, "Re-connecting %s\n", obj->parent->name); + } else { + ast_log(LOG_NOTICE, "Connecting %s\n", obj->parent->name); } - ast_log(LOG_NOTICE, "Connecting %s\n", obj->name); - res = SQLConnect(obj->con, - (SQLCHAR *) obj->dsn, SQL_NTS, - (SQLCHAR *) obj->username, SQL_NTS, - (SQLCHAR *) obj->password, SQL_NTS); + (SQLCHAR *) obj->parent->dsn, SQL_NTS, + (SQLCHAR *) obj->parent->username, SQL_NTS, + (SQLCHAR *) obj->parent->password, SQL_NTS); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, stat, &err, msg, 100, &mlen); - SQLFreeHandle(SQL_HANDLE_ENV, obj->env); ast_mutex_unlock(&obj->lock); ast_log(LOG_WARNING, "res_odbc: Error SQLConnect=%d errno=%d %s\n", res, (int)err, msg); return ODBC_FAIL; } else { - - ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->name, obj->dsn); + ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->parent->name, obj->parent->dsn); obj->up = 1; } @@ -577,23 +511,159 @@ odbc_status odbc_obj_connect(odbc_obj *obj) LOCAL_USER_DECL; -static int unload_module(void *mod) +static int reload(void *mod) { - STANDARD_HANGUP_LOCALUSERS; - odbc_destroy(); - ast_cli_unregister(&odbc_disconnect_struct); - ast_cli_unregister(&odbc_connect_struct); - ast_cli_unregister(&odbc_show_struct); - ast_log(LOG_NOTICE, "res_odbc unloaded.\n"); + static char *cfg = "res_odbc.conf"; + struct ast_config *config; + struct ast_variable *v; + char *cat, *dsn, *username, *password; + int enabled, pooling, limit; + int connect = 0, res = 0; + + struct odbc_class *new, *class; + struct odbc_obj *current; + + /* First, mark all to be purged */ + AST_LIST_LOCK(&odbc_list); + AST_LIST_TRAVERSE(&odbc_list, class, list) { + class->delme = 1; + } + + config = ast_config_load(cfg); + if (config) { + for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) { + if (!strcasecmp(cat, "ENV")) { + for (v = ast_variable_browse(config, cat); v; v = v->next) { + setenv(v->name, v->value, 1); + ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value); + } + } else { + /* Reset all to defaults for each class of odbc connections */ + dsn = username = password = NULL; + enabled = 1; + connect = 0; + pooling = 0; + limit = 0; + for (v = ast_variable_browse(config, cat); v; v = v->next) { + if (!strcasecmp(v->name, "pooling")) { + pooling = 1; + } else if (!strcasecmp(v->name, "limit")) { + sscanf(v->value, "%d", &limit); + if (ast_true(v->value) && !limit) { + 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); + limit = 1023; + } else if (ast_false(v->value)) { + ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Disabling ODBC class '%s'.\n", v->value, cat); + enabled = 0; + break; + } + } else if (!strcasecmp(v->name, "enabled")) { + enabled = ast_true(v->value); + } else if (!strcasecmp(v->name, "pre-connect")) { + connect = ast_true(v->value); + } else if (!strcasecmp(v->name, "dsn")) { + dsn = v->value; + } else if (!strcasecmp(v->name, "username")) { + username = v->value; + } else if (!strcasecmp(v->name, "password")) { + password = v->value; + } + } + + if (enabled && !ast_strlen_zero(dsn)) { + /* First, check the list to see if it already exists */ + AST_LIST_TRAVERSE(&odbc_list, class, list) { + if (!strcmp(class->name, cat)) { + class->delme = 0; + break; + } + } + + if (class) { + new = class; + } else { + new = ast_calloc(1, sizeof(*new)); + } + + if (!new) { + ast_log(LOG_ERROR, "Memory error while loading configuration.\n"); + res = -1; + break; + } + + if (cat) + ast_copy_string(new->name, cat, sizeof(new->name)); + if (dsn) + ast_copy_string(new->dsn, dsn, sizeof(new->dsn)); + if (username) + ast_copy_string(new->username, username, sizeof(new->username)); + if (password) + ast_copy_string(new->password, password, sizeof(new->password)); + + if (!class) { + SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env); + res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0); + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n"); + SQLFreeHandle(SQL_HANDLE_ENV, new->env); + AST_LIST_UNLOCK(&odbc_list); + return res; + } + } + + if (pooling) { + new->haspool = pooling; + if (limit) { + new->limit = limit; + } else { + ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless. Changing limit from 0 to 5.\n"); + new->limit = 5; + } + } + + if (class) { + ast_log(LOG_NOTICE, "Refreshing ODBC class '%s' dsn->[%s]\n", cat, dsn); + } else { + odbc_register_class(new, connect); + ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn); + } + } + } + } + ast_config_destroy(config); + } + + /* Purge classes that we know can go away (pooled with 0, only) */ + AST_LIST_TRAVERSE_SAFE_BEGIN(&odbc_list, class, list) { + if (class->delme && class->haspool && class->count == 0) { + AST_LIST_TRAVERSE_SAFE_BEGIN(&(class->odbc_obj), current, list) { + AST_LIST_REMOVE_CURRENT(&(class->odbc_obj), list); + odbc_obj_disconnect(current); + ast_mutex_destroy(¤t->lock); + free(current); + } + AST_LIST_TRAVERSE_SAFE_END; + + AST_LIST_REMOVE_CURRENT(&odbc_list, list); + free(class); + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&odbc_list); + return 0; } +static int unload_module(void *mod) +{ + /* Prohibit unloading */ + return -1; +} + static int load_module(void *mod) { - odbc_init(); load_odbc_config(); - ast_cli_register(&odbc_disconnect_struct); - ast_cli_register(&odbc_connect_struct); ast_cli_register(&odbc_show_struct); ast_log(LOG_NOTICE, "res_odbc loaded.\n"); return 0; @@ -609,4 +679,4 @@ static const char *key(void) return ASTERISK_GPL_KEY; } -STD_MOD(MOD_0, NULL, NULL, NULL); +STD_MOD(MOD_0, reload, NULL, NULL); -- GitLab