Skip to content
Snippets Groups Projects
func_odbc.c 53.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • 			res = ast_odbc_ast_str_SQLGetData(&coldata, -1, stmt, x + 1, SQL_CHAR, &indicator);
    
    			if (indicator == SQL_NULL_DATA) {
    
    				ast_debug(3, "Got NULL data\n");
    				ast_str_reset(coldata);
    
    				res = SQL_SUCCESS;
    
    			if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    				ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", ast_str_buffer(sql));
    
    				buf[0] = '\0';
    
    				goto end_acf_read;
    
    			ast_debug(2, "Got coldata of '%s'\n", ast_str_buffer(coldata));
    
    
    			/* Copy data, encoding '\' and ',' for the argument parser */
    
    			ptrcoldata = ast_str_buffer(coldata);
    			for (i = 0; i < ast_str_strlen(coldata); i++) {
    				if (escapecommas && (ptrcoldata[i] == '\\' || ptrcoldata[i] == ',')) {
    
    					buf[buflen++] = '\\';
    				}
    
    			buf[buflen] = '\0';
    
    			ast_debug(2, "buf is now set to '%s'\n", buf);
    
    		ast_debug(2, "buf is now set to '%s'\n", buf);
    
    			row = ast_calloc(1, sizeof(*row) + buflen + 1);
    
    			if (!row) {
    				ast_log(LOG_ERROR, "Unable to allocate space for more rows in this resultset.\n");
    
    				goto end_acf_read;
    			}
    			strcpy((char *)row + sizeof(*row), buf);
    			AST_LIST_INSERT_TAIL(resultset, row, list);
    
    			/* Get next row */
    			res = SQLFetch(stmt);
    			if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    				if (res != SQL_NO_DATA) {
    					ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, ast_str_buffer(sql));
    				}
    				/* Number of rows in the resultset */
    
    	if (!bogus_chan) {
    		snprintf(rowcount, sizeof(rowcount), "%d", y);
    		pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
    		pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status);
    		pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(colnames));
    		if (resultset) {
    			struct ast_datastore *odbc_store;
    
    				uid = ast_atomic_fetchadd_int(&resultcount, +1) + 1;
    				snprintf(buf, len, "%d", uid);
    			} else {
    				/* Name of the query is name of the resultset */
    				ast_copy_string(buf, cmd, len);
    
    				/* If there's one with the same name already, free it */
    				ast_channel_lock(chan);
    				if ((odbc_store = ast_channel_datastore_find(chan, &odbc_info, buf))) {
    					ast_channel_datastore_remove(chan, odbc_store);
    
    					ast_datastore_free(odbc_store);
    
    			odbc_store = ast_datastore_alloc(&odbc_info, buf);
    			if (!odbc_store) {
    				ast_log(LOG_ERROR, "Rows retrieved, but unable to store it in the channel.  Results fail.\n");
    				odbc_datastore_free(resultset);
    				SQLCloseCursor(stmt);
    				SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    				release_obj_or_dsn (&obj, &dsn);
    
    				pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR");
    
    				ast_autoservice_stop(chan);
    
    				return -1;
    			}
    			odbc_store->data = resultset;
    
    			ast_channel_datastore_add(chan, odbc_store);
    
    	SQLCloseCursor(stmt);
    
    	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    	release_obj_or_dsn (&obj, &dsn);
    
    	if (resultset && !multirow) {
    		/* Fetch the first resultset */
    		if (!acf_fetch(chan, "", buf, buf, len)) {
    			buf[0] = '\0';
    		}
    	}
    
    		ast_autoservice_stop(chan);
    
    static int acf_escape(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len, char character)
    
    	char *out = buf;
    
    	for (; *data && out - buf < len; data++) {
    
    		if (*data == character) {
    			*out = character;
    
    		*out++ = *data;
    
    static int acf_escape_ticks(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    {
    	return acf_escape(chan, cmd, data, buf, len, '\'');
    }
    
    
    static struct ast_custom_function escape_function = {
    
    	.read = acf_escape_ticks,
    	.write = NULL,
    };
    
    static int acf_escape_backslashes(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    {
    	return acf_escape(chan, cmd, data, buf, len, '\\');
    }
    
    static struct ast_custom_function escape_backslashes_function = {
    	.name = "SQL_ESC_BACKSLASHES",
    	.read = acf_escape_backslashes,
    
    static int acf_fetch(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    {
    	struct ast_datastore *store;
    	struct odbc_datastore *resultset;
    	struct odbc_datastore_row *row;
    
    	if (!chan) {
    		ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
    		return -1;
    	}
    
    
    	store = ast_channel_datastore_find(chan, &odbc_info, data);
    	if (!store) {
    
    		pbx_builtin_setvar_helper(chan, "ODBC_FETCH_STATUS", "FAILURE");
    
    		return -1;
    	}
    	resultset = store->data;
    	AST_LIST_LOCK(resultset);
    	row = AST_LIST_REMOVE_HEAD(resultset, list);
    	AST_LIST_UNLOCK(resultset);
    	if (!row) {
    		/* Cleanup datastore */
    		ast_channel_datastore_remove(chan, store);
    
    		pbx_builtin_setvar_helper(chan, "ODBC_FETCH_STATUS", "FAILURE");
    
    		return -1;
    	}
    	pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", resultset->names);
    
    	ast_copy_string(buf, row->data, len);
    	ast_free(row);
    
    	pbx_builtin_setvar_helper(chan, "ODBC_FETCH_STATUS", "SUCCESS");
    
    	return 0;
    }
    
    static struct ast_custom_function fetch_function = {
    	.name = "ODBC_FETCH",
    	.read = acf_fetch,
    	.write = NULL,
    };
    
    static char *app_odbcfinish = "ODBCFinish";
    
    
    static int exec_odbcfinish(struct ast_channel *chan, const char *data)
    
    	struct ast_datastore *store;
    
    	ast_channel_lock(chan);
    	store = ast_channel_datastore_find(chan, &odbc_info, data);
    	if (store) {
    		ast_channel_datastore_remove(chan, store);
    		ast_datastore_free(store);
    	}
    	ast_channel_unlock(chan);
    
    static int free_acf_query(struct acf_odbc_query *query)
    {
    	if (query) {
    		if (query->acf) {
    			if (query->acf->name)
    				ast_free((char *)query->acf->name);
    			ast_string_field_free_memory(query->acf);
    			ast_free(query->acf);
    		}
    		ast_free(query->sql_read);
    		ast_free(query->sql_write);
    		ast_free(query->sql_insert);
    		ast_free(query);
    	}
    	return 0;
    }
    
    
    static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_query **query)
    {
    
    	const char *tmp2 = NULL;
    
    	if (!(*query = ast_calloc(1, sizeof(**query)))) {
    
    
    	if (((tmp = ast_variable_retrieve(cfg, catg, "writehandle"))) || ((tmp = ast_variable_retrieve(cfg, catg, "dsn")))) {
    		char *tmp2 = ast_strdupa(tmp);
    
    		AST_STANDARD_APP_ARGS(writeconf, tmp2);
    
    			if (!ast_strlen_zero(writeconf.dsn[i]))
    				ast_copy_string((*query)->writehandle[i], writeconf.dsn[i], sizeof((*query)->writehandle[i]));
    
    	if ((tmp = ast_variable_retrieve(cfg, catg, "readhandle"))) {
    		char *tmp2 = ast_strdupa(tmp);
    
    		AST_STANDARD_APP_ARGS(readconf, tmp2);
    
    			if (!ast_strlen_zero(readconf.dsn[i]))
    				ast_copy_string((*query)->readhandle[i], readconf.dsn[i], sizeof((*query)->readhandle[i]));
    
    		/* 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, "readsql")) ||
    			(tmp2 = ast_variable_retrieve(cfg, catg, "read"))) {
    		if (!tmp) {
    			ast_log(LOG_WARNING, "Parameter 'read' is deprecated for category %s.  Please use 'readsql' instead.\n", catg);
    			tmp = tmp2;
    		}
    		if (*tmp != '\0') { /* non-empty string */
    			if (!((*query)->sql_read = ast_strdup(tmp))) {
    				free_acf_query(*query);
    				*query = NULL;
    				return ENOMEM;
    			}
    		}
    
    	if ((*query)->sql_read && ast_strlen_zero((*query)->readhandle[0])) {
    
    		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, "writesql")) ||
    			(tmp2 = ast_variable_retrieve(cfg, catg, "write"))) {
    		if (!tmp) {
    			ast_log(LOG_WARNING, "Parameter 'write' is deprecated for category %s.  Please use 'writesql' instead.\n", catg);
    			tmp = tmp2;
    		}
    		if (*tmp != '\0') { /* non-empty string */
    			if (!((*query)->sql_write = ast_strdup(tmp))) {
    				free_acf_query(*query);
    				*query = NULL;
    				return ENOMEM;
    			}
    		}
    
    	if ((*query)->sql_write && ast_strlen_zero((*query)->writehandle[0])) {
    
    		ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for writing: %s\n", catg);
    		return EINVAL;
    
    	if ((tmp = ast_variable_retrieve(cfg, catg, "insertsql"))) {
    
    		if (*tmp != '\0') { /* non-empty string */
    			if (!((*query)->sql_insert = ast_strdup(tmp))) {
    				free_acf_query(*query);
    				*query = NULL;
    				return ENOMEM;
    			}
    		}
    
    	/* Allow escaping of embedded commas in fields to be turned off */
    	ast_set_flag((*query), OPT_ESCAPECOMMAS);
    	if ((tmp = ast_variable_retrieve(cfg, catg, "escapecommas"))) {
    		if (ast_false(tmp))
    			ast_clear_flag((*query), OPT_ESCAPECOMMAS);
    	}
    
    
    	if ((tmp = ast_variable_retrieve(cfg, catg, "mode"))) {
    		if (strcasecmp(tmp, "multirow") == 0)
    			ast_set_flag((*query), OPT_MULTIROW);
    		if ((tmp = ast_variable_retrieve(cfg, catg, "rowlimit")))
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			sscanf(tmp, "%30d", &((*query)->rowlimit));
    
    	if ((tmp = ast_variable_retrieve(cfg, catg, "minargs"))) {
    		sscanf(tmp, "%30d", &((*query)->minargs));
    	}
    
    
    	(*query)->acf = ast_calloc(1, sizeof(struct ast_custom_function));
    
    	if (!(*query)->acf) {
    
    	if (ast_string_field_init((*query)->acf, 128)) {
    
    		*query = NULL;
    		return ENOMEM;
    	}
    
    
    	if ((tmp = ast_variable_retrieve(cfg, catg, "prefix")) && !ast_strlen_zero(tmp)) {
    
    		if (ast_asprintf((char **)&((*query)->acf->name), "%s_%s", tmp, catg) < 0) {
    			(*query)->acf->name = NULL;
    
    		if (ast_asprintf((char **)&((*query)->acf->name), "ODBC_%s", catg) < 0) {
    			(*query)->acf->name = NULL;
    
    	if (!(*query)->acf->name) {
    
    	if ((tmp = ast_variable_retrieve(cfg, catg, "syntax")) && !ast_strlen_zero(tmp)) {
    
    		ast_string_field_build((*query)->acf, syntax, "%s(%s)", (*query)->acf->name, tmp);
    
    		ast_string_field_build((*query)->acf, syntax, "%s(<arg1>[...[,<argN>]])", (*query)->acf->name);
    
    	if (ast_strlen_zero((*query)->acf->syntax)) {
    
    	if ((tmp = ast_variable_retrieve(cfg, catg, "synopsis")) && !ast_strlen_zero(tmp)) {
    
    		ast_string_field_set((*query)->acf, synopsis, tmp);
    
    		ast_string_field_set((*query)->acf, synopsis, "Runs the referenced query with the specified arguments");
    
    	if (ast_strlen_zero((*query)->acf->synopsis)) {
    
    	if ((*query)->sql_read && (*query)->sql_write) {
    
    		ast_string_field_build((*query)->acf, desc,
    
    					"Runs the following query, as defined in func_odbc.conf, performing\n"
    
    					"substitution of the arguments into the query as specified by ${ARG1},\n"
    
    					"${ARG2}, ... ${ARGn}.  When setting the function, the values are provided\n"
    					"either in whole as ${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
    
    					"\nRead:\n%s\n\nWrite:\n%s%s%s",
    					(*query)->sql_insert ?
    
    						"If the write query affects no rows, the insert query will be\n"
    
    						"performed.\n" : "",
    
    					(*query)->sql_insert ? "\n\nInsert:\n" : "",
    					(*query)->sql_insert ? (*query)->sql_insert : "");
    	} else if ((*query)->sql_read) {
    
    		ast_string_field_build((*query)->acf, desc,
    
    					"Runs the following query, as defined in func_odbc.conf, performing\n"
    
    					"substitution of the arguments into the query as specified by ${ARG1},\n"
    					"${ARG2}, ... ${ARGn}.  This function may only be read, not set.\n\nSQL:\n%s",
    					(*query)->sql_read);
    	} else if ((*query)->sql_write) {
    		ast_string_field_build((*query)->acf, desc,
    					"Runs the following query, as defined in func_odbc.conf, performing\n"
    					"substitution of the arguments into the query as specified by ${ARG1},\n"
    
    					"${ARG2}, ... ${ARGn}.  The values are provided either in whole as\n"
    					"${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
    
    					"This function may only be set.\n%s\nSQL:\n%s%s%s",
    					(*query)->sql_insert ?
    
    						"If the write query affects no rows, the insert query will be\n"
    
    						"performed.\n" : "",
    
    					(*query)->sql_insert ? "\n\nInsert:\n" : "",
    					(*query)->sql_insert ? (*query)->sql_insert : "");
    
    		ast_log(LOG_WARNING, "Section '%s' was found, but there was no SQL to execute.  Ignoring.\n", catg);
    
    	if (ast_strlen_zero((*query)->acf->desc)) {
    
    	if ((*query)->sql_read) {
    
    	if ((*query)->sql_write) {
    
    static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(field)[100];
    	);
    
    	char *char_args, varname[15];
    
    	struct acf_odbc_query *query;
    	struct ast_channel *chan;
    	int i;
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "odbc read";
    		e->usage =
    			"Usage: odbc read <name> <args> [exec]\n"
    			"       Evaluates the SQL provided in the ODBC function <name>, and\n"
    			"       optionally executes the function.  This function is intended for\n"
    			"       testing purposes.  Remember to quote arguments containing spaces.\n";
    		return NULL;
    	case CLI_GENERATE:
    		if (a->pos == 2) {
    			int wordlen = strlen(a->word), which = 0;
    			/* Complete function name */
    			AST_RWLIST_RDLOCK(&queries);
    			AST_RWLIST_TRAVERSE(&queries, query, list) {
    				if (!strncasecmp(query->acf->name, a->word, wordlen)) {
    					if (++which > a->n) {
    						char *res = ast_strdup(query->acf->name);
    						AST_RWLIST_UNLOCK(&queries);
    						return res;
    					}
    				}
    			}
    			AST_RWLIST_UNLOCK(&queries);
    			return NULL;
    		} else if (a->pos == 4) {
    
    			static const char * const completions[] = { "exec", NULL };
    			return ast_cli_complete(a->word, completions, a->n);
    
    		} else {
    			return NULL;
    		}
    	}
    
    	if (a->argc < 4 || a->argc > 5) {
    		return CLI_SHOWUSAGE;
    	}
    
    
    	sql = ast_str_thread_get(&sql_buf, 16);
    	if (!sql) {
    		return CLI_FAILURE;
    	}
    
    
    	AST_RWLIST_RDLOCK(&queries);
    	AST_RWLIST_TRAVERSE(&queries, query, list) {
    		if (!strcmp(query->acf->name, a->argv[2])) {
    			break;
    		}
    	}
    
    	if (!query) {
    		ast_cli(a->fd, "No such query '%s'\n", a->argv[2]);
    		AST_RWLIST_UNLOCK(&queries);
    		return CLI_SHOWUSAGE;
    	}
    
    
    	if (!query->sql_read) {
    
    		ast_cli(a->fd, "The function %s has no readsql parameter.\n", a->argv[2]);
    
    		AST_RWLIST_UNLOCK(&queries);
    		return CLI_SUCCESS;
    	}
    
    	ast_str_make_space(&sql, strlen(query->sql_read) * 2 + 300);
    
    	/* Evaluate function */
    	char_args = ast_strdupa(a->argv[3]);
    
    
    	chan = ast_dummy_channel_alloc();
    
    	if (!chan) {
    		AST_RWLIST_UNLOCK(&queries);
    		return CLI_FAILURE;
    	}
    
    
    	AST_STANDARD_APP_ARGS(args, char_args);
    	for (i = 0; i < args.argc; i++) {
    		snprintf(varname, sizeof(varname), "ARG%d", i + 1);
    		pbx_builtin_pushvar_helper(chan, varname, args.field[i]);
    	}
    
    
    	ast_str_substitute_variables(&sql, 0, chan, query->sql_read);
    
    	chan = ast_channel_unref(chan);
    
    
    	if (a->argc == 5 && !strcmp(a->argv[4], "exec")) {
    		/* Execute the query */
    
    		struct odbc_obj *obj = NULL;
    
    		struct dsn *dsn = NULL;
    		int dsn_num, executed = 0;
    
    		SQLHSTMT stmt;
    		int rows = 0, res, x;
    		SQLSMALLINT colcount = 0, collength;
    
    		struct ast_str *coldata = ast_str_thread_get(&coldata_buf, 16);
    		char colname[256];
    
    		if (!coldata) {
    			AST_RWLIST_UNLOCK(&queries);
    			return CLI_SUCCESS;
    		}
    
    
    		for (dsn_num = 0; dsn_num < 5; dsn_num++) {
    			if (ast_strlen_zero(query->readhandle[dsn_num])) {
    
    			obj = get_odbc_obj(query->readhandle[dsn_num], &dsn);
    			if (!obj) {
    
    			ast_debug(1, "Found handle %s\n", query->readhandle[dsn_num]);
    
    			if (!(stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql)))) {
    				release_obj_or_dsn (&obj, &dsn);
    
    				continue;
    			}
    
    			executed = 1;
    
    			res = SQLNumResultCols(stmt, &colcount);
    			if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    				ast_cli(a->fd, "SQL Column Count error!\n[%s]\n\n", ast_str_buffer(sql));
    
    				SQLCloseCursor(stmt);
    				SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    
    				release_obj_or_dsn (&obj, &dsn);
    
    				AST_RWLIST_UNLOCK(&queries);
    				return CLI_SUCCESS;
    			}
    
    
    			if (colcount <= 0) {
    				SQLCloseCursor(stmt);
    				SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    				release_obj_or_dsn (&obj, &dsn);
    				ast_cli(a->fd, "Returned %d columns.  Query executed on handle %d:%s [%s]\n", colcount, dsn_num, query->readhandle[dsn_num], ast_str_buffer(sql));
    				AST_RWLIST_UNLOCK(&queries);
    				return CLI_SUCCESS;
    			}
    
    
    			res = SQLFetch(stmt);
    			if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    				SQLCloseCursor(stmt);
    				SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    				release_obj_or_dsn (&obj, &dsn);
    
    					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));
    
    					ast_cli(a->fd, "Error %d in FETCH [%s]\n", res, ast_str_buffer(sql));
    
    				}
    				AST_RWLIST_UNLOCK(&queries);
    				return CLI_SUCCESS;
    			}
    			for (;;) {
    				for (x = 0; x < colcount; x++) {
    
    					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);
    					}
    
    
    					octetlength = 0;
    					SQLColAttribute(stmt, x + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &octetlength);
    
    					res = ast_odbc_ast_str_SQLGetData(&coldata, octetlength + 1, stmt, x + 1, SQL_CHAR, &indicator);
    
    					if (indicator == SQL_NULL_DATA) {
    						ast_str_set(&coldata, 0, "(nil)");
    						res = SQL_SUCCESS;
    					}
    
    					if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    						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);
    
    						release_obj_or_dsn (&obj, &dsn);
    
    						AST_RWLIST_UNLOCK(&queries);
    						return CLI_SUCCESS;
    
    					ast_cli(a->fd, "%-20.20s  %s\n", colname, ast_str_buffer(coldata));
    
    				/* Get next row */
    				res = SQLFetch(stmt);
    				if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    					break;
    
    				ast_cli(a->fd, "%-20.20s  %s\n", "----------", "----------");
    
    			SQLCloseCursor(stmt);
    			SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    			release_obj_or_dsn (&obj, &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]);
    
    		release_obj_or_dsn (&obj, &dsn);
    
    
    		if (!executed) {
    
    			ast_cli(a->fd, "Failed to execute query. [%s]\n", ast_str_buffer(sql));
    
    	} else { /* No execution, just print out the resulting SQL */
    
    		ast_cli(a->fd, "%s\n", ast_str_buffer(sql));
    
    	}
    	AST_RWLIST_UNLOCK(&queries);
    	return CLI_SUCCESS;
    }
    
    static char *cli_odbc_write(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	AST_DECLARE_APP_ARGS(values,
    		AST_APP_ARG(field)[100];
    	);
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(field)[100];
    	);
    
    	char *char_args, *char_values, varname[15];
    
    	struct acf_odbc_query *query;
    	struct ast_channel *chan;
    	int i;
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "odbc write";
    		e->usage =
    			"Usage: odbc write <name> <args> <value> [exec]\n"
    			"       Evaluates the SQL provided in the ODBC function <name>, and\n"
    			"       optionally executes the function.  This function is intended for\n"
    			"       testing purposes.  Remember to quote arguments containing spaces.\n";
    		return NULL;
    	case CLI_GENERATE:
    		if (a->pos == 2) {
    			int wordlen = strlen(a->word), which = 0;
    			/* Complete function name */
    			AST_RWLIST_RDLOCK(&queries);
    			AST_RWLIST_TRAVERSE(&queries, query, list) {
    				if (!strncasecmp(query->acf->name, a->word, wordlen)) {
    					if (++which > a->n) {
    						char *res = ast_strdup(query->acf->name);
    						AST_RWLIST_UNLOCK(&queries);
    						return res;
    					}
    				}
    			}
    			AST_RWLIST_UNLOCK(&queries);
    			return NULL;
    		} else if (a->pos == 5) {
    
    			static const char * const completions[] = { "exec", NULL };
    			return ast_cli_complete(a->word, completions, a->n);
    
    		} else {
    			return NULL;
    		}
    	}
    
    	if (a->argc < 5 || a->argc > 6) {
    		return CLI_SHOWUSAGE;
    	}
    
    
    	sql = ast_str_thread_get(&sql_buf, 16);
    	if (!sql) {
    		return CLI_FAILURE;
    	}
    
    
    	AST_RWLIST_RDLOCK(&queries);
    	AST_RWLIST_TRAVERSE(&queries, query, list) {
    		if (!strcmp(query->acf->name, a->argv[2])) {
    			break;
    		}
    	}
    
    	if (!query) {
    		ast_cli(a->fd, "No such query '%s'\n", a->argv[2]);
    		AST_RWLIST_UNLOCK(&queries);
    		return CLI_SHOWUSAGE;
    	}
    
    
    	if (!query->sql_write) {
    
    		ast_cli(a->fd, "The function %s has no writesql parameter.\n", a->argv[2]);
    		AST_RWLIST_UNLOCK(&queries);
    		return CLI_SUCCESS;
    	}
    
    
    	/* FIXME: The code below duplicates code found in acf_odbc_write but
    	 * lacks the newer sql_insert additions. */
    
    
    	ast_str_make_space(&sql, strlen(query->sql_write) * 2 + 300);
    
    	/* Evaluate function */
    	char_args = ast_strdupa(a->argv[3]);
    	char_values = ast_strdupa(a->argv[4]);
    
    
    	chan = ast_dummy_channel_alloc();
    
    	if (!chan) {
    		AST_RWLIST_UNLOCK(&queries);
    		return CLI_FAILURE;
    	}
    
    
    	AST_STANDARD_APP_ARGS(args, char_args);
    	for (i = 0; i < args.argc; i++) {
    		snprintf(varname, sizeof(varname), "ARG%d", i + 1);
    		pbx_builtin_pushvar_helper(chan, varname, args.field[i]);
    	}
    
    	/* Parse values, just like arguments */
    	AST_STANDARD_APP_ARGS(values, char_values);
    	for (i = 0; i < values.argc; i++) {
    		snprintf(varname, sizeof(varname), "VAL%d", i + 1);
    		pbx_builtin_pushvar_helper(chan, varname, values.field[i]);
    	}
    
    	/* Additionally set the value as a whole (but push an empty string if value is NULL) */
    	pbx_builtin_pushvar_helper(chan, "VALUE", S_OR(a->argv[4], ""));
    
    	ast_str_substitute_variables(&sql, 0, chan, query->sql_write);
    	ast_debug(1, "SQL is %s\n", ast_str_buffer(sql));
    
    
    	chan = ast_channel_unref(chan);
    
    
    	if (a->argc == 6 && !strcmp(a->argv[5], "exec")) {
    		/* Execute the query */
    
    		struct odbc_obj *obj = NULL;
    		struct dsn *dsn = NULL;
    
    		int dsn_num, executed = 0;
    
    		SQLHSTMT stmt;
    		SQLLEN rows = -1;
    
    
    		for (dsn_num = 0; dsn_num < 5; dsn_num++) {
    			if (ast_strlen_zero(query->writehandle[dsn_num])) {
    
    			obj = get_odbc_obj(query->writehandle[dsn_num], &dsn);
    			if (!obj) {
    
    			if (!(stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql)))) {
    				release_obj_or_dsn (&obj, &dsn);
    
    				continue;
    			}
    
    			SQLRowCount(stmt, &rows);
    			SQLCloseCursor(stmt);
    			SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    			release_obj_or_dsn (&obj, &dsn);
    
    			ast_cli(a->fd, "Affected %d rows.  Query executed on handle %d [%s]\n", (int)rows, dsn_num, query->writehandle[dsn_num]);
    
    		}
    
    		if (!executed) {
    			ast_cli(a->fd, "Failed to execute query.\n");
    		}
    
    	} else { /* No execution, just print out the resulting SQL */
    
    		ast_cli(a->fd, "%s\n", ast_str_buffer(sql));
    
    	}
    	AST_RWLIST_UNLOCK(&queries);
    	return CLI_SUCCESS;
    }
    
    static struct ast_cli_entry cli_func_odbc[] = {
    	AST_CLI_DEFINE(cli_odbc_write, "Test setting a func_odbc function"),
    	AST_CLI_DEFINE(cli_odbc_read, "Test reading a func_odbc function"),
    };
    
    
    {
    	int res = 0;
    	struct ast_config *cfg;
    	char *catg;
    
    	struct ast_flags config_flags = { 0 };
    
    	res |= ast_custom_function_register(&fetch_function);
    
    	res |= ast_register_application_xml(app_odbcfinish, exec_odbcfinish);
    
    	cfg = ast_config_load(config, config_flags);
    
    	if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
    
    		ast_log(LOG_NOTICE, "Unable to load config for func_odbc: %s\n", config);
    
    		return AST_MODULE_LOAD_DECLINE;
    
    	ast_rwlock_wrlock(&single_db_connection_lock);
    	if ((s = ast_variable_retrieve(cfg, "general", "single_db_connection"))) {
    		single_db_connection = ast_true(s);
    	} else {
    		single_db_connection = DEFAULT_SINGLE_DB_CONNECTION;
    	}
    
    	dsns = NULL;
    
    	if (single_db_connection) {
    
    		dsns = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, DSN_BUCKETS,
    			dsn_hash, NULL, dsn_cmp);
    
    		if (!dsns) {
    			ast_log(LOG_ERROR, "Could not initialize DSN container\n");
    			ast_rwlock_unlock(&single_db_connection_lock);
    			return AST_MODULE_LOAD_DECLINE;
    		}
    	}
    	ast_rwlock_unlock(&single_db_connection_lock);
    
    	AST_RWLIST_WRLOCK(&queries);
    
    	for (catg = ast_category_browse(cfg, NULL);
    
    	     catg;
    	     catg = ast_category_browse(cfg, catg)) {
    
    		if (!strcasecmp(catg, "general")) {
    			continue;
    		}
    
    
    		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);
    
    			AST_RWLIST_INSERT_HEAD(&queries, query, list);
    
    			ast_custom_function_register(query->acf);
    		}
    	}
    
    	ast_config_destroy(cfg);
    
    	res |= ast_custom_function_register(&escape_function);
    
    	res |= ast_custom_function_register(&escape_backslashes_function);
    
    	ast_cli_register_multiple(cli_func_odbc, ARRAY_LEN(cli_func_odbc));
    
    	AST_RWLIST_WRLOCK(&queries);
    	while (!AST_RWLIST_EMPTY(&queries)) {
    		query = AST_RWLIST_REMOVE_HEAD(&queries, list);
    
    		ast_custom_function_unregister(query->acf);
    		free_acf_query(query);
    
    	res |= ast_custom_function_unregister(&escape_function);
    
    	res |= ast_custom_function_unregister(&escape_backslashes_function);
    
    	res |= ast_custom_function_unregister(&fetch_function);
    	res |= ast_unregister_application(app_odbcfinish);
    
    	ast_cli_unregister_multiple(cli_func_odbc, ARRAY_LEN(cli_func_odbc));
    
    	/* Allow any threads waiting for this lock to pass (avoids a race) */
    
    {
    	int res = 0;
    	struct ast_config *cfg;
    
    	struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED };
    
    	cfg = ast_config_load(config, config_flags);
    
    	if (cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID)
    
    	ast_rwlock_wrlock(&single_db_connection_lock);
    
    	if (dsns) {
    		ao2_ref(dsns, -1);
    		dsns = NULL;
    	}
    
    	if (cfg && (s = ast_variable_retrieve(cfg, "general", "single_db_connection"))) {
    		single_db_connection = ast_true(s);
    	} else {
    		single_db_connection = DEFAULT_SINGLE_DB_CONNECTION;
    	}
    
    	if (single_db_connection) {
    
    		dsns = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, DSN_BUCKETS,
    			dsn_hash, NULL, dsn_cmp);
    
    		if (!dsns) {
    			ast_log(LOG_ERROR, "Could not initialize DSN container\n");
    			ast_rwlock_unlock(&single_db_connection_lock);
    			return 0;
    		}
    	}
    	ast_rwlock_unlock(&single_db_connection_lock);
    
    
    	while (!AST_RWLIST_EMPTY(&queries)) {
    		oldquery = AST_RWLIST_REMOVE_HEAD(&queries, list);
    
    		ast_custom_function_unregister(oldquery->acf);
    		free_acf_query(oldquery);
    
    	}
    
    	if (!cfg) {
    		ast_log(LOG_WARNING, "Unable to load config for func_odbc: %s\n", config);
    		goto reload_out;
    	}
    
    	for (catg = ast_category_browse(cfg, NULL);
    
    	     catg;
    	     catg = ast_category_browse(cfg, catg)) {
    
    		struct acf_odbc_query *query = NULL;
    
    
    		if (!strcasecmp(catg, "general")) {
    			continue;
    		}
    
    
    		if (init_acf_query(cfg, catg, &query)) {
    
    			ast_log(LOG_ERROR, "Cannot initialize query %s\n", catg);
    
    			AST_RWLIST_INSERT_HEAD(&queries, query, list);
    
    			ast_custom_function_register(query->acf);
    
    		}
    	}
    
    	ast_config_destroy(cfg);
    reload_out: