[asterisk-commits] twilson: trunk r368181 - in /trunk: ./ apps/ configs/ include/asterisk/ main/...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri Jun 1 11:33:30 CDT 2012


Author: twilson
Date: Fri Jun  1 11:33:25 2012
New Revision: 368181

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=368181
Log:
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.

Added:
    trunk/configs/app_skel.conf.sample
      - copied unchanged from r368180, team/twilson/config_work/configs/app_skel.conf.sample
    trunk/include/asterisk/config_options.h
      - copied unchanged from r368180, team/twilson/config_work/include/asterisk/config_options.h
    trunk/main/config_options.c
      - copied unchanged from r368180, team/twilson/config_work/main/config_options.c
Modified:
    trunk/Makefile
    trunk/apps/app_skel.c
    trunk/configure
    trunk/configure.ac
    trunk/include/asterisk/astobj2.h
    trunk/include/asterisk/config.h
    trunk/include/asterisk/stringfields.h
    trunk/include/asterisk/utils.h
    trunk/main/asterisk.exports.in
    trunk/main/astobj2.c
    trunk/main/config.c
    trunk/main/udptl.c
    trunk/makeopts.in
    trunk/tests/test_config.c

Modified: trunk/Makefile
URL: http://svnview.digium.com/svn/asterisk/trunk/Makefile?view=diff&rev=368181&r1=368180&r2=368181
==============================================================================
--- trunk/Makefile (original)
+++ trunk/Makefile Fri Jun  1 11:33:25 2012
@@ -191,6 +191,7 @@
   _ASTCFLAGS+=-Wunused
   _ASTCFLAGS+=$(AST_DECLARATION_AFTER_STATEMENT)
   _ASTCFLAGS+=$(AST_FORTIFY_SOURCE)
+  _ASTCFLAGS+=$(AST_TRAMPOLINES)
   _ASTCFLAGS+=-Wundef
   _ASTCFLAGS+=-Wmissing-format-attribute
   _ASTCFLAGS+=-Wformat=2

Modified: trunk/apps/app_skel.c
URL: http://svnview.digium.com/svn/asterisk/trunk/apps/app_skel.c?view=diff&rev=368181&r1=368180&r2=368181
==============================================================================
--- trunk/apps/app_skel.c (original)
+++ trunk/apps/app_skel.c Fri Jun  1 11:33:25 2012
@@ -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 name="c">
+						<para>The computer should cheat</para>
 					</option>
-					<option name="b">
-						<para>Option B.</para>
-					</option>
-					<option name="c">
-						<para>Option C.</para>
+					<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),
 });
 
