res_sorcery_config.c 13 KB

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