summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorTerry Wilson <twilson@digium.com>2012-06-01 16:33:25 +0000
committerTerry Wilson <twilson@digium.com>2012-06-01 16:33:25 +0000
commitd54717c39e62f4cc8b290ac4836c4d4469d87c24 (patch)
tree5a765e82be880f3b8c2407133fbcc15c4b4349d0 /main
parent463f9d729aed1a5ed538aa3deed1a3fed9462111 (diff)
Add new config-parsing framework
This framework adds a way to register the various options in a config file with Asterisk and to handle loading and reloading of that config in a consistent and atomic manner. Review: https://reviewboard.asterisk.org/r/1873/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@368181 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'main')
-rw-r--r--main/asterisk.exports.in2
-rw-r--r--main/astobj2.c19
-rw-r--r--main/config.c14
-rw-r--r--main/config_options.c693
-rw-r--r--main/udptl.c288
5 files changed, 906 insertions, 110 deletions
diff --git a/main/asterisk.exports.in b/main/asterisk.exports.in
index 7f0aa901a..49d3a44a4 100644
--- a/main/asterisk.exports.in
+++ b/main/asterisk.exports.in
@@ -5,6 +5,8 @@
LINKER_SYMBOL_PREFIX__ast_*;
LINKER_SYMBOL_PREFIXpbx_*;
LINKER_SYMBOL_PREFIXastman_*;
+ LINKER_SYMBOL_PREFIXaco_*;
+ LINKER_SYMBOL_PREFIX__aco_*;
LINKER_SYMBOL_PREFIXao2_*;
LINKER_SYMBOL_PREFIX__ao2_*;
LINKER_SYMBOL_PREFIXoption_debug;
diff --git a/main/astobj2.c b/main/astobj2.c
index 0dd07103c..4c82ea467 100644
--- a/main/astobj2.c
+++ b/main/astobj2.c
@@ -638,6 +638,7 @@ void __ao2_global_obj_release(struct ao2_global_obj *holder, const char *tag, co
{
if (!holder) {
/* For sanity */
+ ast_log(LOG_ERROR, "Must be called with a global object!\n");
return;
}
if (__ast_rwlock_wrlock(file, line, func, &holder->lock, name)) {
@@ -660,6 +661,7 @@ void *__ao2_global_obj_replace(struct ao2_global_obj *holder, void *obj, const c
if (!holder) {
/* For sanity */
+ ast_log(LOG_ERROR, "Must be called with a global object!\n");
return NULL;
}
if (__ast_rwlock_wrlock(file, line, func, &holder->lock, name)) {
@@ -696,8 +698,10 @@ void *__ao2_global_obj_ref(struct ao2_global_obj *holder, const char *tag, const
if (!holder) {
/* For sanity */
+ ast_log(LOG_ERROR, "Must be called with a global object!\n");
return NULL;
}
+
if (__ast_rwlock_rdlock(file, line, func, &holder->lock, name)) {
/* Could not get the read lock. */
return NULL;
@@ -713,7 +717,6 @@ void *__ao2_global_obj_ref(struct ao2_global_obj *holder, const char *tag, const
return obj;
}
-
/* internal callback to destroy a container. */
static void container_destruct(void *c);
@@ -1519,6 +1522,20 @@ struct ao2_container *__ao2_container_clone_debug(struct ao2_container *orig, en
return clone;
}
+void ao2_cleanup(void *obj)
+{
+ if (obj) {
+ ao2_ref(obj, -1);
+ }
+}
+
+void ao2_iterator_cleanup(struct ao2_iterator *iter)
+{
+ if (iter) {
+ ao2_iterator_destroy(iter);
+ }
+}
+
#ifdef AO2_DEBUG
static int print_cb(void *obj, void *arg, int flag)
{
diff --git a/main/config.c b/main/config.c
index 7f7f35558..9d9eefc25 100644
--- a/main/config.c
+++ b/main/config.c
@@ -2658,6 +2658,13 @@ int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
goto int32_done;
}
error = (x < low) || (x > high);
+ if (flags & PARSE_RANGE_DEFAULTS) {
+ if (x < low) {
+ def = low;
+ } else if (x > high) {
+ def = high;
+ }
+ }
if (flags & PARSE_OUT_RANGE) {
error = !error;
}
@@ -2704,6 +2711,13 @@ int32_done:
goto uint32_done;
}
error = (x < low) || (x > high);
+ if (flags & PARSE_RANGE_DEFAULTS) {
+ if (x < low) {
+ def = low;
+ } else if (x > high) {
+ def = high;
+ }
+ }
if (flags & PARSE_OUT_RANGE) {
error = !error;
}
diff --git a/main/config_options.c b/main/config_options.c
new file mode 100644
index 000000000..091c7f4c0
--- /dev/null
+++ b/main/config_options.c
@@ -0,0 +1,693 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ * \brief Configuration Option-handling
+ * \author Terry Wilson <twilson@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <regex.h>
+
+#include "asterisk/config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/acl.h"
+#include "asterisk/frame.h"
+
+#ifdef LOW_MEMORY
+#define CONFIG_OPT_BUCKETS 5
+#else
+#define CONFIG_OPT_BUCKETS 53
+#endif /* LOW_MEMORY */
+
+/*! \brief Bits of aco_info that shouldn't be assigned outside this file
+ * \internal
+ */
+struct aco_info_internal {
+ void *pending; /*!< The user-defined config object awaiting application */
+};
+
+struct aco_type_internal {
+ regex_t *regex;
+ struct ao2_container *opts; /*!< The container of options registered to the aco_info */
+};
+
+struct aco_option {
+ const char *name;
+ enum aco_matchtype match_type;
+ regex_t *name_regex;
+ const char *default_val;
+ struct aco_type **obj;
+ enum aco_option_type type;
+ aco_option_handler handler;
+ unsigned int flags;
+ size_t argc;
+ intptr_t args[0];
+};
+
+void *aco_pending_config(struct aco_info *info)
+{
+ if (!(info && info->internal)) {
+ ast_log(LOG_ERROR, "This may not be called without an initialized aco_info!\n");
+ return NULL;
+ }
+ return info->internal->pending;
+}
+
+static void config_option_destroy(void *obj)
+{
+ struct aco_option *opt = obj;
+ if (opt->match_type == ACO_REGEX && opt->name_regex) {
+ regfree(opt->name_regex);
+ ast_free(opt->name_regex);
+ }
+}
+
+static int int_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
+static int uint_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
+static int double_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
+static int sockaddr_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
+static int stringfield_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
+static int bool_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
+static int acl_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
+static int codec_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
+
+static aco_option_handler ast_config_option_default_handler(enum aco_option_type type)
+{
+ switch(type) {
+ case OPT_ACL_T: return acl_handler_fn;
+ case OPT_BOOL_T: return bool_handler_fn;
+ case OPT_CODEC_T: return codec_handler_fn;
+ case OPT_DOUBLE_T: return double_handler_fn;
+ case OPT_INT_T: return int_handler_fn;
+ case OPT_SOCKADDR_T: return sockaddr_handler_fn;
+ case OPT_STRINGFIELD_T: return stringfield_handler_fn;
+ case OPT_UINT_T: return uint_handler_fn;
+
+ case OPT_CUSTOM_T: return NULL;
+ }
+
+ return NULL;
+}
+
+static regex_t *build_regex(const char *text)
+{
+ int res;
+ regex_t *regex;
+
+ if (!(regex = ast_malloc(sizeof(*regex)))) {
+ return NULL;
+ }
+
+ if ((res = regcomp(regex, text, REG_EXTENDED | REG_ICASE | REG_NOSUB))) {
+ size_t len = regerror(res, regex, NULL, 0);
+ char buf[len];
+ regerror(res, regex, buf, len);
+ ast_log(LOG_ERROR, "Could not compile regex '%s': %s\n", text, buf);
+ ast_free(regex);
+ return NULL;
+ }
+
+ return regex;
+}
+
+int __aco_option_register(struct aco_info *info, const char *name, enum aco_matchtype matchtype, struct aco_type **types,
+ const char *default_val, enum aco_option_type kind, aco_option_handler handler, unsigned int flags, size_t argc, ...)
+{
+ struct aco_option *opt;
+ struct aco_type *type;
+ va_list ap;
+ int tmp;
+
+ /* Custom option types require a handler */
+ if (!handler && kind == OPT_CUSTOM_T) {
+ return -1;
+ }
+
+ if (!(types && types[0])) {
+ return -1;
+ }
+
+ if (!(opt = ao2_alloc(sizeof(*opt) + argc * sizeof(opt->args[0]), config_option_destroy))) {
+ return -1;
+ }
+
+ if (matchtype == ACO_REGEX && !(opt->name_regex = build_regex(name))) {
+ ao2_ref(opt, -1);
+ return -1;
+ }
+
+ va_start(ap, argc);
+ for (tmp = 0; tmp < argc; tmp++) {
+ opt->args[tmp] = va_arg(ap, size_t);
+ }
+ va_end(ap);
+
+ opt->name = name;
+ opt->match_type = matchtype;
+ opt->default_val = default_val;
+ opt->type = kind;
+ opt->handler = handler;
+ opt->flags = flags;
+ opt->argc = argc;
+
+ if (!opt->handler && !(opt->handler = ast_config_option_default_handler(opt->type))) {
+ /* This should never happen */
+ ast_log(LOG_ERROR, "No handler provided, and no default handler exists for type %d\n", opt->type);
+ ao2_ref(opt, -1);
+ return -1;
+ };
+
+ tmp = 0;
+ while ((type = types[tmp++])) {
+ if (!ao2_link(type->internal->opts, opt)) {
+ while (--tmp) {
+ ao2_unlink(types[tmp]->internal->opts, opt);
+ }
+ ao2_ref(opt, -1);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int config_opt_hash(const void *obj, const int flags)
+{
+ const struct aco_option *opt = obj;
+ const char *name = (flags & OBJ_KEY) ? obj : opt->name;
+ return ast_str_case_hash(name);
+}
+
+static int config_opt_cmp(void *obj, void *arg, int flags)
+{
+ struct aco_option *opt1 = obj, *opt2 = arg;
+ const char *name = (flags & OBJ_KEY) ? arg : opt2->name;
+ return strcasecmp(opt1->name, name) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+static int find_option_cb(void *obj, void *arg, int flags)
+{
+ struct aco_option *match = obj;
+ const char *name = arg;
+
+ switch (match->match_type) {
+ case ACO_EXACT:
+ return strcasecmp(name, match->name) ? 0 : CMP_MATCH | CMP_STOP;
+ case ACO_REGEX:
+ return regexec(match->name_regex, name, 0, NULL, 0) ? 0 : CMP_MATCH | CMP_STOP;
+ }
+ ast_log(LOG_ERROR, "Unknown match type. This should not be possible.\n");
+ return CMP_STOP;
+}
+
+static struct aco_option *aco_option_find(struct aco_type *type, const char *name)
+{
+ struct aco_option *opt;
+ /* Try an exact match with OBJ_KEY for the common/fast case, then iterate through
+ * all options for the regex cases */
+ if (!(opt = ao2_callback(type->internal->opts, OBJ_KEY, find_option_cb, (void *) name))) {
+ opt = ao2_callback(type->internal->opts, 0, find_option_cb, (void *) name);
+ }
+ return opt;
+}
+
+struct ao2_container *aco_option_container_alloc(void)
+{
+ return ao2_container_alloc(CONFIG_OPT_BUCKETS, config_opt_hash, config_opt_cmp);
+}
+
+static struct aco_type *internal_aco_type_find(struct aco_file *file, struct ast_config *cfg, const char *category)
+{
+ size_t x;
+ struct aco_type *match;
+ const char *val;
+
+ for (x = 0, match = file->types[x]; match; match = file->types[++x]) {
+ /* First make sure we are an object that can service this category */
+ if (!regexec(match->internal->regex, category, 0, NULL, 0) == !match->category_match) {
+ continue;
+ }
+
+ /* Then, see if we need to match a particular field */
+ if (!ast_strlen_zero(match->matchfield) && (!ast_strlen_zero(match->matchvalue) || match->matchfunc)) {
+ if (!(val = ast_variable_retrieve(cfg, category, match->matchfield))) {
+ ast_log(LOG_ERROR, "Required match field '%s' not found\n", match->matchfield);
+ return NULL;
+ }
+ if (match->matchfunc) {
+ if (!match->matchfunc(val)) {
+ continue;
+ }
+ } else if (strcasecmp(val, match->matchvalue)) {
+ continue;
+ }
+ }
+ /* If we get this far, we're a match */
+ break;
+ }
+
+ return match;
+}
+
+static int is_preload(struct aco_file *file, const char *cat)
+{
+ int i;
+
+ if (!file->preload) {
+ return 0;
+ }
+
+ for (i = 0; !ast_strlen_zero(file->preload[i]); i++) {
+ if (!strcasecmp(cat, file->preload[i])) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int process_category(struct ast_config *cfg, struct aco_info *info, struct aco_file *file, const char *cat, int preload) {
+ RAII_VAR(void *, new_item, NULL, ao2_cleanup);
+ struct aco_type *type;
+ /* For global types, field is the global option struct. For non-global, it is the container for items.
+ * We do not grab a reference to these objects, as the info already holds references to them. This
+ * pointer is just a convenience. Do not actually store it somewhere. */
+ void **field;
+
+ /* Skip preloaded categories if we aren't preloading */
+ if (!preload && is_preload(file, cat)) {
+ return 0;
+ }
+
+ /* Find aco_type by category, if not found it is an error */
+ if (!(type = internal_aco_type_find(file, cfg, cat))) {
+ ast_log(LOG_ERROR, "Could not find config type for category '%s' in '%s'\n", cat, file->filename);
+ return -1;
+ }
+
+ field = info->internal->pending + type->item_offset;
+ if (!*field) {
+ ast_log(LOG_ERROR, "No object to update!\n");
+ return -1;
+ }
+
+ if (type->type == ACO_GLOBAL && *field) {
+ if (aco_set_defaults(type, cat, *field)) {
+ ast_log(LOG_ERROR, "In %s: Setting defaults for %s failed\n", file->filename, cat);
+ return -1;
+ }
+ if (aco_process_category_options(type, cfg, cat, *field)) {
+ ast_log(LOG_ERROR, "In %s: Processing options for %s failed\n", file->filename, cat);
+ return -1;
+ }
+ } else if (type->type == ACO_ITEM) {
+ int new = 0;
+ /* If we have multiple definitions of a category in a file, or can set the values from multiple
+ * files, look up the entry if we've already added it so we can merge the values together.
+ * Otherwise, alloc a new item. */
+ if (*field) {
+ if (!(new_item = type->item_find(*field, cat))) {
+ if (!(new_item = type->item_alloc(cat))) {
+ ast_log(LOG_ERROR, "In %s: Could not create item for %s\n", file->filename, cat);
+ return -1;
+ }
+ if (aco_set_defaults(type, cat, new_item)) {
+ ast_log(LOG_ERROR, "In %s: Setting defaults for %s failed\n", file->filename, cat);
+ return -1;
+ }
+ new = 1;
+ }
+ }
+
+ if (type->item_pre_process && type->item_pre_process(new_item)) {
+ ast_log(LOG_ERROR, "In %s: Preprocess callback for %s failed\n", file->filename, cat);
+ return -1;
+ }
+
+ if (aco_process_category_options(type, cfg, cat, new_item)) {
+ ast_log(LOG_ERROR, "In %s: Processing options for %s failed\n", file->filename, cat);
+ return -1;
+ }
+
+ if (type->item_prelink && type->item_prelink(new_item)) {
+ ast_log(LOG_ERROR, "In %s: Pre-link callback for %s failed\n", file->filename, cat);
+ return -1;
+ }
+
+ if (new && !ao2_link(*field, new_item)) {
+ ast_log(LOG_ERROR, "In %s: Linking config for %s failed\n", file->filename, cat);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int apply_config(struct aco_info *info)
+{
+ ao2_global_obj_replace_unref(*info->global_obj, info->internal->pending);
+
+ return 0;
+}
+
+static int internal_process_ast_config(struct aco_info *info, struct aco_file *file, struct ast_config *cfg)
+{
+ const char *cat = NULL;
+
+ if (file->preload) {
+ int i;
+ for (i = 0; !ast_strlen_zero(file->preload[i]); i++) {
+ if (process_category(cfg, info, file, file->preload[i], 1)) {
+ return -1;
+ }
+ }
+ }
+
+ while ((cat = ast_category_browse(cfg, cat))) {
+ if (process_category(cfg, info, file, cat, 0)) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int aco_process_ast_config(struct aco_info *info, struct aco_file *file, struct ast_config *cfg)
+{
+ if (!(info->internal->pending = info->snapshot_alloc())) {
+ ast_log(LOG_ERROR, "In %s: Could not allocate temporary objects\n", file->filename);
+ goto error;
+ }
+
+ if (internal_process_ast_config(info, file, cfg)) {
+ goto error;
+ }
+
+ if (info->pre_apply_config && info->pre_apply_config()) {
+ goto error;
+ }
+
+ if (apply_config(info)) {
+ goto error;
+ };
+
+ ao2_cleanup(info->internal->pending);
+ return 0;
+
+error:
+ ao2_cleanup(info->internal->pending);
+ return -1;
+}
+
+int aco_process_config(struct aco_info *info, int reload)
+{
+ struct ast_config *cfg;
+ struct ast_flags cfg_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0, };
+ int res = 0, x = 0;
+ struct aco_file *file;
+
+ if (!(info->files[0])) {
+ ast_log(LOG_ERROR, "No filename given, cannot proceed!\n");
+ return -1;
+ }
+
+ if (!(info->internal->pending = info->snapshot_alloc())) {
+ ast_log(LOG_ERROR, "In %s: Could not allocate temporary objects\n", info->module);
+ return -1;
+ }
+
+ while (!res && (file = info->files[x++])) {
+ if (!(cfg = ast_config_load(file->filename, cfg_flags))) {
+ ast_log(LOG_ERROR, "Unable to load config file '%s'\n", file->filename);
+ res = -1;
+ break;
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
+ ast_debug(1, "%s was unchanged\n", file->filename);
+ res = 0;
+ continue;
+ } else if (cfg == CONFIG_STATUS_FILEINVALID) {
+ ast_log(LOG_ERROR, "Contents of %s are invalid and cannot be parsed\n", file->filename);
+ res = -1;
+ break;
+ } else if (cfg == CONFIG_STATUS_FILEMISSING) {
+ ast_log(LOG_ERROR, "%s is missing! Cannot load %s\n", file->filename, info->module);
+ res = -1;
+ break;
+ }
+
+ res = internal_process_ast_config(info, file, cfg);
+ ast_config_destroy(cfg);
+ }
+
+ if (res || (res = ((info->pre_apply_config && info->pre_apply_config()) || apply_config(info)))) {
+ ;
+ };
+
+ ao2_cleanup(info->internal->pending);
+ return res;
+}
+
+int aco_process_category_options(struct aco_type *type, struct ast_config *cfg, const char *cat, void *obj)
+{
+ struct ast_variable *var;
+
+ for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
+ RAII_VAR(struct aco_option *, opt, aco_option_find(type, var->name), ao2_cleanup);
+ if (!opt) {
+ ast_log(LOG_ERROR, "Could not find option suitable for category '%s' named '%s' at line %d of %s\n", cat, var->name, var->lineno, var->file);
+ return -1;
+ }
+ if (!opt->handler) {
+ /* It should be impossible for an option to not have a handler */
+ ast_log(LOG_ERROR, "BUG! Somehow a config option for %s/%s was created with no handler!\n", cat, var->name);
+ return -1;
+ }
+ if (opt->handler(opt, var, obj)) {
+ ast_log(LOG_ERROR, "Error parsing %s=%s at line %d of %s\n", var->name, var->value, var->lineno, var->file);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void internal_type_destroy(struct aco_type *type)
+{
+ if (type->internal->regex) {
+ regfree(type->internal->regex);
+ ast_free(type->internal->regex);
+ }
+ ao2_cleanup(type->internal->opts);
+ type->internal->opts = NULL;
+ ast_free(type->internal);
+ type->internal = NULL;
+}
+
+static void internal_file_types_destroy(struct aco_file *file)
+{
+ size_t x;
+ struct aco_type *t;
+
+ for (x = 0, t = file->types[x]; t; t = file->types[++x]) {
+ internal_type_destroy(t);
+ t = NULL;
+ }
+}
+
+static int internal_type_init(struct aco_type *type)
+{
+ if (!(type->internal = ast_calloc(1, sizeof(*type->internal)))) {
+ return -1;
+ }
+
+ if (!(type->internal->regex = build_regex(type->category))) {
+ internal_type_destroy(type);
+ }
+
+ if (!(type->internal->opts = aco_option_container_alloc())) {
+ internal_type_destroy(type);
+ }
+
+ return 0;
+}
+
+int aco_info_init(struct aco_info *info)
+{
+ size_t x, y;
+
+ if (!(info->internal = ast_calloc(1, sizeof(*info->internal)))) {
+ return -1;
+ }
+
+ for (x = 0; info->files[x]; x++) {
+ for (y = 0; info->files[x]->types[y]; y++) {
+ if (internal_type_init(info->files[x]->types[y])) {
+ goto error;
+ }
+ }
+ }
+
+ return 0;
+error:
+ aco_info_destroy(info);
+ return -1;
+}
+
+void aco_info_destroy(struct aco_info *info)
+{
+ int x;
+ /* It shouldn't be possible for internal->pending to be in use when this is called because
+ * of the locks in loader.c around reloads and unloads and the fact that internal->pending
+ * only exists while those locks are held */
+ ast_free(info->internal);
+ info->internal = NULL;
+
+ for (x = 0; info->files[x]; x++) {
+ internal_file_types_destroy(info->files[x]);
+ }
+}
+
+int aco_set_defaults(struct aco_type *type, const char *category, void *obj)
+{
+ struct aco_option *opt;
+ struct ao2_iterator iter;
+
+ iter = ao2_iterator_init(type->internal->opts, 0);
+
+ while ((opt = ao2_iterator_next(&iter))) {
+ RAII_VAR(struct ast_variable *, var, NULL, ast_variables_destroy);
+
+ if (ast_strlen_zero(opt->default_val)) {
+ ao2_ref(opt, -1);
+ continue;
+ }
+ if (!(var = ast_variable_new(opt->name, opt->default_val, ""))) {
+ ao2_ref(opt, -1);
+ ao2_iterator_destroy(&iter);
+ return -1;
+ }
+ if (opt->handler(opt, var, obj)) {
+ ast_log(LOG_ERROR, "Unable to set default for %s, %s=%s\n", category, var->name, var->value);
+ ao2_ref(opt, -1);
+ ao2_iterator_destroy(&iter);
+ return -1;
+ }
+ ao2_ref(opt, -1);
+ }
+ ao2_iterator_destroy(&iter);
+
+ return 0;
+}
+
+/* default config option handlers */
+static int int_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj) {
+ int *field = (int *)(obj + opt->args[0]);
+ unsigned int flags = PARSE_INT32 | opt->flags;
+ int res = 0;
+ if (opt->flags & PARSE_IN_RANGE) {
+ res = opt->flags & PARSE_DEFAULT ?
+ ast_parse_arg(var->value, flags, field, (int) opt->args[1], (int) opt->args[2], opt->args[3]) :
+ ast_parse_arg(var->value, flags, field, (int) opt->args[1], (int) opt->args[2]);
+ if (res) {
+ if (opt->flags & PARSE_RANGE_DEFAULTS) {
+ ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]);
+ res = 0;
+ } else if (opt->flags & PARSE_DEFAULT) {
+ ast_log(LOG_WARNING, "Failed to set %s=%s, Set to default value %d instead.\n", var->name, var->value, *field);
+ res = 0;
+ }
+ }
+ } else if ((opt->flags & PARSE_DEFAULT) && ast_parse_arg(var->value, flags, field, (int) opt->args[1])) {
+ ast_log(LOG_WARNING, "Attempted to set %s=%s, but set it to %d instead due to default)\n", var->name, var->value, *field);
+ } else {
+ res = ast_parse_arg(var->value, flags, field);
+ }
+
+ return res;
+}
+
+static int uint_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj) {
+ unsigned int *field = (unsigned int *)(obj + opt->args[0]);
+ unsigned int flags = PARSE_INT32 | opt->flags;
+ int res = 0;
+ if (opt->flags & PARSE_IN_RANGE) {
+ res = opt->flags & PARSE_DEFAULT ?
+ ast_parse_arg(var->value, flags, field, (unsigned int) opt->args[1], (unsigned int) opt->args[2], opt->args[3]) :
+ ast_parse_arg(var->value, flags, field, (unsigned int) opt->args[1], (unsigned int) opt->args[2]);
+ if (res) {
+ if (opt->flags & PARSE_RANGE_DEFAULTS) {
+ ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]);
+ res = 0;
+ } else if (opt->flags & PARSE_DEFAULT) {
+ ast_log(LOG_WARNING, "Failed to set %s=%s, Set to default value %d instead.\n", var->name, var->value, *field);
+ res = 0;
+ }
+ }
+ } else if ((opt->flags & PARSE_DEFAULT) && ast_parse_arg(var->value, flags, field, (unsigned int) opt->args[1])) {
+ ast_log(LOG_WARNING, "Attempted to set %s=%s, but set it to %u instead due to default)\n", var->name, var->value, *field);
+ } else {
+ res = ast_parse_arg(var->value, flags, field);
+ }
+
+ return res;
+}
+
+static int double_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj) {
+ double *field = (double *)(obj + opt->args[0]);
+ return ast_parse_arg(var->value, PARSE_DOUBLE | opt->flags, field);
+}
+
+static int acl_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj) {
+ struct ast_ha **ha = (struct ast_ha **)(obj + opt->args[0]);
+ const char *permit = (const char *) opt->args[1];
+ int error = 0;
+ *ha = ast_append_ha(permit, var->value, *ha, &error);
+ return error;
+}
+
+/* opt->args[0] = struct ast_codec_pref, opt->args[1] struct ast_format_cap * */
+static int codec_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj) {
+ struct ast_codec_pref *pref = (struct ast_codec_pref *)(obj + opt->args[0]);
+ struct ast_format_cap **cap = (struct ast_format_cap **)(obj + opt->args[1]);
+ return ast_parse_allow_disallow(pref, *cap, var->value, opt->flags);
+}
+
+/* opt->args[0] = ast_string_field, opt->args[1] = field_mgr_pool, opt->args[2] = field_mgr */
+static int stringfield_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ ast_string_field *field = (const char **)(obj + opt->args[0]);
+ struct ast_string_field_pool **pool = (struct ast_string_field_pool **)(obj + opt->args[1]);
+ struct ast_string_field_mgr *mgr = (struct ast_string_field_mgr *)(obj + opt->args[2]);
+ ast_string_field_ptr_set_by_fields(*pool, *mgr, field, var->value);
+ return 0;
+}
+
+static int bool_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ unsigned int *field = (unsigned int *)(obj + opt->args[0]);
+ *field = opt->flags ? ast_true(var->value) : ast_false(var->value);
+ return 0;
+}
+
+static int sockaddr_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct ast_sockaddr *field = (struct ast_sockaddr *)(obj + opt->args[0]);
+ return ast_parse_arg(var->value, PARSE_ADDR | opt->flags, field);
+}
diff --git a/main/udptl.c b/main/udptl.c
index 01401617a..a550ff16f 100644
--- a/main/udptl.c
+++ b/main/udptl.c
@@ -61,7 +61,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/frame.h"
#include "asterisk/channel.h"
#include "asterisk/acl.h"
-#include "asterisk/config.h"
+#include "asterisk/config_options.h"
#include "asterisk/lock.h"
#include "asterisk/utils.h"
#include "asterisk/netsock2.h"
@@ -82,16 +82,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#define DEFAULT_UDPTLSTART 4000
#define DEFAULT_UDPTLEND 4999
-static int udptlstart = DEFAULT_UDPTLSTART;
-static int udptlend = DEFAULT_UDPTLEND;
static int udptldebug; /*!< Are we debugging? */
static struct ast_sockaddr udptldebugaddr; /*!< Debug packets to/from this host */
-#ifdef SO_NO_CHECK
-static int nochecksums;
-#endif
-static int udptlfecentries;
-static int udptlfecspan;
-static int use_even_ports;
#define LOCAL_FAX_MAX_DATAGRAM 1400
#define DEFAULT_FAX_MAX_DATAGRAM 400
@@ -182,6 +174,43 @@ struct ast_udptl {
static AST_RWLIST_HEAD_STATIC(protos, ast_udptl_protocol);
+struct udptl_global_options {
+ unsigned int start; /*< The UDPTL start port */
+ unsigned int end; /*< The UDPTL end port */
+ unsigned int fecentries;
+ unsigned int fecspan;
+ unsigned int nochecksums;
+ unsigned int use_even_ports;
+};
+
+static AO2_GLOBAL_OBJ_STATIC(globals);
+
+struct udptl_config {
+ struct udptl_global_options *general;
+};
+
+static void *udptl_snapshot_alloc(void);
+static int udptl_pre_apply_config(void);
+
+static struct aco_type general_option = {
+ .type = ACO_GLOBAL,
+ .category_match = ACO_WHITELIST,
+ .item_offset = offsetof(struct udptl_config, general),
+ .category = "^general$",
+};
+
+static struct aco_type *general_options[] = ACO_TYPES(&general_option);
+
+static struct aco_file udptl_conf = {
+ .filename = "udptl.conf",
+ .types = ACO_TYPES(&general_option),
+};
+
+CONFIG_INFO_STANDARD(cfg_info, globals, udptl_snapshot_alloc,
+ .files = ACO_FILES(&udptl_conf),
+ .pre_apply_config = udptl_pre_apply_config,
+);
+
static inline int udptl_debug_test_addr(const struct ast_sockaddr *addr)
{
if (udptldebug == 0)
@@ -922,12 +951,19 @@ struct ast_udptl *ast_udptl_new_with_bindaddr(struct ast_sched_context *sched, s
int startplace;
int i;
long int flags;
+ RAII_VAR(struct udptl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
+
+ if (!cfg || !cfg->general) {
+ ast_log(LOG_ERROR, "Could not access global udptl options!\n");
+ return NULL;
+ }
- if (!(udptl = ast_calloc(1, sizeof(*udptl))))
+ if (!(udptl = ast_calloc(1, sizeof(*udptl)))) {
return NULL;
+ }
- udptl->error_correction_span = udptlfecspan;
- udptl->error_correction_entries = udptlfecentries;
+ udptl->error_correction_span = cfg->general->fecspan;
+ udptl->error_correction_entries = cfg->general->fecentries;
udptl->far_max_datagram = -1;
udptl->far_max_ifp = -1;
@@ -947,13 +983,15 @@ struct ast_udptl *ast_udptl_new_with_bindaddr(struct ast_sched_context *sched, s
}
flags = fcntl(udptl->fd, F_GETFL);
fcntl(udptl->fd, F_SETFL, flags | O_NONBLOCK);
+
#ifdef SO_NO_CHECK
- if (nochecksums)
- setsockopt(udptl->fd, SOL_SOCKET, SO_NO_CHECK, &nochecksums, sizeof(nochecksums));
+ if (cfg->general->nochecksums)
+ setsockopt(udptl->fd, SOL_SOCKET, SO_NO_CHECK, &cfg->general->nochecksums, sizeof(cfg->general->nochecksums));
#endif
+
/* Find us a place */
- x = (udptlstart == udptlend) ? udptlstart : (ast_random() % (udptlend - udptlstart)) + udptlstart;
- if (use_even_ports && (x & 1)) {
+ x = (cfg->general->start == cfg->general->end) ? cfg->general->start : (ast_random() % (cfg->general->end - cfg->general->start)) + cfg->general->start;
+ if (cfg->general->use_even_ports && (x & 1)) {
++x;
}
startplace = x;
@@ -969,13 +1007,13 @@ struct ast_udptl *ast_udptl_new_with_bindaddr(struct ast_sched_context *sched, s
ast_free(udptl);
return NULL;
}
- if (use_even_ports) {
+ if (cfg->general->use_even_ports) {
x += 2;
} else {
++x;
}
- if (x > udptlend)
- x = udptlstart;
+ if (x > cfg->general->end)
+ x = cfg->general->start;
if (x == startplace) {
ast_log(LOG_WARNING, "No UDPTL ports remaining\n");
close(udptl->fd);
@@ -989,6 +1027,7 @@ struct ast_udptl *ast_udptl_new_with_bindaddr(struct ast_sched_context *sched, s
udptl->io = io;
udptl->ioid = ast_io_add(udptl->io, udptl->fd, udptlread, AST_IO_IN, udptl);
}
+
return udptl;
}
@@ -1303,109 +1342,110 @@ static char *handle_cli_udptl_set_debug(struct ast_cli_entry *e, int cmd, struct
return CLI_SUCCESS;
}
+static char *handle_cli_show_config(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ RAII_VAR(struct udptl_config *, cfg, NULL, ao2_cleanup);
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "udptl show config";
+ e->usage =
+ "Usage: udptl show config\n"
+ " Display UDPTL configuration options\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (!(cfg = ao2_global_obj_ref(globals))) {
+ return CLI_FAILURE;
+ }
+
+ ast_cli(a->fd, "UDPTL Global options\n");
+ ast_cli(a->fd, "--------------------\n");
+ ast_cli(a->fd, "udptlstart: %u\n", cfg->general->start);
+ ast_cli(a->fd, "udptlend: %u\n", cfg->general->end);
+ ast_cli(a->fd, "udptlfecentries: %u\n", cfg->general->fecentries);
+ ast_cli(a->fd, "udptlfecspan: %u\n", cfg->general->fecspan);
+ ast_cli(a->fd, "use_even_ports: %s\n", AST_CLI_YESNO(cfg->general->use_even_ports));
+ ast_cli(a->fd, "udptlchecksums: %s\n", AST_CLI_YESNO(!cfg->general->nochecksums));
+
+ return CLI_SUCCESS;
+}
static struct ast_cli_entry cli_udptl[] = {
- AST_CLI_DEFINE(handle_cli_udptl_set_debug, "Enable/Disable UDPTL debugging")
+ AST_CLI_DEFINE(handle_cli_udptl_set_debug, "Enable/Disable UDPTL debugging"),
+ AST_CLI_DEFINE(handle_cli_show_config, "Show UDPTL config options"),
};
-static void __ast_udptl_reload(int reload)
+static void udptl_config_destructor(void *obj)
+{
+ struct udptl_config *cfg = obj;
+ ao2_cleanup(cfg->general);
+}
+
+static void *udptl_snapshot_alloc(void)
{
- struct ast_config *cfg;
- const char *s;
- struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+ struct udptl_config *cfg;
- cfg = ast_config_load2("udptl.conf", "udptl", config_flags);
- if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
- return;
+ if (!(cfg = ao2_alloc(sizeof(*cfg), udptl_config_destructor))) {
+ return NULL;
+ }
+ if (!(cfg->general = ao2_alloc(sizeof(*cfg->general), NULL))) {
+ ao2_ref(cfg, -1);
+ return NULL;
}
- udptlstart = DEFAULT_UDPTLSTART;
- udptlend = DEFAULT_UDPTLEND;
- udptlfecentries = 0;
- udptlfecspan = 0;
- use_even_ports = 0;
+ return cfg;
+}
- if (cfg) {
- if ((s = ast_variable_retrieve(cfg, "general", "udptlstart"))) {
- udptlstart = atoi(s);
- if (udptlstart < 1024) {
- ast_log(LOG_WARNING, "Ports under 1024 are not allowed for T.38.\n");
- udptlstart = 1024;
- }
- if (udptlstart > 65535) {
- ast_log(LOG_WARNING, "Ports over 65535 are invalid.\n");
- udptlstart = 65535;
- }
- }
- if ((s = ast_variable_retrieve(cfg, "general", "udptlend"))) {
- udptlend = atoi(s);
- if (udptlend < 1024) {
- ast_log(LOG_WARNING, "Ports under 1024 are not allowed for T.38.\n");
- udptlend = 1024;
- }
- if (udptlend > 65535) {
- ast_log(LOG_WARNING, "Ports over 65535 are invalid.\n");
- udptlend = 65535;
- }
- }
- if ((s = ast_variable_retrieve(cfg, "general", "udptlchecksums"))) {
-#ifdef SO_NO_CHECK
- if (ast_false(s))
- nochecksums = 1;
- else
- nochecksums = 0;
-#else
- if (ast_false(s))
- ast_log(LOG_WARNING, "Disabling UDPTL checksums is not supported on this operating system!\n");
-#endif
- }
- if ((s = ast_variable_retrieve(cfg, "general", "T38FaxUdpEC"))) {
- ast_log(LOG_WARNING, "T38FaxUdpEC in udptl.conf is no longer supported; use the t38pt_udptl configuration option in sip.conf instead.\n");
- }
- if ((s = ast_variable_retrieve(cfg, "general", "T38FaxMaxDatagram"))) {
- ast_log(LOG_WARNING, "T38FaxMaxDatagram in udptl.conf is no longer supported; value is now supplied by T.38 applications.\n");
- }
- if ((s = ast_variable_retrieve(cfg, "general", "UDPTLFECEntries"))) {
- udptlfecentries = atoi(s);
- if (udptlfecentries < 1) {
- ast_log(LOG_WARNING, "Too small UDPTLFECEntries value. Defaulting to 1.\n");
- udptlfecentries = 1;
- }
- if (udptlfecentries > MAX_FEC_ENTRIES) {
- ast_log(LOG_WARNING, "Too large UDPTLFECEntries value. Defaulting to %d.\n", MAX_FEC_ENTRIES);
- udptlfecentries = MAX_FEC_ENTRIES;
- }
- }
- if ((s = ast_variable_retrieve(cfg, "general", "UDPTLFECSpan"))) {
- udptlfecspan = atoi(s);
- if (udptlfecspan < 1) {
- ast_log(LOG_WARNING, "Too small UDPTLFECSpan value. Defaulting to 1.\n");
- udptlfecspan = 1;
- }
- if (udptlfecspan > MAX_FEC_SPAN) {
- ast_log(LOG_WARNING, "Too large UDPTLFECSpan value. Defaulting to %d.\n", MAX_FEC_SPAN);
- udptlfecspan = MAX_FEC_SPAN;
- }
- }
- if ((s = ast_variable_retrieve(cfg, "general", "use_even_ports"))) {
- use_even_ports = ast_true(s);
- }
- ast_config_destroy(cfg);
+static int removed_options_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ if (!strcasecmp(var->name, "t38faxudpec")) {
+ ast_log(LOG_WARNING, "t38faxudpec in udptl.conf is no longer supported; use the t38pt_udptl configuration option in sip.conf instead.\n");
+ } else if (!strcasecmp(var->name, "t38faxmaxdatagram")) {
+ ast_log(LOG_WARNING, "t38faxmaxdatagram in udptl.conf is no longer supported; value is now supplied by T.38 applications.\n");
+ }
+ return 0;
+}
+
+static void __ast_udptl_reload(int reload)
+{
+ if (aco_process_config(&cfg_info, reload)) {
+ ast_log(LOG_WARNING, "Could not reload udptl config\n");
+ }
+}
+
+static int udptl_pre_apply_config(void) {
+ struct udptl_config *cfg = aco_pending_config(&cfg_info);
+
+ if (!cfg->general) {
+ return -1;
}
- if (use_even_ports && (udptlstart & 1)) {
- ++udptlstart;
- ast_log(LOG_NOTICE, "Odd numbered udptlstart specified but use_even_ports enabled. udptlstart is now %d\n", udptlstart);
+
+#ifndef SO_NO_CHECK
+ if (cfg->general->nochecksums) {
+ ast_log(LOG_WARNING, "Disabling UDPTL checksums is not supported on this operating system!\n");
+ cfg->general->nochecksums = 0;
+ }
+#endif
+
+ /* Fix up any global config values that we can handle before replacing the config */
+ if (cfg->general->use_even_ports && (cfg->general->start & 1)) {
+ ++cfg->general->start;
+ ast_log(LOG_NOTICE, "Odd numbered udptlstart specified but use_even_ports enabled. udptlstart is now %d\n", cfg->general->start);
}
- if (udptlstart > udptlend) {
- ast_log(LOG_WARNING, "Unreasonable values for UDPTL start/end ports; defaulting to %d-%d.\n", DEFAULT_UDPTLSTART, DEFAULT_UDPTLEND);
- udptlstart = DEFAULT_UDPTLSTART;
- udptlend = DEFAULT_UDPTLEND;
+ if (cfg->general->start > cfg->general->end) {
+ ast_log(LOG_WARNING, "Unreasonable values for UDPTL start/end ports; defaulting to %s-%s.\n", __stringify(DEFAULT_UDPTLSTART), __stringify(DEFAULT_UDPTLEND));
+ cfg->general->start = DEFAULT_UDPTLSTART;
+ cfg->general->end = DEFAULT_UDPTLEND;
}
- if (use_even_ports && (udptlend & 1)) {
- --udptlend;
- ast_log(LOG_NOTICE, "Odd numbered udptlend specified but use_even_ports enabled. udptlend is now %d\n", udptlend);
+ if (cfg->general->use_even_ports && (cfg->general->end & 1)) {
+ --cfg->general->end;
+ ast_log(LOG_NOTICE, "Odd numbered udptlend specified but use_even_ports enabled. udptlend is now %d\n", cfg->general->end);
}
- ast_verb(2, "UDPTL allocating from port range %d -> %d\n", udptlstart, udptlend);
+
+ return 0;
}
int ast_udptl_reload(void)
@@ -1416,6 +1456,36 @@ int ast_udptl_reload(void)
void ast_udptl_init(void)
{
+ if (aco_info_init(&cfg_info)) {
+ return;
+ }
+
+ aco_option_register(&cfg_info, "udptlstart", ACO_EXACT, general_options, __stringify(DEFAULT_UDPTLSTART),
+ OPT_UINT_T, PARSE_IN_RANGE | PARSE_DEFAULT,
+ FLDSET(struct udptl_global_options, start), DEFAULT_UDPTLSTART, 1024, 65535);
+
+ aco_option_register(&cfg_info, "udptlend", ACO_EXACT, general_options, __stringify(DEFAULT_UDPTLEND),
+ OPT_UINT_T, PARSE_IN_RANGE | PARSE_DEFAULT,
+ FLDSET(struct udptl_global_options, end), DEFAULT_UDPTLEND, 1024, 65535);
+
+ aco_option_register(&cfg_info, "udptlfecentries", ACO_EXACT, general_options, NULL,
+ OPT_UINT_T, PARSE_IN_RANGE | PARSE_RANGE_DEFAULTS,
+ FLDSET(struct udptl_global_options, fecentries), 1, MAX_FEC_ENTRIES);
+
+ aco_option_register(&cfg_info, "udptlfecspan", ACO_EXACT, general_options, NULL,
+ OPT_UINT_T, PARSE_IN_RANGE | PARSE_RANGE_DEFAULTS,
+ FLDSET(struct udptl_global_options, fecspan), 1, MAX_FEC_SPAN);
+
+ aco_option_register(&cfg_info, "udptlchecksums", ACO_EXACT, general_options, "yes",
+ OPT_BOOL_T, 0, FLDSET(struct udptl_global_options, nochecksums));
+
+ aco_option_register(&cfg_info, "use_even_ports", ACO_EXACT, general_options, "no",
+ OPT_BOOL_T, 1, FLDSET(struct udptl_global_options, use_even_ports));
+
+ aco_option_register_custom(&cfg_info, "t38faxudpec", ACO_EXACT, general_options, NULL, removed_options_handler, 0);
+ aco_option_register_custom(&cfg_info, "t38faxmaxdatagram", ACO_EXACT, general_options, NULL, removed_options_handler, 0);
+
ast_cli_register_multiple(cli_udptl, ARRAY_LEN(cli_udptl));
+
__ast_udptl_reload(0);
}