Merge "res_pjsip: New endpoint option "refer_blind_progress""
[asterisk/asterisk.git] / res / res_sorcery_config.c
index 509538f..0de34c6 100644 (file)
@@ -30,8 +30,6 @@
 
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
 #include <regex.h>
 
 #include "asterisk/module.h"
@@ -39,9 +37,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/astobj2.h"
 #include "asterisk/config.h"
 #include "asterisk/uuid.h"
-
-/*! \brief Default number of buckets for sorcery objects */
-#define DEFAULT_OBJECT_BUCKETS 53
+#include "asterisk/hashtab.h"
 
 /*! \brief Structure for storing configuration file sourced objects */
 struct sorcery_config {
@@ -111,27 +107,10 @@ static void sorcery_config_destructor(void *obj)
        ast_variables_destroy(config->criteria);
 }
 
-/*! \brief Hashing function for sorcery objects */
-static int sorcery_config_hash(const void *obj, const int flags)
-{
-       const char *id = obj;
-
-       return ast_str_hash(flags & OBJ_KEY ? id : ast_sorcery_object_get_id(obj));
-}
-
-/*! \brief Comparator function for sorcery objects */
-static int sorcery_config_cmp(void *obj, void *arg, int flags)
-{
-       const char *id = arg;
-
-       return !strcmp(ast_sorcery_object_get_id(obj), flags & OBJ_KEY ? id : ast_sorcery_object_get_id(arg)) ? CMP_MATCH | CMP_STOP : 0;
-}
-
 static int sorcery_config_fields_cmp(void *obj, void *arg, int flags)
 {
        const struct sorcery_config_fields_cmp_params *params = arg;
        RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy);
-       RAII_VAR(struct ast_variable *, diff, NULL, ast_variables_destroy);
 
        if (params->regex) {
                /* If a regular expression has been provided see if it matches, otherwise move on */
@@ -141,22 +120,23 @@ static int sorcery_config_fields_cmp(void *obj, void *arg, int flags)
                return 0;
        } else if (params->fields &&
            (!(objset = ast_sorcery_objectset_create(params->sorcery, obj)) ||
-            (ast_sorcery_changeset_create(objset, params->fields, &diff)) ||
-            diff)) {
+            (!ast_variable_lists_match(objset, params->fields, 0)))) {
                /* If we can't turn the object into an object set OR if differences exist between the fields
-            * passed in and what are present on the object they are not a match.
-            */
+                * passed in and what are present on the object they are not a match.
+                */
                return 0;
        }
 
+       /* We want this object */
        if (params->container) {
+               /*
+                * We are putting the found objects into the given container instead
+                * of the normal container traversal return mechanism.
+                */
                ao2_link(params->container, obj);
-
-               /* As multiple objects are being returned keep going */
                return 0;
        } else {
-               /* Immediately stop and return, we only want a single object */
-               return CMP_MATCH | CMP_STOP;
+               return CMP_MATCH;
        }
 }
 
@@ -183,7 +163,7 @@ static void *sorcery_config_retrieve_id(const struct ast_sorcery *sorcery, void
        struct sorcery_config *config = data;
        RAII_VAR(struct ao2_container *, objects, ao2_global_obj_ref(config->objects), ao2_cleanup);
 
-       return objects ? ao2_find(objects, id, OBJ_KEY | OBJ_NOLOCK) : NULL;
+       return objects ? ao2_find(objects, id, OBJ_SEARCH_KEY) : NULL;
 }
 
 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)
@@ -200,7 +180,7 @@ static void sorcery_config_retrieve_multiple(const struct ast_sorcery *sorcery,
                return;
        }
 
-       ao2_callback(config_objects, 0, sorcery_config_fields_cmp, &params);
+       ao2_callback(config_objects, OBJ_NODATA | OBJ_MULTIPLE, sorcery_config_fields_cmp, &params);
 }
 
 static void sorcery_config_retrieve_regex(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const char *regex)
@@ -214,11 +194,15 @@ static void sorcery_config_retrieve_regex(const struct ast_sorcery *sorcery, voi
                .regex = &expression,
        };
 
+       if (ast_strlen_zero(regex)) {
+               regex = ".";
+       }
+
        if (!config_objects || regcomp(&expression, regex, REG_EXTENDED | REG_NOSUB)) {
                return;
        }
 
-       ao2_callback(config_objects, 0, sorcery_config_fields_cmp, &params);
+       ao2_callback(config_objects, OBJ_NODATA | OBJ_MULTIPLE, sorcery_config_fields_cmp, &params);
        regfree(&expression);
 }
 
@@ -235,8 +219,10 @@ static void sorcery_config_internal_load(void *data, const struct ast_sorcery *s
        struct sorcery_config *config = data;
        struct ast_flags flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
        struct ast_config *cfg = ast_config_load2(config->filename, config->uuid, flags);
+       struct ast_category *category = NULL;
        RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup);
        const char *id = NULL;
