diff --git a/funcs/func_odbc.c b/funcs/func_odbc.c index 23930ed4d8b5a7a6a73489f249ac2843313a8544..ca15d703fc429dd16e5717aac2500e924c3fe7fb 100644 --- a/funcs/func_odbc.c +++ b/funcs/func_odbc.c @@ -137,6 +137,163 @@ struct odbc_datastore { char names[0]; }; +/* \brief Data source name + * + * This holds data that pertains to a DSN + */ +struct dsn { + /*! A connection to the database */ + struct odbc_obj *connection; + /*! The name of the DSN as defined in res_odbc.conf */ + char name[0]; +}; + +#define DSN_BUCKETS 37 + +struct ao2_container *dsns; + +static int dsn_hash(const void *obj, const int flags) +{ + const struct dsn *object; + const char *key; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_KEY: + key = obj; + break; + case OBJ_SEARCH_OBJECT: + object = obj; + key = object->name; + break; + default: + ast_assert(0); + return 0; + } + return ast_str_hash(key); +} + +static int dsn_cmp(void *obj, void *arg, int flags) +{ + const struct dsn *object_left = obj; + const struct dsn *object_right = arg; + const char *right_key = arg; + int cmp; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_OBJECT: + right_key = object_right->name; + /* Fall through */ + case OBJ_SEARCH_KEY: + cmp = strcmp(object_left->name, right_key); + break; + case OBJ_SEARCH_PARTIAL_KEY: + cmp = strncmp(object_left->name, right_key, strlen(right_key)); + break; + default: + cmp = 0; + break; + } + + if (cmp) { + return 0; + } + + return CMP_MATCH; +} + +static void dsn_destructor(void *obj) +{ + struct dsn *dsn = obj; + + if (dsn->connection) { + ast_odbc_release_obj(dsn->connection); + } +} + +/*! + * \brief Create a DSN and connect to the database + * + * \param name The name of the DSN as found in res_odbc.conf + * \retval NULL Fail + * \retval non-NULL The newly-created structure + */ +static struct dsn *create_dsn(const char *name) +{ + struct dsn *dsn; + + dsn = ao2_alloc(sizeof(*dsn) + strlen(name) + 1, dsn_destructor); + if (!dsn) { + return NULL; + } + + /* Safe */ + strcpy(dsn->name, name); + + dsn->connection = ast_odbc_request_obj(name, 0); + if (!dsn->connection) { + ao2_ref(dsn, -1); + return NULL; + } + + if (!ao2_link_flags(dsns, dsn, OBJ_NOLOCK)) { + ao2_ref(dsn, -1); + return NULL; + } + + return dsn; +} + +/*! + * \brief Retrieve a DSN, or create it if it does not exist. + * + * The created DSN is returned locked. This should be inconsequential + * to callers in most cases. + * + * When finished with the returned structure, the caller must call + * \ref release_dsn + * + * \param name Name of the DSN as found in res_odbc.conf + * \retval NULL Unable to retrieve or create the DSN + * \retval non-NULL The retrieved/created locked DSN + */ +static struct dsn *get_dsn(const char *name) +{ + struct dsn *dsn; + + ao2_lock(dsns); + dsn = ao2_find(dsns, name, OBJ_SEARCH_KEY | OBJ_NOLOCK); + if (!dsn) { + dsn = create_dsn(name); + } + ao2_unlock(dsns); + + if (!dsn) { + return NULL; + } + + ao2_lock(dsn->connection); + + return dsn; +} + +/*! + * \brief Unlock and unreference a DSN + * + * \param dsn The dsn to unlock and unreference + * \return NULL + */ +static void *release_dsn(struct dsn *dsn) +{ + if (!dsn) { + return NULL; + } + + ao2_unlock(dsn->connection); + ao2_ref(dsn, -1); + + return NULL; +} + static AST_RWLIST_HEAD_STATIC(queries, acf_odbc_query); static int resultcount = 0; @@ -214,7 +371,7 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co struct odbc_obj *obj = NULL; struct acf_odbc_query *query; char *t, varname[15]; - int i, dsn, bogus_chan = 0; + int i, dsn_num, bogus_chan = 0; int transactional = 0; AST_DECLARE_APP_ARGS(values, AST_APP_ARG(field)[100]; @@ -227,6 +384,7 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co struct ast_str *buf = ast_str_thread_get(&sql_buf, 16); struct ast_str *insertbuf = ast_str_thread_get(&sql2_buf, 16); const char *status = "FAILURE"; + struct dsn *dsn = NULL; if (!buf || !insertbuf) { return -1; @@ -324,17 +482,21 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co * to multiple DSNs. We MUST have a single handle all the way through the * transaction, or else we CANNOT enforce atomicity. */ - for (dsn = 0; dsn < 5; dsn++) { - if (!ast_strlen_zero(query->writehandle[dsn])) { + for (dsn_num = 0; dsn_num < 5; dsn_num++) { + if (!ast_strlen_zero(query->writehandle[dsn_num])) { if (transactional) { /* This can only happen second time through or greater. */ ast_log(LOG_WARNING, "Transactions do not work well with multiple DSNs for 'writehandle'\n"); } - if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn]))) { + if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn_num]))) { transactional = 1; } else { - obj = ast_odbc_request_obj(query->writehandle[dsn], 0); + dsn = get_dsn(query->writehandle[dsn_num]); + if (!dsn) { + continue; + } + obj = dsn->connection; transactional = 0; } @@ -342,10 +504,7 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co break; } - if (obj && !transactional) { - ast_odbc_release_obj(obj); - obj = NULL; - } + dsn = release_dsn(dsn); } } @@ -358,25 +517,25 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co status = "SUCCESS"; } else if (query->sql_insert) { - if (obj && !transactional) { - ast_odbc_release_obj(obj); - obj = NULL; - } + dsn = release_dsn(dsn); - for (transactional = 0, dsn = 0; dsn < 5; dsn++) { - if (!ast_strlen_zero(query->writehandle[dsn])) { + for (transactional = 0, dsn_num = 0; dsn_num < 5; dsn_num++) { + if (!ast_strlen_zero(query->writehandle[dsn_num])) { if (transactional) { /* This can only happen second time through or greater. */ ast_log(LOG_WARNING, "Transactions do not work well with multiple DSNs for 'writehandle'\n"); } else if (obj) { - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); } - if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn]))) { + if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn_num]))) { transactional = 1; } else { - obj = ast_odbc_request_obj(query->writehandle[dsn], 0); + dsn = get_dsn(query->writehandle[dsn_num]); + if (!dsn) { + continue; + } + obj = dsn->connection; transactional = 0; } if (obj) { @@ -406,10 +565,7 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status); } - if (obj && !transactional) { - ast_odbc_release_obj(obj); - obj = NULL; - } + dsn = release_dsn(dsn); if (!bogus_chan) { ast_autoservice_stop(chan); @@ -420,11 +576,10 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, char *buf, size_t len) { - struct odbc_obj *obj = NULL; struct acf_odbc_query *query; char varname[15], rowcount[12] = "-1"; struct ast_str *colnames = ast_str_thread_get(&colnames_buf, 16); - int res, x, y, buflen = 0, escapecommas, rowlimit = 1, multirow = 0, dsn, bogus_chan = 0; + int res, x, y, buflen = 0, escapecommas, rowlimit = 1, multirow = 0, dsn_num, bogus_chan = 0; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(field)[100]; ); @@ -436,6 +591,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha struct odbc_datastore_row *row = NULL; struct ast_str *sql = ast_str_thread_get(&sql_buf, 16); const char *status = "FAILURE"; + struct dsn *dsn = NULL; if (!sql || !colnames) { if (chan) { @@ -523,28 +679,23 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha } AST_RWLIST_UNLOCK(&queries); - for (dsn = 0; dsn < 5; dsn++) { - if (!ast_strlen_zero(query->readhandle[dsn])) { - obj = ast_odbc_request_obj(query->readhandle[dsn], 0); - if (obj) { - stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql)); + for (dsn_num = 0; dsn_num < 5; dsn_num++) { + if (!ast_strlen_zero(query->readhandle[dsn_num])) { + dsn = get_dsn(query->readhandle[dsn_num]); + if (!dsn) { + continue; } + stmt = ast_odbc_direct_execute(dsn->connection, generic_execute, ast_str_buffer(sql)); } if (stmt) { break; } - if (obj) { - ast_odbc_release_obj(obj); - obj = NULL; - } + dsn = release_dsn(dsn); } if (!stmt) { ast_log(LOG_ERROR, "Unable to execute query [%s]\n", ast_str_buffer(sql)); - if (obj) { - ast_odbc_release_obj(obj); - obj = NULL; - } + dsn = release_dsn(dsn); if (!bogus_chan) { pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount); ast_autoservice_stop(chan); @@ -558,8 +709,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", ast_str_buffer(sql)); SQLCloseCursor(stmt); SQLFreeHandle (SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); if (!bogus_chan) { pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount); ast_autoservice_stop(chan); @@ -583,8 +733,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha } SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); if (!bogus_chan) { pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount); pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status); @@ -607,8 +756,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha odbc_datastore_free(resultset); SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); if (!bogus_chan) { pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR"); ast_autoservice_stop(chan); @@ -640,8 +788,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha odbc_datastore_free(resultset); SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); if (!bogus_chan) { pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount); pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR"); @@ -750,8 +897,7 @@ end_acf_read: odbc_datastore_free(resultset); SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR"); ast_autoservice_stop(chan); return -1; @@ -764,8 +910,7 @@ end_acf_read: } SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); if (resultset && !multirow) { /* Fetch the first resultset */ if (!acf_fetch(chan, "", buf, buf, len)) { @@ -1192,8 +1337,8 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args if (a->argc == 5 && !strcmp(a->argv[4], "exec")) { /* Execute the query */ - struct odbc_obj *obj = NULL; - int dsn, executed = 0; + struct dsn *dsn = NULL; + int dsn_num, executed = 0; SQLHSTMT stmt; int rows = 0, res, x; SQLSMALLINT colcount = 0, collength; @@ -1207,19 +1352,18 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args return CLI_SUCCESS; } - for (dsn = 0; dsn < 5; dsn++) { - if (ast_strlen_zero(query->readhandle[dsn])) { + for (dsn_num = 0; dsn_num < 5; dsn_num++) { + if (ast_strlen_zero(query->readhandle[dsn_num])) { continue; } - ast_debug(1, "Found handle %s\n", query->readhandle[dsn]); - if (!(obj = ast_odbc_request_obj(query->readhandle[dsn], 0))) { + dsn = get_dsn(query->readhandle[dsn_num]); + if (!dsn) { continue; } + ast_debug(1, "Found handle %s\n", query->readhandle[dsn_num]); - ast_debug(1, "Got obj\n"); - if (!(stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql)))) { - ast_odbc_release_obj(obj); - obj = NULL; + if (!(stmt = ast_odbc_direct_execute(dsn->connection, generic_execute, ast_str_buffer(sql)))) { + dsn = release_dsn(dsn); continue; } @@ -1230,8 +1374,7 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args ast_cli(a->fd, "SQL Column Count error!\n[%s]\n\n", ast_str_buffer(sql)); SQLCloseCursor(stmt); SQLFreeHandle (SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); AST_RWLIST_UNLOCK(&queries); return CLI_SUCCESS; } @@ -1240,10 +1383,9 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); if (res == SQL_NO_DATA) { - ast_cli(a->fd, "Returned %d rows. Query executed on handle %d:%s [%s]\n", rows, dsn, query->readhandle[dsn], ast_str_buffer(sql)); + ast_cli(a->fd, "Returned %d rows. Query executed on handle %d:%s [%s]\n", rows, dsn_num, query->readhandle[dsn_num], ast_str_buffer(sql)); break; } else { ast_cli(a->fd, "Error %d in FETCH [%s]\n", res, ast_str_buffer(sql)); @@ -1270,8 +1412,7 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args ast_cli(a->fd, "SQL Get Data error %d!\n[%s]\n\n", res, ast_str_buffer(sql)); SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); AST_RWLIST_UNLOCK(&queries); return CLI_SUCCESS; } @@ -1289,15 +1430,11 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args } SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; - ast_cli(a->fd, "Returned %d row%s. Query executed on handle %d [%s]\n", rows, rows == 1 ? "" : "s", dsn, query->readhandle[dsn]); + dsn = release_dsn(dsn); + ast_cli(a->fd, "Returned %d row%s. Query executed on handle %d [%s]\n", rows, rows == 1 ? "" : "s", dsn_num, query->readhandle[dsn_num]); break; } - if (obj) { - ast_odbc_release_obj(obj); - obj = NULL; - } + dsn = release_dsn(dsn); if (!executed) { ast_cli(a->fd, "Failed to execute query. [%s]\n", ast_str_buffer(sql)); @@ -1420,30 +1557,29 @@ static char *cli_odbc_write(struct ast_cli_entry *e, int cmd, struct ast_cli_arg if (a->argc == 6 && !strcmp(a->argv[5], "exec")) { /* Execute the query */ - struct odbc_obj *obj = NULL; - int dsn, executed = 0; + struct dsn *dsn; + int dsn_num, executed = 0; SQLHSTMT stmt; SQLLEN rows = -1; - for (dsn = 0; dsn < 5; dsn++) { - if (ast_strlen_zero(query->writehandle[dsn])) { + for (dsn_num = 0; dsn_num < 5; dsn_num++) { + if (ast_strlen_zero(query->writehandle[dsn_num])) { continue; } - if (!(obj = ast_odbc_request_obj(query->writehandle[dsn], 0))) { + dsn = get_dsn(query->writehandle[dsn_num]); + if (!dsn) { continue; } - if (!(stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql)))) { - ast_odbc_release_obj(obj); - obj = NULL; + if (!(stmt = ast_odbc_direct_execute(dsn->connection, generic_execute, ast_str_buffer(sql)))) { + dsn = release_dsn(dsn); continue; } SQLRowCount(stmt, &rows); SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; - ast_cli(a->fd, "Affected %d rows. Query executed on handle %d [%s]\n", (int)rows, dsn, query->writehandle[dsn]); + dsn = release_dsn(dsn); + ast_cli(a->fd, "Affected %d rows. Query executed on handle %d [%s]\n", (int)rows, dsn_num, query->writehandle[dsn_num]); executed = 1; break; } @@ -1470,6 +1606,11 @@ static int load_module(void) char *catg; struct ast_flags config_flags = { 0 }; + dsns = ao2_container_alloc(DSN_BUCKETS, dsn_hash, dsn_cmp); + if (!dsns) { + return AST_MODULE_LOAD_DECLINE; + } + res |= ast_custom_function_register(&fetch_function); res |= ast_register_application_xml(app_odbcfinish, exec_odbcfinish); AST_RWLIST_WRLOCK(&queries); @@ -1478,6 +1619,7 @@ static int load_module(void) if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_NOTICE, "Unable to load config for func_odbc: %s\n", config); AST_RWLIST_UNLOCK(&queries); + ao2_ref(dsns, -1); return AST_MODULE_LOAD_DECLINE; } @@ -1531,6 +1673,8 @@ static int unload_module(void) AST_RWLIST_WRLOCK(&queries); AST_RWLIST_UNLOCK(&queries); + + ao2_ref(dsns, -1); return res; }