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 --- Makefile | 1 + apps/app_skel.c | 631 +++++++++++++++++++++++++++++++--- configs/app_skel.conf.sample | 27 ++ configure | 16 +- configure.ac | 10 + include/asterisk/astobj2.h | 6 + include/asterisk/config.h | 43 +-- include/asterisk/config_options.h | 482 ++++++++++++++++++++++++++ include/asterisk/stringfields.h | 36 +- include/asterisk/utils.h | 47 +++ main/asterisk.exports.in | 2 + main/astobj2.c | 19 +- main/config.c | 14 + main/config_options.c | 693 ++++++++++++++++++++++++++++++++++++++ main/udptl.c | 288 ++++++++++------ makeopts.in | 1 + tests/test_config.c | 301 +++++++++++++++++ 17 files changed, 2424 insertions(+), 193 deletions(-) create mode 100644 configs/app_skel.conf.sample create mode 100644 include/asterisk/config_options.h create mode 100644 main/config_options.c diff --git a/Makefile b/Makefile index b9e135e98..020b78449 100644 --- a/Makefile +++ b/Makefile @@ -191,6 +191,7 @@ ifeq ($(AST_DEVMODE),yes) _ASTCFLAGS+=-Wunused _ASTCFLAGS+=$(AST_DECLARATION_AFTER_STATEMENT) _ASTCFLAGS+=$(AST_FORTIFY_SOURCE) + _ASTCFLAGS+=$(AST_TRAMPOLINES) _ASTCFLAGS+=-Wundef _ASTCFLAGS+=-Wmissing-format-attribute _ASTCFLAGS+=-Wformat=2 diff --git a/apps/app_skel.c b/apps/app_skel.c index bd3b546e0..02f09d717 100644 --- a/apps/app_skel.c +++ b/apps/app_skel.c @@ -15,8 +15,8 @@ * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. * - * Please follow coding guidelines - * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES + * Please follow coding guidelines + * https://wiki.asterisk.org/wiki/display/AST/Coding+Guidelines */ /*! \file @@ -24,8 +24,8 @@ * \brief Skeleton application * * \author\verbatim <> \endverbatim - * - * This is a skeleton for development of an Asterisk application + * + * This is a skeleton for development of an Asterisk application * \ingroup applications */ @@ -38,79 +38,306 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") +#include /* log10 */ #include "asterisk/file.h" #include "asterisk/channel.h" #include "asterisk/pbx.h" #include "asterisk/module.h" #include "asterisk/lock.h" #include "asterisk/app.h" +#include "asterisk/config.h" +#include "asterisk/config_options.h" +#include "asterisk/say.h" +#include "asterisk/astobj2.h" +#include "asterisk/acl.h" +#include "asterisk/netsock2.h" +#include "asterisk/strings.h" +#include "asterisk/cli.h" /*** DOCUMENTATION - + - Simple one line explaination. + An example number guessing game - + - - + - This application is a template to build other applications from. - It shows you the basic structure to create your own Asterisk applications. + This simple number guessing application is a template to build other applications + from. It shows you the basic structure to create your own Asterisk applications. ***/ -static char *app = "Skel"; +static char *app = "SkelGuessNumber"; enum option_flags { - OPTION_A = (1 << 0), - OPTION_B = (1 << 1), - OPTION_C = (1 << 2), + OPTION_CHEAT = (1 << 0), + OPTION_NUMGAMES = (1 << 1), }; enum option_args { - OPTION_ARG_B = 0, - OPTION_ARG_C = 1, + OPTION_ARG_NUMGAMES, /* This *must* be the last value in this enum! */ - OPTION_ARG_ARRAY_SIZE = 2, + OPTION_ARG_ARRAY_SIZE, }; AST_APP_OPTIONS(app_opts,{ - AST_APP_OPTION('a', OPTION_A), - AST_APP_OPTION_ARG('b', OPTION_B, OPTION_ARG_B), - AST_APP_OPTION_ARG('c', OPTION_C, OPTION_ARG_C), + AST_APP_OPTION('c', OPTION_CHEAT), + AST_APP_OPTION_ARG('n', OPTION_NUMGAMES, OPTION_ARG_NUMGAMES), }); +/*! \brief A structure to hold global configuration-related options */ +struct skel_global_config { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(prompt); /*!< The comma-separated list of sounds to prompt to enter a number */ + AST_STRING_FIELD(wrong); /*!< The comma-separated list of sounds to indicate a wrong guess */ + AST_STRING_FIELD(right); /*!< The comma-separated list of sounds to indicate a right guess */ + AST_STRING_FIELD(high); /*!< The comma-separated list of sounds to indicate a high guess */ + AST_STRING_FIELD(low); /*!< The comma-separated list of sounds to indicate a low guess */ + AST_STRING_FIELD(lose); /*!< The comma-separated list of sounds to indicate a lost game */ + ); + uint32_t num_games; /*!< The number of games to play before hanging up */ + unsigned char cheat:1; /*!< Whether the computer can cheat or not */ +}; + +/*! \brief A structure to maintain level state across reloads */ +struct skel_level_state { + uint32_t wins; /*!< How many wins for this level */ + uint32_t losses; /*!< How many losses for this level */ + double avg_guesses; /*!< The average number of guesses to win for this level */ +}; + +/*! \brief Object to hold level config information. + * \note This object should hold a reference to an an object that holds state across reloads. + * The other fields are just examples of the kind of data that might be stored in an level. + */ +struct skel_level { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(name); /*!< The name of the level */ + ); + uint32_t max_num; /*!< The upper value on th range of numbers to guess */ + uint32_t max_guesses; /*!< The maximum number of guesses before losing */ + struct skel_level_state *state; /*!< A pointer to level state that must exist across all reloads */ +}; + +/*! \brief Information about a currently running set of games + * \note Because we want to be able to show true running information about the games + * regardless of whether or not a reload has modified what the level looks like, it + * is important to either copy the information we need from the level to the + * current_game struct, or as we do here, store a reference to the level as it is for + * the running game. + */ +struct skel_current_game { + uint32_t total_games; /*! The total number of games for this call to to the app */ + uint32_t games_left; /*! How many games are left to play in this set */ + uint32_t cheat; /*! Whether or not cheating was enabled for the game */ + struct skel_level *level_info; /*! The level information for the running game */ +}; + +/* Treat the levels as an array--there won't be many and this will maintain the order */ +#define LEVEL_BUCKETS 1 + +/*! \brief A container that holds all config-related information + * \note This object should contain a pointer to structs for global data and containers for + * any levels that are configured. Objects of this type will be swapped out on reload. If an + * level needs to maintain state across reloads, it needs to allocate a refcounted object to + * hold that state and ensure that a reference is passed to that state when creating a new + * level for reload. */ +struct skel_config { + struct skel_global_config *global; + struct ao2_container *levels; +}; + +/* Config Options API callbacks */ + +/*! \brief Allocate a skel_config to hold a snapshot of the complete results of parsing a config + * \internal + * \returns A void pointer to a newly allocated skel_config + */ +static void *skel_config_alloc(void); + +/*! \brief Allocate a skel_level based on a category in a configuration file + * \param cat The category to base the level on + * \returns A void pointer to a newly allocated skel_level + */ +static void *skel_level_alloc(const char *cat); + +/*! \brief Find a skel level in the specified container + * \note This function *does not* look for a skel_level in the active container. It is used + * internally by the Config Options code to check if an level has already been added to the + * container that will be swapped for the live container on a successul reload. + * + * \param container A non-active container to search for a level + * \param category The category associated with the level to check for + * \retval non-NULL The level from the container + * \retval NULL The level does not exist in the container + */ +static void *skel_level_find(struct ao2_container *tmp_container, const char *category); + +/*! \brief An aco_type structure to link the "general" category to the skel_global_config type */ +static struct aco_type global_option = { + .type = ACO_GLOBAL, + .item_offset = offsetof(struct skel_config, global), + .category_match = ACO_WHITELIST, + .category = "^general$", +}; + +struct aco_type *global_options[] = ACO_TYPES(&global_option); + +/*! \brief An aco_type structure to link the "sounds" category to the skel_global_config type */ +static struct aco_type sound_option = { + .type = ACO_GLOBAL, + .item_offset = offsetof(struct skel_config, global), + .category_match = ACO_WHITELIST, + .category = "^sounds$", +}; + +struct aco_type *sound_options[] = ACO_TYPES(&sound_option); + +/*! \brief An aco_type structure to link the everything but the "general" and "sounds" categories to the skel_level type */ +static struct aco_type level_option = { + .type = ACO_ITEM, + .category_match = ACO_BLACKLIST, + .category = "^(general|sounds)$", + .item_alloc = skel_level_alloc, + .item_find = skel_level_find, + .item_offset = offsetof(struct skel_config, levels), +}; + +struct aco_type *level_options[] = ACO_TYPES(&level_option); + +struct aco_file app_skel_conf = { + .filename = "app_skel.conf", + .types = ACO_TYPES(&global_option, &sound_option, &level_option), +}; + +/*! \brief A global object container that will contain the skel_config that gets swapped out on reloads */ +static AO2_GLOBAL_OBJ_STATIC(globals); + +/*! \brief The container of active games */ +static struct ao2_container *games; + +/*! \brief Register information about the configs being processed by this module */ +CONFIG_INFO_STANDARD(cfg_info, globals, skel_config_alloc, + .files = ACO_FILES(&app_skel_conf), +); + +static void skel_global_config_destructor(void *obj) +{ + struct skel_global_config *global = obj; + ast_string_field_free_memory(global); +} + +static void skel_game_destructor(void *obj) +{ + struct skel_current_game *game = obj; + ao2_cleanup(game->level_info); +} + +static void skel_state_destructor(void *obj) +{ + return; +} + +static struct skel_current_game *skel_game_alloc(struct skel_level *level) +{ + struct skel_current_game *game; + if (!(game = ao2_alloc(sizeof(struct skel_current_game), skel_game_destructor))) { + return NULL; + } + ao2_ref(level, +1); + game->level_info = level; + return game; +} + +static void skel_level_destructor(void *obj) +{ + struct skel_level *level = obj; + ast_string_field_free_memory(level); + ao2_cleanup(level->state); +} + +static int skel_level_hash(const void *obj, const int flags) +{ + const struct skel_level *level = obj; + const char *name = (flags & OBJ_KEY) ? obj : level->name; + return ast_str_case_hash(name); +} + +static int skel_level_cmp(void *obj, void *arg, int flags) +{ + struct skel_level *one = obj, *two = arg; + const char *match = (flags & OBJ_KEY) ? arg : two->name; + return strcasecmp(one->name, match) ? 0 : (CMP_MATCH | CMP_STOP); +} + +/*! \brief A custom bitfield handler + * \internal + * \note It is not possible to take the address of a bitfield, therefor all + * bitfields in the config struct will have to use a custom handler + * \param opt The opaque config option + * \param var The ast_variable containing the option name and value + * \param obj The object registerd for this option type + * \retval 0 Success + * \retval non-zero Failure + */ +static int custom_bitfield_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct skel_global_config *global = obj; + + if (!strcasecmp(var->name, "cheat")) { + global->cheat = ast_true(var->value); + } else { + return -1; + } + + return 0; +} + +static void play_files_helper(struct ast_channel *chan, const char *prompts) +{ + char *prompt, *rest = ast_strdupa(prompts); + + ast_stopstream(chan); + while ((prompt = strsep(&rest, "&")) && !ast_stream_and_wait(chan, prompt, "")) { + ast_stopstream(chan); + } +} static int app_exec(struct ast_channel *chan, const char *data) { - int res = 0; - struct ast_flags flags; + int win = 0; + uint32_t guesses; + RAII_VAR(struct skel_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct skel_level *, level, NULL, ao2_cleanup); + RAII_VAR(struct skel_current_game *, game, NULL, ao2_cleanup); char *parse, *opts[OPTION_ARG_ARRAY_SIZE]; + struct ast_flags flags; AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(dummy); + AST_APP_ARG(level); AST_APP_ARG(options); ); - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "%s requires an argument (dummy[,options])\n", app); + if (!cfg) { + ast_log(LOG_ERROR, "Couldn't access configuratino data!\n"); return -1; } - /* Do our thing here */ + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "%s requires an argument (level[,options])\n", app); + return -1; + } /* We need to make a copy of the input string if we are going to modify it! */ parse = ast_strdupa(data); @@ -121,34 +348,350 @@ static int app_exec(struct ast_channel *chan, const char *data) ast_app_parse_options(app_opts, &flags, opts, args.options); } - if (!ast_strlen_zero(args.dummy)) { - ast_log(LOG_NOTICE, "Dummy value is : %s\n", args.dummy); + if (ast_strlen_zero(args.level)) { + ast_log(LOG_ERROR, "%s requires a level argument\n", app); + return -1; + } + + if (!(level = ao2_find(cfg->levels, args.level, OBJ_KEY))) { + ast_log(LOG_ERROR, "Unknown level: %s\n", args.level); + return -1; + } + + if (!(game = skel_game_alloc(level))) { + return -1; + } + + ao2_link(games, game); + + /* Use app-specified values, or the options specified in [general] if they aren't passed to the app */ + if (!ast_test_flag(&flags, OPTION_NUMGAMES) || + ast_strlen_zero(opts[OPTION_ARG_NUMGAMES]) || + ast_parse_arg(opts[OPTION_ARG_NUMGAMES], PARSE_UINT32, &game->total_games)) { + game->total_games = cfg->global->num_games; } + game->games_left = game->total_games; + game->cheat = ast_test_flag(&flags, OPTION_CHEAT) || cfg->global->cheat; + + for (game->games_left = game->total_games; game->games_left; game->games_left--) { + uint32_t num = ast_random() % level->max_num; /* random number between 0 and level->max_num */ + + ast_debug(1, "They should totally should guess %u\n", num); + + /* Play the prompt */ + play_files_helper(chan, cfg->global->prompt); + ast_say_number(chan, level->max_num, "", ast_channel_language(chan), ""); + + for (guesses = 0; guesses < level->max_guesses; guesses++) { + size_t buflen = log10(level->max_num) + 1; + char buf[buflen]; + int guess; + buf[buflen] = '\0'; - if (ast_test_flag(&flags, OPTION_A)) { - ast_log(LOG_NOTICE, "Option A is set\n"); + /* Read the number pressed */ + ast_readstring(chan, buf, buflen - 1, 2000, 10000, ""); + if (ast_parse_arg(buf, PARSE_INT32 | PARSE_IN_RANGE, &guess, 0, level->max_num)) { + if (guesses < level->max_guesses - 1) { + play_files_helper(chan, cfg->global->wrong); + } + continue; + } + + /* Inform whether the guess was right, low, or high */ + if (guess == num && !game->cheat) { + /* win */ + win = 1; + play_files_helper(chan, cfg->global->right); + guesses++; + break; + } else if (guess < num) { + play_files_helper(chan, cfg->global->low); + } else { + play_files_helper(chan, cfg->global->high); + } + + if (guesses < level->max_guesses - 1) { + play_files_helper(chan, cfg->global->wrong); + } + } + + /* Process game stats */ + ao2_lock(level->state); + if (win) { + ++level->state->wins; + level->state->avg_guesses = ((level->state->wins - 1) * level->state->avg_guesses + guesses) / level->state->wins; + } else { + /* lose */ + level->state->losses++; + play_files_helper(chan, cfg->global->lose); + } + ao2_unlock(level->state); } - if (ast_test_flag(&flags, OPTION_B)) { - ast_log(LOG_NOTICE, "Option B is set with : %s\n", opts[OPTION_ARG_B] ? opts[OPTION_ARG_B] : ""); + ao2_unlink(games, game); + + return 0; +} + +static struct skel_level *skel_state_alloc(const char *name) +{ + struct skel_level *level; + + if (!(level = ao2_alloc(sizeof(*level), skel_state_destructor))) { + return NULL; } - if (ast_test_flag(&flags, OPTION_C)) { - ast_log(LOG_NOTICE, "Option C is set with : %s\n", opts[OPTION_ARG_C] ? opts[OPTION_ARG_C] : ""); + return level; +} + +static void *skel_level_find(struct ao2_container *tmp_container, const char *category) +{ + return ao2_find(tmp_container, category, OBJ_KEY); +} + +/*! \brief Look up an existing state object, or create a new one + * \internal + * \note Since the reload code will create a new level from scratch, it + * is important for any state that must persist between reloads to be + * in a separate refcounted object. This function allows the level alloc + * function to get a ref to an existing state object if it exists, + * otherwise it will return a reference to a newly allocated state object. + */ +static void *skel_find_or_create_state(const char *category) +{ + RAII_VAR(struct skel_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct skel_level *, level, NULL, ao2_cleanup); + if (!cfg || !cfg->levels || !(level = ao2_find(cfg->levels, category, OBJ_KEY))) { + return skel_state_alloc(category); } + ao2_ref(level->state, +1); + return level->state; +} - return res; +static void *skel_level_alloc(const char *cat) +{ + struct skel_level *level; + + if (!(level = ao2_alloc(sizeof(*level), skel_level_destructor))) { + return NULL; + } + + if (ast_string_field_init(level, 128)) { + ao2_ref(level, -1); + return NULL; + } + + /* Since the level has state information that needs to persist between reloads, + * it is important to handle that here in the level's allocation function. + * If not separated out into its own object, the data would be destroyed on + * reload. */ + if (!(level->state = skel_find_or_create_state(cat))) { + ao2_ref(level, -1); + return NULL; + } + + ast_string_field_set(level, name, cat); + + return level; +} + +static void skel_config_destructor(void *obj) +{ + struct skel_config *cfg = obj; + ao2_cleanup(cfg->global); + ao2_cleanup(cfg->levels); +} + +static void *skel_config_alloc(void) +{ + struct skel_config *cfg; + + if (!(cfg = ao2_alloc(sizeof(*cfg), skel_config_destructor))) { + return NULL; + } + + /* Allocate/initialize memory */ + if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), skel_global_config_destructor))) { + goto error; + } + + if (ast_string_field_init(cfg->global, 128)) { + goto error; + } + + if (!(cfg->levels = ao2_container_alloc(LEVEL_BUCKETS, skel_level_hash, skel_level_cmp))) { + goto error; + } + + return cfg; +error: + ao2_ref(cfg, -1); + return NULL; +} + +static char *handle_skel_show_config(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + RAII_VAR(struct skel_config *, cfg, NULL, ao2_cleanup); + + switch(cmd) { + case CLI_INIT: + e->command = "skel show config"; + e->usage = + "Usage: skel show config\n" + " List app_skel global config\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (!(cfg = ao2_global_obj_ref(globals)) || !cfg->global) { + return NULL; + } + + ast_cli(a->fd, "games per call: %u\n", cfg->global->num_games); + ast_cli(a->fd, "computer cheats: %s\n", AST_CLI_YESNO(cfg->global->cheat)); + ast_cli(a->fd, "\n"); + ast_cli(a->fd, "Sounds\n"); + ast_cli(a->fd, " prompt: %s\n", cfg->global->prompt); + ast_cli(a->fd, " wrong guess: %s\n", cfg->global->wrong); + ast_cli(a->fd, " right guess: %s\n", cfg->global->right); + + return CLI_SUCCESS; +} + +static char *handle_skel_show_games(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ao2_iterator iter; + struct skel_current_game *game; + + switch(cmd) { + case CLI_INIT: + e->command = "skel show games"; + e->usage = + "Usage: skel show games\n" + " List app_skel active games\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + +#define SKEL_FORMAT "%-15.15s %-15.15s %-15.15s\n" +#define SKEL_FORMAT1 "%-15.15s %-15u %-15u\n" + ast_cli(a->fd, SKEL_FORMAT, "Level", "Total Games", "Games Left"); + iter = ao2_iterator_init(games, 0); + while ((game = ao2_iterator_next(&iter))) { + ast_cli(a->fd, SKEL_FORMAT1, game->level_info->name, game->total_games, game->games_left); + ao2_ref(game, -1); + } + ao2_iterator_destroy(&iter); +#undef SKEL_FORMAT +#undef SKEL_FORMAT1 + return CLI_SUCCESS; +} + +static char *handle_skel_show_levels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + RAII_VAR(struct skel_config *, cfg, NULL, ao2_cleanup); + struct ao2_iterator iter; + struct skel_level *level; + + switch(cmd) { + case CLI_INIT: + e->command = "skel show levels"; + e->usage = + "Usage: skel show levels\n" + " List the app_skel levels\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (!(cfg = ao2_global_obj_ref(globals)) || !cfg->levels) { + return NULL; + } + +#define SKEL_FORMAT "%-15.15s %-11.11s %-12.12s %-8.8s %-8.8s %-12.12s\n" +#define SKEL_FORMAT1 "%-15.15s %-11u %-12u %-8u %-8u %-8f\n" + ast_cli(a->fd, SKEL_FORMAT, "Name", "Max number", "Max Guesses", "Wins", "Losses", "Avg Guesses"); + iter = ao2_iterator_init(cfg->levels, 0); + while ((level = ao2_iterator_next(&iter))) { + ast_cli(a->fd, SKEL_FORMAT1, level->name, level->max_num, level->max_guesses, level->state->wins, level->state->losses, level->state->avg_guesses); + ao2_ref(level, -1); + } + ao2_iterator_destroy(&iter); +#undef SKEL_FORMAT +#undef SKEL_FORMAT1 + + return CLI_SUCCESS; +} + +static struct ast_cli_entry skel_cli[] = { + AST_CLI_DEFINE(handle_skel_show_config, "Show app_skel global config options"), + AST_CLI_DEFINE(handle_skel_show_levels, "Show app_skel levels"), + AST_CLI_DEFINE(handle_skel_show_games, "Show app_skel active games"), +}; + +static int reload_module(void) +{ + if (aco_process_config(&cfg_info, 1)) { + return AST_MODULE_LOAD_DECLINE; + } + + return 0; } static int unload_module(void) { + ast_cli_unregister_multiple(skel_cli, ARRAY_LEN(skel_cli)); + aco_info_destroy(&cfg_info); + ao2_global_obj_release(globals); return ast_unregister_application(app); } static int load_module(void) { - return ast_register_application_xml(app, app_exec) ? - AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS; + if (aco_info_init(&cfg_info)) { + goto error; + } + if (!(games = ao2_container_alloc(1, NULL, NULL))) { + goto error; + } + + /* Global options */ + aco_option_register(&cfg_info, "games", ACO_EXACT, global_options, "3", OPT_UINT_T, 0, FLDSET(struct skel_global_config, num_games)); + aco_option_register_custom(&cfg_info, "cheat", ACO_EXACT, global_options, "no", custom_bitfield_handler, 0); + + /* Sound options */ + aco_option_register(&cfg_info, "prompt", ACO_EXACT, sound_options, "please-enter-your&number&queue-less-than", OPT_STRINGFIELD_T, 0, STRFLDSET(struct skel_global_config, prompt)); + aco_option_register(&cfg_info, "wrong_guess", ACO_EXACT, sound_options, "vm-pls-try-again", OPT_STRINGFIELD_T, 0, STRFLDSET(struct skel_global_config, wrong)); + aco_option_register(&cfg_info, "right_guess", ACO_EXACT, sound_options, "auth-thankyou", OPT_STRINGFIELD_T, 0, STRFLDSET(struct skel_global_config, right)); + aco_option_register(&cfg_info, "too_high", ACO_EXACT, sound_options, "high", OPT_STRINGFIELD_T, 0, STRFLDSET(struct skel_global_config, high)); + aco_option_register(&cfg_info, "too_low", ACO_EXACT, sound_options, "low", OPT_STRINGFIELD_T, 0, STRFLDSET(struct skel_global_config, low)); + aco_option_register(&cfg_info, "lose", ACO_EXACT, sound_options, "vm-goodbye", OPT_STRINGFIELD_T, 0, STRFLDSET(struct skel_global_config, lose)); + + /* Level options */ + aco_option_register(&cfg_info, "max_number", ACO_EXACT, level_options, NULL, OPT_UINT_T, 0, FLDSET(struct skel_level, max_num)); + aco_option_register(&cfg_info, "max_guesses", ACO_EXACT, level_options, NULL, OPT_UINT_T, 1, FLDSET(struct skel_level, max_guesses)); + + if (aco_process_config(&cfg_info, 0)) { + goto error; + } + + ast_cli_register_multiple(skel_cli, ARRAY_LEN(skel_cli)); + if (ast_register_application_xml(app, app_exec)) { + goto error; + } + return AST_MODULE_LOAD_SUCCESS; + +error: + aco_info_destroy(&cfg_info); + ao2_cleanup(games); + return AST_MODULE_LOAD_DECLINE; } -AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Skeleton (sample) Application"); +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Skeleton (sample) Application", + .load = load_module, + .unload = unload_module, + .reload = reload_module, + .load_pri = AST_MODPRI_DEFAULT, +); diff --git a/configs/app_skel.conf.sample b/configs/app_skel.conf.sample new file mode 100644 index 000000000..ada8461e3 --- /dev/null +++ b/configs/app_skel.conf.sample @@ -0,0 +1,27 @@ +[general] +games=3 +cheat=no + +[sounds] +prompt=please-enter-your,number,queue-less-than +wrong_guess=vm-pls-try-again +right_guess=auth-thankyou +too_high=high +too_low=low +lose=vm-goodbye + +[easy] +max_number=10 +max_guesses=4 + +[medium] +max_number=100 +max_guesses=6 + +[hard] +max_number=1000 +max_guesses=7 + +[nightmare] +max_number=1000 +max_guesses=1 diff --git a/configure b/configure index 42730ee74..2a6d3564a 100755 --- a/configure +++ b/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.ac Revision: 364444 . +# From configure.ac Revision: 366649 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.68 for asterisk trunk. # @@ -662,6 +662,7 @@ AST_NATIVE_ARCH AST_SHADOW_WARNINGS AST_NO_STRICT_OVERFLOW AST_FORTIFY_SOURCE +AST_TRAMPOLINES AST_DECLARATION_AFTER_STATEMENT GC_LDFLAGS GC_CFLAGS @@ -15914,6 +15915,19 @@ $as_echo "no" >&6; } fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for -Wtrampolines support" >&5 +$as_echo_n "checking for -Wtrampolines support... " >&6; } +if $(${CC} -Wtrampolines -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + AST_TRAMPOLINES=-Wtrampolines +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + AST_TRAMPOLINES= +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for _FORTIFY_SOURCE support" >&5 $as_echo_n "checking for _FORTIFY_SOURCE support... " >&6; } if $(${CC} -D_FORTIFY_SOURCE=2 -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then diff --git a/configure.ac b/configure.ac index d94f58dac..06f5fdd58 100644 --- a/configure.ac +++ b/configure.ac @@ -980,6 +980,16 @@ else fi AC_SUBST(AST_DECLARATION_AFTER_STATEMENT) +AC_MSG_CHECKING(for -Wtrampolines support) +if $(${CC} -Wtrampolines -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then + AC_MSG_RESULT(yes) + AST_TRAMPOLINES=-Wtrampolines +else + AC_MSG_RESULT(no) + AST_TRAMPOLINES= +fi +AC_SUBST(AST_TRAMPOLINES) + AC_MSG_CHECKING(for _FORTIFY_SOURCE support) if $(${CC} -D_FORTIFY_SOURCE=2 -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then AC_MSG_RESULT(yes) diff --git a/include/asterisk/astobj2.h b/include/asterisk/astobj2.h index 15ed4892e..a5068ebe6 100644 --- a/include/asterisk/astobj2.h +++ b/include/asterisk/astobj2.h @@ -1461,4 +1461,10 @@ void *__ao2_iterator_next(struct ao2_iterator *iter); /* extra functions */ void ao2_bt(void); /* backtrace */ +/*! gcc __attribute__(cleanup()) functions + * \note they must be able to handle NULL parameters because most of the + * allocation/find functions can fail and we don't want to try to tear + * down a NULL */ +void ao2_cleanup(void *obj); +void ao2_iterator_cleanup(struct ao2_iterator *iter); #endif /* _ASTERISK_ASTOBJ2_H */ diff --git a/include/asterisk/config.h b/include/asterisk/config.h index 86c2bb5dd..5e71d80b1 100644 --- a/include/asterisk/config.h +++ b/include/asterisk/config.h @@ -644,8 +644,9 @@ enum ast_parse_flags { * the range (inclusive). An error is returned if the value * is outside or inside the range, respectively. */ - PARSE_IN_RANGE = 0x0020, /* accept values inside a range */ - PARSE_OUT_RANGE = 0x0040, /* accept values outside a range */ + PARSE_IN_RANGE = 0x0020, /* accept values inside a range */ + PARSE_OUT_RANGE = 0x0040, /* accept values outside a range */ + PARSE_RANGE_DEFAULTS = 0x0080, /* default to range min/max on range error */ /* Port handling, for ast_sockaddr. accept/ignore/require/forbid * port number after the hostname or address. @@ -661,32 +662,32 @@ enum ast_parse_flags { * * \param arg the string to parse. It is not modified. * \param flags combination of ast_parse_flags to specify the - * return type and additional checks. + * return type and additional checks. * \param result pointer to the result. NULL is valid here, and can - * be used to perform only the validity checks. + * be used to perform only the validity checks. * \param ... extra arguments are required according to flags. * * \retval 0 in case of success, != 0 otherwise. * \retval result returns the parsed value in case of success, - * the default value in case of error, or it is left unchanged - * in case of error and no default specified. Note that in certain - * cases (e.g. sockaddr_in, with multi-field return values) some - * of the fields in result may be changed even if an error occurs. + * the default value in case of error, or it is left unchanged + * in case of error and no default specified. Note that in certain + * cases (e.g. sockaddr_in, with multi-field return values) some + * of the fields in result may be changed even if an error occurs. * * \details * Examples of use: - * ast_parse_arg("223", PARSE_INT32|PARSE_IN_RANGE, - * &a, -1000, 1000); - * returns 0, a = 223 - * ast_parse_arg("22345", PARSE_INT32|PARSE_IN_RANGE|PARSE_DEFAULT, - * &a, 9999, 10, 100); - * returns 1, a = 9999 - * ast_parse_arg("22345ssf", PARSE_UINT32|PARSE_IN_RANGE, &b, 10, 100); - * returns 1, b unchanged - * ast_parse_arg("www.foo.biz:44", PARSE_INADDR, &sa); - * returns 0, sa contains address and port - * ast_parse_arg("www.foo.biz", PARSE_INADDR|PARSE_PORT_REQUIRE, &sa); - * returns 1 because port is missing, sa contains address + * ast_parse_arg("223", PARSE_INT32|PARSE_IN_RANGE, &a, -1000, 1000); + * returns 0, a = 223 + * ast_parse_arg("22345", PARSE_INT32|PARSE_IN_RANGE|PARSE_DEFAULT, &a, 9999, 10, 100); + * returns 1, a = 9999 + * ast_parse_arg("22345ssf", PARSE_UINT32|PARSE_IN_RANGE, &b, 10, 100); + * returns 1, b unchanged + * ast_parse_arg("12", PARSE_UINT32|PARSE_IN_RANGE|PARSE_RANGE_DEFAULTS, &a, 1, 10); + * returns 1, a = 10 + * ast_parse_arg("www.foo.biz:44", PARSE_INADDR, &sa); + * returns 0, sa contains address and port + * ast_parse_arg("www.foo.biz", PARSE_INADDR|PARSE_PORT_REQUIRE, &sa); + * returns 1 because port is missing, sa contains address */ int ast_parse_arg(const char *arg, enum ast_parse_flags flags, void *result, ...); @@ -696,7 +697,7 @@ int ast_parse_arg(const char *arg, enum ast_parse_flags flags, * string in a switch() statement, yet we need a similar behaviour, with many * branches and a break on a matching one. * The following somehow simplifies the job: we create a block using - * the CV_START and CV_END macros, and then within the block we can run + * the CV_START and CV_END macros, and then within the block we can run * actions such as "if (condition) { body; break; }" * Additional macros are present to run simple functions (e.g. ast_copy_string) * or to pass arguments to ast_parse_arg() diff --git a/include/asterisk/config_options.h b/include/asterisk/config_options.h new file mode 100644 index 000000000..a1b7f3714 --- /dev/null +++ b/include/asterisk/config_options.h @@ -0,0 +1,482 @@ +/* + * 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 + */ + +#ifndef _ASTERISK_CONFIG_OPTIONS_H +#define _ASTERISK_CONFIG_OPTIONS_H + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +#include + +#include "asterisk/config.h" +#include "asterisk/astobj2.h" + +struct aco_option; +struct aco_info_internal; +struct aco_type_internal; + +enum aco_type_t { + ACO_GLOBAL, + ACO_ITEM, +}; + +/*! \brief Whether a category regex is a blackist or a whitelist */ +enum aco_category_op { + ACO_BLACKLIST = 0, + ACO_WHITELIST, +}; + +/*! \brief What kind of matching should be done on an option name */ +enum aco_matchtype { + ACO_EXACT = 1, + ACO_REGEX, +}; + +/*! Callback functions for option parsing via aco_process_config() */ + +/*! \brief Allocate a configurable ao2 object + * \param category The config category the object is being generated for + * \retval NULL error + * \retval non-NULL a new configurable ao2 object + */ +typedef void *(*aco_type_item_alloc)(const char *category); + +/*! \brief Find a item given a category and container of items + * \param container The container to search for the item + * \param category The category associated with the item + * \retval non-NULL item from the container + * \retval NULL item does not exist in container + */ +typedef void *(*aco_type_item_find)(struct ao2_container *newcontainer, const char *category); + +/*! \brief Callback function that is called after a config object is initialized with defaults + * + * \note This callback is called during config processing after a new config is allocated and + * and defaults applied but before values from the config are read. This callback could be used + * to merge in settings inherited from the global settings if necessary, despite that being a + * bad thing to do! + * + * \param newitem The newly allocated config object with defaults populated + * \retval 0 succes, continue processing + * \retval non-zero failure, stop processing + */ +typedef int (*aco_type_item_pre_process)(void *newitem); + +/*! \brief Callback function that is called after config processing, but before linking + * + * \note This callback is called after config processing, but before linking the object + * in the config container. This callback can be used to verify that all settings make + * sense together, that required options have been set, etc. + * + * \param newitem The newly configured object + * \retval 0 success, continue processing + * \retval non-zero failure, stop processing + */ +typedef int (*aco_type_prelink)(void *newitem); + +/*! \brief A function for determining whether the value for the matchfield in an aco_type is sufficient for a match + * \param text The value of the option + * \retval -1 The value is sufficient for a match + * \retval 0 The value is not sufficient for a match + */ +typedef int (*aco_matchvalue_func)(const char *text); + +/*! \struct aco_type + * \brief Type information about a category-level configurable object + */ +struct aco_type { + /* common stuff */ + enum aco_type_t type; /*!< Whether this is a global or item type */ + const char *category; /*!< A regular expression for matching categories to be allowed or denied */ + const char *matchfield; /*!< An option name to match for this type (i.e. a 'type'-like column) */ + const char *matchvalue; /*!< The value of the option to require for matching (i.e. 'peer' for type= in sip.conf) */ + aco_matchvalue_func matchfunc; /*!< A function for determing whether the option value matches (i.e. hassip= requires ast_true()) */ + enum aco_category_op category_match; /*!< Whether the following category regex is a whitelist or blacklist */ + size_t item_offset; /*!< The offset in the config snapshot for the global config or item config container */ + + /* non-global callbacks */ + aco_type_item_alloc item_alloc; /*!< An allocation function for item associated with this type */ + aco_type_item_find item_find; /*!< A callback function to find an existing item in a particular container */ + aco_type_item_pre_process item_pre_process; /*!< An optional callback function that is called after defaults are applied, but before config processing */ + aco_type_prelink item_prelink; /*!< An optional callback function that is called after config processing, but before applying changes */ + struct aco_type_internal *internal; +}; + +/*! \brief A callback function for applying the config changes + * \retval 0 Success + * \retval non-zero Failure. Changes not applied + */ +typedef int (*aco_pre_apply_config)(void); + +/*! \brief A callback functino for allocating an object to hold all config objects + * \retval NULL error + * \retval non-NULL a config object container + */ +typedef void *(*aco_snapshot_alloc)(void); + +struct aco_file { + const char *filename; + const char **preload; + struct aco_type *types[]; /*!< The list of types for this config. Required. Use a sentinel! */ +}; + +struct aco_info { + const char *module; /*!< The name of the module whose config is being processed */ + aco_pre_apply_config pre_apply_config; /*!< A callback called after processing, but before changes are applied */ + aco_snapshot_alloc snapshot_alloc; /*!< Allocate an object to hold all global configs and item containers */ + struct ao2_global_obj *global_obj; /*!< The global object array that holds the user-defined config object */ + struct aco_info_internal *internal; + struct aco_file *files[]; /*!< The config filename */ +}; + +/*! \brief A helper macro to ensure that aco_info types always have a sentinel */ +#define ACO_TYPES(...) { __VA_ARGS__, NULL, } +#define ACO_FILES(...) { __VA_ARGS__, NULL, } + +/*! \brief Get pending config changes + * \note This will most likely be called from the pre_apply_config callback function + * \param info An initialized aco_info + * \retval NULL error + * \retval non-NULL A pointer to the user-defined config object with un-applied changes + */ +void *aco_pending_config(struct aco_info *info); + +/*! \def CONFIG_INFO_STANDARD + * \brief Declare an aco_info struct with default module and preload values + * \param name The name of the struct + * \param fn The filename of the config + * \param arr The global object array for holding the user-defined config object + * \param alloc The allocater for the user-defined config object + * + * Example: + * \code + * static AO2_GLOBAL_OBJ_STATIC(globals, 1); + * CONFIG_INFO_STANDARD(cfg_info, globals, skel_config_alloc, + * .pre_apply_config = skel_pre_apply_config, + * .files = { &app_skel_conf, NULL }, + * ); + * ... + * if (aco_info_init(&cfg_info)) { + * return AST_MODULE_LOAD_DECLINE; + * } + * ... + * aco_info_destroy(&cfg_info); + * \endcode + */ +#define CONFIG_INFO_STANDARD(name, arr, alloc, ...) \ +static struct aco_info name = { \ + .module = AST_MODULE, \ + .global_obj = &arr, \ + .snapshot_alloc = alloc, \ + __VA_ARGS__ \ +}; + +/*! \brief Initialize an aco_info structure + * \note aco_info_destroy must be called if this succeeds + * \param info The address of an aco_info struct to initialize + * \retval 0 Success + * \retval non-zero Failure + */ +int aco_info_init(struct aco_info *info); + +/*! \brief Destroy an initialized aco_info struct + * \param info The address of the aco_info struct to destroy + */ +void aco_info_destroy(struct aco_info *info); + +/*! \brief The option types with default handlers + * + * \note aco_option_register takes an option type which is used + * to look up the handler for that type. Each non-custom type requires + * field names for specific types in the struct being configured. Each + * option below is commented with the field types, *in the order + * they must be passed* to aco_option_register. The fields + * are located in the args array in the ast_config_option passed to + * the default handler function. + * */ +enum aco_option_type { + OPT_ACL_T, /*!< fields: struct ast_ha * */ + OPT_BOOL_T, /*!< fields: unsigned int */ + OPT_CODEC_T, /*!< fields: struct ast_codec pref, struct ast_format_cap * */ + OPT_CUSTOM_T, /*!< fields: none */ + OPT_DOUBLE_T, /*!< fields: double */ + OPT_INT_T, /*!< fields: int */ + OPT_SOCKADDR_T, /*!< fields: struct ast_sockaddr */ + OPT_STRINGFIELD_T, /*!< fields: ast_string_field */ + OPT_UINT_T, /*!< fields: unsigned int */ +}; + +/*! \brief A callback function for handling a particular option + * \param opt The option being configured + * \param var The config variable to use to configure \a obj + * \param obj The object to be configured + * + * \retval 0 Parsing and recording the config value succeeded + * \retval non-zero Failure. Parsing should stop and no reload applied + */ +typedef int (*aco_option_handler)(const struct aco_option *opt, struct ast_variable *var, void *obj); + +/*! \brief Allocate a container to hold config options */ +struct ao2_container *aco_option_container_alloc(void); + +/*! \brief Process a config info via the options registered with an aco_info + * + * \param info The config_options_info to be used for handling the config + * \param reload Whether or not this is a reload + * + * \retval 0 Success + * \retval -1 Failure + */ +int aco_process_config(struct aco_info *info, int reload); + +/*! \brief Process config info from an ast_config via options registered with an aco_info + * + * \param info The aco_info to be used for handling the config + * \param file The file attached to aco_info that the config represents + * \param cfg A pointer to a loaded ast_config to parse + * \param reload Whether or not this is a reload + * + * \retval 0 Success + * \retval -1 Failure + */ +int aco_process_ast_config(struct aco_info *info, struct aco_file *file, struct ast_config *cfg); + +/*! \brief Parse each option defined in a config category + * \param type The aco_type with the options for parsing + * \param cfg The ast_config being parsed + * \param cat The config category being parsed + * \param obj The user-defined config object that will store the parsed config items + * + * \retval 0 Success + * \retval -1 Failure + */ +int aco_process_category_options(struct aco_type *type, struct ast_config *cfg, const char *cat, void *obj); + +/*! \brief Set all default options of \a obj + * \param info The aco_type with the options + * \param category The configuration category from which \a obj is being configured + * \param obj The object being configured + * + * \retval 0 Success + * \retval -1 Failure + */ +int aco_set_defaults(struct aco_type *type, const char *category, void *obj); + +/*! \brief register a config option + * + * \note this should probably only be called by one of the aco_option_register* macros + * + * \param info The aco_info holding this module's config information + * \param name The name of the option + * \param types An array of valid option types for matching categories to the correct struct type + * \param default_val The default value of the option in the same format as defined in a config file + * \param type The option type (only for default handlers) + * \param handler The handler function for the option (only for non-default types) + * \param flags \a type specific flags, stored in the option and available to the handler + * \param argc The number for variadic arguments + * \param ... field offsets to store for default handlers + * + * \retval 0 success + * \retval -1 failure + */ +int __aco_option_register(struct aco_info *info, const char *name, enum aco_matchtype match_type, struct aco_type **types, + const char *default_val, enum aco_option_type type, aco_option_handler handler, unsigned int flags, size_t argc, ...); + +/*! \brief Register a config option + * \param info A pointer to the aco_info struct + * \param name The name of the option + * \param types An array of valid option types for matching categories to the correct struct type + * \param default_val The default value of the option in the same format as defined in a config file + * \param opt_type The option type for default option type handling + * \param flags \a type specific flags, stored in the option and available to the handler + * + * \returns An option on success, NULL on failure + */ +#define aco_option_register(info, name, matchtype, types, default_val, opt_type, flags, ...) \ + __aco_option_register(info, name, matchtype, types, default_val, opt_type, NULL, flags, VA_NARGS(__VA_ARGS__), __VA_ARGS__); + +/*! \brief Register a config option + * \param info A pointer to the aco_info struct + * \param name The name of the option + * \param types An array of valid option types for matching categories to the correct struct type + * \param default_val The default value of the option in the same format as defined in a config file + * \param handler The handler callback for the option + * \param flags \a type specific flags, stored in the option and available to the handler + * + * \returns An option on success, NULL on failure + */ +#define aco_option_register_custom(info, name, matchtype, type, default_val, handler, flags) \ + __aco_option_register(info, name, matchtype, type, default_val, OPT_CUSTOM_T, handler, flags, 0); + +/*! \note Everything below this point is to handle converting varargs + * containing field names, to varargs containing a count of args, followed + * by the offset of each of the field names in the struct type that is + * passed in. It is currently limited to 8 arguments, but 8 variadic + * arguments, like 640K, should be good enough for anyone. If not, it is + * easy to add more. + * */ + +/*! \def ARGMAP(func, func_arg, x, ...) + * \brief Map \a func(\a func_arg, field) across all fields including \a x + * \param func The function (almost certainly offsetof) to map across the fields + * \param func_arg The first argument (almost certainly a type (e.g. "struct mystruct") + * \param x The first field + * \param varargs The rest of the fields + * + * Example usage: + * \code + * struct foo { + * int a; + * char *b; + * foo *c; + * }; + * ARGMAP(offsetof, struct foo, a, c) + * \endcode + * produces the string: + * \code + * 2, offsetof(struct foo, a), offsetof(struct foo, b) + * \encode + * which can be passed as the varargs to some other function + * + * The macro isn't limited to offsetof, but that is the only purpose for + * which it has been tested. + * + * As an example of how the processing works: + * + * ARGMAP(offsetof, struct foo, a, b, c) -> + * ARGMAP_(3, offsetof, struct foo, a, b, c) -> + * ARGMAP_3(offsetof, struct foo, 3, a, b, c) -> + * ARGMAP_2(offsetof, struct foo, ARGIFY(3, offsetof(struct foo, a)), b, c) -> + * ARGMAP_1(offsetof, struct foo, ARGIFY(3, offsetof(struct foo, a), offsetof(struct foo, b)), c) -> + * ARGIFY(3, offsetof(struct foo, a), offsetof(struct foo, b), offsetof(struct foo, c)) -> + * 3, offsetof(struct foo, a), offsetof(struct foo, b), offsetof(struct foo, c) + */ +#define ARGMAP(func, func_arg, x, ...) ARGMAP_(VA_NARGS(x, ##__VA_ARGS__), func, func_arg, x, __VA_ARGS__) + +/*! \note This is sneaky. On the very first argument, we set "in" to N, the number of arguments, so + * that the accumulation both works properly for the first argument (since "in" can't be empty) and + * we get the number of arguments in our varargs as a bonus */ +#define ARGMAP_(N, func, func_arg, x, ...) PASTE(ARGMAP_, N)(func, func_arg, N, x, __VA_ARGS__) + +/*! \def PASTE(arg1, arg2) + * \brief Paste two arguments together, even if they are macros themselves + * \note Uses two levels to handle the case where arg1 and arg2 are macros themselves + */ +#define PASTE(arg1, arg2) PASTE1(arg1, arg2) +#define PASTE1(arg1, arg2) arg1##arg2 + +/*! \brief Take a comma-separated list and allow it to be passed as a single argument to another macro */ +#define ARGIFY(...) __VA_ARGS__ + +/*! \brief The individual field handlers for ARGMAP + * \param func The function (most likely offsetof) + * \param func_arg The first argument to func (most likely a type e.g. "struct my_struct") + * \param in The accumulated function-mapped field names so far + * \param x The next field name + * \param varargs The rest of the field names + */ +#define ARGMAP_1(func, func_arg, in, x, ...) ARGIFY(in, func(func_arg, x)) +#define ARGMAP_2(func, func_arg, in, x, ...)\ + ARGMAP_1(func, func_arg, ARGIFY(in, func(func_arg, x)), __VA_ARGS__) +#define ARGMAP_3(func, func_arg, in, x, ...)\ + ARGMAP_2(func, func_arg, ARGIFY(in, func(func_arg, x)), __VA_ARGS__) +#define ARGMAP_4(func, func_arg, in, x, ...)\ + ARGMAP_3(func, func_arg, ARGIFY(in, func(func_arg, x)), __VA_ARGS__) +#define ARGMAP_5(func, func_arg, in, x, ...)\ + ARGMAP_4(func, func_arg, ARGIFY(in, func(func_arg, x)), __VA_ARGS__) +#define ARGMAP_6(func, func_arg, in, x, ...)\ + ARGMAP_5(func, func_arg, ARGIFY(in, func(func_arg, x)), __VA_ARGS__) +#define ARGMAP_7(func, func_arg, in, x, ...)\ + ARGMAP_6(func, func_arg, ARGIFY(in, func(func_arg, x)), __VA_ARGS__) +#define ARGMAP_8(func, func_arg, in, x, ...)\ + ARGMAP_7(func, func_arg, ARGIFY(in, func(func_arg, x)), __VA_ARGS__) + +/*! \def VA_NARGS(...) + * \brief Results in the number of arguments passed to it + * \note Currently only up to 8, but expanding is easy. This macro basically counts + * commas + 1. To visualize: + * + * VA_NARGS(one, two, three) -> v + * VA_NARGS1(one, two, three, 8, 7, 6, 5, 4, 3, 2, 1, 0) -> + * VA_NARGS1( _1, _2, _3, _4, _5, _6, _7, _8, N, ... ) N -> 3 + * + * Note that VA_NARGS *does not* work when there are no arguments passed. Pasting an empty + * __VA_ARGS__ with a comma like ", ##__VA_ARGS__" will delete the leading comma, but it + * does not work when __VA_ARGS__ is the first argument. Instead, 1 is returned instead of 0: + * + * VA_NARGS() -> v + * VA_NARGS1( , 8, 7, 6, 5, 4, 3, 2, 1, 0) -> + * VA_NARGS1(_1, _2, _3, _4, _5, _6, _7, _8, N) -> 1 + */ +#define VA_NARGS(...) VA_NARGS1(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#define VA_NARGS1(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N + +/*! \def FLDSET(type, ...) + * \brief Convert a struct and list of fields to an argument list of field offsets + * \param type The type with the fields (e.g. "struct my_struct") + * \param varags The fields in the struct whose offsets are needed as arguments + * + * For example: + * \code + * struct foo {int a, char b[128], char *c}; + * FLDSET(struct foo, a, c) + * \endcode + * + * produces + * \code + * offsetof(struct foo, a), offsetof(struct foo, c) + * \endcode + */ +#define FLDSET(type, ...) FLDSET1(type, ##__VA_ARGS__) +#define FLDSET1(type, ...) POPPED(ARGMAP(offsetof, type, ##__VA_ARGS__)) + +/*! \def STRFLDSET(type, ...) + * \brief Convert a struct and a list of stringfield fields to an argument list of field offsets + * \note Stringfields require the passing of the field manager pool, and field manager to the + * default stringfield option handler, so registering options that point to stringfields requires + * this macro to be called instead of the FLDSET macro. + * \param type The type with the fields (e.g. "struct my_struct") + * \param varargs The fields in the struct whose offsets are needed as arguments + */ +#define STRFLDSET(type, ...) FLDSET(type, __VA_ARGS__, __field_mgr_pool, __field_mgr) + +/*! \def POPPED(...) + * \brief A list of arguments without the first argument + * \note Used internally to remove the leading "number of arguments" argument from ARGMAP for + * FLDSET. This is because a call to FLDSET may be followed by additional arguments in + * aco_register_option, so the true number of arguments will possibly be different than what + * ARGMAP returns. + * \params varags A list of arguments + * + * POPPED(a, b, c) -> b, c + */ +#define POPPED(...) POPPED1(__VA_ARGS__) +#define POPPED1(x, ...) __VA_ARGS__ + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* _ASTERISK_CONFIG_OPTIONS_H */ diff --git a/include/asterisk/stringfields.h b/include/asterisk/stringfields.h index 86e265beb..b5fdf9c8e 100644 --- a/include/asterisk/stringfields.h +++ b/include/asterisk/stringfields.h @@ -310,25 +310,27 @@ void __ast_string_field_release_active(struct ast_string_field_pool *pool_head, \brief Set a field to a simple string value \param x Pointer to a structure containing fields \param ptr Pointer to a field within the structure - \param data String value to be copied into the field + \param data String value to be copied into the field \return nothing */ -#define ast_string_field_ptr_set(x, ptr, data) do { \ - const char *__d__ = (data); \ - size_t __dlen__ = (__d__) ? strlen(__d__) + 1 : 1; \ - ast_string_field *__p__ = (ast_string_field *) (ptr); \ - if (__dlen__ == 1) { \ - __ast_string_field_release_active((x)->__field_mgr_pool, *__p__); \ - *__p__ = __ast_string_field_empty; \ - } else if ((__dlen__ <= AST_STRING_FIELD_ALLOCATION(*__p__)) || \ - (!__ast_string_field_ptr_grow(&(x)->__field_mgr, &(x)->__field_mgr_pool, __dlen__, __p__)) || \ - (*__p__ = __ast_string_field_alloc_space(&(x)->__field_mgr, &(x)->__field_mgr_pool, __dlen__))) { \ - if (*__p__ != (*ptr)) { \ - __ast_string_field_release_active((x)->__field_mgr_pool, (*ptr)); \ - } \ - memcpy(* (void **) __p__, __d__, __dlen__); \ - } \ - } while (0) +#define ast_string_field_ptr_set(x, ptr, data) ast_string_field_ptr_set_by_fields((x)->__field_mgr_pool, (x)->__field_mgr, ptr, data) + +#define ast_string_field_ptr_set_by_fields(field_mgr_pool, field_mgr, ptr, data) do { \ + const char *__d__ = (data); \ + size_t __dlen__ = (__d__) ? strlen(__d__) + 1 : 1; \ + ast_string_field *__p__ = (ast_string_field *) (ptr); \ + if (__dlen__ == 1) { \ + __ast_string_field_release_active(field_mgr_pool, *__p__); \ + *__p__ = __ast_string_field_empty; \ + } else if ((__dlen__ <= AST_STRING_FIELD_ALLOCATION(*__p__)) || \ + (!__ast_string_field_ptr_grow(&field_mgr, &field_mgr_pool, __dlen__, __p__)) || \ + (*__p__ = __ast_string_field_alloc_space(&field_mgr, &field_mgr_pool, __dlen__))) { \ + if (*__p__ != (*ptr)) { \ + __ast_string_field_release_active(field_mgr_pool, (*ptr)); \ + } \ + memcpy(* (void **) __p__, __d__, __dlen__); \ + } \ + } while (0) /*! \brief Set a field to a simple string value diff --git a/include/asterisk/utils.h b/include/asterisk/utils.h index c33f42322..4ebd3ead8 100644 --- a/include/asterisk/utils.h +++ b/include/asterisk/utils.h @@ -863,4 +863,51 @@ int ast_get_tid(void); */ char *ast_utils_which(const char *binary, char *fullpath, size_t fullpath_size); +/*! \brief Declare a variable that will call a destructor function when it goes out of scope + * \param vartype The type of the variable + * \param varname The name of the variable + * \param initval The initial value of the variable + * \param dtor The destructor function of type' void func(vartype *)' + * + * \code + * void mything_cleanup(struct mything *t) + * { + * if (t) { + * ast_free(t->stuff); + * } + * } + * + * void do_stuff(const char *name) + * { + * RAII_VAR(struct mything *, thing, mything_alloc(name), mything_cleanup); + * ... + * } + * + * \note This macro is especially useful for working with ao2 objects. A common idiom + * would be a function that needed to look up an ao2 object and might have several error + * conditions after the allocation that would normally need to unref the ao2 object. + * With RAII_VAR, it is possible to just return and leave the cleanup to the destructor + * function. For example: + * \code + * void do_stuff(const char *name) + * { + * RAII_VAR(struct mything *, thing, find_mything(name), ao2_cleanup); + * if (!thing) { + * return; + * } + * if (error) { + * return; + * } + * do_stuff_with_thing(thing); + * return; + * } + * } + * \encode + * + */ +#define RAII_VAR(vartype, varname, initval, dtor) \ + auto void _dtor_ ## varname (vartype * v); \ + auto void _dtor_ ## varname (vartype * v) { dtor(*v); } \ + vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval) + #endif /* _ASTERISK_UTILS_H */ 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); } diff --git a/makeopts.in b/makeopts.in index 0ca01aa86..a1f97b875 100644 --- a/makeopts.in +++ b/makeopts.in @@ -101,6 +101,7 @@ AST_CODE_COVERAGE=@AST_CODE_COVERAGE@ AST_ASTERISKSSL=@AST_ASTERISKSSL@ AST_DECLARATION_AFTER_STATEMENT=@AST_DECLARATION_AFTER_STATEMENT@ +AST_TRAMPOLINES=@AST_TRAMPOLINES@ AST_NO_STRICT_OVERFLOW=@AST_NO_STRICT_OVERFLOW@ AST_SHADOW_WARNINGS=@AST_SHADOW_WARNINGS@ AST_FORTIFY_SOURCE=@AST_FORTIFY_SOURCE@ diff --git a/tests/test_config.c b/tests/test_config.c index 1a58e46ff..c8f7a077b 100644 --- a/tests/test_config.c +++ b/tests/test_config.c @@ -38,6 +38,11 @@ ASTERISK_FILE_VERSION(__FILE__, "") #include "asterisk/test.h" #include "asterisk/module.h" #include "asterisk/config.h" +#include "asterisk/config_options.h" +#include "asterisk/netsock2.h" +#include "asterisk/acl.h" +#include "asterisk/frame.h" +#include "asterisk/utils.h" #include "asterisk/logger.h" enum { @@ -274,15 +279,311 @@ AST_TEST_DEFINE(ast_parse_arg_test) return ret; } +struct test_item { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(name); + AST_STRING_FIELD(stropt); + ); + int32_t intopt; + uint32_t uintopt; + double doubleopt; + struct ast_sockaddr sockaddropt; + int boolopt; + struct ast_ha *aclopt; + struct ast_codec_pref codecprefopt; + struct ast_format_cap *codeccapopt; + unsigned int customopt:1; +}; +struct test_config { + struct test_item *global; + struct test_item *global_defaults; + struct ao2_container *items; +}; + +static int test_item_hash(const void *obj, const int flags) +{ + const struct test_item *item = obj; + const char *name = (flags & OBJ_KEY) ? obj : item->name; + return ast_str_case_hash(name); +} +static int test_item_cmp(void *obj, void *arg, int flags) +{ + struct test_item *one = obj, *two = arg; + const char *match = (flags & OBJ_KEY) ? arg : two->name; + return strcasecmp(one->name, match) ? 0 : (CMP_MATCH | CMP_STOP); +} +static void test_item_destructor(void *obj) +{ + struct test_item *item = obj; + ast_string_field_free_memory(item); + if (item->codeccapopt) { + ast_format_cap_destroy(item->codeccapopt); + } + if (item->aclopt) { + ast_free_ha(item->aclopt); + } + return; +} +static void *test_item_alloc(const char *cat) +{ + struct test_item *item; + if (!(item = ao2_alloc(sizeof(*item), test_item_destructor))) { + return NULL; + } + if (ast_string_field_init(item, 128)) { + ao2_ref(item, -1); + return NULL; + } + if (!(item->codeccapopt = ast_format_cap_alloc())) { + ao2_ref(item, -1); + return NULL; + } + ast_string_field_set(item, name, cat); + return item; +} +static void test_config_destructor(void *obj) +{ + struct test_config *cfg = obj; + ao2_cleanup(cfg->global); + ao2_cleanup(cfg->global_defaults); + ao2_cleanup(cfg->items); +} +static void *test_config_alloc(void) +{ + struct test_config *cfg; + if (!(cfg = ao2_alloc(sizeof(*cfg), test_config_destructor))) { + goto error; + } + if (!(cfg->global = test_item_alloc("global"))) { + goto error; + } + if (!(cfg->global_defaults = test_item_alloc("global_defaults"))) { + goto error; + } + if (!(cfg->items = ao2_container_alloc(1, test_item_hash, test_item_cmp))) { + goto error; + } + return cfg; +error: + ao2_cleanup(cfg); + return NULL; +} +static void *test_item_find(struct ao2_container *container, const char *cat) +{ + return ao2_find(container, cat, OBJ_KEY); +} + +static int customopt_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct test_item *item = obj; + if (!strcasecmp(var->name, "customopt")) { + item->customopt = ast_true(var->value); + } else { + return -1; + } + + return 0; +} + +static struct aco_type global = { + .type = ACO_GLOBAL, + .item_offset = offsetof(struct test_config, global), + .category_match = ACO_WHITELIST, + .category = "^global$", +}; +static struct aco_type global_defaults = { + .type = ACO_GLOBAL, + .item_offset = offsetof(struct test_config, global_defaults), + .category_match = ACO_WHITELIST, + .category = "^global_defaults$", +}; +static struct aco_type item = { + .type = ACO_ITEM, + .category_match = ACO_BLACKLIST, + .category = "^(global|global_defaults)$", + .item_alloc = test_item_alloc, + .item_find = test_item_find, + .item_offset = offsetof(struct test_config, items), +}; + +struct aco_file config_test_conf = { + .filename = "config_test.conf", + .types = ACO_TYPES(&global, &global_defaults, &item), +}; + +static AO2_GLOBAL_OBJ_STATIC(global_obj); +CONFIG_INFO_STANDARD(cfg_info, global_obj, test_config_alloc, + .files = ACO_FILES(&config_test_conf), +); + +AST_TEST_DEFINE(config_options_test) +{ + int res = AST_TEST_PASS, x, error; + struct test_item defaults = { 0, }, configs = { 0, }; + struct test_item *arr[4]; + struct ast_sockaddr acl_allow = {{ 0, }}, acl_fail = {{ 0, }}; + RAII_VAR(struct test_config *, cfg, NULL, ao2_cleanup); + RAII_VAR(struct test_item *, item, NULL, ao2_cleanup); + RAII_VAR(struct test_item *, item_defaults, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "config_options_test"; + info->category = "/config/"; + info->summary = "Config opptions unit test"; + info->description = + "Tests the Config Options API"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + +#define INT_DEFAULT "-2" +#define INT_CONFIG "-1" +#define UINT_DEFAULT "2" +#define UINT_CONFIG "1" +#define DOUBLE_DEFAULT "1.1" +#define DOUBLE_CONFIG "0.1" +#define SOCKADDR_DEFAULT "4.3.2.1:4321" +#define SOCKADDR_CONFIG "1.2.3.4:1234" +#define BOOL_DEFAULT "false" +#define BOOL_CONFIG "true" +#define ACL_DEFAULT NULL +#define ACL_CONFIG_PERMIT "1.2.3.4/32" +#define ACL_CONFIG_DENY "0.0.0.0/0" +#define CODEC_DEFAULT "!all,alaw" +#define CODEC_CONFIG "!all,ulaw,g729" +#define STR_DEFAULT "default" +#define STR_CONFIG "test" +#define CUSTOM_DEFAULT "no" +#define CUSTOM_CONFIG "yes" + + if (aco_info_init(&cfg_info)) { + ast_test_status_update(test, "Could not init cfg info\n"); + return AST_TEST_FAIL; + } + + /* Register all options */ + aco_option_register(&cfg_info, "intopt", ACO_EXACT, config_test_conf.types, INT_DEFAULT, OPT_INT_T, 0, FLDSET(struct test_item, intopt)); + aco_option_register(&cfg_info, "uintopt", ACO_EXACT, config_test_conf.types, UINT_DEFAULT, OPT_UINT_T, 0, FLDSET(struct test_item, uintopt)); + aco_option_register(&cfg_info, "doubleopt", ACO_EXACT, config_test_conf.types, DOUBLE_DEFAULT, OPT_DOUBLE_T, 0, FLDSET(struct test_item, doubleopt)); + aco_option_register(&cfg_info, "sockaddropt", ACO_EXACT, config_test_conf.types, SOCKADDR_DEFAULT, OPT_SOCKADDR_T, 0, FLDSET(struct test_item, sockaddropt)); + aco_option_register(&cfg_info, "boolopt", ACO_EXACT, config_test_conf.types, BOOL_DEFAULT, OPT_BOOL_T, 1, FLDSET(struct test_item, boolopt)); + aco_option_register(&cfg_info, "aclpermitopt", ACO_EXACT, config_test_conf.types, ACL_DEFAULT, OPT_ACL_T, 1, FLDSET(struct test_item, aclopt), "permit"); + aco_option_register(&cfg_info, "acldenyopt", ACO_EXACT, config_test_conf.types, ACL_DEFAULT, OPT_ACL_T, 0, FLDSET(struct test_item, aclopt), "deny"); + aco_option_register(&cfg_info, "codecopt", ACO_EXACT, config_test_conf.types, CODEC_DEFAULT, OPT_CODEC_T, 1, FLDSET(struct test_item, codecprefopt, codeccapopt)); + aco_option_register(&cfg_info, "stropt", ACO_EXACT, config_test_conf.types, STR_DEFAULT, OPT_STRINGFIELD_T, 0, STRFLDSET(struct test_item, stropt)); + aco_option_register_custom(&cfg_info, "customopt", ACO_EXACT, config_test_conf.types, CUSTOM_DEFAULT, customopt_handler, 0); + + if (aco_process_config(&cfg_info, 0)) { + ast_test_status_update(test, "Could not parse config\n"); + return AST_TEST_FAIL; + } + + ast_parse_arg(INT_DEFAULT, PARSE_INT32, &defaults.intopt); + ast_parse_arg(INT_CONFIG, PARSE_INT32, &configs.intopt); + ast_parse_arg(UINT_DEFAULT, PARSE_UINT32, &defaults.uintopt); + ast_parse_arg(UINT_CONFIG, PARSE_UINT32, &configs.uintopt); + ast_parse_arg(DOUBLE_DEFAULT, PARSE_DOUBLE, &defaults.doubleopt); + ast_parse_arg(DOUBLE_CONFIG, PARSE_DOUBLE, &configs.doubleopt); + ast_parse_arg(SOCKADDR_DEFAULT, PARSE_ADDR, &defaults.sockaddropt); + ast_parse_arg(SOCKADDR_CONFIG, PARSE_ADDR, &configs.sockaddropt); + defaults.boolopt = ast_true(BOOL_DEFAULT); + configs.boolopt = ast_true(BOOL_CONFIG); + + defaults.aclopt = NULL; + configs.aclopt = ast_append_ha("deny", ACL_CONFIG_DENY, configs.aclopt, &error); + configs.aclopt = ast_append_ha("permit", ACL_CONFIG_PERMIT, configs.aclopt, &error); + ast_sockaddr_parse(&acl_allow, "1.2.3.4", PARSE_PORT_FORBID); + ast_sockaddr_parse(&acl_fail, "1.1.1.1", PARSE_PORT_FORBID); + + defaults.codeccapopt = ast_format_cap_alloc(); + ast_parse_allow_disallow(&defaults.codecprefopt, defaults.codeccapopt, CODEC_DEFAULT, 1); + + configs.codeccapopt = ast_format_cap_alloc(); + ast_parse_allow_disallow(&configs.codecprefopt, configs.codeccapopt, CODEC_CONFIG, 1); + + ast_string_field_init(&defaults, 128); + ast_string_field_init(&configs, 128); + ast_string_field_set(&defaults, stropt, STR_DEFAULT); + ast_string_field_set(&configs, stropt, STR_CONFIG); + + defaults.customopt = ast_true(CUSTOM_DEFAULT); + configs.customopt = ast_true(CUSTOM_CONFIG); + + + cfg = ao2_global_obj_ref(global_obj); + if (!(item = ao2_find(cfg->items, "item", OBJ_KEY))) { + ast_test_status_update(test, "could not look up 'item'\n"); + return AST_TEST_FAIL; + } + if (!(item_defaults = ao2_find(cfg->items, "item_defaults", OBJ_KEY))) { + ast_test_status_update(test, "could not look up 'item_defaults'\n"); + return AST_TEST_FAIL; + } + arr[0] = cfg->global; + arr[1] = item; + arr[2] = cfg->global_defaults; + arr[3] = item_defaults; + /* Test global and item against configs, global_defaults and item_defaults against defaults */ + +#define NOT_EQUAL_FAIL(field) \ + if (arr[x]->field != control->field) { \ + ast_test_status_update(test, "%s di not match: %d != %d with x = %d\n", #field, arr[x]->field, control->field, x); \ + res = AST_TEST_FAIL; \ + } + for (x = 0; x < 4; x++) { + struct test_item *control = x < 2 ? &configs : &defaults; + + NOT_EQUAL_FAIL(intopt); + NOT_EQUAL_FAIL(uintopt); + NOT_EQUAL_FAIL(boolopt); + NOT_EQUAL_FAIL(customopt); + if (fabs(arr[x]->doubleopt - control->doubleopt) > 0.001) { + ast_test_status_update(test, "doubleopt did not match: %f vs %f on loop %d\n", arr[x]->doubleopt, control->doubleopt, x); + res = AST_TEST_FAIL; + } + if (ast_sockaddr_cmp(&arr[x]->sockaddropt, &control->sockaddropt)) { + ast_test_status_update(test, "sockaddr did not match on loop %d\n", x); + res = AST_TEST_FAIL; + } + if (!ast_format_cap_identical(arr[x]->codeccapopt, control->codeccapopt)) { + char buf1[128], buf2[128]; + ast_getformatname_multiple(buf1, sizeof(buf1), arr[x]->codeccapopt); + ast_getformatname_multiple(buf2, sizeof(buf2), control->codeccapopt); + ast_test_status_update(test, "format did not match: '%s' vs '%s' on loop %d\n", buf1, buf2, x); + res = AST_TEST_FAIL; + } + if (strcasecmp(arr[x]->stropt, control->stropt)) { + ast_test_status_update(test, "stropt did not match: '%s' vs '%s' on loop %d\n", arr[x]->stropt, control->stropt, x); + res = AST_TEST_FAIL; + } + if (arr[x]->aclopt != control->aclopt && (ast_apply_ha(arr[x]->aclopt, &acl_allow) != ast_apply_ha(control->aclopt, &acl_allow) || + ast_apply_ha(arr[x]->aclopt, &acl_fail) != ast_apply_ha(control->aclopt, &acl_fail))) { + ast_test_status_update(test, "acl not match: on loop %d\n", x); + res = AST_TEST_FAIL; + } + } + + ast_free_ha(configs.aclopt); + ast_format_cap_destroy(defaults.codeccapopt); + ast_format_cap_destroy(configs.codeccapopt); + ast_string_field_free_memory(&defaults); + ast_string_field_free_memory(&configs); + return res; +} + static int unload_module(void) { AST_TEST_UNREGISTER(ast_parse_arg_test); + AST_TEST_UNREGISTER(config_options_test); return 0; } static int load_module(void) { AST_TEST_REGISTER(ast_parse_arg_test); + AST_TEST_REGISTER(config_options_test); return AST_MODULE_LOAD_SUCCESS; } -- cgit v1.2.3