res_sorcery_config.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  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. #include "asterisk/hashtab.h"
  37. /*! \brief Structure for storing configuration file sourced objects */
  38. struct sorcery_config {
  39. /*! \brief UUID for identifying us when opening a configuration file */
  40. char uuid[AST_UUID_STR_LEN];
  41. /*! \brief Objects retrieved from the configuration file */
  42. struct ao2_global_obj objects;
  43. /*! \brief Any specific variable criteria for considering a defined category for this object */
  44. struct ast_variable *criteria;
  45. /*! \brief An explicit name for the configuration section, with it there can be only one */
  46. char *explicit_name;
  47. /*! \brief Number of buckets to use for objects */
  48. unsigned int buckets;
  49. /*! \brief Enable file level integrity instead of object level */
  50. unsigned int file_integrity:1;
  51. /*! \brief Enable enforcement of a single configuration object of this type */
  52. unsigned int single_object:1;
  53. /*! \brief Filename of the configuration file */
  54. char filename[];
  55. };
  56. /*! \brief Structure used for fields comparison */
  57. struct sorcery_config_fields_cmp_params {
  58. /*! \brief Pointer to the sorcery structure */
  59. const struct ast_sorcery *sorcery;
  60. /*! \brief Pointer to the fields to check */
  61. const struct ast_variable *fields;
  62. /*! \brief Regular expression for checking object id */
  63. regex_t *regex;
  64. /*! \brief Prefix for matching object id */
  65. const char *prefix;
  66. /*! \brief Prefix length in bytes for matching object id */
  67. const size_t prefix_len;
  68. /*! \brief Optional container to put object into */
  69. struct ao2_container *container;
  70. };
  71. static void *sorcery_config_open(const char *data);
  72. static void sorcery_config_load(void *data, const struct ast_sorcery *sorcery, const char *type);
  73. static void sorcery_config_reload(void *data, const struct ast_sorcery *sorcery, const char *type);
  74. static void *sorcery_config_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id);
  75. static void *sorcery_config_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type, const struct ast_variable *fields);
  76. static void sorcery_config_retrieve_multiple(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects,
  77. const struct ast_variable *fields);
  78. static void sorcery_config_retrieve_regex(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const char *regex);
  79. static void sorcery_config_retrieve_prefix(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const char *prefix, const size_t prefix_len);
  80. static void sorcery_config_close(void *data);
  81. static struct ast_sorcery_wizard config_object_wizard = {
  82. .name = "config",
  83. .open = sorcery_config_open,
  84. .load = sorcery_config_load,
  85. .reload = sorcery_config_reload,
  86. .retrieve_id = sorcery_config_retrieve_id,
  87. .retrieve_fields = sorcery_config_retrieve_fields,
  88. .retrieve_multiple = sorcery_config_retrieve_multiple,
  89. .retrieve_regex = sorcery_config_retrieve_regex,
  90. .retrieve_prefix = sorcery_config_retrieve_prefix,
  91. .close = sorcery_config_close,
  92. };
  93. /*! \brief Destructor function for sorcery config */
  94. static void sorcery_config_destructor(void *obj)
  95. {
  96. struct sorcery_config *config = obj;
  97. ao2_global_obj_release(config->objects);
  98. ast_rwlock_destroy(&config->objects.lock);
  99. ast_variables_destroy(config->criteria);
  100. ast_free(config->explicit_name);
  101. }
  102. static int sorcery_config_fields_cmp(void *obj, void *arg, int flags)
  103. {
  104. const struct sorcery_config_fields_cmp_params *params = arg;
  105. RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy);
  106. if (params->regex) {
  107. /* If a regular expression has been provided see if it matches, otherwise move on */
  108. if (!regexec(params->regex, ast_sorcery_object_get_id(obj), 0, NULL, 0)) {
  109. ao2_link(params->container, obj);
  110. }
  111. return 0;
  112. } else if (params->prefix) {
  113. if (!strncmp(params->prefix, ast_sorcery_object_get_id(obj), params->prefix_len)) {
  114. ao2_link(params->container, obj);
  115. }
  116. return 0;
  117. } else if (params->fields &&
  118. (!(objset = ast_sorcery_objectset_create(params->sorcery, obj)) ||
  119. (!ast_variable_lists_match(objset, params->fields, 0)))) {
  120. /* If we can't turn the object into an object set OR if differences exist between the fields
  121. * passed in and what are present on the object they are not a match.
  122. */
  123. return 0;
  124. }
  125. /* We want this object */
  126. if (params->container) {
  127. /*
  128. * We are putting the found objects into the given container instead
  129. * of the normal container traversal return mechanism.
  130. */
  131. ao2_link(params->container, obj);
  132. return 0;
  133. } else {
  134. return CMP_MATCH;
  135. }
  136. }
  137. static void *sorcery_config_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type, const struct ast_variable *fields)
  138. {
  139. struct sorcery_config *config = data;
  140. RAII_VAR(struct ao2_container *, objects, ao2_global_obj_ref(config->objects), ao2_cleanup);
  141. struct sorcery_config_fields_cmp_params params = {
  142. .sorcery = sorcery,
  143. .fields = fields,
  144. .container = NULL,
  145. };
  146. /* If no fields are present return nothing, we require *something*, same goes if no objects exist yet */
  147. if (!objects || !fields) {
  148. return NULL;
  149. }
  150. return ao2_callback(objects, 0, sorcery_config_fields_cmp, &params);
  151. }
  152. static void *sorcery_config_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id)
  153. {
  154. struct sorcery_config *config = data;
  155. RAII_VAR(struct ao2_container *, objects, ao2_global_obj_ref(config->objects), ao2_cleanup);
  156. return objects ? ao2_find(objects, id, OBJ_SEARCH_KEY) : NULL;
  157. }
  158. 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)
  159. {
  160. struct sorcery_config *config = data;
  161. RAII_VAR(struct ao2_container *, config_objects, ao2_global_obj_ref(config->objects), ao2_cleanup);
  162. struct sorcery_config_fields_cmp_params params = {
  163. .sorcery = sorcery,
  164. .fields = fields,
  165. .container = objects,
  166. };
  167. if (!config_objects) {
  168. return;
  169. }
  170. ao2_callback(config_objects, OBJ_NODATA | OBJ_MULTIPLE, sorcery_config_fields_cmp, &params);
  171. }
  172. static void sorcery_config_retrieve_regex(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const char *regex)
  173. {
  174. struct sorcery_config *config = data;
  175. RAII_VAR(struct ao2_container *, config_objects, ao2_global_obj_ref(config->objects), ao2_cleanup);
  176. regex_t expression;
  177. struct sorcery_config_fields_cmp_params params = {
  178. .sorcery = sorcery,
  179. .container = objects,
  180. .regex = &expression,
  181. };
  182. if (ast_strlen_zero(regex)) {
  183. regex = ".";
  184. }
  185. if (!config_objects || regcomp(&expression, regex, REG_EXTENDED | REG_NOSUB)) {
  186. return;
  187. }
  188. ao2_callback(config_objects, OBJ_NODATA | OBJ_MULTIPLE, sorcery_config_fields_cmp, &params);
  189. regfree(&expression);
  190. }
  191. static void sorcery_config_retrieve_prefix(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const char *prefix, const size_t prefix_len)
  192. {
  193. struct sorcery_config *config = data;
  194. RAII_VAR(struct ao2_container *, config_objects, ao2_global_obj_ref(config->objects), ao2_cleanup);
  195. struct sorcery_config_fields_cmp_params params = {
  196. .sorcery = sorcery,
  197. .container = objects,
  198. .prefix = prefix,
  199. .prefix_len = prefix_len,
  200. };
  201. if (!config_objects) {
  202. return;
  203. }
  204. ao2_callback(config_objects, OBJ_NODATA | OBJ_MULTIPLE, sorcery_config_fields_cmp, &params);
  205. }
  206. /*! \brief Internal function which determines if a category matches based on explicit name */
  207. static int sorcery_is_explicit_name_met(const struct ast_sorcery *sorcery, const char *type,
  208. struct ast_category *category, struct sorcery_config *config)
  209. {
  210. struct ast_sorcery_object_type *object_type;
  211. struct ast_variable *field;
  212. int met = 1;
  213. if (ast_strlen_zero(config->explicit_name) || strcmp(ast_category_get_name(category), config->explicit_name)) {
  214. return 0;
  215. }
  216. object_type = ast_sorcery_get_object_type(sorcery, type);
  217. if (!object_type) {
  218. return 0;
  219. }
  220. /* We iterate the configured fields to see if we don't know any, if we don't then
  221. * this is likely not for the given type and we skip it. If it actually is then criteria
  222. * may pick it up in which case it would just get rejected as an invalid configuration later.
  223. */
  224. for (field = ast_category_first(category); field; field = field->next) {
  225. if (!ast_sorcery_is_object_field_registered(object_type, field->name)) {
  226. met = 0;
  227. break;
  228. }
  229. }
  230. ao2_ref(object_type, -1);
  231. return met;
  232. }
  233. /*! \brief Internal function which determines if a category matches based on criteria */
  234. static int sorcery_is_criteria_met(struct ast_category *category, struct sorcery_config *config)
  235. {
  236. RAII_VAR(struct ast_variable *, diff, NULL, ast_variables_destroy);
  237. if (!config->criteria) {
  238. return 0;
  239. }
  240. return (!ast_sorcery_changeset_create(ast_category_first(category), config->criteria, &diff) && !diff) ? 1 : 0;
  241. }
  242. /*! \brief Internal function which determines if criteria has been met for considering an object set applicable */
  243. static int sorcery_is_configuration_met(const struct ast_sorcery *sorcery, const char *type,
  244. struct ast_category *category, struct sorcery_config *config)
  245. {
  246. if (!config->criteria && ast_strlen_zero(config->explicit_name)) {
  247. /* Nothing is configured to allow specific matching, so accept it! */
  248. return 1;
  249. } else if (sorcery_is_explicit_name_met(sorcery, type, category, config)) {
  250. return 1;
  251. } else if (sorcery_is_criteria_met(category, config)) {
  252. return 1;
  253. } else {
  254. /* Nothing explicitly matched so reject */
  255. return 0;
  256. }
  257. }
  258. static void sorcery_config_internal_load(void *data, const struct ast_sorcery *sorcery, const char *type, unsigned int reload)
  259. {
  260. struct sorcery_config *config = data;
  261. struct ast_flags flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
  262. struct ast_config *cfg = ast_config_load2(config->filename, config->uuid, flags);
  263. struct ast_category *category = NULL;
  264. RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup);
  265. const char *id = NULL;
  266. unsigned int buckets = 0;
  267. if (!cfg) {
  268. ast_log(LOG_ERROR, "Unable to load config file '%s'\n", config->filename);
  269. return;
  270. } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
  271. ast_debug(1, "Config file '%s' was unchanged\n", config->filename);
  272. return;
  273. } else if (cfg == CONFIG_STATUS_FILEINVALID) {
  274. ast_log(LOG_ERROR, "Contents of config file '%s' are invalid and cannot be parsed\n", config->filename);
  275. return;
  276. }
  277. if (!config->buckets) {
  278. while ((category = ast_category_browse_filtered(cfg, NULL, category, NULL))) {
  279. /* If given configuration has not been met skip the category, it is not applicable */
  280. if (!sorcery_is_configuration_met(sorcery, type, category, config)) {
  281. continue;
  282. }
  283. buckets++;
  284. }
  285. /* Determine the optimal number of buckets */
  286. while (buckets && !ast_is_prime(buckets)) {
  287. /* This purposely goes backwards to ensure that the container doesn't have a ton of
  288. * empty buckets for objects that will never get added.
  289. */
  290. buckets--;
  291. }
  292. if (!buckets) {
  293. buckets = 1;
  294. }
  295. } else {
  296. buckets = config->buckets;
  297. }
  298. /* For single object configurations there can only ever be one bucket, if there's more than the single
  299. * object requirement has been violated.
  300. */
  301. if (config->single_object && buckets > 1) {
  302. ast_log(LOG_ERROR, "Config file '%s' could not be loaded; configuration contains more than one object of type '%s'\n",
  303. config->filename, type);
  304. ast_config_destroy(cfg);
  305. return;
  306. }
  307. ast_debug(2, "Using bucket size of '%d' for objects of type '%s' from '%s'\n",
  308. buckets, type, config->filename);
  309. objects = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, buckets,
  310. ast_sorcery_object_id_hash, NULL, ast_sorcery_object_id_compare);
  311. if (!objects) {
  312. ast_log(LOG_ERROR, "Could not create bucket for new objects from '%s', keeping existing objects\n",
  313. config->filename);
  314. ast_config_destroy(cfg);
  315. return;
  316. }
  317. while ((category = ast_category_browse_filtered(cfg, NULL, category, NULL))) {
  318. RAII_VAR(void *, obj, NULL, ao2_cleanup);
  319. id = ast_category_get_name(category);
  320. /* If given configurationhas not been met skip the category, it is not applicable */
  321. if (!sorcery_is_configuration_met(sorcery, type, category, config)) {
  322. continue;
  323. }
  324. /* Confirm an object with this id does not already exist in the bucket.
  325. * If it exists, however, the configuration is invalid so stop
  326. * processing and destroy it. */
  327. obj = ao2_find(objects, id, OBJ_SEARCH_KEY);
  328. if (obj) {
  329. ast_log(LOG_ERROR, "Config file '%s' could not be loaded; configuration contains a duplicate object: '%s' of type '%s'\n",
  330. config->filename, id, type);
  331. ast_config_destroy(cfg);
  332. return;
  333. }
  334. if (!(obj = ast_sorcery_alloc(sorcery, type, id)) ||
  335. ast_sorcery_objectset_apply(sorcery, obj, ast_category_first(category))) {
  336. if (config->file_integrity) {
  337. ast_log(LOG_ERROR, "Config file '%s' could not be loaded due to error with object '%s' of type '%s'\n",
  338. config->filename, id, type);
  339. ast_config_destroy(cfg);
  340. return;
  341. } else {
  342. ast_log(LOG_ERROR, "Could not create an object of type '%s' with id '%s' from configuration file '%s'\n",
  343. type, id, config->filename);
  344. }
  345. ao2_cleanup(obj);
  346. /* 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 */
  347. if (!(obj = sorcery_config_retrieve_id(sorcery, data, type, id))) {
  348. continue;
  349. }
  350. ast_log(LOG_NOTICE, "Retaining existing configuration for object of type '%s' with id '%s'\n", type, id);
  351. }
  352. ao2_link(objects, obj);
  353. }
  354. ao2_global_obj_replace_unref(config->objects, objects);
  355. ast_config_destroy(cfg);
  356. }
  357. static void sorcery_config_load(void *data, const struct ast_sorcery *sorcery, const char *type)
  358. {
  359. sorcery_config_internal_load(data, sorcery, type, 0);
  360. }
  361. static void sorcery_config_reload(void *data, const struct ast_sorcery *sorcery, const char *type)
  362. {
  363. sorcery_config_internal_load(data, sorcery, type, 1);
  364. }
  365. static void *sorcery_config_open(const char *data)
  366. {
  367. char *tmp;
  368. char *filename;
  369. char *option;
  370. struct sorcery_config *config;
  371. if (ast_strlen_zero(data)) {
  372. return NULL;
  373. }
  374. tmp = ast_strdupa(data);
  375. filename = strsep(&tmp, ",");
  376. if (ast_strlen_zero(filename) || !(config = ao2_alloc_options(sizeof(*config) + strlen(filename) + 1, sorcery_config_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
  377. return NULL;
  378. }
  379. ast_uuid_generate_str(config->uuid, sizeof(config->uuid));
  380. ast_rwlock_init(&config->objects.lock);
  381. strcpy(config->filename, filename);
  382. while ((option = strsep(&tmp, ","))) {
  383. char *name = strsep(&option, "="), *value = option;
  384. if (!strcasecmp(name, "buckets")) {
  385. if (sscanf(value, "%30u", &config->buckets) != 1) {
  386. ast_log(LOG_ERROR, "Unsupported bucket size of '%s' used for configuration file '%s', defaulting to automatic determination\n",
  387. value, filename);
  388. }
  389. } else if (!strcasecmp(name, "integrity")) {
  390. if (!strcasecmp(value, "file")) {
  391. config->file_integrity = 1;
  392. } else if (!strcasecmp(value, "object")) {
  393. config->file_integrity = 0;
  394. } else {
  395. ast_log(LOG_ERROR, "Unsupported integrity value of '%s' used for configuration file '%s', defaulting to 'object'\n",
  396. value, filename);
  397. }
  398. } else if (!strcasecmp(name, "criteria")) {
  399. char *field = strsep(&value, "=");
  400. struct ast_variable *criteria = ast_variable_new(field, value, "");
  401. if (criteria) {
  402. criteria->next = config->criteria;
  403. config->criteria = criteria;
  404. } else {
  405. /* This is fatal since not following criteria would potentially yield invalid objects */
  406. ast_log(LOG_ERROR, "Could not create criteria entry of field '%s' with value '%s' for configuration file '%s'\n",
  407. field, value, filename);
  408. ao2_ref(config, -1);
  409. return NULL;
  410. }
  411. } else if (!strcasecmp(name, "explicit_name")) {
  412. ast_free(config->explicit_name);
  413. config->explicit_name = ast_strdup(value);
  414. if (ast_strlen_zero(config->explicit_name)) {
  415. /* This is fatal since it could stop a configuration section from getting applied */
  416. ast_log(LOG_ERROR, "Could not create explicit name entry of '%s' for configuration file '%s'\n",
  417. value, filename);
  418. ao2_ref(config, -1);
  419. return NULL;
  420. }
  421. } else if (!strcasecmp(name, "single_object")) {
  422. if (ast_strlen_zero(value)) {
  423. ast_log(LOG_ERROR, "Could not set single object value for configuration file '%s' as the value is empty\n",
  424. filename);
  425. ao2_ref(config, -1);
  426. return NULL;
  427. }
  428. config->single_object = ast_true(value);
  429. } else {
  430. ast_log(LOG_ERROR, "Unsupported option '%s' used for configuration file '%s'\n", name, filename);
  431. }
  432. }
  433. return config;
  434. }
  435. static void sorcery_config_close(void *data)
  436. {
  437. struct sorcery_config *config = data;
  438. ao2_ref(config, -1);
  439. }
  440. static int load_module(void)
  441. {
  442. if (ast_sorcery_wizard_register(&config_object_wizard)) {
  443. return AST_MODULE_LOAD_DECLINE;
  444. }
  445. return AST_MODULE_LOAD_SUCCESS;
  446. }
  447. static int unload_module(void)
  448. {
  449. ast_sorcery_wizard_unregister(&config_object_wizard);
  450. return 0;
  451. }
  452. AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Sorcery Configuration File Object Wizard",
  453. .support_level = AST_MODULE_SUPPORT_CORE,
  454. .load = load_module,
  455. .unload = unload_module,
  456. .load_pri = AST_MODPRI_REALTIME_DRIVER,
  457. );