ca6dad1a198dfc414d585a5c7299e401adde5958
[asterisk/asterisk.git] / funcs / func_config.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2008, Digium, Inc.
5  *
6  * Russell Bryant <russell@digium.com>
7  * Tilghman Lesher <func_config__200803@the-tilghman.com>
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 /*! \file
21  *
22  * \brief A function to retrieve variables from an Asterisk configuration file
23  *
24  * \author Russell Bryant <russell@digium.com>
25  * \author Tilghman Lesher <func_config__200803@the-tilghman.com>
26  * 
27  * \ingroup functions
28  */
29
30 /*** MODULEINFO
31         <support_level>core</support_level>
32  ***/
33
34 #include "asterisk.h"
35
36 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
37
38 #include "asterisk/module.h"
39 #include "asterisk/channel.h"
40 #include "asterisk/pbx.h"
41 #include "asterisk/app.h"
42
43 /*** DOCUMENTATION
44         <function name="AST_CONFIG" language="en_US">
45                 <synopsis>
46                         Retrieve a variable from a configuration file.
47                 </synopsis>
48                 <syntax>
49                         <parameter name="config_file" required="true" />
50                         <parameter name="category" required="true" />
51                         <parameter name="variable_name" required="true" />
52                         <parameter name="index" required="false">
53                                 <para>If there are multiple variables with the same name, you can specify
54                                 <literal>0</literal> for the first item (default), <literal>-1</literal> for the last
55                                 item, or any other number for that specific item.  <literal>-1</literal> is useful
56                                 when the variable is derived from a template and you want the effective value (the last
57                                 occurrence), not the value from the template (the first occurrence).</para>
58                         </parameter>
59                 </syntax>
60                 <description>
61                         <para>This function reads a variable from an Asterisk configuration file.</para>
62                 </description>
63         </function>
64
65 ***/
66
67 struct config_item {
68         AST_RWLIST_ENTRY(config_item) entry;
69         struct ast_config *cfg;
70         char filename[0];
71 };
72
73 static AST_RWLIST_HEAD_STATIC(configs, config_item);
74
75 static int config_function_read(struct ast_channel *chan, const char *cmd, char *data,
76         char *buf, size_t len)
77 {
78         struct ast_config *cfg;
79         struct ast_flags cfg_flags = { CONFIG_FLAG_FILEUNCHANGED };
80         char *parse;
81         struct config_item *cur;
82         int index = 0;
83         struct ast_variable *var;
84         struct ast_variable *found = NULL;
85         int ix = 0;
86         AST_DECLARE_APP_ARGS(args,
87                 AST_APP_ARG(filename);
88                 AST_APP_ARG(category);
89                 AST_APP_ARG(variable);
90                 AST_APP_ARG(index);
91         );
92
93         if (ast_strlen_zero(data)) {
94                 ast_log(LOG_ERROR, "AST_CONFIG() requires an argument\n");
95                 return -1;
96         }
97
98         parse = ast_strdupa(data);
99         AST_STANDARD_APP_ARGS(args, parse);
100
101         if (ast_strlen_zero(args.filename)) {
102                 ast_log(LOG_ERROR, "AST_CONFIG() requires a filename\n");
103                 return -1;
104         }
105
106         if (ast_strlen_zero(args.category)) {
107                 ast_log(LOG_ERROR, "AST_CONFIG() requires a category\n");
108                 return -1;
109         }
110         
111         if (ast_strlen_zero(args.variable)) {
112                 ast_log(LOG_ERROR, "AST_CONFIG() requires a variable\n");
113                 return -1;
114         }
115
116         if (!ast_strlen_zero(args.index)) {
117                 if (!sscanf(args.index, "%d", &index)) {
118                         ast_log(LOG_ERROR, "AST_CONFIG() index must be an integer\n");
119                         return -1;
120                 }
121         }
122
123         if (!(cfg = ast_config_load(args.filename, cfg_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
124                 return -1;
125         }
126
127         if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
128                 /* Retrieve cfg from list */
129                 AST_RWLIST_RDLOCK(&configs);
130                 AST_RWLIST_TRAVERSE(&configs, cur, entry) {
131                         if (!strcmp(cur->filename, args.filename)) {
132                                 break;
133                         }
134                 }
135
136                 if (!cur) {
137                         /* At worst, we might leak an entry while upgrading locks */
138                         AST_RWLIST_UNLOCK(&configs);
139                         AST_RWLIST_WRLOCK(&configs);
140                         if (!(cur = ast_calloc(1, sizeof(*cur) + strlen(args.filename) + 1))) {
141                                 AST_RWLIST_UNLOCK(&configs);
142                                 return -1;
143                         }
144
145                         strcpy(cur->filename, args.filename);
146
147                         ast_clear_flag(&cfg_flags, CONFIG_FLAG_FILEUNCHANGED);
148                         if (!(cfg = ast_config_load(args.filename, cfg_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
149                                 ast_free(cur);
150                                 AST_RWLIST_UNLOCK(&configs);
151                                 return -1;
152                         }
153
154                         cur->cfg = cfg;
155                         AST_RWLIST_INSERT_TAIL(&configs, cur, entry);
156                 }
157
158                 cfg = cur->cfg;
159         } else {
160                 /* Replace cfg in list */
161                 AST_RWLIST_WRLOCK(&configs);
162                 AST_RWLIST_TRAVERSE(&configs, cur, entry) {
163                         if (!strcmp(cur->filename, args.filename)) {
164                                 break;
165                         }
166                 }
167
168                 if (!cur) {
169                         if (!(cur = ast_calloc(1, sizeof(*cur) + strlen(args.filename) + 1))) {
170                                 AST_RWLIST_UNLOCK(&configs);
171                                 return -1;
172                         }
173
174                         strcpy(cur->filename, args.filename);
175                         cur->cfg = cfg;
176
177                         AST_RWLIST_INSERT_TAIL(&configs, cur, entry);
178                 } else {
179                         ast_config_destroy(cur->cfg);
180                         cur->cfg = cfg;
181                 }
182         }
183
184         for (var = ast_category_root(cfg, args.category); var; var = var->next) {
185                 if (strcasecmp(args.variable, var->name)) {
186                         continue;
187                 }
188                 found = var;
189                 if (index == -1) {
190                         continue;
191                 }
192                 if (ix == index) {
193                         break;
194                 }
195                 found = NULL;
196                 ix++;
197         }
198
199         if (!found) {
200                 ast_debug(1, "'%s' not found at index %d in [%s] of '%s'.  Maximum index found: %d\n",
201                         args.variable, index, args.category, args.filename, ix);
202                 AST_RWLIST_UNLOCK(&configs);
203                 return -1;
204         }
205
206         ast_copy_string(buf, found->value, len);
207
208         /* Unlock down here, so there's no chance the struct goes away while we're using it. */
209         AST_RWLIST_UNLOCK(&configs);
210
211         return 0;
212 }
213
214 static struct ast_custom_function config_function = {
215         .name = "AST_CONFIG",
216         .read = config_function_read,
217 };
218
219 static int unload_module(void)
220 {
221         struct config_item *current;
222         int res = ast_custom_function_unregister(&config_function);
223
224         AST_RWLIST_WRLOCK(&configs);
225         while ((current = AST_RWLIST_REMOVE_HEAD(&configs, entry))) {
226                 ast_config_destroy(current->cfg);
227                 ast_free(current);
228         }
229         AST_RWLIST_UNLOCK(&configs);
230
231         return res;
232 }
233
234 static int load_module(void)
235 {
236         return ast_custom_function_register(&config_function);
237 }
238
239 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Asterisk configuration file variable access");