From d54717c39e62f4cc8b290ac4836c4d4469d87c24 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Fri, 1 Jun 2012 16:33:25 +0000 Subject: 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 --- main/asterisk.exports.in | 2 + main/astobj2.c | 19 +- main/config.c | 14 + main/config_options.c | 693 +++++++++++++++++++++++++++++++++++++++++++++++ main/udptl.c | 288 ++++++++++++-------- 5 files changed, 906 insertions(+), 110 deletions(-) create mode 100644 main/config_options.c (limited to 'main') 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 + * + * 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 + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include + +#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); } -- cgit v1.2.3