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
This commit is contained in:
parent
463f9d729a
commit
d54717c39e
1
Makefile
1
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
|
||||
|
|
643
apps/app_skel.c
643
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 <Your Name Here> <<Your Email Here>> \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 <math.h> /* 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
|
||||
<application name="Skel" language="en_US">
|
||||
<application name="SkelGuessNumber" language="en_US">
|
||||
<synopsis>
|
||||
Simple one line explaination.
|
||||
An example number guessing game
|
||||
</synopsis>
|
||||
<syntax>
|
||||
<parameter name="dummy" required="true"/>
|
||||
<parameter name="level" required="true"/>
|
||||
<parameter name="options">
|
||||
<optionlist>
|
||||
<option name="a">
|
||||
<para>Option A.</para>
|
||||
</option>
|
||||
<option name="b">
|
||||
<para>Option B.</para>
|
||||
</option>
|
||||
<option name="c">
|
||||
<para>Option C.</para>
|
||||
<para>The computer should cheat</para>
|
||||
</option>
|
||||
<option name="n">
|
||||
<para>How many games to play before hanging up</para>
|
||||
</option>
|
||||
</optionlist>
|
||||
</parameter>
|
||||
</syntax>
|
||||
<description>
|
||||
<para>This application is a template to build other applications from.
|
||||
It shows you the basic structure to create your own Asterisk applications.</para>
|
||||
<para>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.</para>
|
||||
</description>
|
||||
</application>
|
||||
***/
|
||||
|
||||
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),
|
||||
});
|
||||
|
||||
|
||||
static int app_exec(struct ast_channel *chan, const char *data)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_flags flags;
|
||||
char *parse, *opts[OPTION_ARG_ARRAY_SIZE];
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(dummy);
|
||||
AST_APP_ARG(options);
|
||||
/*! \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 */
|
||||
};
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "%s requires an argument (dummy[,options])\n", app);
|
||||
/*! \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;
|
||||
}
|
||||
|
||||
/* Do our thing here */
|
||||
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 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(level);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
if (!cfg) {
|
||||
ast_log(LOG_ERROR, "Couldn't access configuratino data!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
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 (ast_test_flag(&flags, OPTION_A)) {
|
||||
ast_log(LOG_NOTICE, "Option A is set\n");
|
||||
if (!(level = ao2_find(cfg->levels, args.level, OBJ_KEY))) {
|
||||
ast_log(LOG_ERROR, "Unknown level: %s\n", args.level);
|
||||
return -1;
|
||||
}
|
||||
|
||||
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] : "<unspecified>");
|
||||
if (!(game = skel_game_alloc(level))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
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] : "<unspecified>");
|
||||
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';
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
return res;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
10
configure.ac
10
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)
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -0,0 +1,482 @@
|
|||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2012, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* See http://www.asterisk.org for more information about
|
||||
* the Asterisk project. Please do not directly contact
|
||||
* any of the maintainers of this project for assistance;
|
||||
* the project provides a web site, mailing lists and IRC
|
||||
* channels for your use.
|
||||
*
|
||||
* This program is free software, distributed under the terms of
|
||||
* the GNU General Public License Version 2. See the LICENSE file
|
||||
* at the top of the source tree.
|
||||
*/
|
||||
|
||||
/*! \file
|
||||
* \brief Configuration option-handling
|
||||
* \author Terry Wilson <twilson@digium.com>
|
||||
*/
|
||||
|
||||
#ifndef _ASTERISK_CONFIG_OPTIONS_H
|
||||
#define _ASTERISK_CONFIG_OPTIONS_H
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <regex.h>
|
||||
|
||||
#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 */
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,693 @@
|
|||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2012, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* See http://www.asterisk.org for more information about
|
||||
* the Asterisk project. Please do not directly contact
|
||||
* any of the maintainers of this project for assistance;
|
||||
* the project provides a web site, mailing lists and IRC
|
||||
* channels for your use.
|
||||
*
|
||||
* This program is free software, distributed under the terms of
|
||||
* the GNU General Public License Version 2. See the LICENSE file
|
||||
* at the top of the source tree.
|
||||
*/
|
||||
|
||||
/*! \file
|
||||
* \brief Configuration Option-handling
|
||||
* \author Terry Wilson <twilson@digium.com>
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <regex.h>
|
||||
|
||||
#include "asterisk/config.h"
|
||||
#include "asterisk/config_options.h"
|
||||
#include "asterisk/stringfields.h"
|
||||
#include "asterisk/acl.h"
|
||||
#include "asterisk/frame.h"
|
||||
|
||||
#ifdef LOW_MEMORY
|
||||
#define CONFIG_OPT_BUCKETS 5
|
||||
#else
|
||||
#define CONFIG_OPT_BUCKETS 53
|
||||
#endif /* LOW_MEMORY */
|
||||
|
||||
/*! \brief Bits of aco_info that shouldn't be assigned outside this file
|
||||
* \internal
|
||||
*/
|
||||
struct aco_info_internal {
|
||||
void *pending; /*!< The user-defined config object awaiting application */
|
||||
};
|
||||
|
||||
struct aco_type_internal {
|
||||
regex_t *regex;
|
||||
struct ao2_container *opts; /*!< The container of options registered to the aco_info */
|
||||
};
|
||||
|
||||
struct aco_option {
|
||||
const char *name;
|
||||
enum aco_matchtype match_type;
|
||||
regex_t *name_regex;
|
||||
const char *default_val;
|
||||
struct aco_type **obj;
|
||||
enum aco_option_type type;
|
||||
aco_option_handler handler;
|
||||
unsigned int flags;
|
||||
size_t argc;
|
||||
intptr_t args[0];
|
||||
};
|
||||
|
||||
void *aco_pending_config(struct aco_info *info)
|
||||
{
|
||||
if (!(info && info->internal)) {
|
||||
ast_log(LOG_ERROR, "This may not be called without an initialized aco_info!\n");
|
||||
return NULL;
|
||||
}
|
||||
return info->internal->pending;
|
||||
}
|
||||
|
||||
static void config_option_destroy(void *obj)
|
||||
{
|
||||
struct aco_option *opt = obj;
|
||||
if (opt->match_type == ACO_REGEX && opt->name_regex) {
|
||||
regfree(opt->name_regex);
|
||||
ast_free(opt->name_regex);
|
||||
}
|
||||
}
|
||||
|
||||
static int int_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
|
||||
static int uint_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
|
||||
static int double_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
|
||||
static int sockaddr_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
|
||||
static int stringfield_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
|
||||
static int bool_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
|
||||
static int acl_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
|
||||
static int codec_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
|
||||
|
||||
static aco_option_handler ast_config_option_default_handler(enum aco_option_type type)
|
||||
{
|
||||
switch(type) {
|
||||
case OPT_ACL_T: return acl_handler_fn;
|
||||
case OPT_BOOL_T: return bool_handler_fn;
|
||||
case OPT_CODEC_T: return codec_handler_fn;
|
||||
case OPT_DOUBLE_T: return double_handler_fn;
|
||||
case OPT_INT_T: return int_handler_fn;
|
||||
case OPT_SOCKADDR_T: return sockaddr_handler_fn;
|
||||
case OPT_STRINGFIELD_T: return stringfield_handler_fn;
|
||||
case OPT_UINT_T: return uint_handler_fn;
|
||||
|
||||
case OPT_CUSTOM_T: return NULL;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static regex_t *build_regex(const char *text)
|
||||
{
|
||||
int res;
|
||||
regex_t *regex;
|
||||
|
||||
if (!(regex = ast_malloc(sizeof(*regex)))) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((res = regcomp(regex, text, REG_EXTENDED | REG_ICASE | REG_NOSUB))) {
|
||||
size_t len = regerror(res, regex, NULL, 0);
|
||||
char buf[len];
|
||||
regerror(res, regex, buf, len);
|
||||
ast_log(LOG_ERROR, "Could not compile regex '%s': %s\n", text, buf);
|
||||
ast_free(regex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return regex;
|
||||
}
|
||||
|
||||
int __aco_option_register(struct aco_info *info, const char *name, enum aco_matchtype matchtype, struct aco_type **types,
|
||||
const char *default_val, enum aco_option_type kind, aco_option_handler handler, unsigned int flags, size_t argc, ...)
|
||||
{
|
||||
struct aco_option *opt;
|
||||
struct aco_type *type;
|
||||
va_list ap;
|
||||
int tmp;
|
||||
|
||||
/* Custom option types require a handler */
|
||||
if (!handler && kind == OPT_CUSTOM_T) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(types && types[0])) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(opt = ao2_alloc(sizeof(*opt) + argc * sizeof(opt->args[0]), config_option_destroy))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (matchtype == ACO_REGEX && !(opt->name_regex = build_regex(name))) {
|
||||
ao2_ref(opt, -1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
va_start(ap, argc);
|
||||
for (tmp = 0; tmp < argc; tmp++) {
|
||||
opt->args[tmp] = va_arg(ap, size_t);
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
opt->name = name;
|
||||
opt->match_type = matchtype;
|
||||
opt->default_val = default_val;
|
||||
opt->type = kind;
|
||||
opt->handler = handler;
|
||||
opt->flags = flags;
|
||||
opt->argc = argc;
|
||||
|
||||
if (!opt->handler && !(opt->handler = ast_config_option_default_handler(opt->type))) {
|
||||
/* This should never happen */
|
||||
ast_log(LOG_ERROR, "No handler provided, and no default handler exists for type %d\n", opt->type);
|
||||
ao2_ref(opt, -1);
|
||||
return -1;
|
||||
};
|
||||
|
||||
tmp = 0;
|
||||
while ((type = types[tmp++])) {
|
||||
if (!ao2_link(type->internal->opts, opt)) {
|
||||
while (--tmp) {
|
||||
ao2_unlink(types[tmp]->internal->opts, opt);
|
||||
}
|
||||
ao2_ref(opt, -1);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_opt_hash(const void *obj, const int flags)
|
||||
{
|
||||
const struct aco_option *opt = obj;
|
||||
const char *name = (flags & OBJ_KEY) ? obj : opt->name;
|
||||
return ast_str_case_hash(name);
|
||||
}
|
||||
|
||||
static int config_opt_cmp(void *obj, void *arg, int flags)
|
||||
{
|
||||
struct aco_option *opt1 = obj, *opt2 = arg;
|
||||
const char *name = (flags & OBJ_KEY) ? arg : opt2->name;
|
||||
return strcasecmp(opt1->name, name) ? 0 : CMP_MATCH | CMP_STOP;
|
||||
}
|
||||
|
||||
static int find_option_cb(void *obj, void *arg, int flags)
|
||||
{
|
||||
struct aco_option *match = obj;
|
||||
const char *name = arg;
|
||||
|
||||
switch (match->match_type) {
|
||||
case ACO_EXACT:
|
||||
return strcasecmp(name, match->name) ? 0 : CMP_MATCH | CMP_STOP;
|
||||
case ACO_REGEX:
|
||||
return regexec(match->name_regex, name, 0, NULL, 0) ? 0 : CMP_MATCH | CMP_STOP;
|
||||
}
|
||||
ast_log(LOG_ERROR, "Unknown match type. This should not be possible.\n");
|
||||
return CMP_STOP;
|
||||
}
|
||||
|
||||
static struct aco_option *aco_option_find(struct aco_type *type, const char *name)
|
||||
{
|
||||
struct aco_option *opt;
|
||||
/* Try an exact match with OBJ_KEY for the common/fast case, then iterate through
|
||||
* all options for the regex cases */
|
||||
if (!(opt = ao2_callback(type->internal->opts, OBJ_KEY, find_option_cb, (void *) name))) {
|
||||
opt = ao2_callback(type->internal->opts, 0, find_option_cb, (void *) name);
|
||||
}
|
||||
return opt;
|
||||
}
|
||||
|
||||
struct ao2_container *aco_option_container_alloc(void)
|
||||
{
|
||||
return ao2_container_alloc(CONFIG_OPT_BUCKETS, config_opt_hash, config_opt_cmp);
|
||||
}
|
||||
|
||||
static struct aco_type *internal_aco_type_find(struct aco_file *file, struct ast_config *cfg, const char *category)
|
||||
{
|
||||
size_t x;
|
||||
struct aco_type *match;
|
||||
const char *val;
|
||||
|
||||
for (x = 0, match = file->types[x]; match; match = file->types[++x]) {
|
||||
/* First make sure we are an object that can service this category */
|
||||
if (!regexec(match->internal->regex, category, 0, NULL, 0) == !match->category_match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Then, see if we need to match a particular field */
|
||||
if (!ast_strlen_zero(match->matchfield) && (!ast_strlen_zero(match->matchvalue) || match->matchfunc)) {
|
||||
if (!(val = ast_variable_retrieve(cfg, category, match->matchfield))) {
|
||||
ast_log(LOG_ERROR, "Required match field '%s' not found\n", match->matchfield);
|
||||
return NULL;
|
||||
}
|
||||
if (match->matchfunc) {
|
||||
if (!match->matchfunc(val)) {
|
||||
continue;
|
||||
}
|
||||
} else if (strcasecmp(val, match->matchvalue)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
/* If we get this far, we're a match */
|
||||
break;
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
static int is_preload(struct aco_file *file, const char *cat)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!file->preload) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; !ast_strlen_zero(file->preload[i]); i++) {
|
||||
if (!strcasecmp(cat, file->preload[i])) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_category(struct ast_config *cfg, struct aco_info *info, struct aco_file *file, const char *cat, int preload) {
|
||||
RAII_VAR(void *, new_item, NULL, ao2_cleanup);
|
||||
struct aco_type *type;
|
||||
/* For global types, field is the global option struct. For non-global, it is the container for items.
|
||||
* We do not grab a reference to these objects, as the info already holds references to them. This
|
||||
* pointer is just a convenience. Do not actually store it somewhere. */
|
||||
void **field;
|
||||
|
||||
/* Skip preloaded categories if we aren't preloading */
|
||||
if (!preload && is_preload(file, cat)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find aco_type by category, if not found it is an error */
|
||||
if (!(type = internal_aco_type_find(file, cfg, cat))) {
|
||||
ast_log(LOG_ERROR, "Could not find config type for category '%s' in '%s'\n", cat, file->filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
field = info->internal->pending + type->item_offset;
|
||||
if (!*field) {
|
||||
ast_log(LOG_ERROR, "No object to update!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (type->type == ACO_GLOBAL && *field) {
|
||||
if (aco_set_defaults(type, cat, *field)) {
|
||||
ast_log(LOG_ERROR, "In %s: Setting defaults for %s failed\n", file->filename, cat);
|
||||
return -1;
|
||||
}
|
||||
if (aco_process_category_options(type, cfg, cat, *field)) {
|
||||
ast_log(LOG_ERROR, "In %s: Processing options for %s failed\n", file->filename, cat);
|
||||
return -1;
|
||||
}
|
||||
} else if (type->type == ACO_ITEM) {
|
||||
int new = 0;
|
||||
/* If we have multiple definitions of a category in a file, or can set the values from multiple
|
||||
* files, look up the entry if we've already added it so we can merge the values together.
|
||||
* Otherwise, alloc a new item. */
|
||||
if (*field) {
|
||||
if (!(new_item = type->item_find(*field, cat))) {
|
||||
if (!(new_item = type->item_alloc(cat))) {
|
||||
ast_log(LOG_ERROR, "In %s: Could not create item for %s\n", file->filename, cat);
|
||||
return -1;
|
||||
}
|
||||
if (aco_set_defaults(type, cat, new_item)) {
|
||||
ast_log(LOG_ERROR, "In %s: Setting defaults for %s failed\n", file->filename, cat);
|
||||
return -1;
|
||||
}
|
||||
new = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (type->item_pre_process && type->item_pre_process(new_item)) {
|
||||
ast_log(LOG_ERROR, "In %s: Preprocess callback for %s failed\n", file->filename, cat);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (aco_process_category_options(type, cfg, cat, new_item)) {
|
||||
ast_log(LOG_ERROR, "In %s: Processing options for %s failed\n", file->filename, cat);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (type->item_prelink && type->item_prelink(new_item)) {
|
||||
ast_log(LOG_ERROR, "In %s: Pre-link callback for %s failed\n", file->filename, cat);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (new && !ao2_link(*field, new_item)) {
|
||||
ast_log(LOG_ERROR, "In %s: Linking config for %s failed\n", file->filename, cat);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apply_config(struct aco_info *info)
|
||||
{
|
||||
ao2_global_obj_replace_unref(*info->global_obj, info->internal->pending);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int internal_process_ast_config(struct aco_info *info, struct aco_file *file, struct ast_config *cfg)
|
||||
{
|
||||
const char *cat = NULL;
|
||||
|
||||
if (file->preload) {
|
||||
int i;
|
||||
for (i = 0; !ast_strlen_zero(file->preload[i]); i++) {
|
||||
if (process_category(cfg, info, file, file->preload[i], 1)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ((cat = ast_category_browse(cfg, cat))) {
|
||||
if (process_category(cfg, info, file, cat, 0)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int aco_process_ast_config(struct aco_info *info, struct aco_file *file, struct ast_config *cfg)
|
||||
{
|
||||
if (!(info->internal->pending = info->snapshot_alloc())) {
|
||||
ast_log(LOG_ERROR, "In %s: Could not allocate temporary objects\n", file->filename);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (internal_process_ast_config(info, file, cfg)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (info->pre_apply_config && info->pre_apply_config()) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (apply_config(info)) {
|
||||
goto error;
|
||||
};
|
||||
|
||||
ao2_cleanup(info->internal->pending);
|
||||
return 0;
|
||||
|
||||
error:
|
||||
ao2_cleanup(info->internal->pending);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int aco_process_config(struct aco_info *info, int reload)
|
||||
{
|
||||
struct ast_config *cfg;
|
||||
struct ast_flags cfg_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0, };
|
||||
int res = 0, x = 0;
|
||||
struct aco_file *file;
|
||||
|
||||
if (!(info->files[0])) {
|
||||
ast_log(LOG_ERROR, "No filename given, cannot proceed!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(info->internal->pending = info->snapshot_alloc())) {
|
||||
ast_log(LOG_ERROR, "In %s: Could not allocate temporary objects\n", info->module);
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (!res && (file = info->files[x++])) {
|
||||
if (!(cfg = ast_config_load(file->filename, cfg_flags))) {
|
||||
ast_log(LOG_ERROR, "Unable to load config file '%s'\n", file->filename);
|
||||
res = -1;
|
||||
break;
|
||||
} else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
|
||||
ast_debug(1, "%s was unchanged\n", file->filename);
|
||||
res = 0;
|
||||
continue;
|
||||
} else if (cfg == CONFIG_STATUS_FILEINVALID) {
|
||||
ast_log(LOG_ERROR, "Contents of %s are invalid and cannot be parsed\n", file->filename);
|
||||
res = -1;
|
||||
break;
|
||||
} else if (cfg == CONFIG_STATUS_FILEMISSING) {
|
||||
ast_log(LOG_ERROR, "%s is missing! Cannot load %s\n", file->filename, info->module);
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
res = internal_process_ast_config(info, file, cfg);
|
||||
ast_config_destroy(cfg);
|
||||
}
|
||||
|
||||
if (res || (res = ((info->pre_apply_config && info->pre_apply_config()) || apply_config(info)))) {
|
||||
;
|
||||
};
|
||||
|
||||
ao2_cleanup(info->internal->pending);
|
||||
return res;
|
||||
}
|
||||
|
||||
int aco_process_category_options(struct aco_type *type, struct ast_config *cfg, const char *cat, void *obj)
|
||||
{
|
||||
struct ast_variable *var;
|
||||
|
||||
for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
|
||||
RAII_VAR(struct aco_option *, opt, aco_option_find(type, var->name), ao2_cleanup);
|
||||
if (!opt) {
|
||||
ast_log(LOG_ERROR, "Could not find option suitable for category '%s' named '%s' at line %d of %s\n", cat, var->name, var->lineno, var->file);
|
||||
return -1;
|
||||
}
|
||||
if (!opt->handler) {
|
||||
/* It should be impossible for an option to not have a handler */
|
||||
ast_log(LOG_ERROR, "BUG! Somehow a config option for %s/%s was created with no handler!\n", cat, var->name);
|
||||
return -1;
|
||||
}
|
||||
if (opt->handler(opt, var, obj)) {
|
||||
ast_log(LOG_ERROR, "Error parsing %s=%s at line %d of %s\n", var->name, var->value, var->lineno, var->file);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void internal_type_destroy(struct aco_type *type)
|
||||
{
|
||||
if (type->internal->regex) {
|
||||
regfree(type->internal->regex);
|
||||
ast_free(type->internal->regex);
|
||||
}
|
||||
ao2_cleanup(type->internal->opts);
|
||||
type->internal->opts = NULL;
|
||||
ast_free(type->internal);
|
||||
type->internal = NULL;
|
||||
}
|
||||
|
||||
static void internal_file_types_destroy(struct aco_file *file)
|
||||
{
|
||||
size_t x;
|
||||
struct aco_type *t;
|
||||
|
||||
for (x = 0, t = file->types[x]; t; t = file->types[++x]) {
|
||||
internal_type_destroy(t);
|
||||
t = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int internal_type_init(struct aco_type *type)
|
||||
{
|
||||
if (!(type->internal = ast_calloc(1, sizeof(*type->internal)))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(type->internal->regex = build_regex(type->category))) {
|
||||
internal_type_destroy(type);
|
||||
}
|
||||
|
||||
if (!(type->internal->opts = aco_option_container_alloc())) {
|
||||
internal_type_destroy(type);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int aco_info_init(struct aco_info *info)
|
||||
{
|
||||
size_t x, y;
|
||||
|
||||
if (!(info->internal = ast_calloc(1, sizeof(*info->internal)))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (x = 0; info->files[x]; x++) {
|
||||
for (y = 0; info->files[x]->types[y]; y++) {
|
||||
if (internal_type_init(info->files[x]->types[y])) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
aco_info_destroy(info);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void aco_info_destroy(struct aco_info *info)
|
||||
{
|
||||
int x;
|
||||
/* It shouldn't be possible for internal->pending to be in use when this is called because
|
||||
* of the locks in loader.c around reloads and unloads and the fact that internal->pending
|
||||
* only exists while those locks are held */
|
||||
ast_free(info->internal);
|
||||
info->internal = NULL;
|
||||
|
||||
for (x = 0; info->files[x]; x++) {
|
||||
internal_file_types_destroy(info->files[x]);
|
||||
}
|
||||
}
|
||||
|
||||
int aco_set_defaults(struct aco_type *type, const char *category, void *obj)
|
||||
{
|
||||
struct aco_option *opt;
|
||||
struct ao2_iterator iter;
|
||||
|
||||
iter = ao2_iterator_init(type->internal->opts, 0);
|
||||
|
||||
while ((opt = ao2_iterator_next(&iter))) {
|
||||
RAII_VAR(struct ast_variable *, var, NULL, ast_variables_destroy);
|
||||
|
||||
if (ast_strlen_zero(opt->default_val)) {
|
||||
ao2_ref(opt, -1);
|
||||
continue;
|
||||
}
|
||||
if (!(var = ast_variable_new(opt->name, opt->default_val, ""))) {
|
||||
ao2_ref(opt, -1);
|
||||
ao2_iterator_destroy(&iter);
|
||||
return -1;
|
||||
}
|
||||
if (opt->handler(opt, var, obj)) {
|
||||
ast_log(LOG_ERROR, "Unable to set default for %s, %s=%s\n", category, var->name, var->value);
|
||||
ao2_ref(opt, -1);
|
||||
ao2_iterator_destroy(&iter);
|
||||
return -1;
|
||||
}
|
||||
ao2_ref(opt, -1);
|
||||
}
|
||||
ao2_iterator_destroy(&iter);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* default config option handlers */
|
||||
static int int_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj) {
|
||||
int *field = (int *)(obj + opt->args[0]);
|
||||
unsigned int flags = PARSE_INT32 | opt->flags;
|
||||
int res = 0;
|
||||
if (opt->flags & PARSE_IN_RANGE) {
|
||||
res = opt->flags & PARSE_DEFAULT ?
|
||||
ast_parse_arg(var->value, flags, field, (int) opt->args[1], (int) opt->args[2], opt->args[3]) :
|
||||
ast_parse_arg(var->value, flags, field, (int) opt->args[1], (int) opt->args[2]);
|
||||
if (res) {
|
||||
if (opt->flags & PARSE_RANGE_DEFAULTS) {
|
||||
ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]);
|
||||
res = 0;
|
||||
} else if (opt->flags & PARSE_DEFAULT) {
|
||||
ast_log(LOG_WARNING, "Failed to set %s=%s, Set to default value %d instead.\n", var->name, var->value, *field);
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
} else if ((opt->flags & PARSE_DEFAULT) && ast_parse_arg(var->value, flags, field, (int) opt->args[1])) {
|
||||
ast_log(LOG_WARNING, "Attempted to set %s=%s, but set it to %d instead due to default)\n", var->name, var->value, *field);
|
||||
} else {
|
||||
res = ast_parse_arg(var->value, flags, field);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int uint_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj) {
|
||||
unsigned int *field = (unsigned int *)(obj + opt->args[0]);
|
||||
unsigned int flags = PARSE_INT32 | opt->flags;
|
||||
int res = 0;
|
||||
if (opt->flags & PARSE_IN_RANGE) {
|
||||
res = opt->flags & PARSE_DEFAULT ?
|
||||
ast_parse_arg(var->value, flags, field, (unsigned int) opt->args[1], (unsigned int) opt->args[2], opt->args[3]) :
|
||||
ast_parse_arg(var->value, flags, field, (unsigned int) opt->args[1], (unsigned int) opt->args[2]);
|
||||
if (res) {
|
||||
if (opt->flags & PARSE_RANGE_DEFAULTS) {
|
||||
ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]);
|
||||
res = 0;
|
||||
} else if (opt->flags & PARSE_DEFAULT) {
|
||||
ast_log(LOG_WARNING, "Failed to set %s=%s, Set to default value %d instead.\n", var->name, var->value, *field);
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
} else if ((opt->flags & PARSE_DEFAULT) && ast_parse_arg(var->value, flags, field, (unsigned int) opt->args[1])) {
|
||||
ast_log(LOG_WARNING, "Attempted to set %s=%s, but set it to %u instead due to default)\n", var->name, var->value, *field);
|
||||
} else {
|
||||
res = ast_parse_arg(var->value, flags, field);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int double_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj) {
|
||||
double *field = (double *)(obj + opt->args[0]);
|
||||
return ast_parse_arg(var->value, PARSE_DOUBLE | opt->flags, field);
|
||||
}
|
||||
|
||||
static int acl_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj) {
|
||||
struct ast_ha **ha = (struct ast_ha **)(obj + opt->args[0]);
|
||||
const char *permit = (const char *) opt->args[1];
|
||||
int error = 0;
|
||||
*ha = ast_append_ha(permit, var->value, *ha, &error);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* opt->args[0] = struct ast_codec_pref, opt->args[1] struct ast_format_cap * */
|
||||
static int codec_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj) {
|
||||
struct ast_codec_pref *pref = (struct ast_codec_pref *)(obj + opt->args[0]);
|
||||
struct ast_format_cap **cap = (struct ast_format_cap **)(obj + opt->args[1]);
|
||||
return ast_parse_allow_disallow(pref, *cap, var->value, opt->flags);
|
||||
}
|
||||
|
||||
/* opt->args[0] = ast_string_field, opt->args[1] = field_mgr_pool, opt->args[2] = field_mgr */
|
||||
static int stringfield_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj)
|
||||
{
|
||||
ast_string_field *field = (const char **)(obj + opt->args[0]);
|
||||
struct ast_string_field_pool **pool = (struct ast_string_field_pool **)(obj + opt->args[1]);
|
||||
struct ast_string_field_mgr *mgr = (struct ast_string_field_mgr *)(obj + opt->args[2]);
|
||||
ast_string_field_ptr_set_by_fields(*pool, *mgr, field, var->value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bool_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj)
|
||||
{
|
||||
unsigned int *field = (unsigned int *)(obj + opt->args[0]);
|
||||
*field = opt->flags ? ast_true(var->value) : ast_false(var->value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sockaddr_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj)
|
||||
{
|
||||
struct ast_sockaddr *field = (struct ast_sockaddr *)(obj + opt->args[0]);
|
||||
return ast_parse_arg(var->value, PARSE_ADDR | opt->flags, field);
|
||||
}
|
288
main/udptl.c
288
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 (!(udptl = ast_calloc(1, sizeof(*udptl))))
|
||||
if (!cfg || !cfg->general) {
|
||||
ast_log(LOG_ERROR, "Could not access global udptl options!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
udptl->error_correction_span = udptlfecspan;
|
||||
udptl->error_correction_entries = udptlfecentries;
|
||||
if (!(udptl = ast_calloc(1, sizeof(*udptl)))) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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 udptl_config_destructor(void *obj)
|
||||
{
|
||||
struct udptl_config *cfg = obj;
|
||||
ao2_cleanup(cfg->general);
|
||||
}
|
||||
|
||||
static void *udptl_snapshot_alloc(void)
|
||||
{
|
||||
struct udptl_config *cfg;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return 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)
|
||||
{
|
||||
struct ast_config *cfg;
|
||||
const char *s;
|
||||
struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
|
||||
if (aco_process_config(&cfg_info, reload)) {
|
||||
ast_log(LOG_WARNING, "Could not reload udptl config\n");
|
||||
}
|
||||
}
|
||||
|
||||
cfg = ast_config_load2("udptl.conf", "udptl", config_flags);
|
||||
if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
|
||||
return;
|
||||
static int udptl_pre_apply_config(void) {
|
||||
struct udptl_config *cfg = aco_pending_config(&cfg_info);
|
||||
|
||||
if (!cfg->general) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
udptlstart = DEFAULT_UDPTLSTART;
|
||||
udptlend = DEFAULT_UDPTLEND;
|
||||
udptlfecentries = 0;
|
||||
udptlfecspan = 0;
|
||||
use_even_ports = 0;
|
||||
|
||||
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");
|
||||
#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
|
||||
}
|
||||
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);
|
||||
|
||||
/* 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 (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);
|
||||
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 (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->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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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@
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue