dect
/
asterisk
Archived
13
0
Fork 0

Integrate functionality tested on svncommunity users back into trunk

git-svn-id: http://svn.digium.com/svn/asterisk/trunk@49030 f38db490-d61c-443f-a65b-d21fe96a405b
This commit is contained in:
tilghman 2006-12-28 20:13:00 +00:00
parent 068fb38804
commit 8d086303b6
3 changed files with 340 additions and 66 deletions

View File

@ -17,25 +17,54 @@
; If you have data which may potentially contain single ticks, you may wish
; to use the dialplan function SQL_ESC() to escape the data prior to its
; inclusion in the SQL statement.
;
;
; The following variables are available in this configuration file:
;
; readhandle A comma-separated list of DSNs (from res_odbc.conf) to use when
; executing the readsql statement. Each DSN is tried, in
; succession, until the statement succeeds. You may specify up to
; 5 DSNs per function class. If not specified, it will default to
; the value of writehandle or dsn, if specified.
; writehandle A comma-separated list of DSNs (from res_odbc.conf) to use when
; executing the writesql statement. The same rules apply as to
; readhandle. "dsn" is a synonym for "writehandle".
; readsql The statement to execute when reading from the function class.
; writesql The statement to execute when writing to the function class.
; prefix Normally, all function classes are prefixed with "ODBC" to keep
; them uniquely named. You may choose to change this prefix, which
; may be useful to segregate a collection of certain function
; classes from others.
; escapecommas This option may be used to turn off the default behavior of
; escaping commas which occur within a field. If commas are
; escaped (the default behavior), then fields containing commas
; will be treated as a single value when assigning to ARRAY() or
; HASH(). If commas are not escaped, then values will be separated
; at the comma within fields. Please note that turning this option
; off is incompatible with the functionality of HASH().
; ODBC_SQL - Allow an SQL statement to be built entirely in the dialplan
[SQL]
dsn=mysql1
read=${ARG1}
readsql=${ARG1}
; ODBC_ANTIGF - A blacklist.
[ANTIGF]
dsn=mysql1
read=SELECT COUNT(*) FROM exgirlfriends WHERE callerid='${SQL_ESC(${ARG1})}'
dsn=mysql1,mysql2 ; Use mysql1 as the primary handle, but fall back to mysql2
; if mysql1 is down. Supports up to 5 comma-separated
; DSNs. "dsn" may also be specified as "readhandle" and
; "writehandle", if it is important to separate reads and
; writes to different databases.
readsql=SELECT COUNT(*) FROM exgirlfriends WHERE callerid='${SQL_ESC(${ARG1})}'
; ODBC_PRESENCE - Retrieve and update presence
[PRESENCE]
dsn=mysql1
read=SELECT location FROM presence WHERE id='${SQL_ESC(${ARG1})}'
write=UPDATE presence SET location='${SQL_ESC(${VAL1})}' WHERE id='${SQL_ESC(${ARG1})}'
;prefix=OFFICE ; Changes this function from ODBC_PRESENCE to OFFICE_PRESENCE
;escapecommas=no ; Normally, commas within a field are escaped such that each
; field may be separated into individual variables with ARRAY.
; This option turns that behavior off [default=yes].
readsql=SELECT location FROM presence WHERE id='${SQL_ESC(${ARG1})}'
writesql=UPDATE presence SET location='${SQL_ESC(${VAL1})}' WHERE id='${SQL_ESC(${ARG1})}'
;prefix=OFFICE ; Changes this function from ODBC_PRESENCE to OFFICE_PRESENCE
;escapecommas=no ; Normally, commas within a field are escaped such that each
; field may be separated into individual variables with ARRAY.
; This option turns that behavior off [default=yes].

View File

@ -37,6 +37,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#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, "dsn"))) {
ast_copy_string((*query)->dsn, tmp, sizeof((*query)->dsn));
} else {
return -1;
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, "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 {
/* 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(<arg1>[...[,<argN>]])", (*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;
int err;
if (init_acf_query(cfg, catg, &query)) {
ast_log(LOG_ERROR, "Out of memory\n");
free_acf_query(query);
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",

View File

@ -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(<hashname>)\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(<hashname>)",
.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;
}