dda45243bfdb079d47e4c98d90f694e22bd1a625
[asterisk/asterisk.git] / addons / app_mysql.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2004, Constantine Filin and Christos Ricudis
5  *
6  * Christos Ricudis <ricudis@itc.auth.gr>
7  * Constantine Filin <cf@intermedia.net>
8  *
9  * See http://www.asterisk.org for more information about
10  * the Asterisk project. Please do not directly contact
11  * any of the maintainers of this project for assistance;
12  * the project provides a web site, mailing lists and IRC
13  * channels for your use.
14  *
15  * This program is free software, distributed under the terms of
16  * the GNU General Public License Version 2. See the LICENSE file
17  * at the top of the source tree.
18  */
19
20 /*!
21  * \file
22  * \brief MYSQL dialplan application
23  * \ingroup applications
24  */
25
26 /*! \li \ref app_mysql.c uses the configuration file \ref app_mysql.conf
27  * \addtogroup configuration_file Configuration Files
28  */
29
30 /*! 
31  * \page app_mysql.conf app_mysql.conf
32  * \verbinclude app_mysql.conf.sample
33  */
34
35 /*** MODULEINFO
36         <depend>mysqlclient</depend>
37         <defaultenabled>no</defaultenabled>
38         <support_level>deprecated</support_level>
39         <replacement>func_odbc</replacement>
40  ***/
41
42 #include "asterisk.h"
43
44 #include <mysql/mysql.h>
45
46 #include "asterisk/file.h"
47 #include "asterisk/logger.h"
48 #include "asterisk/channel.h"
49 #include "asterisk/pbx.h"
50 #include "asterisk/module.h"
51 #include "asterisk/linkedlists.h"
52 #include "asterisk/chanvars.h"
53 #include "asterisk/lock.h"
54 #include "asterisk/options.h"
55 #include "asterisk/app.h"
56 #include "asterisk/config.h"
57
58 #define EXTRA_LOG 0
59
60 enum { NULLSTRING, NULLVALUE, EMPTYSTRING } nullvalue = NULLSTRING;
61
62 static const char app[] = "MYSQL";
63
64 static const char synopsis[] = "Do several mySQLy things";
65
66 static const char descrip[] =
67 "MYSQL():  Do several mySQLy things\n"
68 "Syntax:\n"
69 "  MYSQL(Set timeout <num>)\n"
70 "    Set the connection timeout, in seconds.\n"
71 "  MYSQL(Connect connid dhhost[:dbport] dbuser dbpass dbname [dbcharset])\n"
72 "    Connects to a database.  Arguments contain standard MySQL parameters\n"
73 "    passed to function mysql_real_connect.  Optional parameter dbcharset\n"
74 "    defaults to 'latin1'.  Connection identifer returned in ${connid}\n"
75 "  MYSQL(Query resultid ${connid} query-string)\n"
76 "    Executes standard MySQL query contained in query-string using established\n"
77 "    connection identified by ${connid}. Result of query is stored in ${resultid}.\n"
78 "  MYSQL(Nextresult resultid ${connid}\n"
79 "    If last query returned more than one result set, it stores the next\n"
80 "    result set in ${resultid}. It's useful with stored procedures\n"
81 "  MYSQL(Fetch fetchid ${resultid} var1 var2 ... varN)\n"
82 "    Fetches a single row from a result set contained in ${result_identifier}.\n"
83 "    Assigns returned fields to ${var1} ... ${varn}.  ${fetchid} is set TRUE\n"
84 "    if additional rows exist in result set.\n"
85 "  MYSQL(Clear ${resultid})\n"
86 "    Frees memory and datastructures associated with result set.\n"
87 "  MYSQL(Disconnect ${connid})\n"
88 "    Disconnects from named connection to MySQL.\n"
89 "  On exit, always returns 0. Sets MYSQL_STATUS to 0 on success and -1 on error.\n";
90
91 /*
92 EXAMPLES OF USE :
93
94 exten => s,2,MYSQL(Connect connid localhost asterisk mypass credit utf8)
95 exten => s,3,MYSQL(Query resultid ${connid} SELECT username,credit FROM credit WHERE callerid=${CALLERIDNUM})
96 exten => s,4,MYSQL(Fetch fetchid ${resultid} datavar1 datavar2)
97 exten => s,5,GotoIf(${fetchid}?6:8)
98 exten => s,6,Festival("User ${datavar1} currently has credit balance of ${datavar2} dollars.")
99 exten => s,7,Goto(s,4)
100 exten => s,8,MYSQL(Clear ${resultid})
101 exten => s,9,MYSQL(Disconnect ${connid})
102 */
103
104 AST_MUTEX_DEFINE_STATIC(_mysql_mutex);
105
106 #define MYSQL_CONFIG "app_mysql.conf"
107 #define MYSQL_CONFIG_OLD "mysql.conf"
108 #define AST_MYSQL_ID_DUMMY   0
109 #define AST_MYSQL_ID_CONNID  1
110 #define AST_MYSQL_ID_RESID   2
111 #define AST_MYSQL_ID_FETCHID 3
112
113 static int autoclear = 0;
114
115 static void mysql_ds_destroy(void *data);
116 static void mysql_ds_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan);
117
118 static const struct ast_datastore_info mysql_ds_info = {
119         .type = "APP_ADDON_SQL_MYSQL",
120         .destroy = mysql_ds_destroy,
121         .chan_fixup = mysql_ds_fixup,
122 };
123
124 struct ast_MYSQL_id {
125         struct ast_channel *owner;
126         int identifier_type; /* 0=dummy, 1=connid, 2=resultid */
127         int identifier;
128         void *data;
129         AST_LIST_ENTRY(ast_MYSQL_id) entries;
130 } *ast_MYSQL_id;
131
132 AST_LIST_HEAD(MYSQLidshead,ast_MYSQL_id) _mysql_ids_head;
133
134 static void mysql_ds_destroy(void *data)
135 {
136         /* Destroy any IDs owned by the channel */
137         struct ast_MYSQL_id *i;
138         if (AST_LIST_LOCK(&_mysql_ids_head)) {
139                 ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
140         } else {
141                 AST_LIST_TRAVERSE_SAFE_BEGIN(&_mysql_ids_head, i, entries) {
142                         if (i->owner == data) {
143                                 AST_LIST_REMOVE_CURRENT(entries);
144                                 if (i->identifier_type == AST_MYSQL_ID_CONNID) {
145                                         /* Drop connection */
146                                         mysql_close(i->data);
147                                 } else if (i->identifier_type == AST_MYSQL_ID_RESID) {
148                                         /* Drop result */
149                                         mysql_free_result(i->data);
150                                 }
151                                 ast_free(i);
152                         }
153                 }
154                 AST_LIST_TRAVERSE_SAFE_END
155                 AST_LIST_UNLOCK(&_mysql_ids_head);
156         }
157 }
158
159 static void mysql_ds_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan)
160 {
161         /* Destroy any IDs owned by the channel */
162         struct ast_MYSQL_id *i;
163         if (AST_LIST_LOCK(&_mysql_ids_head)) {
164                 ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
165         } else {
166                 AST_LIST_TRAVERSE_SAFE_BEGIN(&_mysql_ids_head, i, entries) {
167                         if (i->owner == data) {
168                                 AST_LIST_REMOVE_CURRENT(entries);
169                                 if (i->identifier_type == AST_MYSQL_ID_CONNID) {
170                                         /* Drop connection */
171                                         mysql_close(i->data);
172                                 } else if (i->identifier_type == AST_MYSQL_ID_RESID) {
173                                         /* Drop result */
174                                         mysql_free_result(i->data);
175                                 }
176                                 ast_free(i);
177                         }
178                 }
179                 AST_LIST_TRAVERSE_SAFE_END
180                 AST_LIST_UNLOCK(&_mysql_ids_head);
181         }
182 }
183
184 /* helpful procs */
185 static void *find_identifier(int identifier, int identifier_type)
186 {
187         struct MYSQLidshead *headp = &_mysql_ids_head;
188         struct ast_MYSQL_id *i;
189         void *res=NULL;
190         int found=0;
191
192         if (AST_LIST_LOCK(headp)) {
193                 ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
194         } else {
195                 AST_LIST_TRAVERSE(headp, i, entries) {
196                         if ((i->identifier == identifier) && (i->identifier_type == identifier_type)) {
197                                 found = 1;
198                                 res = i->data;
199                                 break;
200                         }
201                 }
202                 if (!found) {
203                         ast_log(LOG_WARNING, "Identifier %d, identifier_type %d not found in identifier list\n", identifier, identifier_type);
204                 }
205                 AST_LIST_UNLOCK(headp);
206         }
207
208         return res;
209 }
210
211 static int add_identifier(struct ast_channel *chan, int identifier_type, void *data)
212 {
213         struct ast_MYSQL_id *i = NULL, *j = NULL;
214         struct MYSQLidshead *headp = &_mysql_ids_head;
215         int maxidentifier = 0;
216
217         if (AST_LIST_LOCK(headp)) {
218                 ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
219                 return -1;
220         } else {
221                 i = malloc(sizeof(*i));
222                 AST_LIST_TRAVERSE(headp, j, entries) {
223                         if (j->identifier > maxidentifier) {
224                                 maxidentifier = j->identifier;
225                         }
226                 }
227                 i->identifier = maxidentifier + 1;
228                 i->identifier_type = identifier_type;
229                 i->data = data;
230                 i->owner = chan;
231                 AST_LIST_INSERT_HEAD(headp, i, entries);
232                 AST_LIST_UNLOCK(headp);
233         }
234         return i->identifier;
235 }
236
237 static int del_identifier(int identifier, int identifier_type)
238 {
239         struct ast_MYSQL_id *i;
240         struct MYSQLidshead *headp = &_mysql_ids_head;
241         int found = 0;
242
243         if (AST_LIST_LOCK(headp)) {
244                 ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
245         } else {
246                 AST_LIST_TRAVERSE(headp, i, entries) {
247                         if ((i->identifier == identifier) &&
248                             (i->identifier_type == identifier_type)) {
249                                 AST_LIST_REMOVE(headp, i, entries);
250                                 free(i);
251                                 found = 1;
252                                 break;
253                         }
254                 }
255                 AST_LIST_UNLOCK(headp);
256         }
257
258         if (found == 0) {
259                 ast_log(LOG_WARNING, "Could not find identifier %d, identifier_type %d in list to delete\n", identifier, identifier_type);
260                 return -1;
261         } else {
262                 return 0;
263         }
264 }
265
266 static int set_asterisk_int(struct ast_channel *chan, char *varname, int id)
267 {
268         if (id >= 0) {
269                 char s[12] = "";
270                 snprintf(s, sizeof(s), "%d", id);
271                 ast_debug(5, "MYSQL: setting var '%s' to value '%s'\n", varname, s);
272                 pbx_builtin_setvar_helper(chan, varname, s);
273         }
274         return id;
275 }
276
277 static int add_identifier_and_set_asterisk_int(struct ast_channel *chan, char *varname, int identifier_type, void *data)
278 {
279         return set_asterisk_int(chan, varname, add_identifier(chan, identifier_type, data));
280 }
281
282 static int safe_scan_int(char **data, char *delim, int def)
283 {
284         char *end;
285         int res = def;
286         char *s = strsep(data, delim);
287         if (s) {
288                 res = strtol(s, &end, 10);
289                 if (*end)
290                         res = def;  /* not an integer */
291         }
292         return res;
293 }
294
295 static int aMYSQL_set(struct ast_channel *chan, char *data)
296 {
297         char *var, *tmp;
298         AST_DECLARE_APP_ARGS(args,
299                 AST_APP_ARG(set);
300                 AST_APP_ARG(variable);
301                 AST_APP_ARG(value);
302         );
303
304         AST_NONSTANDARD_APP_ARGS(args, data, ' ');
305
306         if (args.argc == 3) {
307                 var = ast_alloca(6 + strlen(args.variable) + 1);
308                 sprintf(var, "MYSQL_%s", args.variable);
309
310                 /* Make the parameter case-insensitive */
311                 for (tmp = var + 6; *tmp; tmp++)
312                         *tmp = toupper(*tmp);
313
314                 pbx_builtin_setvar_helper(chan, var, args.value);
315         }
316         return 0;
317 }
318
319 /* MYSQL operations */
320 static int aMYSQL_connect(struct ast_channel *chan, char *data)
321 {
322         AST_DECLARE_APP_ARGS(args,
323                 AST_APP_ARG(connect);
324                 AST_APP_ARG(connid);
325                 AST_APP_ARG(dbhost);
326                 AST_APP_ARG(dbuser);
327                 AST_APP_ARG(dbpass);
328                 AST_APP_ARG(dbname);
329                 AST_APP_ARG(dbcharset);
330         );
331         MYSQL *mysql;
332         int timeout;
333         const char *ctimeout;
334         unsigned int port = 0;
335         char *port_str;
336
337         AST_NONSTANDARD_APP_ARGS(args, data, ' ');
338
339         if (args.argc < 6) {
340                 ast_log(LOG_WARNING, "MYSQL_connect is missing some arguments\n");
341                 return -1;
342         }
343
344         if (!(mysql = mysql_init(NULL))) {
345                 ast_log(LOG_WARNING, "mysql_init returned NULL\n");
346                 return -1;
347         }
348
349         ctimeout = pbx_builtin_getvar_helper(chan, "MYSQL_TIMEOUT");
350         if (ctimeout && sscanf(ctimeout, "%30d", &timeout) == 1) {
351                 mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&timeout);
352         }
353         if(args.dbcharset && strlen(args.dbcharset) > 2){
354                 char set_names[255];
355                 char statement[512];
356                 snprintf(set_names, sizeof(set_names), "SET NAMES %s", args.dbcharset);
357                 mysql_real_escape_string(mysql, statement, set_names, sizeof(set_names));
358                 mysql_options(mysql, MYSQL_INIT_COMMAND, set_names);
359                 mysql_options(mysql, MYSQL_SET_CHARSET_NAME, args.dbcharset);
360         }
361
362         if ((port_str = strchr(args.dbhost, ':'))) {
363                 *port_str++ = '\0';
364                 if (sscanf(port_str, "%u", &port) != 1) {
365                         ast_log(LOG_WARNING, "Invalid port: '%s'\n", port_str);
366                         port = 0;
367                 }
368         }
369
370         if (!mysql_real_connect(mysql, args.dbhost, args.dbuser, args.dbpass, args.dbname, port, NULL,
371 #ifdef CLIENT_MULTI_STATEMENTS
372                         CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS
373 #elif defined(CLIENT_MULTI_QUERIES)
374                         CLIENT_MULTI_QUERIES
375 #else
376                         0
377 #endif
378                 )) {
379                 ast_log(LOG_WARNING, "mysql_real_connect(mysql,%s,%s,dbpass,%s,...) failed(%d): %s\n",
380                                 args.dbhost, args.dbuser, args.dbname, mysql_errno(mysql), mysql_error(mysql));
381                 return -1;
382         }
383
384         add_identifier_and_set_asterisk_int(chan, args.connid, AST_MYSQL_ID_CONNID, mysql);
385         return 0;
386 }
387
388 static int aMYSQL_query(struct ast_channel *chan, char *data)
389 {
390         AST_DECLARE_APP_ARGS(args,
391                 AST_APP_ARG(query);
392                 AST_APP_ARG(resultid);
393                 AST_APP_ARG(connid);
394                 AST_APP_ARG(sql);
395         );
396         MYSQL       *mysql;
397         MYSQL_RES   *mysqlres;
398         int connid;
399         int mysql_query_res;
400
401         AST_NONSTANDARD_APP_ARGS(args, data, ' ');
402
403         if (args.argc != 4 || (connid = atoi(args.connid)) == 0) {
404                 ast_log(LOG_WARNING, "missing some arguments\n");
405                 return -1;
406         }
407
408         if (!(mysql = find_identifier(connid, AST_MYSQL_ID_CONNID))) {
409                 ast_log(LOG_WARNING, "Invalid connection identifier %s passed in aMYSQL_query\n", args.connid);
410                 return -1;
411         }
412
413         if ((mysql_query_res = mysql_query(mysql, args.sql)) != 0) {
414                 ast_log(LOG_WARNING, "aMYSQL_query: mysql_query failed. Error: %s\n", mysql_error(mysql));
415                 return -1;
416         }
417
418         if ((mysqlres = mysql_store_result(mysql))) {
419                 add_identifier_and_set_asterisk_int(chan, args.resultid, AST_MYSQL_ID_RESID, mysqlres);
420                 return 0;
421         } else if (!mysql_field_count(mysql)) {
422                 return 0;
423         } else
424                 ast_log(LOG_WARNING, "mysql_store_result() failed on query %s\n", args.sql);
425
426         return -1;
427 }
428
429 static int aMYSQL_nextresult(struct ast_channel *chan, char *data)
430 {
431         MYSQL       *mysql;
432         MYSQL_RES   *mysqlres;
433         AST_DECLARE_APP_ARGS(args,
434                 AST_APP_ARG(nextresult);
435                 AST_APP_ARG(resultid);
436                 AST_APP_ARG(connid);
437         );
438         int connid = -1;
439
440         AST_NONSTANDARD_APP_ARGS(args, data, ' ');
441         sscanf(args.connid, "%30d", &connid);
442
443         if (args.argc != 3 || connid <= 0) {
444                 ast_log(LOG_WARNING, "missing some arguments\n");
445                 return -1;
446         }
447
448         if (!(mysql = find_identifier(connid, AST_MYSQL_ID_CONNID))) {
449                 ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aMYSQL_query\n", connid);
450                 return -1;
451         }
452
453         if (mysql_more_results(mysql)) {
454                 mysql_next_result(mysql);
455                 if ((mysqlres = mysql_store_result(mysql))) {
456                         add_identifier_and_set_asterisk_int(chan, args.resultid, AST_MYSQL_ID_RESID, mysqlres);
457                         return 0;
458                 } else if (!mysql_field_count(mysql)) {
459                         return 0;
460                 } else
461                         ast_log(LOG_WARNING, "mysql_store_result() failed on storing next_result\n");
462         } else
463                 ast_log(LOG_WARNING, "mysql_more_results() result set has no more results\n");
464
465         return 0;
466 }
467
468
469 static int aMYSQL_fetch(struct ast_channel *chan, char *data)
470 {
471         MYSQL_RES *mysqlres;
472         MYSQL_ROW mysqlrow;
473         AST_DECLARE_APP_ARGS(args,
474                 AST_APP_ARG(fetch);
475                 AST_APP_ARG(resultvar);
476                 AST_APP_ARG(fetchid);
477                 AST_APP_ARG(vars);
478         );
479         char *s5, *parse;
480         int resultid = -1, numFields, j;
481
482         parse = ast_strdupa(data);
483         AST_NONSTANDARD_APP_ARGS(args, parse, ' ');
484         sscanf(args.fetchid, "%30d", &resultid);
485
486         if (args.resultvar && (resultid >= 0) ) {
487                 if ((mysqlres = find_identifier(resultid, AST_MYSQL_ID_RESID)) != NULL) {
488                         /* Grab the next row */
489                         if ((mysqlrow = mysql_fetch_row(mysqlres)) != NULL) {
490                                 numFields = mysql_num_fields(mysqlres);
491                                 for (j = 0; j < numFields; j++) {
492                                         s5 = strsep(&args.vars, " ");
493                                         if (s5 == NULL) {
494                                                 ast_log(LOG_WARNING, "ast_MYSQL_fetch: More fields (%d) than variables (%d)\n", numFields, j);
495                                                 break;
496                                         }
497
498                                         pbx_builtin_setvar_helper(chan, s5, mysqlrow[j] ? mysqlrow[j] :
499                                                 nullvalue == NULLSTRING ? "NULL" :
500                                                 nullvalue == EMPTYSTRING ? "" :
501                                                 NULL);
502                                 }
503                                 ast_debug(5, "ast_MYSQL_fetch: numFields=%d\n", numFields);
504                                 set_asterisk_int(chan, args.resultvar, 1); /* try more rows */
505                         } else {
506                                 ast_debug(5, "ast_MYSQL_fetch : EOF\n");
507                                 set_asterisk_int(chan, args.resultvar, 0); /* no more rows */
508                         }
509                         return 0;
510                 } else {
511                         set_asterisk_int(chan, args.resultvar, 0);
512                         ast_log(LOG_WARNING, "aMYSQL_fetch: Invalid result identifier %d passed\n", resultid);
513                 }
514         } else {
515                 ast_log(LOG_WARNING, "aMYSQL_fetch: missing some arguments\n");
516         }
517
518         return -1;
519 }
520
521 static int aMYSQL_clear(struct ast_channel *chan, char *data)
522 {
523         MYSQL_RES *mysqlres;
524
525         int id;
526         strsep(&data, " "); /* eat the first token, we already know it :P */
527         id = safe_scan_int(&data, " \n", -1);
528         if ((mysqlres = find_identifier(id, AST_MYSQL_ID_RESID)) == NULL) {
529                 ast_log(LOG_WARNING, "Invalid result identifier %d passed in aMYSQL_clear\n", id);
530         } else {
531                 mysql_free_result(mysqlres);
532                 del_identifier(id, AST_MYSQL_ID_RESID);
533         }
534
535         return 0;
536 }
537
538 static int aMYSQL_disconnect(struct ast_channel *chan, char *data)
539 {
540         MYSQL *mysql;
541         int id;
542         strsep(&data, " "); /* eat the first token, we already know it :P */
543
544         id = safe_scan_int(&data, " \n", -1);
545         if ((mysql = find_identifier(id, AST_MYSQL_ID_CONNID)) == NULL) {
546                 ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aMYSQL_disconnect\n", id);
547         } else {
548                 mysql_close(mysql);
549                 del_identifier(id, AST_MYSQL_ID_CONNID);
550         }
551
552         return 0;
553 }
554
555 static int MYSQL_exec(struct ast_channel *chan, const char *data)
556 {
557         int result;
558         char sresult[10];
559
560         ast_debug(5, "MYSQL: data=%s\n", data);
561
562         if (!data) {
563                 ast_log(LOG_WARNING, "MYSQL requires an argument (see manual)\n");
564                 return -1;
565         }
566
567         result = 0;
568
569         if (autoclear) {
570                 struct ast_datastore *mysql_store = NULL;
571
572                 ast_channel_lock(chan);
573                 mysql_store = ast_channel_datastore_find(chan, &mysql_ds_info, NULL);
574                 if (!mysql_store) {
575                         if (!(mysql_store = ast_datastore_alloc(&mysql_ds_info, NULL))) {
576                                 ast_log(LOG_WARNING, "Unable to allocate new datastore.\n");
577                         } else {
578                                 mysql_store->data = chan;
579                                 ast_channel_datastore_add(chan, mysql_store);
580                         }
581                 }
582                 ast_channel_unlock(chan);
583         }
584         ast_mutex_lock(&_mysql_mutex);
585
586         if (strncasecmp("connect", data, strlen("connect")) == 0) {
587                 result = aMYSQL_connect(chan, ast_strdupa(data));
588         } else if (strncasecmp("query", data, strlen("query")) == 0) {
589                 result = aMYSQL_query(chan, ast_strdupa(data));
590         } else if (strncasecmp("nextresult", data, strlen("nextresult")) == 0) {
591                 result = aMYSQL_nextresult(chan, ast_strdupa(data));
592         } else if (strncasecmp("fetch", data, strlen("fetch")) == 0) {
593                 result = aMYSQL_fetch(chan, ast_strdupa(data));
594         } else if (strncasecmp("clear", data, strlen("clear")) == 0) {
595                 result = aMYSQL_clear(chan, ast_strdupa(data));
596         } else if (strncasecmp("disconnect", data, strlen("disconnect")) == 0) {
597                 result = aMYSQL_disconnect(chan, ast_strdupa(data));
598         } else if (strncasecmp("set", data, 3) == 0) {
599                 result = aMYSQL_set(chan, ast_strdupa(data));
600         } else {
601                 ast_log(LOG_WARNING, "Unknown argument to MYSQL application : %s\n", data);
602                 result = -1;
603         }
604
605         ast_mutex_unlock(&_mysql_mutex);
606
607         snprintf(sresult, sizeof(sresult), "%d", result);
608         pbx_builtin_setvar_helper(chan, "MYSQL_STATUS", sresult);
609         return 0;
610 }
611
612 static int unload_module(void)
613 {
614         return ast_unregister_application(app);
615 }
616
617 /*!
618  * \brief Load the module
619  *
620  * Module loading including tests for configuration or dependencies.
621  * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
622  * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
623  * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the 
624  * configuration file or other non-critical problem return 
625  * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
626  */
627 static int load_module(void)
628 {
629         struct MYSQLidshead *headp = &_mysql_ids_head;
630         struct ast_flags config_flags = { 0 };
631         struct ast_config *cfg = ast_config_load(MYSQL_CONFIG, config_flags);
632         const char *temp;
633
634         if (!cfg) {
635                 /* Backwards compatibility ftw */
636                 cfg = ast_config_load(MYSQL_CONFIG_OLD, config_flags);
637         }
638
639         if (cfg) {
640                 if ((temp = ast_variable_retrieve(cfg, "general", "nullvalue"))) {
641                         if (!strcasecmp(temp, "nullstring")) {
642                                 nullvalue = NULLSTRING;
643                         } else if (!strcasecmp(temp, "emptystring")) {
644                                 nullvalue = EMPTYSTRING;
645                         } else if (!strcasecmp(temp, "null")) {
646                                 nullvalue = NULLVALUE;
647                         } else {
648                                 ast_log(LOG_WARNING, "Illegal value for 'nullvalue': '%s' (must be 'nullstring', 'null', or 'emptystring')\n", temp);
649                         }
650                 }
651                 if ((temp = ast_variable_retrieve(cfg, "general", "autoclear")) && ast_true(temp)) {
652                         autoclear = 1;
653                 }
654                 ast_config_destroy(cfg);
655         }
656
657         AST_LIST_HEAD_INIT(headp);
658         return ast_register_application(app, MYSQL_exec, synopsis, descrip);
659 }
660
661 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Mysql Interface");