Cache config files, when possible, for speed
[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  *
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 /*! \file
20  *
21  * \brief A function to retrieve variables from an Asterisk configuration file
22  *
23  * \author Russell Bryant <russell@digium.com>
24  * 
25  * \ingroup functions
26  */
27
28 #include "asterisk.h"
29
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
31
32 #include "asterisk/module.h"
33 #include "asterisk/channel.h"
34 #include "asterisk/pbx.h"
35 #include "asterisk/app.h"
36
37 struct config_item {
38         AST_RWLIST_ENTRY(config_item) entry;
39         struct ast_config *cfg;
40         char filename[0];
41 };
42
43 static AST_RWLIST_HEAD_STATIC(configs, config_item);
44
45 static int config_function_read(struct ast_channel *chan, const char *cmd, char *data, 
46         char *buf, size_t len) 
47 {
48         struct ast_config *cfg;
49         struct ast_flags cfg_flags = { CONFIG_FLAG_FILEUNCHANGED };
50         const char *val;
51         char *parse;
52         struct config_item *cur;
53         AST_DECLARE_APP_ARGS(args,
54                 AST_APP_ARG(filename);
55                 AST_APP_ARG(category);
56                 AST_APP_ARG(variable);
57                 AST_APP_ARG(index);
58         );
59
60         if (ast_strlen_zero(data)) {
61                 ast_log(LOG_ERROR, "AST_CONFIG() requires an argument\n");
62                 return -1;
63         }
64
65         parse = ast_strdupa(data);
66         AST_STANDARD_APP_ARGS(args, parse);
67
68         if (ast_strlen_zero(args.filename)) {
69                 ast_log(LOG_ERROR, "AST_CONFIG() requires a filename\n");
70                 return -1;
71         }
72
73         if (ast_strlen_zero(args.category)) {
74                 ast_log(LOG_ERROR, "AST_CONFIG() requires a category\n");
75                 return -1;
76         }
77         
78         if (ast_strlen_zero(args.variable)) {
79                 ast_log(LOG_ERROR, "AST_CONFIG() requires a variable\n");
80                 return -1;
81         }
82
83         if (!(cfg = ast_config_load(args.filename, cfg_flags))) {
84                 return -1;
85         }
86
87         if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
88                 /* Retrieve cfg from list */
89                 AST_RWLIST_RDLOCK(&configs);
90                 AST_RWLIST_TRAVERSE(&configs, cur, entry) {
91                         if (!strcmp(cur->filename, args.filename)) {
92                                 break;
93                         }
94                 }
95
96                 if (!cur) {
97                         /* At worst, we might leak an entry while upgrading locks */
98                         AST_RWLIST_UNLOCK(&configs);
99                         AST_RWLIST_WRLOCK(&configs);
100                         if (!(cur = ast_malloc(sizeof(*cur) + strlen(args.filename) + 1))) {
101                                 AST_RWLIST_UNLOCK(&configs);
102                                 return -1;
103                         }
104
105                         strcpy(cur->filename, args.filename);
106
107                         ast_clear_flag(&cfg_flags, CONFIG_FLAG_FILEUNCHANGED);
108                         if (!(cfg = ast_config_load(args.filename, cfg_flags))) {
109                                 ast_free(cur);
110                                 AST_RWLIST_UNLOCK(&configs);
111                                 return -1;
112                         }
113
114                         cur->cfg = cfg;
115                         AST_RWLIST_INSERT_TAIL(&configs, cur, entry);
116                 }
117
118                 cfg = cur->cfg;
119         } else {
120                 /* Replace cfg in list */
121                 AST_RWLIST_WRLOCK(&configs);
122                 AST_RWLIST_TRAVERSE(&configs, cur, entry) {
123                         if (!strcmp(cur->filename, args.filename)) {
124                                 break;
125                         }
126                 }
127
128                 if (!cur) {
129                         if (!(cur = ast_malloc(sizeof(*cur) + strlen(args.filename) + 1))) {
130                                 AST_RWLIST_UNLOCK(&configs);
131                                 return -1;
132                         }
133
134                         strcpy(cur->filename, args.filename);
135                         cur->cfg = cfg;
136
137                         AST_RWLIST_INSERT_TAIL(&configs, cur, entry);
138                 } else {
139                         ast_config_destroy(cur->cfg);
140                         cur->cfg = cfg;
141                 }
142         }
143
144         if (!(val = ast_variable_retrieve(cfg, args.category, args.variable))) {
145                 ast_log(LOG_ERROR, "'%s' not found in [%s] of '%s'\n", args.variable, 
146                         args.category, args.filename);
147                 return -1;
148         }
149
150         ast_copy_string(buf, val, len);
151
152         /* Unlock down here, so there's no chance the struct goes away while we're using it. */
153         AST_RWLIST_UNLOCK(&configs);
154
155         return 0;
156 }
157
158 static struct ast_custom_function config_function = {
159         .name = "AST_CONFIG",
160         .syntax = "AST_CONFIG(config_file,category,variable_name)",
161         .synopsis = "Retrieve a variable from a configuration file",
162         .desc = 
163         "   This function reads a variable from an Asterisk configuration file.\n"
164         "",
165         .read = config_function_read,
166 };
167
168 static int unload_module(void)
169 {
170         struct config_item *cur;
171         int res = ast_custom_function_unregister(&config_function);
172
173         /* Allow anything already in the routine to exit */
174         usleep(1);
175         AST_RWLIST_WRLOCK(&configs);
176         usleep(1);
177         AST_RWLIST_UNLOCK(&configs);
178         /* Even if it needed to upgrade a lock */
179         usleep(1);
180         AST_RWLIST_WRLOCK(&configs);
181         /* At this point, no other thread should be queued inside this module */
182         while ((cur = AST_RWLIST_REMOVE_HEAD(&configs, entry))) {
183                 ast_config_destroy(cur->cfg);
184                 ast_free(cur);
185         }
186         AST_RWLIST_UNLOCK(&configs);
187         return res;
188 }
189
190 static int load_module(void)
191 {
192         return ast_custom_function_register(&config_function);
193 }
194
195 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Asterisk configuration file variable access");