+/*! \brief A structure to hold global configuration-related options */
+struct skel_global_config {
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(prompt); /*!< The comma-separated list of sounds to prompt to enter a number */
+		AST_STRING_FIELD(wrong);  /*!< The comma-separated list of sounds to indicate a wrong guess */
+		AST_STRING_FIELD(right);  /*!< The comma-separated list of sounds to indicate a right guess */
+		AST_STRING_FIELD(high);   /*!< The comma-separated list of sounds to indicate a high guess */
+		AST_STRING_FIELD(low);    /*!< The comma-separated list of sounds to indicate a low guess */
+		AST_STRING_FIELD(lose);  /*!< The comma-separated list of sounds to indicate a lost game */
+	);
+	uint32_t num_games;    /*!< The number of games to play before hanging up */
+	unsigned char cheat:1; /*!< Whether the computer can cheat or not */
+};
+
+/*! \brief A structure to maintain level state across reloads */
+struct skel_level_state {
+	uint32_t wins;      /*!< How many wins for this level */
+	uint32_t losses;    /*!< How many losses for this level */
+	double avg_guesses; /*!< The average number of guesses to win for this level */
+};
+
+/*! \brief Object to hold level config information.
+ * \note This object should hold a reference to an an object that holds state across reloads.
+ * The other fields are just examples of the kind of data that might be stored in an level.
+ */
+struct skel_level {
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(name);      /*!< The name of the level */
+	);
+	uint32_t max_num;                /*!< The upper value on th range of numbers to guess */
+	uint32_t max_guesses;            /*!< The maximum number of guesses before losing */
+	struct skel_level_state *state;  /*!< A pointer to level state that must exist across all reloads */
+};
+
+/*! \brief Information about a currently running set of games
+ * \note Because we want to be able to show true running information about the games
+ * regardless of whether or not a reload has modified what the level looks like, it
+ * is important to either copy the information we need from the level to the
+ * current_game struct, or as we do here, store a reference to the level as it is for
+ * the running game.
+ */
+struct skel_current_game {
+	uint32_t total_games;          /*! The total number of games for this call to to the app */
+	uint32_t games_left;           /*! How many games are left to play in this set */
+	uint32_t cheat;                /*! Whether or not cheating was enabled for the game */
+	struct skel_level *level_info; /*! The level information for the running game */
+};
+
+/* Treat the levels as an array--there won't be many and this will maintain the order */
+#define LEVEL_BUCKETS 1
+
+/*! \brief A container that holds all config-related information
+ * \note This object should contain a pointer to structs for global data and containers for
+ * any levels that are configured. Objects of this type will be swapped out on reload. If an
+ * level needs to maintain state across reloads, it needs to allocate a refcounted object to
+ * hold that state and ensure that a reference is passed to that state when creating a new
+ * level for reload. */
+struct skel_config {
+	struct skel_global_config *global;
+	struct ao2_container *levels;
+};
+
+/* Config Options API callbacks */
+
+/*! \brief Allocate a skel_config to hold a snapshot of the complete results of parsing a config
+ * \internal
+ * \returns A void pointer to a newly allocated skel_config
+ */
+static void *skel_config_alloc(void);
+
+/*! \brief Allocate a skel_level based on a category in a configuration file
+ * \param cat The category to base the level on
+ * \returns A void pointer to a newly allocated skel_level
+ */
+static void *skel_level_alloc(const char *cat);
+
+/*! \brief Find a skel level in the specified container
+ * \note This function *does not* look for a skel_level in the active container. It is used
+ * internally by the Config Options code to check if an level has already been added to the
+ * container that will be swapped for the live container on a successul reload.
+ *
+ * \param container A non-active container to search for a level
+ * \param category The category associated with the level to check for
+ * \retval non-NULL The level from the container
+ * \retval NULL The level does not exist in the container
+ */
+static void *skel_level_find(struct ao2_container *tmp_container, const char *category);
+
+/*! \brief An aco_type structure to link the "general" category to the skel_global_config type */
+static struct aco_type global_option = {
+	.type = ACO_GLOBAL,
+	.item_offset = offsetof(struct skel_config, global),
+	.category_match = ACO_WHITELIST,
+	.category = "^general$",
+};
+
+struct aco_type *global_options[] = ACO_TYPES(&global_option);
+
+/*! \brief An aco_type structure to link the "sounds" category to the skel_global_config type */
+static struct aco_type sound_option = {
+	.type = ACO_GLOBAL,
+	.item_offset = offsetof(struct skel_config, global),
+	.category_match = ACO_WHITELIST,
+	.category = "^sounds$",
+};
+
+struct aco_type *sound_options[] = ACO_TYPES(&sound_option);
+
+/*! \brief An aco_type structure to link the everything but the "general" and "sounds" categories to the skel_level type */
+static struct aco_type level_option = {
+	.type = ACO_ITEM,
+	.category_match = ACO_BLACKLIST,
+	.category = "^(general|sounds)$",
+	.item_alloc = skel_level_alloc,
+	.item_find = skel_level_find,
+	.item_offset = offsetof(struct skel_config, levels),
+};
+
+struct aco_type *level_options[] = ACO_TYPES(&level_option);
+
+struct aco_file app_skel_conf = {
+	.filename = "app_skel.conf",
+	.types = ACO_TYPES(&global_option, &sound_option, &level_option),
+};
+
+/*! \brief A global object container that will contain the skel_config that gets swapped out on reloads */
+static AO2_GLOBAL_OBJ_STATIC(globals);
+
+/*! \brief The container of active games */
+static struct ao2_container *games;
+
+/*! \brief Register information about the configs being processed by this module */
+CONFIG_INFO_STANDARD(cfg_info, globals, skel_config_alloc,
+	.files = ACO_FILES(&app_skel_conf),
+);
+
+static void skel_global_config_destructor(void *obj)
+{
+	struct skel_global_config *global = obj;
+	ast_string_field_free_memory(global);
+}
+
+static void skel_game_destructor(void *obj)
+{
+	struct skel_current_game *game = obj;
+	ao2_cleanup(game->level_info);
+}
+
+static void skel_state_destructor(void *obj)
+{
+	return;
+}
+
+static struct skel_current_game *skel_game_alloc(struct skel_level *level)
+{
+	struct skel_current_game *game;
+	if (!(game = ao2_alloc(sizeof(struct skel_current_game), skel_game_destructor))) {
+		return NULL;
+	}
+	ao2_ref(level, +1);
+	game->level_info = level;
+	return game;
+}
+
+static void skel_level_destructor(void *obj)
+{
+	struct skel_level *level = obj;
+	ast_string_field_free_memory(level);
+	ao2_cleanup(level->state);
+}
+
+static int skel_level_hash(const void *obj, const int flags)
+{
+	const struct skel_level *level = obj;
+	const char *name = (flags & OBJ_KEY) ? obj : level->name;
+	return ast_str_case_hash(name);
+}
+
+static int skel_level_cmp(void *obj, void *arg, int flags)
+{
+	struct skel_level *one = obj, *two = arg;
+	const char *match = (flags & OBJ_KEY) ? arg : two->name;
+	return strcasecmp(one->name, match) ? 0 : (CMP_MATCH | CMP_STOP);
+}
+
+/*! \brief A custom bitfield handler
+ * \internal
+ * \note It is not possible to take the address of a bitfield, therefor all
+ * bitfields in the config struct will have to use a custom handler
+ * \param opt The opaque config option
+ * \param var The ast_variable containing the option name and value
+ * \param obj The object registerd for this option type
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+static int custom_bitfield_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct skel_global_config *global = obj;
+
+	if (!strcasecmp(var->name, "cheat")) {
+		global->cheat = ast_true(var->value);
+	} else {
+		return -1;
+	}
+
+	return 0;
+}
+
+static void play_files_helper(struct ast_channel *chan, const char *prompts)
+{
+	char *prompt, *rest = ast_strdupa(prompts);
+
+	ast_stopstream(chan);
+	while ((prompt = strsep(&rest, "&")) && !ast_stream_and_wait(chan, prompt, "")) {
+		ast_stopstream(chan);
+	}
+}
 
 static int app_exec(struct ast_channel *chan, const char *data)
 {
-	int res = 0;
+	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;
-	char *parse, *opts[OPTION_ARG_ARRAY_SIZE];
 	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(dummy);
+		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 (dummy[,options])\n", app);
+		ast_log(LOG_WARNING, "%s requires an argument (level[,options])\n", app);
 		return -1;
 	}
-
-	/* Do our thing here */
 
 	/* 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 @@
 		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_test_flag(&flags, OPTION_A)) {
-		ast_log(LOG_NOTICE, "Option A is set\n");
-	}
-
-	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 (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>");
-	}
-
-	return res;
+	if (ast_strlen_zero(args.level)) {
+		ast_log(LOG_ERROR, "%s requires a level argument\n", app);
+		return -1;
+	}
+
+	if (!(level = ao2_find(cfg->levels, args.level, OBJ_KEY))) {
+		ast_log(LOG_ERROR, "Unknown level: %s\n", args.level);
+		return -1;
+	}
+
+	if (!(game = skel_game_alloc(level))) {
+		return -1;
+	}
+
+	ao2_link(games, game);
+
+	/* Use app-specified values, or the options specified in [general] if they aren't passed to the app */
+	if (!ast_test_flag(&flags, OPTION_NUMGAMES) ||
+			ast_strlen_zero(opts[OPTION_ARG_NUMGAMES]) ||
+			ast_parse_arg(opts[OPTION_ARG_NUMGAMES], PARSE_UINT32, &game->total_games)) {
+		game->total_games = cfg->global->num_games;
+	}
+	game->games_left = game->total_games;
+	game->cheat = ast_test_flag(&flags, OPTION_CHEAT) || cfg->global->cheat;
+
+	for (game->games_left = game->total_games; game->games_left; game->games_left--) {
+		uint32_t num = ast_random() % level->max_num; /* random number between 0 and level->max_num */
+
+		ast_debug(1, "They should totally should guess %u\n", num);
+
+		/* Play the prompt */
+		play_files_helper(chan, cfg->global->prompt);
+		ast_say_number(chan, level->max_num, "", ast_channel_language(chan), "");
+
+		for (guesses = 0; guesses < level->max_guesses; guesses++) {
+			size_t buflen = log10(level->max_num) + 1;
+			char buf[buflen];
+			int guess;
+			buf[buflen] = '\0';
+
+			/* 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);
+	}
+
+	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;
-}
-
-AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Skeleton (sample) Application");
+	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(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Skeleton (sample) Application",
+	.load = load_module,
+	.unload = unload_module,
+	.reload = reload_module,
+	.load_pri = AST_MODPRI_DEFAULT,
+);

Modified: trunk/configure.ac
URL: http://svnview.digium.com/svn/asterisk/trunk/configure.ac?view=diff&rev=368181&r1=368180&r2=368181
==============================================================================
--- trunk/configure.ac (original)
+++ trunk/configure.ac Fri Jun  1 11:33:25 2012
@@ -980,6 +980,16 @@
 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)

Modified: trunk/include/asterisk/astobj2.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/astobj2.h?view=diff&rev=368181&r1=368180&r2=368181
==============================================================================
--- trunk/include/asterisk/astobj2.h (original)
+++ trunk/include/asterisk/astobj2.h Fri Jun  1 11:33:25 2012
@@ -1461,4 +1461,10 @@
 /* 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 */

Modified: trunk/include/asterisk/config.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/config.h?view=diff&rev=368181&r1=368180&r2=368181
==============================================================================
--- trunk/include/asterisk/config.h (original)
+++ trunk/include/asterisk/config.h Fri Jun  1 11:33:25 2012
@@ -644,8 +644,9 @@
 	 * 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 @@
  *
  * \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 @@
  * 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()

Modified: trunk/include/asterisk/stringfields.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/stringfields.h?view=diff&rev=368181&r1=368180&r2=368181
==============================================================================
--- trunk/include/asterisk/stringfields.h (original)
+++ trunk/include/asterisk/stringfields.h Fri Jun  1 11:33:25 2012
@@ -310,25 +310,27 @@
   \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 
-  \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)
+  \param data String value to be copied into the field
+  \return nothing
+*/
+#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

Modified: trunk/include/asterisk/utils.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/utils.h?view=diff&rev=368181&r1=368180&r2=368181
==============================================================================
--- trunk/include/asterisk/utils.h (original)
+++ trunk/include/asterisk/utils.h Fri Jun  1 11:33:25 2012
@@ -863,4 +863,51 @@
  */
 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 */

Modified: trunk/main/asterisk.exports.in
URL: http://svnview.digium.com/svn/asterisk/trunk/main/asterisk.exports.in?view=diff&rev=368181&r1=368180&r2=368181
==============================================================================
--- trunk/main/asterisk.exports.in (original)
+++ trunk/main/asterisk.exports.in Fri Jun  1 11:33:25 2012
@@ -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;

Modified: trunk/main/astobj2.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/astobj2.c?view=diff&rev=368181&r1=368180&r2=368181
==============================================================================
--- trunk/main/astobj2.c (original)
+++ trunk/main/astobj2.c Fri Jun  1 11:33:25 2012
@@ -638,6 +638,7 @@
 {
 	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 @@
 
 	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 @@
 
 	if (!holder) {
 		/* For sanity */
-		return NULL;
-	}
+		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;
@@ -712,7 +716,6 @@
 
 	return obj;
 }
-
 
 /* internal callback to destroy a container. */
 static void container_destruct(void *c);
@@ -1519,6 +1522,20 @@
 	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)
 {

Modified: trunk/main/config.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/config.c?view=diff&rev=368181&r1=368180&r2=368181
==============================================================================
--- trunk/main/config.c (original)
+++ trunk/main/config.c Fri Jun  1 11:33:25 2012
@@ -2658,6 +2658,13 @@
 			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 @@
 			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;
 		}

Modified: trunk/main/udptl.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/udptl.c?view=diff&rev=368181&r1=368180&r2=368181
==============================================================================
--- trunk/main/udptl.c (original)
+++ trunk/main/udptl.c Fri Jun  1 11:33:25 2012
@@ -61,7 +61,7 @@
 #include "asterisk/frame.h"
 #include "asterisk/channel.h"
 #include "asterisk/acl.h"

[... 725 lines stripped ...]



More information about the asterisk-commits mailing list