Make sorcery modules global, since they are required by other modules that are global.
[asterisk/asterisk.git] / res / res_sorcery_config.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2012 - 2013, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@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 /*!
20  * \file
21  *
22  * \brief Sorcery Configuration File Object Wizard
23  *
24  * \author Joshua Colp <jcolp@digium.com>
25  */
26
27 /*** MODULEINFO
28         <support_level>core</support_level>
29  ***/
30
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include "asterisk/module.h"
36 #include "asterisk/sorcery.h"
37 #include "asterisk/astobj2.h"
38 #include "asterisk/config.h"
39
40 /*! \brief Default number of buckets for sorcery objects */
41 #define DEFAULT_OBJECT_BUCKETS 53
42
43 /*! \brief Structure for storing configuration file sourced objects */
44 struct sorcery_config {
45         /*! \brief Objects retrieved from the configuration file */
46         struct ao2_global_obj objects;
47
48         /*! \brief Any specific variable criteria for considering a defined category for this object */
49         struct ast_variable *criteria;
50
51         /*! \brief Number of buckets to use for objects */
52         unsigned int buckets;
53
54         /*! \brief Enable file level integrity instead of object level */
55         unsigned int file_integrity:1;
56
57         /*! \brief Filename of the configuration file */
58         char filename[];
59 };
60
61 /*! \brief Structure used for fields comparison */
62 struct sorcery_config_fields_cmp_params {
63         /*! \brief Pointer to the sorcery structure */
64         const struct ast_sorcery *sorcery;
65
66         /*! \brief Pointer to the fields to check */
67         const struct ast_variable *fields;
68
69         /*! \brief Optional container to put object into */
70         struct ao2_container *container;
71 };
72
73 static void *sorcery_config_open(const char *data);
74 static void sorcery_config_load(void *data, const struct ast_sorcery *sorcery, const char *type);
75 static void sorcery_config_reload(void *data, const struct ast_sorcery *sorcery, const char *type);
76 static void *sorcery_config_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id);
77 static void *sorcery_config_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type, const struct ast_variable *fields);
78 static void sorcery_config_retrieve_multiple(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects,
79                                              const struct ast_variable *fields);
80 static void sorcery_config_close(void *data);
81
82 static struct ast_sorcery_wizard config_object_wizard = {
83         .name = "config",
84         .open = sorcery_config_open,
85         .load = sorcery_config_load,
86         .reload = sorcery_config_reload,
87         .retrieve_id = sorcery_config_retrieve_id,
88         .retrieve_fields = sorcery_config_retrieve_fields,
89         .retrieve_multiple = sorcery_config_retrieve_multiple,
90         .close = sorcery_config_close,
91 };
92
93 /*! \brief Destructor function for sorcery config */
94 static void sorcery_config_destructor(void *obj)
95 {
96         struct sorcery_config *config = obj;
97
98         ao2_global_obj_release(config->objects);
99         ast_rwlock_destroy(&config->objects.lock);
100         ast_variables_destroy(config->criteria);
101 }
102
103 /*! \brief Hashing function for sorcery objects */
104 static int sorcery_config_hash(const void *obj, const int flags)
105 {
106         const char *id = obj;
107
108         return ast_str_hash(flags & OBJ_KEY ? id : ast_sorcery_object_get_id(obj));
109 }
110
111 /*! \brief Comparator function for sorcery objects */
112 static int sorcery_config_cmp(void *obj, void *arg, int flags)
113 {
114         const char *id = arg;
115
116         return !strcmp(ast_sorcery_object_get_id(obj), flags & OBJ_KEY ? id : ast_sorcery_object_get_id(arg)) ? CMP_MATCH | CMP_STOP : 0;
117 }
118
119 static int sorcery_config_fields_cmp(void *obj, void *arg, int flags)
120 {
121         const struct sorcery_config_fields_cmp_params *params = arg;
122         RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy);
123         RAII_VAR(struct ast_variable *, diff, NULL, ast_variables_destroy);
124
125         /* If we can't turn the object into an object set OR if differences exist between the fields
126          * passed in and what are present on the object they are not a match.
127          */
128         if (params->fields &&
129             (!(objset = ast_sorcery_objectset_create(params->sorcery, obj)) ||
130              (ast_sorcery_changeset_create(objset, params->fields, &diff)) ||
131              diff)) {
132                 return 0;
133         }
134
135         if (params->container) {
136                 ao2_link(params->container, obj);
137
138                 /* As multiple objects are being returned keep going */
139                 return 0;
140         } else {
141                 /* Immediately stop and return, we only want a single object */
142                 return CMP_MATCH | CMP_STOP;
143         }
144 }
145
146 static void *sorcery_config_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type, const struct ast_variable *fields)
147 {
148         struct sorcery_config *config = data;
149         RAII_VAR(struct ao2_container *, objects, ao2_global_obj_ref(config->objects), ao2_cleanup);
150         struct sorcery_config_fields_cmp_params params = {
151                 .sorcery = sorcery,
152                 .fields = fields,
153                 .container = NULL,
154         };
155
156         /* If no fields are present return nothing, we require *something*, same goes if no objects exist yet */
157         if (!objects || !fields) {
158                 return NULL;
159         }
160
161         return ao2_callback(objects, 0, sorcery_config_fields_cmp, &params);
162 }
163
164 static void *sorcery_config_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id)
165 {
166         struct sorcery_config *config = data;
167         RAII_VAR(struct ao2_container *, objects, ao2_global_obj_ref(config->objects), ao2_cleanup);
168
169         return objects ? ao2_find(objects, id, OBJ_KEY | OBJ_NOLOCK) : NULL;
170 }
171
172 static void sorcery_config_retrieve_multiple(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const struct ast_variable *fields)
173 {
174         struct sorcery_config *config = data;
175         RAII_VAR(struct ao2_container *, config_objects, ao2_global_obj_ref(config->objects), ao2_cleanup);
176         struct sorcery_config_fields_cmp_params params = {
177                 .sorcery = sorcery,
178                 .fields = fields,
179                 .container = objects,
180         };
181
182         if (!config_objects) {
183                 return;
184         }
185
186         ao2_callback(config_objects, 0, sorcery_config_fields_cmp, &params);
187 }
188
189 /*! \brief Internal function which determines if criteria has been met for considering an object set applicable */
190 static int sorcery_is_criteria_met(struct ast_variable *objset, struct ast_variable *criteria)
191 {
192         RAII_VAR(struct ast_variable *, diff, NULL, ast_variables_destroy);
193
194         return (!criteria || (!ast_sorcery_changeset_create(objset, criteria, &diff) && !diff)) ? 1 : 0;
195 }
196
197 static void sorcery_config_internal_load(void *data, const struct ast_sorcery *sorcery, const char *type, unsigned int reload)
198 {
199         struct sorcery_config *config = data;
200         struct ast_flags flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
201         struct ast_config *cfg = ast_config_load2(config->filename, "res_sorcery_config", flags);
202         RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup);
203         const char *id = NULL;
204
205         if (!cfg) {
206                 ast_log(LOG_ERROR, "Unable to load config file '%s'\n", config->filename);
207                 return;
208         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
209                 ast_debug(1, "Config file '%s' was unchanged\n", config->filename);
210                 return;
211         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
212                 ast_log(LOG_ERROR, "Contents of config file '%s' are invalid and cannot be parsed\n", config->filename);
213                 return;
214         }
215
216         if (!(objects = ao2_container_alloc(config->buckets, sorcery_config_hash, sorcery_config_cmp))) {
217                 ast_log(LOG_ERROR, "Could not create bucket for new objects from '%s', keeping existing objects\n",
218                         config->filename);
219                 ast_config_destroy(cfg);
220                 return;
221         }
222
223         while ((id = ast_category_browse(cfg, id))) {
224                 RAII_VAR(void *, obj, NULL, ao2_cleanup);
225
226                 /* If given criteria has not been met skip the category, it is not applicable */
227                 if (!sorcery_is_criteria_met(ast_variable_browse(cfg, id), config->criteria)) {
228                         continue;
229                 }
230
231                 if (!(obj = ast_sorcery_alloc(sorcery, type, id)) ||
232                     ast_sorcery_objectset_apply(sorcery, obj, ast_variable_browse(cfg, id))) {
233                         ast_debug(1, "Could not create an object of type '%s' with id '%s' from configuration file '%s'\n",
234                                   type, id, config->filename);
235
236                         if (config->file_integrity) {
237                                 ast_log(LOG_ERROR, "Config file '%s' could not be loaded due to error with object '%s' of type '%s'\n",
238                                         config->filename, id, type);
239                                 ast_config_destroy(cfg);
240                                 return;
241                         }
242
243                         ao2_cleanup(obj);
244
245                         /* To ensure we don't lose the object that already exists we retrieve it from the old objects container and add it to the new one */
246                         if (!(obj = sorcery_config_retrieve_id(sorcery, data, type, id))) {
247                                 continue;
248                         }
249                 }
250
251                 ao2_link_flags(objects, obj, OBJ_NOLOCK);
252         }
253
254         ao2_global_obj_replace_unref(config->objects, objects);
255         ast_config_destroy(cfg);
256 }
257
258 static void sorcery_config_load(void *data, const struct ast_sorcery *sorcery, const char *type)
259 {
260         sorcery_config_internal_load(data, sorcery, type, 0);
261 }
262
263 static void sorcery_config_reload(void *data, const struct ast_sorcery *sorcery, const char *type)
264 {
265         sorcery_config_internal_load(data, sorcery, type, 1);
266 }
267
268 static void *sorcery_config_open(const char *data)
269 {
270         char *tmp = ast_strdupa(data), *filename = strsep(&tmp, ","), *option;
271         struct sorcery_config *config;
272
273         if (ast_strlen_zero(filename) || !(config = ao2_alloc_options(sizeof(*config) + strlen(filename) + 1, sorcery_config_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
274                 return NULL;
275         }
276
277         ast_rwlock_init(&config->objects.lock);
278         config->buckets = DEFAULT_OBJECT_BUCKETS;
279         strcpy(config->filename, filename);
280
281         while ((option = strsep(&tmp, ","))) {
282                 char *name = strsep(&option, "="), *value = option;
283
284                 if (!strcasecmp(name, "buckets")) {
285                         if (sscanf(value, "%30d", &config->buckets) != 1) {
286                                 ast_log(LOG_ERROR, "Unsupported bucket size of '%s' used for configuration file '%s', defaulting to '%d'\n",
287                                         value, filename, DEFAULT_OBJECT_BUCKETS);
288                         }
289                 } else if (!strcasecmp(name, "integrity")) {
290                         if (!strcasecmp(value, "file")) {
291                                 config->file_integrity = 1;
292                         } else if (!strcasecmp(value, "object")) {
293                                 config->file_integrity = 0;
294                         } else {
295                                 ast_log(LOG_ERROR, "Unsupported integrity value of '%s' used for configuration file '%s', defaulting to 'object'\n",
296                                         value, filename);
297                         }
298                 } else if (!strcasecmp(name, "criteria")) {
299                         char *field = strsep(&value, "=");
300                         struct ast_variable *criteria = ast_variable_new(field, value, "");
301
302                         if (criteria) {
303                                 criteria->next = config->criteria;
304                                 config->criteria = criteria;
305                         } else {
306                                 /* This is fatal since not following criteria would potentially yield invalid objects */
307                                 ast_log(LOG_ERROR, "Could not create criteria entry of field '%s' with value '%s' for configuration file '%s'\n",
308                                         field, value, filename);
309                                 ao2_ref(config, -1);
310                                 return NULL;
311                         }
312                 } else {
313                         ast_log(LOG_ERROR, "Unsupported option '%s' used for configuration file '%s'\n", name, filename);
314                 }
315         }
316
317         return config;
318 }
319
320 static void sorcery_config_close(void *data)
321 {
322         struct sorcery_config *config = data;
323
324         ao2_ref(config, -1);
325 }
326
327 static int load_module(void)
328 {
329         if (ast_sorcery_wizard_register(&config_object_wizard)) {
330                 return AST_MODULE_LOAD_DECLINE;
331         }
332
333         return AST_MODULE_LOAD_SUCCESS;
334 }
335
336 static int unload_module(void)
337 {
338         ast_sorcery_wizard_unregister(&config_object_wizard);
339         return 0;
340 }
341
342 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Sorcery Configuration File Object Wizard",
343         .load = load_module,
344         .unload = unload_module,
345         .load_pri = AST_MODPRI_CHANNEL_DEPEND,
346 );