From 1e1fd3c3e02f30a4116c83131b82462fd9a7c26f Mon Sep 17 00:00:00 2001 From: Tilghman Lesher Date: Thu, 28 Dec 2006 20:13:00 +0000 Subject: Integrate functionality tested on svncommunity users back into trunk git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@49030 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- funcs/func_odbc.c | 184 ++++++++++++++++++++++++++++++++++++--------------- funcs/func_strings.c | 177 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 303 insertions(+), 58 deletions(-) (limited to 'funcs') diff --git a/funcs/func_odbc.c b/funcs/func_odbc.c index 529a342ff..bc50451f5 100644 --- a/funcs/func_odbc.c +++ b/funcs/func_odbc.c @@ -37,6 +37,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include #include #include +#include #include "asterisk/module.h" #include "asterisk/file.h" @@ -57,7 +58,8 @@ enum { struct acf_odbc_query { AST_LIST_ENTRY(acf_odbc_query) list; - char dsn[30]; + char readhandle[5][30]; + char writehandle[5][30]; char sql_read[2048]; char sql_write[2048]; unsigned int flags; @@ -96,7 +98,7 @@ static int acf_odbc_write(struct ast_channel *chan, char *cmd, char *s, const ch struct odbc_obj *obj; struct acf_odbc_query *query; char *t, buf[2048]="", varname[15]; - int i; + int i, dsn; AST_DECLARE_APP_ARGS(values, AST_APP_ARG(field)[100]; ); @@ -119,14 +121,6 @@ static int acf_odbc_write(struct ast_channel *chan, char *cmd, char *s, const ch return -1; } - obj = ast_odbc_request_obj(query->dsn, 0); - - if (!obj) { - ast_log(LOG_ERROR, "No database handle available with the name of '%s' (check res_odbc.conf)\n", query->dsn); - AST_LIST_UNLOCK(&queries); - return -1; - } - /* Parse our arguments */ t = value ? ast_strdupa(value) : ""; @@ -169,7 +163,15 @@ static int acf_odbc_write(struct ast_channel *chan, char *cmd, char *s, const ch AST_LIST_UNLOCK(&queries); - stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, buf); + for (dsn = 0; dsn < 5; dsn++) { + if (!ast_strlen_zero(query->writehandle[dsn])) { + obj = ast_odbc_request_obj(query->writehandle[dsn], 0); + if (obj) + stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, buf); + } + if (stmt) + break; + } if (stmt) { /* Rows affected */ @@ -195,14 +197,15 @@ static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf { struct odbc_obj *obj; struct acf_odbc_query *query; - char sql[2048] = "", varname[15]; - int res, x, buflen = 0, escapecommas; + char sql[2048] = "", varname[15], colnames[2048] = ""; + int res, x, buflen = 0, escapecommas, dsn; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(field)[100]; ); SQLHSTMT stmt; SQLSMALLINT colcount=0; SQLLEN indicator; + SQLSMALLINT collength; AST_LIST_LOCK(&queries); AST_LIST_TRAVERSE(&queries, query, list) { @@ -217,14 +220,6 @@ static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf return -1; } - obj = ast_odbc_request_obj(query->dsn, 0); - - if (!obj) { - ast_log(LOG_ERROR, "No such DSN registered (or out of connections): %s (check res_odbc.conf)\n", query->dsn); - AST_LIST_UNLOCK(&queries); - return -1; - } - AST_STANDARD_APP_ARGS(args, s); for (x = 0; x < args.argc; x++) { snprintf(varname, sizeof(varname), "ARG%d", x + 1); @@ -244,10 +239,20 @@ static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf AST_LIST_UNLOCK(&queries); - stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, sql); + for (dsn = 0; dsn < 5; dsn++) { + if (!ast_strlen_zero(query->writehandle[dsn])) { + obj = ast_odbc_request_obj(query->writehandle[dsn], 0); + if (obj) + stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, sql); + } + if (stmt) + break; + } if (!stmt) { - ast_odbc_release_obj(obj); + ast_log(LOG_ERROR, "Unable to execute query [%s]\n", sql); + if (obj) + ast_odbc_release_obj(obj); return -1; } @@ -278,8 +283,33 @@ static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf } for (x = 0; x < colcount; x++) { - int i; - char coldata[256]; + int i, namelen; + char coldata[256], colname[256]; + + res = SQLDescribeCol(stmt, x + 1, (unsigned char *)colname, sizeof(colname), &collength, NULL, NULL, NULL, NULL); + if (((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) || collength == 0) { + snprintf(colname, sizeof(colname), "field%d", x); + } + + if (!ast_strlen_zero(colnames)) + strncat(colnames, ",", sizeof(colnames) - 1); + namelen = strlen(colnames); + + /* Copy data, encoding '\' and ',' for the argument parser */ + for (i = 0; i < sizeof(colname); i++) { + if (escapecommas && (colname[i] == '\\' || colname[i] == ',')) { + colnames[namelen++] = '\\'; + } + colnames[namelen++] = colname[i]; + + if (namelen >= sizeof(colnames) - 2) { + colnames[namelen >= sizeof(colnames) ? sizeof(colnames) - 1 : namelen] = '\0'; + break; + } + + if (colname[i] == '\0') + break; + } buflen = strlen(buf); res = SQLGetData(stmt, x + 1, SQL_CHAR, coldata, sizeof(coldata), &indicator); @@ -315,6 +345,8 @@ static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf /* Trim trailing comma */ buf[buflen - 1] = '\0'; + pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); ast_odbc_release_obj(obj); return 0; @@ -351,27 +383,68 @@ static struct ast_custom_function escape_function = { static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_query **query) { const char *tmp; + int i; if (!cfg || !catg) { - return -1; + return EINVAL; } *query = ast_calloc(1, sizeof(struct acf_odbc_query)); if (! (*query)) - return -1; + return ENOMEM; + + if (((tmp = ast_variable_retrieve(cfg, catg, "writehandle"))) || ((tmp = ast_variable_retrieve(cfg, catg, "dsn")))) { + char *tmp2 = ast_strdupa(tmp); + AST_DECLARE_APP_ARGS(write, + AST_APP_ARG(dsn)[5]; + ); + AST_NONSTANDARD_APP_ARGS(write, tmp2, ','); + for (i = 0; i < 5; i++) { + if (!ast_strlen_zero(write.dsn[i])) + ast_copy_string((*query)->writehandle[i], write.dsn[i], sizeof((*query)->writehandle[i])); + } + } - if ((tmp = ast_variable_retrieve(cfg, catg, "dsn"))) { - ast_copy_string((*query)->dsn, tmp, sizeof((*query)->dsn)); + if ((tmp = ast_variable_retrieve(cfg, catg, "readhandle"))) { + char *tmp2 = ast_strdupa(tmp); + AST_DECLARE_APP_ARGS(read, + AST_APP_ARG(dsn)[5]; + ); + AST_NONSTANDARD_APP_ARGS(read, tmp2, ','); + for (i = 0; i < 5; i++) { + if (!ast_strlen_zero(read.dsn[i])) + ast_copy_string((*query)->readhandle[i], read.dsn[i], sizeof((*query)->readhandle[i])); + } } else { - return -1; - } + /* If no separate readhandle, then use the writehandle for reading */ + for (i = 0; i < 5; i++) { + if (!ast_strlen_zero((*query)->writehandle[i])) + ast_copy_string((*query)->readhandle[i], (*query)->writehandle[i], sizeof((*query)->readhandle[i])); + } + } if ((tmp = ast_variable_retrieve(cfg, catg, "read"))) { + ast_log(LOG_WARNING, "Parameter 'read' is deprecated for category %s. Please use 'readsql' instead.\n", catg); + ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read)); + } else if ((tmp = ast_variable_retrieve(cfg, catg, "readsql"))) ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read)); + + if (!ast_strlen_zero((*query)->sql_read) && ast_strlen_zero((*query)->readhandle[0])) { + free(*query); + ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for reading: %s\n", catg); + return EINVAL; } if ((tmp = ast_variable_retrieve(cfg, catg, "write"))) { + ast_log(LOG_WARNING, "Parameter 'write' is deprecated for category %s. Please use 'writesql' instead.\n", catg); ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write)); + } else if ((tmp = ast_variable_retrieve(cfg, catg, "writesql"))) + ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write)); + + if (!ast_strlen_zero((*query)->sql_write) && ast_strlen_zero((*query)->writehandle[0])) { + free(*query); + ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for writing: %s\n", catg); + return EINVAL; } /* Allow escaping of embedded commas in fields to be turned off */ @@ -384,7 +457,7 @@ static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_qu (*query)->acf = ast_calloc(1, sizeof(struct ast_custom_function)); if (! (*query)->acf) { free(*query); - return -1; + return ENOMEM; } if ((tmp = ast_variable_retrieve(cfg, catg, "prefix")) && !ast_strlen_zero(tmp)) { @@ -396,7 +469,7 @@ static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_qu if (!((*query)->acf->name)) { free((*query)->acf); free(*query); - return -1; + return ENOMEM; } asprintf((char **)&((*query)->acf->syntax), "%s([...[,]])", (*query)->acf->name); @@ -405,7 +478,7 @@ static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_qu free((char *)(*query)->acf->name); free((*query)->acf); free(*query); - return -1; + return ENOMEM; } (*query)->acf->synopsis = "Runs the referenced query with the specified arguments"; @@ -432,15 +505,21 @@ static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_qu "${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n" "This function may only be set.\nSQL:\n%s\n", (*query)->sql_write); + } else { + free((char *)(*query)->acf->syntax); + free((char *)(*query)->acf->name); + free((*query)->acf); + free(*query); + ast_log(LOG_WARNING, "Section %s was found, but there was no SQL to execute. Ignoring.\n", catg); + return EINVAL; } - /* Could be out of memory, or could be we have neither sql_read nor sql_write */ if (! ((*query)->acf->desc)) { free((char *)(*query)->acf->syntax); free((char *)(*query)->acf->name); free((*query)->acf); free(*query); - return -1; + return ENOMEM; } if (ast_strlen_zero((*query)->sql_read)) { @@ -475,7 +554,7 @@ static int free_acf_query(struct acf_odbc_query *query) return 0; } -static int odbc_load_module(void) +static int load_module(void) { int res = 0; struct ast_config *cfg; @@ -494,10 +573,15 @@ static int odbc_load_module(void) catg; catg = ast_category_browse(cfg, catg)) { struct acf_odbc_query *query = NULL; - - if (init_acf_query(cfg, catg, &query)) { - ast_log(LOG_ERROR, "Out of memory\n"); - free_acf_query(query); + int err; + + if ((err = init_acf_query(cfg, catg, &query))) { + if (err == ENOMEM) + ast_log(LOG_ERROR, "Out of memory\n"); + else if (err == EINVAL) + ast_log(LOG_ERROR, "Invalid parameters for category %s\n", catg); + else + ast_log(LOG_ERROR, "%s (%d)\n", strerror(err), err); } else { AST_LIST_INSERT_HEAD(&queries, query, list); ast_custom_function_register(query->acf); @@ -505,15 +589,16 @@ static int odbc_load_module(void) } ast_config_destroy(cfg); - ast_custom_function_register(&escape_function); + res |= ast_custom_function_register(&escape_function); AST_LIST_UNLOCK(&queries); return res; } -static int odbc_unload_module(void) +static int unload_module(void) { struct acf_odbc_query *query; + int res = 0; AST_LIST_LOCK(&queries); while (!AST_LIST_EMPTY(&queries)) { @@ -522,10 +607,11 @@ static int odbc_unload_module(void) free_acf_query(query); } - ast_custom_function_unregister(&escape_function); + res |= ast_custom_function_unregister(&escape_function); /* Allow any threads waiting for this lock to pass (avoids a race) */ AST_LIST_UNLOCK(&queries); + usleep(1); AST_LIST_LOCK(&queries); AST_LIST_UNLOCK(&queries); @@ -572,16 +658,6 @@ reload_out: return res; } -static int unload_module(void) -{ - return odbc_unload_module(); -} - -static int load_module(void) -{ - return odbc_load_module(); -} - /* XXX need to revise usecount - set if query_lock is set */ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "ODBC lookups", diff --git a/funcs/func_strings.c b/funcs/func_strings.c index 47b7806ab..2b1bc17d2 100644 --- a/funcs/func_strings.c +++ b/funcs/func_strings.c @@ -155,6 +155,37 @@ static struct ast_custom_function regex_function = { .read = regex, }; +#define HASH_PREFIX "~HASH~%s~" +#define HASH_FORMAT HASH_PREFIX "%s~" + +static char *app_clearhash = "ClearHash"; +static char *syn_clearhash = "Clear the keys from a specified hashname"; +static char *desc_clearhash = +"ClearHash()\n" +" Clears all keys out of the specified hashname\n"; + +/* This function probably should migrate to main/pbx.c, as pbx_builtin_clearvar_prefix() */ +static void clearvar_prefix(struct ast_channel *chan, const char *prefix) +{ + struct ast_var_t *var; + int len = strlen(prefix); + AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->varshead, var, entries) { + if (strncasecmp(prefix, ast_var_name(var), len) == 0) { + AST_LIST_REMOVE_CURRENT(&chan->varshead, entries); + free(var); + } + } + AST_LIST_TRAVERSE_SAFE_END +} + +static int exec_clearhash(struct ast_channel *chan, void *data) +{ + char prefix[80]; + snprintf(prefix, sizeof(prefix), HASH_PREFIX, data ? (char *)data : "null"); + clearvar_prefix(chan, prefix); + return 0; +} + static int array(struct ast_channel *chan, char *cmd, char *var, const char *value) { @@ -164,13 +195,23 @@ static int array(struct ast_channel *chan, char *cmd, char *var, AST_DECLARE_APP_ARGS(arg2, AST_APP_ARG(val)[100]; ); - char *value2; - int i; + char *origvar = "", *value2, varname[256]; + int i, ishash = 0; value2 = ast_strdupa(value); if (!var || !value2) return -1; + if (!strcmp(cmd, "HASH")) { + const char *var2 = pbx_builtin_getvar_helper(chan, "~ODBCFIELDS~"); + origvar = var; + if (var2) + var = ast_strdupa(var2); + else + return -1; + ishash = 1; + } + /* The functions this will generally be used with are SORT and ODBC_*, which * both return comma-delimited lists. However, if somebody uses literal lists, * their commas will be translated to vertical bars by the load, and I don't @@ -194,17 +235,139 @@ static int array(struct ast_channel *chan, char *cmd, char *var, ast_log(LOG_DEBUG, "array set value (%s=%s)\n", arg1.var[i], arg2.val[i]); if (i < arg2.argc) { - pbx_builtin_setvar_helper(chan, arg1.var[i], arg2.val[i]); + if (ishash) { + snprintf(varname, sizeof(varname), HASH_FORMAT, origvar, arg1.var[i]); + pbx_builtin_setvar_helper(chan, varname, arg2.val[i]); + } else { + pbx_builtin_setvar_helper(chan, arg1.var[i], arg2.val[i]); + } } else { /* We could unset the variable, by passing a NULL, but due to * pushvar semantics, that could create some undesired behavior. */ - pbx_builtin_setvar_helper(chan, arg1.var[i], ""); + if (ishash) { + snprintf(varname, sizeof(varname), HASH_FORMAT, origvar, arg1.var[i]); + pbx_builtin_setvar_helper(chan, varname, ""); + } else { + pbx_builtin_setvar_helper(chan, arg1.var[i], ""); + } } } return 0; } +static int hashkeys_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) +{ + struct ast_var_t *newvar; + int plen; + char prefix[80]; + snprintf(prefix, sizeof(prefix), HASH_PREFIX, data); + plen = strlen(prefix); + + memset(buf, 0, len); + AST_LIST_TRAVERSE(&chan->varshead, newvar, entries) { + if (strncasecmp(prefix, ast_var_name(newvar), plen) == 0) { + /* Copy everything after the prefix */ + strncat(buf, ast_var_name(newvar) + plen, len); + /* Trim the trailing ~ */ + buf[strlen(buf) - 1] = ','; + } + } + /* Trim the trailing comma */ + buf[strlen(buf) - 1] = '\0'; + return 0; +} + +static int hash_write(struct ast_channel *chan, char *cmd, char *var, const char *value) +{ + char varname[256]; + AST_DECLARE_APP_ARGS(arg, + AST_APP_ARG(hashname); + AST_APP_ARG(hashkey); + ); + + if (!strchr(var, '|')) { + /* Single argument version */ + return array(chan, "HASH", var, value); + } + + AST_STANDARD_APP_ARGS(arg, var); + snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg.hashkey); + pbx_builtin_setvar_helper(chan, varname, value); + + return 0; +} + +static int hash_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) +{ + char varname[256]; + const char *varvalue; + AST_DECLARE_APP_ARGS(arg, + AST_APP_ARG(hashname); + AST_APP_ARG(hashkey); + ); + + AST_STANDARD_APP_ARGS(arg, data); + if (arg.argc == 2) { + snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg.hashkey); + varvalue = pbx_builtin_getvar_helper(chan, varname); + if (varvalue) + ast_copy_string(buf, varvalue, len); + else + *buf = '\0'; + } else if (arg.argc == 1) { + char colnames[4096]; + int i; + AST_DECLARE_APP_ARGS(arg2, + AST_APP_ARG(col)[100]; + ); + + /* Get column names, in no particular order */ + hashkeys_read(chan, "HASHKEYS", arg.hashname, colnames, sizeof(colnames)); + pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames); + + AST_NONSTANDARD_APP_ARGS(arg2, colnames, ','); + *buf = '\0'; + + /* Now get the corresponding column values, in exactly the same order */ + for (i = 0; i < arg2.argc; i++) { + snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg2.col[i]); + varvalue = pbx_builtin_getvar_helper(chan, varname); + strncat(buf, varvalue, len); + strncat(buf, ",", len); + } + + /* Strip trailing comma */ + buf[strlen(buf) - 1] = '\0'; + } + + return 0; +} + +static struct ast_custom_function hash_function = { + .name = "HASH", + .synopsis = "Implementation of a dialplan associative array", + .syntax = "HASH(hashname[|hashkey])", + .write = hash_write, + .read = hash_read, + .desc = + "In two argument mode, gets and sets values to corresponding keys within a named\n" + "associative array. The single-argument mode will only work when assigned to from\n" + "a function defined by func_odbc.so.\n", +}; + +static struct ast_custom_function hashkeys_function = { + .name = "HASHKEYS", + .synopsis = "Retrieve the keys of a HASH()", + .syntax = "HASHKEYS()", + .read = hashkeys_read, + .desc = + "Returns a comma-delimited list of the current keys of an associative array\n" + "defined by the HASH() function. Note that if you iterate over the keys of\n" + "the result, adding keys during iteration will cause the result of the HASHKEYS\n" + "function to change.\n", +}; + static struct ast_custom_function array_function = { .name = "ARRAY", .synopsis = "Allows setting multiple variables at once", @@ -589,6 +752,9 @@ static int unload_module(void) res |= ast_custom_function_unregister(&eval_function); res |= ast_custom_function_unregister(&keypadhash_function); res |= ast_custom_function_unregister(&sprintf_function); + res |= ast_custom_function_unregister(&hashkeys_function); + res |= ast_custom_function_unregister(&hash_function); + res |= ast_unregister_application(app_clearhash); return res; } @@ -608,6 +774,9 @@ static int load_module(void) res |= ast_custom_function_register(&eval_function); res |= ast_custom_function_register(&keypadhash_function); res |= ast_custom_function_register(&sprintf_function); + res |= ast_custom_function_register(&hashkeys_function); + res |= ast_custom_function_register(&hash_function); + res |= ast_register_application(app_clearhash, exec_clearhash, syn_clearhash, desc_clearhash); return res; } -- cgit v1.2.3