63c58dff4e64d791fe56da1509b0902b7a919ae1
[asterisk/asterisk.git] / funcs / func_odbc.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (c) 2005, 2006 Tilghman Lesher
5  *
6  * Tilghman Lesher <func_odbc__200508@the-tilghman.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*!
20  * \file
21  *
22  * \brief ODBC lookups
23  *
24  * \author Tilghman Lesher <func_odbc__200508@the-tilghman.com>
25  */
26
27 #include <sys/types.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <string.h>
32
33 #include "asterisk.h"
34
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36
37 #include "asterisk/module.h"
38 #include "asterisk/file.h"
39 #include "asterisk/logger.h"
40 #include "asterisk/options.h"
41 #include "asterisk/channel.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/module.h"
44 #include "asterisk/config.h"
45 #include "asterisk/res_odbc.h"
46
47 static char *tdesc = "ODBC lookups";
48
49 static char *config = "func_odbc.conf";
50
51 struct acf_odbc_query {
52         char name[30];
53         char dsn[30];
54         char sql_read[2048];
55         char sql_write[2048];
56         struct ast_custom_function *acf;
57         unsigned int deleteme:1;
58         struct acf_odbc_query *next;
59 };
60
61 static struct acf_odbc_query *queries = NULL;
62 AST_MUTEX_DEFINE_STATIC(query_lock);
63
64 #ifdef NEEDTRACE
65 static void acf_odbc_error(SQLHSTMT stmt, int res)
66 {
67         char state[10] = "", diagnostic[256] = "";
68         SQLINTEGER nativeerror = 0;
69         SQLSMALLINT diagbytes = 0;
70         SQLGetDiagRec(SQL_HANDLE_STMT, stmt, 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
71         ast_log(LOG_WARNING, "SQL return value %d: error %s: %s (len %d)\n", res, state, diagnostic, diagbytes);
72 }
73 #endif
74
75 /*
76  * Master control routine
77  */
78 static int acf_odbc_write(struct ast_channel *chan, char *cmd, char *s, const char *value)
79 {
80         odbc_obj *obj;
81         struct acf_odbc_query *query;
82         char *t, *arg, buf[2048]="", varname[15];
83         int res, argcount=0, valcount=0, i, retry=0;
84         struct ast_channel *ast;
85         SQLHSTMT stmt;
86         SQLINTEGER nativeerror=0, numfields=0, rows=0;
87         SQLSMALLINT diagbytes=0;
88         unsigned char state[10], diagnostic[256];
89 #ifdef NEEDTRACE
90         SQLINTEGER enable = 1;
91         char *tracefile = "/tmp/odbc.trace";
92 #endif
93
94         ast_mutex_lock(&query_lock);
95         for (query=queries; query; query = query->next) {
96                 if (!strcasecmp(query->name, cmd + 5)) {
97                         break;
98                 }
99         }
100
101         if (!query) {
102                 ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
103                 ast_mutex_unlock(&query_lock);
104                 return -1;
105         }
106
107         obj = fetch_odbc_obj(query->dsn, 0);
108
109         if (!obj) {
110                 ast_log(LOG_ERROR, "No such DSN registered: %s (check res_odbc.conf)\n", query->dsn);
111                 ast_mutex_unlock(&query_lock);
112                 return -1;
113         }
114
115         /* Parse our arguments */
116         t = value ? ast_strdupa(value) : "";
117
118         if (!s || !t) {
119                 ast_log(LOG_ERROR, "Out of memory\n");
120                 ast_mutex_unlock(&query_lock);
121                 return -1;
122         }
123
124         /* XXX You might be tempted to change this section into using
125          * pbx_builtin_pushvar_helper().  However, note that if you try
126          * to set a NULL (like for VALUE), then nothing gets set, and the
127          * value doesn't get masked out.  Even worse, when you subsequently
128          * try to remove the value you just set, you'll wind up unsetting
129          * the previous value (which is wholly undesireable).  Hence, this
130          * has to remain the way it is done here. XXX
131          */
132
133         /* Save old arguments as variables in a fake channel */
134         ast = ast_channel_alloc(0);
135         while ((arg = strsep(&s, "|"))) {
136                 argcount++;
137                 snprintf(varname, sizeof(varname), "ARG%d", argcount);
138                 pbx_builtin_setvar_helper(ast, varname, pbx_builtin_getvar_helper(chan, varname));
139                 pbx_builtin_setvar_helper(chan, varname, arg);
140         }
141
142         /* Parse values, just like arguments */
143         while ((arg = strsep(&t, "|"))) {
144                 valcount++;
145                 snprintf(varname, sizeof(varname), "VAL%d", valcount);
146                 pbx_builtin_setvar_helper(ast, varname, pbx_builtin_getvar_helper(chan, varname));
147                 pbx_builtin_setvar_helper(chan, varname, arg);
148         }
149
150         /* Additionally set the value as a whole */
151         /* Note that pbx_builtin_setvar_helper will quite happily take a NULL for the 3rd argument */
152         pbx_builtin_setvar_helper(ast, "VALUE", pbx_builtin_getvar_helper(chan, "VALUE"));
153         pbx_builtin_setvar_helper(chan, "VALUE", value);
154
155         pbx_substitute_variables_helper(chan, query->sql_write, buf, sizeof(buf) - 1);
156
157         /* Restore prior values */
158         for (i=1; i<=argcount; i++) {
159                 snprintf(varname, sizeof(varname), "ARG%d", argcount);
160                 pbx_builtin_setvar_helper(chan, varname, pbx_builtin_getvar_helper(ast, varname));
161         }
162
163         for (i=1; i<=valcount; i++) {
164                 snprintf(varname, sizeof(varname), "VAL%d", argcount);
165                 pbx_builtin_setvar_helper(chan, varname, pbx_builtin_getvar_helper(ast, varname));
166         }
167         pbx_builtin_setvar_helper(chan, "VALUE", pbx_builtin_getvar_helper(ast, "VALUE"));
168
169         ast_channel_free(ast);
170         ast_mutex_unlock(&query_lock);
171
172 retry_write:
173 #ifdef NEEDTRACE
174         SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER);
175         SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile));
176 #endif
177
178         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
179         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
180                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
181                 pbx_builtin_setvar_helper(chan, "ODBCROWS", "-1");
182                 return -1;
183         }
184
185         res = SQLPrepare(stmt, (unsigned char *)buf, SQL_NTS);
186         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
187                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", buf);
188                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
189                 pbx_builtin_setvar_helper(chan, "ODBCROWS", "-1");
190                 return -1;
191         }
192
193         res = SQLExecute(stmt);
194         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
195                 if (res == SQL_ERROR) {
196                         SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
197                         for (i = 0; i <= numfields; i++) {
198                                 SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
199                                 ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
200                                 if (i > 10) {
201                                         ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
202                                         break;
203                                 }
204                         }
205                 }
206                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
207                 odbc_obj_disconnect(obj);
208                 /* All handles are now invalid (after a disconnect), so we gotta redo all handles */
209                 odbc_obj_connect(obj);
210                 if (!retry) {
211                         retry = 1;
212                         goto retry_write;
213                 }
214                 rows = -1;
215         } else {
216                 /* Rows affected */
217                 SQLRowCount(stmt, &rows);
218         }
219
220         /* Output the affected rows, for all cases.  In the event of failure, we
221          * flag this as -1 rows.  Note that this is different from 0 affected rows
222          * which would be the case if we succeeded in our query, but the values did
223          * not change. */
224         snprintf(varname, sizeof(varname), "%d", (int)rows);
225         pbx_builtin_setvar_helper(chan, "ODBCROWS", varname);
226
227         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
228                 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", buf);
229         }
230
231         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
232
233         return 0;
234 }
235
236 static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf, size_t len)
237 {
238         odbc_obj *obj;
239         struct acf_odbc_query *query;
240         char *arg, sql[2048] = "", varname[15];
241         int count=0, res, x;
242         SQLHSTMT stmt;
243         SQLSMALLINT colcount=0;
244         SQLINTEGER indicator;
245 #ifdef NEEDTRACE
246         SQLINTEGER enable = 1;
247         char *tracefile = "/tmp/odbc.trace";
248 #endif
249
250         ast_mutex_lock(&query_lock);
251         for (query=queries; query; query = query->next) {
252                 if (!strcasecmp(query->name, cmd + 5)) {
253                         break;
254                 }
255         }
256
257         if (!query) {
258                 ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
259                 ast_mutex_unlock(&query_lock);
260                 return -1;
261         }
262
263         obj = fetch_odbc_obj(query->dsn, 0);
264
265         if (!obj) {
266                 ast_log(LOG_ERROR, "No such DSN registered: %s (check res_odbc.conf)\n", query->dsn);
267                 ast_mutex_unlock(&query_lock);
268                 return -1;
269         }
270
271 #ifdef NEEDTRACE
272         SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER);
273         SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile));
274 #endif
275
276         while ((arg = strsep(&s, "|"))) {
277                 count++;
278                 snprintf(varname, sizeof(varname), "ARG%d", count);
279                 /* arg is by definition non-NULL, so this works, here */
280                 pbx_builtin_pushvar_helper(chan, varname, arg);
281         }
282
283         pbx_substitute_variables_helper(chan, query->sql_read, sql, sizeof(sql) - 1);
284
285         /* Restore prior values */
286         for (x = 1; x <= count; x++) {
287                 snprintf(varname, sizeof(varname), "ARG%d", x);
288                 pbx_builtin_setvar_helper(chan, varname, NULL);
289         }
290
291         ast_mutex_unlock(&query_lock);
292
293         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
294         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
295                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
296                 return -1;
297         }
298
299         res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
300         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
301                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
302                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
303                 return -1;
304         }
305
306         res = odbc_smart_execute(obj, stmt);
307         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
308                 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
309                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
310                 return -1;
311         }
312
313         res = SQLNumResultCols(stmt, &colcount);
314         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
315                 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
316                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
317                 return -1;
318         }
319
320         *buf = '\0';
321
322         res = SQLFetch(stmt);
323         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
324                 if (res == SQL_NO_DATA) {
325                         if (option_verbose > 3) {
326                                 ast_verbose(VERBOSE_PREFIX_4 "Found no rows [%s]\n", sql);
327                         }
328                 } else if (option_verbose > 3) {
329                         ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, sql);
330                 }
331                 goto acf_out;
332         }
333
334         for (x=0; x<colcount; x++) {
335                 int buflen, coldatalen;
336                 char coldata[256];
337
338                 buflen = strlen(buf);
339                 res = SQLGetData(stmt, x + 1, SQL_CHAR, coldata, sizeof(coldata), &indicator);
340                 if (indicator == SQL_NULL_DATA) {
341                         coldata[0] = '\0';
342                         res = SQL_SUCCESS;
343                 }
344
345                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
346                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
347                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
348                         return -1;
349                 }
350
351                 strncat(buf + buflen, coldata, len - buflen);
352                 coldatalen = strlen(coldata);
353                 strncat(buf + buflen + coldatalen, ",", len - buflen - coldatalen);
354         }
355         /* Trim trailing comma */
356         buf[strlen(buf) - 1] = '\0';
357
358 acf_out:
359         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
360         return 0;
361 }
362
363 static int acf_escape(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
364 {
365         char *out = buf;
366
367         for (; *data && out - buf < len; data++) {
368                 if (*data == '\'') {
369                         *out = '\'';
370                         out++;
371                 }
372                 *out++ = *data;
373         }
374         *out = '\0';
375
376         return 0;
377 }
378
379 static struct ast_custom_function escape_function = {
380         .name = "SQL_ESC",
381         .synopsis = "Escapes single ticks for use in SQL statements",
382         .syntax = "SQL_ESC(<string>)",
383         .desc =
384 "Used in SQL templates to escape data which may contain single ticks (') which\n"
385 "are otherwise used to delimit data.  For example:\n"
386 "SELECT foo FROM bar WHERE baz='${SQL_ESC(${ARG1})}'\n",
387         .read = acf_escape,
388         .write = NULL,
389 };
390
391 static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_query **query)
392 {
393         char *tmp;
394
395         if (!cfg || !catg) {
396                 return -1;
397         }
398
399         *query = calloc(1, sizeof(struct acf_odbc_query));
400         if (! (*query))
401                 return -1;
402
403         ast_copy_string((*query)->name, catg, sizeof((*query)->name));
404
405         if ((tmp = ast_variable_retrieve(cfg, catg, "dsn"))) {
406                 ast_copy_string((*query)->dsn, tmp, sizeof((*query)->dsn));
407         } else {
408                 return -1;
409         }
410
411         if ((tmp = ast_variable_retrieve(cfg, catg, "read"))) {
412                 ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
413         }
414
415         if ((tmp = ast_variable_retrieve(cfg, catg, "write"))) {
416                 ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
417         }
418
419         (*query)->acf = calloc(1, sizeof(struct ast_custom_function));
420         if ((*query)->acf) {
421                 asprintf((char **)&((*query)->acf->name), "ODBC_%s", catg);
422                 asprintf((char **)&((*query)->acf->syntax), "ODBC_%s(<arg1>[...[,<argN>]])", catg);
423                 (*query)->acf->synopsis = "Runs the referenced query with the specified arguments";
424                 if (!ast_strlen_zero((*query)->sql_read) && !ast_strlen_zero((*query)->sql_write)) {
425                         asprintf((char **)&((*query)->acf->desc),
426                                                 "Runs the following query, as defined in func_odbc.conf, performing\n"
427                                                 "substitution of the arguments into the query as specified by ${ARG1},\n"
428                                                 "${ARG2}, ... ${ARGn}.  When setting the function, the values are provided\n"
429                                                 "either in whole as ${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
430                                                 "\nRead:\n%s\n\nWrite:\n%s\n",
431                                                 (*query)->sql_read,
432                                                 (*query)->sql_write);
433                 } else if (!ast_strlen_zero((*query)->sql_read)) {
434                         asprintf((char **)&((*query)->acf->desc),
435                                                 "Runs the following query, as defined in func_odbc.conf, performing\n"
436                                                 "substitution of the arguments into the query as specified by ${ARG1},\n"
437                                                 "${ARG2}, ... ${ARGn}.  This function may only be read, not set.\n\nSQL:\n%s\n",
438                                                 (*query)->sql_read);
439                 } else if (!ast_strlen_zero((*query)->sql_write)) {
440                         asprintf((char **)&((*query)->acf->desc),
441                                                 "Runs the following query, as defined in func_odbc.conf, performing\n"
442                                                 "substitution of the arguments into the query as specified by ${ARG1},\n"
443                                                 "${ARG2}, ... ${ARGn}.  The values are provided either in whole as\n"
444                                                 "${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
445                                                 "This function may only be set.\nSQL:\n%s\n",
446                                                 (*query)->sql_write);
447                 }
448
449                 if (ast_strlen_zero((*query)->sql_read)) {
450                         (*query)->acf->read = NULL;
451                 } else {
452                         (*query)->acf->read = acf_odbc_read;
453                 }
454
455                 if (ast_strlen_zero((*query)->sql_write)) {
456                         (*query)->acf->write = NULL;
457                 } else {
458                         (*query)->acf->write = acf_odbc_write;
459                 }
460
461                 if (! (*query)->acf->name || ! (*query)->acf->syntax || ! (*query)->acf->desc) {
462                         return -1;
463                 }
464         } else {
465                 return -1;
466         }
467         return 0;
468 }
469
470 static int free_acf_query(struct acf_odbc_query *query)
471 {
472         if (query) {
473                 if (query->acf) {
474                         if (query->acf->name)
475                                 free((char *)query->acf->name);
476                         if (query->acf->syntax)
477                                 free((char *)query->acf->syntax);
478                         if (query->acf->desc)
479                                 free((char *)query->acf->desc);
480                         free(query->acf);
481                 }
482                 free(query);
483         }
484         return 0;
485 }
486
487 static int odbc_load_module(void)
488 {
489         int res = 0;
490         struct ast_config *cfg;
491         char *catg;
492
493         ast_mutex_lock(&query_lock);
494
495         cfg = ast_config_load(config);
496         if (!cfg) {
497                 ast_log(LOG_WARNING, "Unable to load config for func_odbc: %s\n", config);
498                 goto out;
499         }
500
501         for (catg = ast_category_browse(cfg, NULL);
502              catg;
503              catg = ast_category_browse(cfg, catg)) {
504                 struct acf_odbc_query *query=NULL;
505
506                 if (init_acf_query(cfg, catg, &query)) {
507                         ast_log(LOG_ERROR, "Out of memory\n");
508                         free_acf_query(query);
509                 } else {
510                         query->next = queries;
511                         queries = query;
512                         ast_custom_function_register(query->acf);
513                 }
514         }
515
516         ast_config_destroy(cfg);
517         ast_custom_function_register(&escape_function);
518 out:
519         ast_mutex_unlock(&query_lock);
520         return res;
521 }
522
523 static int odbc_unload_module(void)
524 {
525         struct acf_odbc_query *query, *lastquery = NULL;
526
527         ast_mutex_lock(&query_lock);
528         for (query = queries; query; query = query->next) {
529                 if (lastquery)
530                         free_acf_query(lastquery);
531                 if (ast_custom_function_unregister(query->acf)) {
532                         ast_log(LOG_ERROR, "Cannot unregister function '%s'?\n", query->acf->name);
533                         /* Keep state valid */
534                         queries = query;
535                         ast_mutex_unlock(&query_lock);
536                         return -1;
537                 } else {
538                         /* If anything is waiting on this lock, this will let it pass (avoids a race) */
539                         ast_mutex_unlock(&query_lock);
540                         ast_mutex_lock(&query_lock);
541                         lastquery = query;
542                 }
543         }
544         if (lastquery)
545                 free(lastquery);
546         queries = NULL;
547
548         ast_custom_function_unregister(&escape_function);
549
550         ast_mutex_unlock(&query_lock);
551         return 0;
552 }
553
554 static int reload(void *mod)
555 {
556         int res = 0;
557         struct ast_config *cfg;
558         struct acf_odbc_query *q, *prevq = NULL, *qdel = NULL;
559         char *catg;
560
561         ast_mutex_lock(&query_lock);
562
563         for (q = queries; q; q = q->next) {
564                 q->deleteme = 1;
565         }
566
567         cfg = ast_config_load(config);
568         if (!cfg) {
569                 ast_log(LOG_WARNING, "Unable to load config for func_odbc: %s\n", config);
570                 goto reload_out;
571         }
572
573         for (catg = ast_category_browse(cfg, NULL);
574              catg;
575              catg = ast_category_browse(cfg, catg)) {
576                 struct acf_odbc_query *query = NULL;
577
578                 /* We do this piecemeal, so that we stay in a consistent state, if there's ever an error */
579                 for (q = queries, prevq=NULL; q; prevq=q, q = q->next) {
580                         if (!strcasecmp(catg, q->name)) {
581                                 break;
582                         }
583                 }
584
585                 if (init_acf_query(cfg, catg, &query)) {
586                         ast_log(LOG_ERROR, "Cannot initialize query ODBC_%s\n", catg);
587                         free_acf_query(query);
588                 } else {
589                         if (q) {
590                                 /* Replacement */
591                                 if (ast_custom_function_unregister(q->acf)) {
592                                         ast_log(LOG_ERROR, "Cannot reload query %s\n", query->acf->name);
593                                         free_acf_query(query);
594                                 } else {
595                                         ast_custom_function_register(query->acf);
596                                         /* Add it to the list */
597                                         if (prevq)
598                                                 prevq->next = query;
599                                         else
600                                                 queries = query;
601                                         query->next = q->next;
602                                         /* Get rid of the old record */
603                                         free_acf_query(q);
604                                 }
605                         } else {
606                                 /* New */
607                                 query->next = queries;
608                                 queries = query;
609                                 ast_custom_function_register(query->acf);
610                         }
611                 }
612         }
613
614         /* Any remaining sets will now be destroyed */
615         for (q = queries; q; q = q->next) {
616                 if (qdel) {
617                         free_acf_query(qdel);
618                         qdel = NULL;
619                 }
620
621                 if (q->deleteme) {
622                         if (ast_custom_function_unregister(q->acf)) {
623                                 ast_log(LOG_ERROR, "Cannot unregister function?  Refusing to make 'ODBC_%s' go away.\n", q->name);
624                         } else {
625                                 /* If anything is waiting on the lock to execute, this will dispose of it (without a race) */
626                                 ast_mutex_unlock(&query_lock);
627                                 ast_mutex_lock(&query_lock);
628
629                                 if (prevq) {
630                                         prevq->next = q->next;
631                                 } else {
632                                         queries = q->next;
633                                 }
634                                 qdel = q;
635                         }
636                 } else {
637                         prevq = q;
638                 }
639         }
640         if (qdel)
641                 free_acf_query(qdel);
642
643         ast_config_destroy(cfg);
644 reload_out:
645         ast_mutex_unlock(&query_lock);
646         return res;
647 }
648
649 static int unload_module(void *mod)
650 {
651         return odbc_unload_module();
652 }
653
654 static int load_module(void *mod)
655 {
656         return odbc_load_module();
657 }
658
659 static const char *description(void)
660 {
661         return tdesc;
662 }
663
664 /* XXX need to revise usecount - set if query_lock is set */
665
666 static const char *key(void)
667 {
668         return ASTERISK_GPL_KEY;
669 }
670 STD_MOD(MOD_1, reload, NULL, NULL);