+       unsigned int buckets = 0;
 
        if (!cfg) {
                ast_log(LOG_ERROR, "Unable to load config file '%s'\n", config->filename);
@@ -249,31 +235,75 @@ static void sorcery_config_internal_load(void *data, const struct ast_sorcery *s
                return;
        }
 
-       if (!(objects = ao2_container_alloc(config->buckets, sorcery_config_hash, sorcery_config_cmp))) {
+       if (!config->buckets) {
+               while ((category = ast_category_browse_filtered(cfg, NULL, category, NULL))) {
+
+                       /* If given criteria has not been met skip the category, it is not applicable */
+                       if (!sorcery_is_criteria_met(ast_category_first(category), config->criteria)) {
+                               continue;
+                       }
+
+                       buckets++;
+               }
+
+               /* Determine the optimal number of buckets */
+               while (buckets && !ast_is_prime(buckets)) {
+                       /* This purposely goes backwards to ensure that the container doesn't have a ton of
+                        * empty buckets for objects that will never get added.
+                        */
+                       buckets--;
+               }
+
+               if (!buckets) {
+                       buckets = 1;
+               }
+       } else {
+               buckets = config->buckets;
+       }
+
+       ast_debug(2, "Using bucket size of '%d' for objects of type '%s' from '%s'\n",
+               buckets, type, config->filename);
+
+       objects = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, buckets,
+               ast_sorcery_object_id_hash, NULL, ast_sorcery_object_id_compare);
+       if (!objects) {
                ast_log(LOG_ERROR, "Could not create bucket for new objects from '%s', keeping existing objects\n",
                        config->filename);
                ast_config_destroy(cfg);
                return;
        }
 
-       while ((id = ast_category_browse(cfg, id))) {
+       while ((category = ast_category_browse_filtered(cfg, NULL, category, NULL))) {
                RAII_VAR(void *, obj, NULL, ao2_cleanup);
+               id = ast_category_get_name(category);
 
                /* If given criteria has not been met skip the category, it is not applicable */
-               if (!sorcery_is_criteria_met(ast_variable_browse(cfg, id), config->criteria)) {
+               if (!sorcery_is_criteria_met(ast_category_first(category), config->criteria)) {
                        continue;
                }
 
+               /*  Confirm an object with this id does not already exist in the bucket.
+                *  If it exists, however, the configuration is invalid so stop
+                *  processing and destroy it. */
+               obj = ao2_find(objects, id, OBJ_SEARCH_KEY);
+               if (obj) {
+                       ast_log(LOG_ERROR, "Config file '%s' could not be loaded; configuration contains a duplicate object: '%s' of type '%s'\n",
+                               config->filename, id, type);
+                       ast_config_destroy(cfg);
+                       return;
+               }
+
                if (!(obj = ast_sorcery_alloc(sorcery, type, id)) ||
-                   ast_sorcery_objectset_apply(sorcery, obj, ast_variable_browse(cfg, id))) {
-                       ast_debug(1, "Could not create an object of type '%s' with id '%s' from configuration file '%s'\n",
-                                 type, id, config->filename);
+                   ast_sorcery_objectset_apply(sorcery, obj, ast_category_first(category))) {
 
                        if (config->file_integrity) {
                                ast_log(LOG_ERROR, "Config file '%s' could not be loaded due to error with object '%s' of type '%s'\n",
                                        config->filename, id, type);
                                ast_config_destroy(cfg);
                                return;
+                       } else {
+                               ast_log(LOG_ERROR, "Could not create an object of type '%s' with id '%s' from configuration file '%s'\n",
+                                       type, id, config->filename);
                        }
 
                        ao2_cleanup(obj);
@@ -282,9 +312,11 @@ static void sorcery_config_internal_load(void *data, const struct ast_sorcery *s
                        if (!(obj = sorcery_config_retrieve_id(sorcery, data, type, id))) {
                                continue;
                        }
+
+                       ast_log(LOG_NOTICE, "Retaining existing configuration for object of type '%s' with id '%s'\n", type, id);
                }
 
-               ao2_link_flags(objects, obj, OBJ_NOLOCK);
+               ao2_link(objects, obj);
        }
 
        ao2_global_obj_replace_unref(config->objects, objects);
@@ -303,9 +335,18 @@ static void sorcery_config_reload(void *data, const struct ast_sorcery *sorcery,
 
 static void *sorcery_config_open(const char *data)
 {
-       char *tmp = ast_strdupa(data), *filename = strsep(&tmp, ","), *option;
+       char *tmp;
+       char *filename;
+       char *option;
        struct sorcery_config *config;
 
+       if (ast_strlen_zero(data)) {
+               return NULL;
+       }
+
+       tmp = ast_strdupa(data);
+       filename = strsep(&tmp, ",");
+
        if (ast_strlen_zero(filename) || !(config = ao2_alloc_options(sizeof(*config) + strlen(filename) + 1, sorcery_config_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
                return NULL;
        }
@@ -313,16 +354,15 @@ static void *sorcery_config_open(const char *data)
        ast_uuid_generate_str(config->uuid, sizeof(config->uuid));
 
        ast_rwlock_init(&config->objects.lock);
-       config->buckets = DEFAULT_OBJECT_BUCKETS;
        strcpy(config->filename, filename);
 
        while ((option = strsep(&tmp, ","))) {
                char *name = strsep(&option, "="), *value = option;
 
                if (!strcasecmp(name, "buckets")) {
-                       if (sscanf(value, "%30d", &config->buckets) != 1) {
-                               ast_log(LOG_ERROR, "Unsupported bucket size of '%s' used for configuration file '%s', defaulting to '%d'\n",
-                                       value, filename, DEFAULT_OBJECT_BUCKETS);
+                       if (sscanf(value, "%30u", &config->buckets) != 1) {
+                               ast_log(LOG_ERROR, "Unsupported bucket size of '%s' used for configuration file '%s', defaulting to automatic determination\n",
+                                       value, filename);
                        }
                } else if (!strcasecmp(name, "integrity")) {
                        if (!strcasecmp(value, "file")) {
@@ -378,6 +418,7 @@ static int unload_module(void)
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Sorcery Configuration File Object Wizard",
+       .support_level = AST_MODULE_SUPPORT_CORE,
        .load = load_module,
        .unload = unload_module,
        .load_pri = AST_MODPRI_REALTIME_DRIVER,