diff options
Diffstat (limited to 'main/features_config.c')
-rw-r--r-- | main/features_config.c | 1534 |
1 files changed, 1534 insertions, 0 deletions
diff --git a/main/features_config.c b/main/features_config.c new file mode 100644 index 000000000..f8bdb1c3c --- /dev/null +++ b/main/features_config.c @@ -0,0 +1,1534 @@ +/* +* Asterisk -- An open source telephony toolkit. +* +* Copyright (C) 2013, Digium, Inc. +* +* Mark Michelson <mmichelson@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. +*/ + +#include "asterisk.h" + +#include "asterisk/features_config.h" +#include "asterisk/config_options.h" +#include "asterisk/datastore.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/app.h" +#include "asterisk/cli.h" + +/* BUGBUG XML Documentation is still needed for configuration options */ +/*** DOCUMENTATION + <function name="FEATURE" language="en_US"> + <synopsis> + Get or set a feature option on a channel. + </synopsis> + <syntax> + <parameter name="option_name" required="true"> + <para>The allowed values are:</para> + <enumlist> + <enum name="inherit"><para>Inherit feature settings made in FEATURE or FEATUREMAP to child channels.</para></enum> + <enum name="featuredigittimeout"><para>Milliseconds allowed between digits when entering a feature code.</para></enum> + <enum name="transferdigittimeout"><para>Milliseconds allowed between digits when dialing a transfer destination</para></enum> + <enum name="atxfernoanswertimeout"><para>Milliseconds to wait for transfer destination to answer</para></enum> + <enum name="atxferdropcall"><para>Hang up the call entirely if the attended transfer fails</para></enum> + <enum name="atxferloopdelay"><para>Milliseconds to wait between attempts to re-dial transfer destination</para></enum> + <enum name="atxfercallbackretries"><para>Number of times to re-attempt dialing a transfer destination</para></enum> + <enum name="xfersound"><para>Sound to play to a transferee when a transfer completes</para></enum> + <enum name="xferfailsound"><para>Sound to play to a transferee when a transfer fails</para></enum> + <enum name="atxferabort"><para>Digits to dial to abort an attended transfer attempt</para></enum> + <enum name="atxfercomplete"><para>Digits to dial to complete an attended transfer</para></enum> + <enum name="atxferthreeway"><para>Digits to dial to change an attended transfer into a three-way call</para></enum> + <enum name="pickupexten"><para>Digits used for picking up ringing calls</para></enum> + <enum name="pickupsound"><para>Sound to play to picker when a call is picked up</para></enum> + <enum name="pickupfailsound"><para>Sound to play to picker when a call cannot be picked up</para></enum> + <enum name="courtesytone"><para>Sound to play when automon or automixmon is activated</para></enum> + </enumlist> + </parameter> + </syntax> + <description> + <para>When this function is used as a read, it will get the current + value of the specified feature option for this channel. It will be + the value of this option configured in features.conf if a channel specific + value has not been set. This function can also be used to set a channel + specific value for the supported feature options.</para> + </description> + <see-also> + <ref type="function">FEATUREMAP</ref> + </see-also> + </function> + <function name="FEATUREMAP" language="en_US"> + <synopsis> + Get or set a feature map to a given value on a specific channel. + </synopsis> + <syntax> + <parameter name="feature_name" required="true"> + <para>The allowed values are:</para> + <enumlist> + <enum name="atxfer"><para>Attended Transfer</para></enum> + <enum name="blindxfer"><para>Blind Transfer</para></enum> + <enum name="automon"><para>Auto Monitor</para></enum> + <enum name="disconnect"><para>Call Disconnect</para></enum> + <enum name="parkcall"><para>Park Call</para></enum> + <enum name="automixmon"><para>Auto MixMonitor</para></enum> + </enumlist> + </parameter> + </syntax> + <description> + <para>When this function is used as a read, it will get the current + digit sequence mapped to the specified feature for this channel. This + value will be the one configured in features.conf if a channel specific + value has not been set. This function can also be used to set a channel + specific value for a feature mapping.</para> + </description> + <see-also> + <ref type="function">FEATURE</ref> + </see-also> + </function> + ***/ +/*! Default general options */ +#define DEFAULT_FEATURE_DIGIT_TIMEOUT 1000 + +/*! Default xfer options */ +#define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000 +#define DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER 15000 +#define DEFAULT_ATXFER_DROP_CALL 0 +#define DEFAULT_ATXFER_LOOP_DELAY 10000 +#define DEFAULT_ATXFER_CALLBACK_RETRIES 2 +#define DEFAULT_XFERSOUND "beep" +#define DEFAULT_XFERFAILSOUND "beeperr" +#define DEFAULT_ATXFER_ABORT "*1" +#define DEFAULT_ATXFER_COMPLETE "*2" +#define DEFAULT_ATXFER_THREEWAY "*3" + +/*! Default pickup options */ +#define DEFAULT_PICKUPEXTEN "*8" +#define DEFAULT_PICKUPSOUND "" +#define DEFAULT_PICKUPFAILSOUND "" + +/*! Default featuremap options */ +#define DEFAULT_FEATUREMAP_BLINDXFER "#" +#define DEFAULT_FEATUREMAP_DISCONNECT "*" +#define DEFAULT_FEATUREMAP_AUTOMON "" +#define DEFAULT_FEATUREMAP_ATXFER "" +#define DEFAULT_FEATUREMAP_PARKCALL "" +#define DEFAULT_FEATUREMAP_AUTOMIXMON "" + +/*! + * \brief Configuration from the "general" section of features.conf + */ +struct features_global_config { + struct ast_features_general_config *general; + struct ast_features_xfer_config *xfer; + struct ast_features_pickup_config *pickup; +}; + +static void ast_applicationmap_item_destructor(void *obj) +{ + struct ast_applicationmap_item *item = obj; + + ast_string_field_free_memory(item); +} + +static int applicationmap_sort(const void *obj, const void *arg, int flags) +{ + const struct ast_applicationmap_item *item1 = obj; + const struct ast_applicationmap_item *item2; + const char *key2; + + switch(flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + case OBJ_KEY: + key2 = arg; + return strcasecmp(item1->name, key2); + case OBJ_PARTIAL_KEY: + key2 = arg; + return strncasecmp(item1->name, key2, strlen(key2)); + default: + case OBJ_POINTER: + item2 = arg; + return strcasecmp(item1->name, item2->name); + } +} + +/*! + * \brief Entry in the container of featuregroups + */ +struct featuregroup_item { + AST_DECLARE_STRING_FIELDS( + /*! The name of the applicationmap item that we are referring to */ + AST_STRING_FIELD(appmap_item_name); + /*! Custom DTMF override to use instead of the default for the applicationmap item */ + AST_STRING_FIELD(dtmf_override); + ); + /*! The applicationmap item that is being referred to */ + struct ast_applicationmap_item *appmap_item; +}; + +static void featuregroup_item_destructor(void *obj) +{ + struct featuregroup_item *item = obj; + + ast_string_field_free_memory(item); + ao2_cleanup(item->appmap_item); +} + +static int group_item_sort(const void *obj, const void *arg, int flags) +{ + const struct featuregroup_item *item1 = obj; + const struct featuregroup_item *item2; + const char *key2; + + switch(flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + case OBJ_KEY: + key2 = arg; + return strcasecmp(item1->appmap_item_name, key2); + case OBJ_PARTIAL_KEY: + key2 = arg; + return strncasecmp(item1->appmap_item_name, key2, strlen(key2)); + case OBJ_POINTER: + item2 = arg; + return strcasecmp(item1->appmap_item_name, item2->appmap_item_name); + default: + return CMP_STOP; + } +} + +/*! + * \brief Featuregroup representation + */ +struct featuregroup { + /*! The name of the featuregroup */ + const char *name; + /*! A container of featuregroup_items */ + struct ao2_container *items; +}; + +static int featuregroup_hash(const void *obj, int flags) +{ + const struct featuregroup *group; + const char *key; + + switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + case OBJ_KEY: + key = obj; + return ast_str_case_hash(key); + case OBJ_PARTIAL_KEY: + ast_assert(0); + return 0; + case OBJ_POINTER: + default: + group = obj; + return ast_str_case_hash(group->name); + } +} + +static int featuregroup_cmp(void *obj, void *arg, int flags) +{ + struct featuregroup *group1 = obj; + struct featuregroup *group2; + const char *key2; + + switch(flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + case OBJ_KEY: + key2 = arg; + return strcasecmp(group1->name, key2) ? 0 : CMP_MATCH; + case OBJ_PARTIAL_KEY: + key2 = arg; + return strncasecmp(group1->name, key2, strlen(key2)) ? 0 : CMP_MATCH; + case OBJ_POINTER: + group2 = arg; + return strcasecmp(group1->name, group2->name) ? 0 : CMP_MATCH; + default: + return CMP_STOP; + } +} + +static void *featuregroup_find(struct ao2_container *group_container, const char *category) +{ + return ao2_find(group_container, category, OBJ_KEY); +} + +static void featuregroup_destructor(void *obj) +{ + struct featuregroup *group = obj; + + ast_free((char *) group->name); + ao2_cleanup(group->items); +} + +static void *featuregroup_alloc(const char *cat) +{ + struct featuregroup *group; + + group = ao2_alloc(sizeof(*group), featuregroup_destructor); + if (!group) { + return NULL; + } + + group->name = ast_strdup(cat); + if (!group->name) { + ao2_cleanup(group); + return NULL; + } + + group->items = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, + AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, group_item_sort, NULL); + if (!group->items) { + ao2_cleanup(group); + return NULL; + } + + return group; +} + +struct features_config { + struct features_global_config *global; + struct ast_featuremap_config *featuremap; + struct ao2_container *applicationmap; + struct ao2_container *featuregroups; +}; + +static struct aco_type global_option = { + .type = ACO_GLOBAL, + .name = "globals", + .category_match = ACO_WHITELIST, + .category = "^general$", + .item_offset = offsetof(struct features_config, global), +}; + +static struct aco_type featuremap_option = { + .type = ACO_GLOBAL, + .name = "featuremap", + .category_match = ACO_WHITELIST, + .category = "^featuremap$", + .item_offset = offsetof(struct features_config, featuremap), +}; + +static struct aco_type applicationmap_option = { + .type = ACO_GLOBAL, + .name = "applicationmap", + .category_match = ACO_WHITELIST, + .category = "^applicationmap$", + .item_offset = offsetof(struct features_config, applicationmap), +}; + +static struct aco_type featuregroup_option = { + .type = ACO_ITEM, + .name = "featuregroup", + .category_match = ACO_BLACKLIST, + .category = "^(general|featuremap|applicationmap|parkinglot_.*)$", + .item_offset = offsetof(struct features_config, featuregroups), + .item_alloc = featuregroup_alloc, + .item_find = featuregroup_find, +}; + +static struct aco_type *global_options[] = ACO_TYPES(&global_option); +static struct aco_type *featuremap_options[] = ACO_TYPES(&featuremap_option); +static struct aco_type *applicationmap_options[] = ACO_TYPES(&applicationmap_option); +static struct aco_type *featuregroup_options[] = ACO_TYPES(&featuregroup_option); + +static struct aco_file features_conf = { + .filename = "features.conf", + .types = ACO_TYPES(&global_option, &featuremap_option, &applicationmap_option, &featuregroup_option), +}; + +AO2_GLOBAL_OBJ_STATIC(globals); + +static void features_config_destructor(void *obj) +{ + struct features_config *cfg = obj; + + ao2_cleanup(cfg->global); + ao2_cleanup(cfg->featuremap); + ao2_cleanup(cfg->applicationmap); + ao2_cleanup(cfg->featuregroups); +} + +static void featuremap_config_destructor(void *obj) +{ + struct ast_featuremap_config *cfg = obj; + + ast_string_field_free_memory(cfg); +} + +static void global_config_destructor(void *obj) +{ + struct features_global_config *cfg = obj; + + ao2_cleanup(cfg->general); + ao2_cleanup(cfg->xfer); + ao2_cleanup(cfg->pickup); +} + +static void general_destructor(void *obj) +{ + struct ast_features_general_config *cfg = obj; + + ast_string_field_free_memory(cfg); +} + +static void xfer_destructor(void *obj) +{ + struct ast_features_xfer_config *cfg = obj; + + ast_string_field_free_memory(cfg); +} + +static void pickup_destructor(void *obj) +{ + struct ast_features_pickup_config *cfg = obj; + + ast_string_field_free_memory(cfg); +} + +static struct features_global_config *global_config_alloc(void) +{ + RAII_VAR(struct features_global_config *, cfg, NULL, ao2_cleanup); + + cfg = ao2_alloc(sizeof(*cfg), global_config_destructor); + if (!cfg) { + return NULL; + } + + cfg->general = ao2_alloc(sizeof(*cfg->general), general_destructor); + if (!cfg->general || ast_string_field_init(cfg->general, 32)) { + return NULL; + } + + cfg->xfer = ao2_alloc(sizeof(*cfg->xfer), xfer_destructor); + if (!cfg->xfer || ast_string_field_init(cfg->xfer, 32)) { + return NULL; + } + + cfg->pickup = ao2_alloc(sizeof(*cfg->pickup), pickup_destructor); + if (!cfg->pickup || ast_string_field_init(cfg->pickup, 32)) { + return NULL; + } + + ao2_ref(cfg, +1); + return cfg; +} + +static struct ao2_container *applicationmap_alloc(void) +{ + return ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, + AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, applicationmap_sort, NULL); +} + +/*! + * \internal + * \brief Allocate the major configuration structure + * + * The parameter is used to determine if the applicationmap and featuregroup + * structures should be allocated. We only want to allocate these structures for + * the global features_config structure. For the datastores on channels, we don't + * need to allocate these structures because they are not used. + * + * \param allocate_applicationmap See previous explanation + * \retval NULL Failed to alloate configuration + * \retval non-NULL Allocated configuration + */ +static struct features_config *__features_config_alloc(int allocate_applicationmap) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + cfg = ao2_alloc(sizeof(*cfg), features_config_destructor); + if (!cfg) { + return NULL; + } + + cfg->global = global_config_alloc();; + if (!cfg->global) { + return NULL; + } + + cfg->featuremap = ao2_alloc(sizeof(*cfg->featuremap), featuremap_config_destructor); + if (!cfg->featuremap || ast_string_field_init(cfg->featuremap, 32)) { + return NULL; + } + + if (allocate_applicationmap) { + cfg->applicationmap = applicationmap_alloc(); + if (!cfg->applicationmap) { + return NULL; + } + + cfg->featuregroups = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 11, featuregroup_hash, + featuregroup_cmp); + if (!cfg->featuregroups) { + return NULL; + } + } + + ao2_ref(cfg, +1); + return cfg; + +} + +static void *features_config_alloc(void) +{ + return __features_config_alloc(1); +} + +static void general_copy(struct ast_features_general_config *dest, const struct ast_features_general_config *src) +{ + ast_string_fields_copy(dest, src); + dest->featuredigittimeout = src->featuredigittimeout; +} + +static void xfer_copy(struct ast_features_xfer_config *dest, const struct ast_features_xfer_config *src) +{ + ast_string_fields_copy(dest, src); + dest->transferdigittimeout = src->transferdigittimeout; + dest->atxfernoanswertimeout = src->atxfernoanswertimeout; + dest->atxferloopdelay = src->atxferloopdelay; + dest->atxfercallbackretries = src->atxfercallbackretries; + dest->atxferdropcall = src->atxferdropcall; +} + +static void pickup_copy(struct ast_features_pickup_config *dest, const struct ast_features_pickup_config *src) +{ + ast_string_fields_copy(dest, src); +} + +static void global_copy(struct features_global_config *dest, const struct features_global_config *src) +{ + general_copy(dest->general, src->general); + xfer_copy(dest->xfer, src->xfer); + pickup_copy(dest->pickup, src->pickup); +} + +static void featuremap_copy(struct ast_featuremap_config *dest, const struct ast_featuremap_config *src) +{ + ast_string_fields_copy(dest, src); +} + +static void features_copy(struct features_config *dest, const struct features_config *src) +{ + global_copy(dest->global, src->global); + featuremap_copy(dest->featuremap, src->featuremap); + + /* applicationmap and featuregroups are purposely not copied. A channel's applicationmap + * is produced on the fly when ast_get_chan_applicationmap() is called + */ +} + +static struct features_config *features_config_dup(const struct features_config *orig) +{ + struct features_config *dup; + + dup = __features_config_alloc(0); + if (!dup) { + return NULL; + } + + features_copy(dup, orig); + + return dup; +} + +static int general_set(struct ast_features_general_config *general, const char *name, + const char *value) +{ + int res = 0; + + if (!strcasecmp(name, "featuredigittimeout")) { + res = ast_parse_arg(value, PARSE_INT32, &general->featuredigittimeout); + } else if (!strcasecmp(name, "courtesytone")) { + ast_string_field_set(general, courtesytone, value); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int general_get(struct ast_features_general_config *general, const char *field, + char *buf, size_t len) +{ + int res = 0; + + if (!strcasecmp(field, "featuredigittimeout")) { + snprintf(buf, len, "%u", general->featuredigittimeout); + } else if (!strcasecmp(field, "courtesytone")) { + ast_copy_string(buf, general->courtesytone, len); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int xfer_set(struct ast_features_xfer_config *xfer, const char *name, + const char *value) +{ + int res = 0; + + if (!strcasecmp(name, "transferdigittimeout")) { + res = ast_parse_arg(value, PARSE_INT32, &xfer->transferdigittimeout); + } else if (!strcasecmp(name, "atxfernoanswertimeout")) { + res = ast_parse_arg(value, PARSE_INT32, &xfer->atxfernoanswertimeout); + } else if (!strcasecmp(name, "atxferloopdelay")) { + res = ast_parse_arg(value, PARSE_INT32, &xfer->atxferloopdelay); + } else if (!strcasecmp(name, "atxfercallbackretries")) { + res = ast_parse_arg(value, PARSE_INT32, &xfer->atxfercallbackretries); + } else if (!strcasecmp(name, "atxferdropcall")) { + xfer->atxferdropcall = ast_true(value); + } else if (!strcasecmp(name, "xfersound")) { + ast_string_field_set(xfer, xfersound, value); + } else if (!strcasecmp(name, "xferfailsound")) { + ast_string_field_set(xfer, xferfailsound, value); + } else if (!strcasecmp(name, "atxferabort")) { + ast_string_field_set(xfer, atxferabort, value); + } else if (!strcasecmp(name, "atxfercomplete")) { + ast_string_field_set(xfer, atxfercomplete, value); + } else if (!strcasecmp(name, "atxferthreeway")) { + ast_string_field_set(xfer, atxferthreeway, value); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int xfer_get(struct ast_features_xfer_config *xfer, const char *field, + char *buf, size_t len) +{ + int res = 0; + + if (!strcasecmp(field, "transferdigittimeout")) { + snprintf(buf, len, "%u", xfer->transferdigittimeout); + } else if (!strcasecmp(field, "atxfernoanswertimeout")) { + snprintf(buf, len, "%u", xfer->atxfernoanswertimeout); + } else if (!strcasecmp(field, "atxferloopdelay")) { + snprintf(buf, len, "%u", xfer->atxferloopdelay); + } else if (!strcasecmp(field, "atxfercallbackretries")) { + snprintf(buf, len, "%u", xfer->atxfercallbackretries); + } else if (!strcasecmp(field, "atxferdropcall")) { + snprintf(buf, len, "%u", xfer->atxferdropcall); + } else if (!strcasecmp(field, "xfersound")) { + ast_copy_string(buf, xfer->xfersound, len); + } else if (!strcasecmp(field, "xferfailsound")) { + ast_copy_string(buf, xfer->xferfailsound, len); + } else if (!strcasecmp(field, "atxferabort")) { + ast_copy_string(buf, xfer->atxferabort, len); + } else if (!strcasecmp(field, "atxfercomplete")) { + ast_copy_string(buf, xfer->atxfercomplete, len); + } else if (!strcasecmp(field, "atxferthreeway")) { + ast_copy_string(buf, xfer->atxferthreeway, len); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int pickup_set(struct ast_features_pickup_config *pickup, const char *name, + const char *value) +{ + int res = 0; + + if (!strcasecmp(name, "pickupsound")) { + ast_string_field_set(pickup, pickupsound, value); + } else if (!strcasecmp(name, "pickupfailsound")) { + ast_string_field_set(pickup, pickupfailsound, value); + } else if (!strcasecmp(name, "pickupexten")) { + ast_string_field_set(pickup, pickupexten, value); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int pickup_get(struct ast_features_pickup_config *pickup, const char *field, + char *buf, size_t len) +{ + int res = 0; + + if (!strcasecmp(field, "pickupsound")) { + ast_copy_string(buf, pickup->pickupsound, len); + } else if (!strcasecmp(field, "pickupfailsound")) { + ast_copy_string(buf, pickup->pickupfailsound, len); + } else if (!strcasecmp(field, "pickupexten")) { + ast_copy_string(buf, pickup->pickupexten, len); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int featuremap_set(struct ast_featuremap_config *featuremap, const char *name, + const char *value) +{ + int res = 0; + + if (!strcasecmp(name, "blindxfer")) { + ast_string_field_set(featuremap, blindxfer, value); + } else if (!strcasecmp(name, "disconnect")) { + ast_string_field_set(featuremap, disconnect, value); + } else if (!strcasecmp(name, "automon")) { + ast_string_field_set(featuremap, automon, value); + } else if (!strcasecmp(name, "atxfer")) { + ast_string_field_set(featuremap, atxfer, value); + } else if (!strcasecmp(name, "automixmon")) { + ast_string_field_set(featuremap, automixmon, value); + } else if (!strcasecmp(name, "parkcall")) { + ast_string_field_set(featuremap, parkcall, value); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static int featuremap_get(struct ast_featuremap_config *featuremap, const char *field, + char *buf, size_t len) +{ + int res = 0; + + if (!strcasecmp(field, "blindxfer")) { + ast_copy_string(buf, featuremap->blindxfer, len); + } else if (!strcasecmp(field, "disconnect")) { + ast_copy_string(buf, featuremap->disconnect, len); + } else if (!strcasecmp(field, "automon")) { + ast_copy_string(buf, featuremap->automon, len); + } else if (!strcasecmp(field, "atxfer")) { + ast_copy_string(buf, featuremap->atxfer, len); + } else if (!strcasecmp(field, "automixmon")) { + ast_copy_string(buf, featuremap->automixmon, len); + } else if (!strcasecmp(field, "parkcall")) { + ast_copy_string(buf, featuremap->parkcall, len); + } else { + /* Unrecognized option */ + res = -1; + } + + return res; +} + +static void feature_ds_destroy(void *data) +{ + struct features_config *cfg = data; + ao2_cleanup(cfg); +} + +static void *feature_ds_duplicate(void *data) +{ + struct features_config *old_cfg = data; + + return features_config_dup(old_cfg); +} + +static const struct ast_datastore_info feature_ds_info = { + .type = "FEATURE", + .destroy = feature_ds_destroy, + .duplicate = feature_ds_duplicate, +}; + +/*! + * \internal + * \brief Find or create feature datastore on a channel + * + * \pre chan is locked + * + * \return the data on the FEATURE datastore, or NULL on error + */ +static struct features_config *get_feature_ds(struct ast_channel *chan) +{ + RAII_VAR(struct features_config *, orig, NULL, ao2_cleanup); + struct features_config *cfg; + struct ast_datastore *ds; + + if ((ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL))) { + cfg = ds->data; + ao2_ref(cfg, +1); + return cfg; + } + + orig = ao2_global_obj_ref(globals); + if (!orig) { + return NULL; + } + + cfg = features_config_dup(orig); + if (!cfg) { + return NULL; + } + + if (!(ds = ast_datastore_alloc(&feature_ds_info, NULL))) { + ao2_cleanup(cfg); + return NULL; + } + + /* Give the datastore a reference to the config */ + ao2_ref(cfg, +1); + ds->data = cfg; + + ast_channel_datastore_add(chan, ds); + + return cfg; +} + +static struct ast_datastore *get_feature_chan_ds(struct ast_channel *chan) +{ + struct ast_datastore *ds; + + if (!(ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL))) { + /* Hasn't been created yet. Trigger creation. */ + RAII_VAR(struct features_config *, cfg, get_feature_ds(chan), ao2_cleanup); + ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL); + } + + return ds; +} + +struct ast_features_general_config *ast_get_chan_features_general_config(struct ast_channel *chan) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + if (chan) { + cfg = get_feature_ds(chan); + } else { + cfg = ao2_global_obj_ref(globals); + } + + if (!cfg) { + return NULL; + } + + ast_assert(cfg->global && cfg->global->general); + + ao2_ref(cfg->global->general, +1); + return cfg->global->general; +} + +struct ast_features_xfer_config *ast_get_chan_features_xfer_config(struct ast_channel *chan) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + if (chan) { + cfg = get_feature_ds(chan); + } else { + cfg = ao2_global_obj_ref(globals); + } + + if (!cfg) { + return NULL; + } + + ast_assert(cfg->global && cfg->global->xfer); + + ao2_ref(cfg->global->xfer, +1); + return cfg->global->xfer; +} + +struct ast_features_pickup_config *ast_get_chan_features_pickup_config(struct ast_channel *chan) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + if (chan) { + cfg = get_feature_ds(chan); + } else { + cfg = ao2_global_obj_ref(globals); + } + + if (!cfg) { + return NULL; + } + + ast_assert(cfg->global && cfg->global->pickup); + + ao2_ref(cfg->global->pickup, +1); + return cfg->global->pickup; +} + +struct ast_featuremap_config *ast_get_chan_featuremap_config(struct ast_channel *chan) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + if (chan) { + cfg = get_feature_ds(chan); + } else { + cfg = ao2_global_obj_ref(globals); + } + + if (!cfg) { + return NULL; + } + + ast_assert(cfg->featuremap != NULL); + + ao2_ref(cfg->featuremap, +1); + return cfg->featuremap; +} + +int ast_get_builtin_feature(struct ast_channel *chan, const char *feature, char *buf, size_t len) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + if (chan) { + cfg = get_feature_ds(chan); + } else { + cfg = ao2_global_obj_ref(globals); + } + + if (!cfg) { + return -1; + } + + return featuremap_get(cfg->featuremap, feature, buf, len); +} + +int ast_get_feature(struct ast_channel *chan, const char *feature, char *buf, size_t len) +{ + RAII_VAR(struct ao2_container *, applicationmap, NULL, ao2_cleanup); + RAII_VAR(struct ast_applicationmap_item *, item, NULL, ao2_cleanup); + if (!ast_get_builtin_feature(chan, feature, buf, len)) { + return 0; + } + + /* Dang, must be in the application map */ + applicationmap = ast_get_chan_applicationmap(chan); + + if (!applicationmap) { + return -1; + } + + item = ao2_find(applicationmap, feature, OBJ_KEY); + if (!item) { + return -1; + } + + ast_copy_string(buf, item->dtmf, len); + return 0; +} + +static struct ast_applicationmap_item *applicationmap_item_alloc(const char *name, + const char *app, const char *app_data, const char *moh_class, const char *dtmf, + unsigned int activate_on_self) +{ + struct ast_applicationmap_item *item; + + item = ao2_alloc(sizeof(*item), ast_applicationmap_item_destructor); + + if (!item || ast_string_field_init(item, 64)) { + return NULL; + } + + ast_string_field_set(item, name, name); + ast_string_field_set(item, app, app); + ast_string_field_set(item, app_data, app_data); + ast_string_field_set(item, moh_class, moh_class); + ast_copy_string(item->dtmf, dtmf, sizeof(item->dtmf)); + item->activate_on_self = activate_on_self; + + return item; +} + +static int add_item(void *obj, void *arg, int flags) +{ + struct featuregroup_item *fg_item = obj; + struct ao2_container *applicationmap = arg; + RAII_VAR(struct ast_applicationmap_item *, appmap_item, NULL, ao2_cleanup); + + /* If there's no DTMF override, then we can just link + * the applicationmap item directly. Otherwise, we need + * to create a copy with the DTMF override in place and + * link that instead + */ + if (ast_strlen_zero(fg_item->dtmf_override)) { + ao2_ref(fg_item->appmap_item, +1); + appmap_item = fg_item->appmap_item; + } else { + appmap_item = applicationmap_item_alloc(fg_item->appmap_item_name, + fg_item->appmap_item->app, fg_item->appmap_item->app_data, + fg_item->appmap_item->moh_class, fg_item->dtmf_override, + fg_item->appmap_item->activate_on_self); + } + + if (!appmap_item) { + return 0; + } + + if (!ao2_link(applicationmap, appmap_item)) { + ast_log(LOG_WARNING, "Unable to add applicationmap item %s. Possible duplicate\n", + fg_item->appmap_item_name); + } + return 0; +} + +struct ao2_container *ast_get_chan_applicationmap(struct ast_channel *chan) +{ + RAII_VAR(struct features_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + struct ao2_container *applicationmap; + char *group_names; + char *name; + + if (!cfg) { + return NULL; + } + + if (!chan) { + if (!cfg->applicationmap || ao2_container_count(cfg->applicationmap) == 0) { + return NULL; + } + ao2_ref(cfg->applicationmap, +1); + return cfg->applicationmap; + } + + group_names = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES"), "")); + if (ast_strlen_zero(group_names)) { + return NULL; + } + + applicationmap = applicationmap_alloc(); + if (!applicationmap) { + return NULL; + } + + while ((name = strsep(&group_names, "#"))) { + RAII_VAR(struct featuregroup *, group, ao2_find(cfg->featuregroups, name, OBJ_KEY), ao2_cleanup); + if (!group) { + RAII_VAR(struct ast_applicationmap_item *, item, ao2_find(cfg->applicationmap, name, OBJ_KEY), ao2_cleanup); + if (item && !ao2_link(applicationmap, item)) { + ast_log(LOG_WARNING, "Unable to add applicationmap item %s. Possible duplicate.\n", item->name); + } + } else { + ao2_callback(group->items, 0, add_item, applicationmap); + } + } + + if (ao2_container_count(applicationmap) == 0) { + ao2_cleanup(applicationmap); + return NULL; + } + + return applicationmap; +} + +static int applicationmap_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + RAII_VAR(struct ast_applicationmap_item *, item, NULL, ao2_cleanup); + struct ao2_container *applicationmap = obj; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(dtmf); + AST_APP_ARG(activate_on); + AST_APP_ARG(app); + AST_APP_ARG(app_data); + AST_APP_ARG(moh_class); + ); + char *parse = ast_strdupa(var->value); + char *slash; + char *paren; + unsigned int activate_on_self; + + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.dtmf) || + ast_strlen_zero(args.activate_on) || + ast_strlen_zero(args.app)) { + ast_log(LOG_WARNING, "Invalid applicationmap syntax for '%s'. Missing required argument\n", var->name); + return -1; + } + + /* features.conf used to have an "activated_by" portion + * in addition to activate_on. Get rid of whatever may be + * there + */ + slash = strchr(args.activate_on, '/'); + if (slash) { + *slash = '\0'; + } + + /* Two syntaxes allowed for applicationmap: + * Old: foo = *1,self,NoOp,Boo!,default + * New: foo = *1,self,NoOp(Boo!),default + * + * We need to handle both + */ + paren = strchr(args.app, '('); + if (paren) { + /* New syntax */ + char *close_paren; + + args.moh_class = args.app_data; + *paren++ = '\0'; + close_paren = strrchr(paren, ')'); + if (close_paren) { + *close_paren = '\0'; + } + args.app_data = paren; + + /* Re-check that the application is not empty */ + if (ast_strlen_zero(args.app)) { + ast_log(LOG_WARNING, "Applicationmap item '%s' does not contain an application name.\n", var->name); + return -1; + } + } else if (strchr(args.app_data, '"')) { + args.app_data = ast_strip_quoted(args.app_data, "\"", "\""); + } + + /* Allow caller and callee to be specified for backwards compatibility */ + if (!strcasecmp(args.activate_on, "self") || !strcasecmp(args.activate_on, "caller")) { + activate_on_self = 1; + } else if (!strcasecmp(args.activate_on, "peer") || !strcasecmp(args.activate_on, "callee")) { + activate_on_self = 0; + } else { + ast_log(LOG_WARNING, "Invalid 'activate_on' value %s for applicationmap item %s\n", + args.activate_on, var->name); + return -1; + } + + ast_debug(1, "Allocating applicationmap item: dtmf = %s, app = %s, app_data = %s, moh_class = %s\n", + args.dtmf, args.app, args.app_data, args.moh_class); + + item = applicationmap_item_alloc(var->name, args.app, args.app_data, + args.moh_class, args.dtmf, activate_on_self); + + if (!item) { + return -1; + } + + if (!ao2_link(applicationmap, item)) { + ast_log(LOG_WARNING, "Unable to add applicationmap item %s. Possible duplicate\n", item->name); + } + + return 0; +} + +static int featuregroup_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + RAII_VAR(struct featuregroup_item *, item, NULL, ao2_cleanup); + struct featuregroup *group = obj; + + item = ao2_alloc(sizeof(*item), featuregroup_item_destructor); + if (!item || ast_string_field_init(item, 32)) { + return -1; + } + + ast_string_field_set(item, appmap_item_name, var->name); + ast_string_field_set(item, dtmf_override, var->value); + + if (!ao2_link(group->items, item)) { + ast_log(LOG_WARNING, "Unable to add featuregroup item %s. Possible duplicate\n", item->appmap_item_name); + } + + /* We wait to look up the application map item in the preapply callback */ + + return 0; +} + +static int general_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct features_global_config *global = obj; + struct ast_features_general_config *general = global->general; + + return general_set(general, var->name, var->value); +} + +static int xfer_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct features_global_config *global = obj; + struct ast_features_xfer_config *xfer = global->xfer; + + return xfer_set(xfer, var->name, var->value); +} + +static int pickup_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct features_global_config *global = obj; + struct ast_features_pickup_config *pickup = global->pickup; + + return pickup_set(pickup, var->name, var->value); +} + +static int featuremap_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct ast_featuremap_config *featuremap = obj; + + return featuremap_set(featuremap, var->name, var->value); +} + +static int check_featuregroup_item(void *obj, void *arg, void *data, int flags) +{ + struct ast_applicationmap_item *appmap_item; + struct featuregroup_item *fg_item = obj; + int *err = arg; + struct ao2_container *applicationmap = data; + + appmap_item = ao2_find(applicationmap, fg_item->appmap_item_name, OBJ_KEY); + if (!appmap_item) { + *err = 1; + return CMP_STOP; + } + + fg_item->appmap_item = appmap_item; + + return 0; +} + +static int check_featuregroup(void *obj, void *arg, void *data, int flags) +{ + struct featuregroup *group = obj; + int *err = arg; + + ao2_callback_data(group->items, 0, check_featuregroup_item, arg, data); + + if (*err) { + ast_log(LOG_WARNING, "Featuregroup %s refers to non-existent applicationmap item\n", + group->name); + } + + return *err ? CMP_STOP : 0; +} + +static int features_pre_apply_config(void); + +CONFIG_INFO_CORE("features", cfg_info, globals, features_config_alloc, + .files = ACO_FILES(&features_conf), + .pre_apply_config = features_pre_apply_config, +); + +static int features_pre_apply_config(void) +{ + struct features_config *cfg = aco_pending_config(&cfg_info); + int err = 0; + + /* Now that the entire config has been processed, we can check that the featuregroup + * items refer to actual applicationmap items. + */ + + ao2_callback_data(cfg->featuregroups, 0, check_featuregroup, &err, cfg->applicationmap); + + return err; +} + +static int feature_read(struct ast_channel *chan, const char *cmd, char *data, + char *buf, size_t len) +{ + int res; + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + SCOPED_CHANNELLOCK(lock, chan); + + if (!strcasecmp(data, "inherit")) { + struct ast_datastore *ds = get_feature_chan_ds(chan); + unsigned int inherit = ds ? ds->inheritance : 0; + + snprintf(buf, len, "%s", inherit ? "yes" : "no"); + return 0; + } + + cfg = get_feature_ds(chan); + if (!cfg) { + return -1; + } + + res = general_get(cfg->global->general, data, buf, len) && + xfer_get(cfg->global->xfer, data, buf, len) && + pickup_get(cfg->global->pickup, data, buf, len); + + if (res) { + ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data); + } + + return res; +} + +static int feature_write(struct ast_channel *chan, const char *cmd, char *data, + const char *value) +{ + int res; + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + SCOPED_CHANNELLOCK(lock, chan); + + if (!strcasecmp(data, "inherit")) { + struct ast_datastore *ds = get_feature_chan_ds(chan); + if (ds) { + ds->inheritance = ast_true(value) ? DATASTORE_INHERIT_FOREVER : 0; + } + return 0; + } + + if (!(cfg = get_feature_ds(chan))) { + return -1; + } + + res = general_set(cfg->global->general, data, value) && + xfer_set(cfg->global->xfer, data, value) && + pickup_set(cfg->global->pickup, data, value); + + if (res) { + ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data); + } + + return res; +} + +static int featuremap_read(struct ast_channel *chan, const char *cmd, char *data, + char *buf, size_t len) +{ + int res; + SCOPED_CHANNELLOCK(lock, chan); + + res = ast_get_builtin_feature(chan, data, buf, len); + + if (res) { + ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data); + } + + return res; +} + +static int featuremap_write(struct ast_channel *chan, const char *cmd, char *data, + const char *value) +{ + int res; + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + SCOPED_CHANNELLOCK(lock, chan); + + if (!(cfg = get_feature_ds(chan))) { + return -1; + } + + res = featuremap_set(cfg->featuremap, data, value); + if (res) { + ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data); + return -1; + } + + return 0; +} + +static struct ast_custom_function feature_function = { + .name = "FEATURE", + .read = feature_read, + .write = feature_write +}; + +static struct ast_custom_function featuremap_function = { + .name = "FEATUREMAP", + .read = featuremap_read, + .write = featuremap_write +}; + +static int load_config(int reload) +{ + if (!reload && aco_info_init(&cfg_info)) { + ast_log(LOG_ERROR, "Unable to initialize configuration info for features\n"); + return -1; + } + + aco_option_register_custom(&cfg_info, "featuredigittimeout", ACO_EXACT, global_options, + __stringify(DEFAULT_FEATURE_DIGIT_TIMEOUT), general_handler, 0); + aco_option_register_custom(&cfg_info, "courtesytone", ACO_EXACT, global_options, + __stringify(DEFAULT_COURTESY_TONE), general_handler, 0); + + aco_option_register_custom(&cfg_info, "transferdigittimeout", ACO_EXACT, global_options, + __stringify(DEFAULT_TRANSFER_DIGIT_TIMEOUT), xfer_handler, 0) + aco_option_register_custom(&cfg_info, "atxfernoanswertimeout", ACO_EXACT, global_options, + __stringify(DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER), xfer_handler, 0); + aco_option_register_custom(&cfg_info, "atxferdropcall", ACO_EXACT, global_options, + __stringify(DEFAULT_ATXFER_DROP_CALL), xfer_handler, 0); + aco_option_register_custom(&cfg_info, "atxferloopdelay", ACO_EXACT, global_options, + __stringify(DEFAULT_ATXFER_LOOP_DELAY), xfer_handler, 0); + aco_option_register_custom(&cfg_info, "atxfercallbackretries", ACO_EXACT, global_options, + __stringify(DEFAULT_ATXFER_CALLBACK_RETRIES), xfer_handler, 0); + aco_option_register_custom(&cfg_info, "xfersound", ACO_EXACT, global_options, + DEFAULT_XFERSOUND, xfer_handler, 0); + aco_option_register_custom(&cfg_info, "xferfailsound", ACO_EXACT, global_options, + DEFAULT_XFERFAILSOUND, xfer_handler, 0); + aco_option_register_custom(&cfg_info, "atxferabort", ACO_EXACT, global_options, + DEFAULT_ATXFER_ABORT, xfer_handler, 0); + aco_option_register_custom(&cfg_info, "atxfercomplete", ACO_EXACT, global_options, + DEFAULT_ATXFER_COMPLETE, xfer_handler, 0); + aco_option_register_custom(&cfg_info, "atxferthreeway", ACO_EXACT, global_options, + DEFAULT_ATXFER_THREEWAY, xfer_handler, 0); + + aco_option_register_custom(&cfg_info, "pickupexten", ACO_EXACT, global_options, + DEFAULT_PICKUPEXTEN, pickup_handler, 0); + aco_option_register_custom(&cfg_info, "pickupsound", ACO_EXACT, global_options, + DEFAULT_PICKUPSOUND, pickup_handler, 0); + aco_option_register_custom(&cfg_info, "pickupfailsound", ACO_EXACT, global_options, + DEFAULT_PICKUPFAILSOUND, pickup_handler, 0); + + aco_option_register_custom(&cfg_info, "blindxfer", ACO_EXACT, featuremap_options, + DEFAULT_FEATUREMAP_BLINDXFER, featuremap_handler, 0); + aco_option_register_custom(&cfg_info, "disconnect", ACO_EXACT, featuremap_options, + DEFAULT_FEATUREMAP_DISCONNECT, featuremap_handler, 0); + aco_option_register_custom(&cfg_info, "automon", ACO_EXACT, featuremap_options, + DEFAULT_FEATUREMAP_AUTOMON, featuremap_handler, 0); + aco_option_register_custom(&cfg_info, "atxfer", ACO_EXACT, featuremap_options, + DEFAULT_FEATUREMAP_ATXFER, featuremap_handler, 0); + aco_option_register_custom(&cfg_info, "parkcall", ACO_EXACT, featuremap_options, + DEFAULT_FEATUREMAP_PARKCALL, featuremap_handler, 0); + aco_option_register_custom(&cfg_info, "automixmon", ACO_EXACT, featuremap_options, + DEFAULT_FEATUREMAP_AUTOMIXMON, featuremap_handler, 0); + + aco_option_register_custom(&cfg_info, "^.*$", ACO_REGEX, applicationmap_options, + "", applicationmap_handler, 0); + + aco_option_register_custom(&cfg_info, "^.*$", ACO_REGEX, featuregroup_options, + "", featuregroup_handler, 0); + + if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) { + ast_log(LOG_ERROR, "Failed to process features.conf configuration!\n"); + if (!reload) { + aco_info_destroy(&cfg_info); + ao2_global_obj_release(globals); + } + return -1; + } + + return 0; +} + +static int print_featuregroup(void *obj, void *arg, int flags) +{ + struct featuregroup_item *item = obj; + struct ast_cli_args *a = arg; + + ast_cli(a->fd, "===> --> %s (%s)\n", item->appmap_item_name, + S_OR(item->dtmf_override, item->appmap_item->dtmf)); + + return 0; +} + +static int print_featuregroups(void *obj, void *arg, int flags) +{ + struct featuregroup *group = obj; + struct ast_cli_args *a = arg; + + ast_cli(a->fd, "===> Group: %s\n", group->name); + + ao2_callback(group->items, 0, print_featuregroup, a); + return 0; +} + +#define HFS_FORMAT "%-25s %-7s %-7s\n" + +static int print_applicationmap(void *obj, void *arg, int flags) +{ + struct ast_applicationmap_item *item = obj; + struct ast_cli_args *a = arg; + + ast_cli(a->fd, HFS_FORMAT, item->name, "no def", item->dtmf); + return 0; +} + +/*! + * \brief CLI command to list configured features + * \param e + * \param cmd + * \param a + * + * \retval CLI_SUCCESS on success. + * \retval NULL when tab completion is used. + */ +static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup); + + switch (cmd) { + + case CLI_INIT: + e->command = "features show"; + e->usage = + "Usage: features show\n" + " Lists configured features\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + cfg = ao2_global_obj_ref(globals); + + ast_cli(a->fd, HFS_FORMAT, "Builtin Feature", "Default", "Current"); + ast_cli(a->fd, HFS_FORMAT, "---------------", "-------", "-------"); + + ast_cli(a->fd, HFS_FORMAT, "Pickup", DEFAULT_PICKUPEXTEN, cfg->global->pickup->pickupexten); + ast_cli(a->fd, HFS_FORMAT, "Blind Transfer", DEFAULT_FEATUREMAP_BLINDXFER, cfg->featuremap->blindxfer); + ast_cli(a->fd, HFS_FORMAT, "Attended Transfer", DEFAULT_FEATUREMAP_ATXFER, cfg->featuremap->atxfer); + ast_cli(a->fd, HFS_FORMAT, "One Touch Monitor", DEFAULT_FEATUREMAP_AUTOMON, cfg->featuremap->automon); + ast_cli(a->fd, HFS_FORMAT, "Disconnect Call", DEFAULT_FEATUREMAP_DISCONNECT, cfg->featuremap->disconnect); + ast_cli(a->fd, HFS_FORMAT, "Park Call", DEFAULT_FEATUREMAP_PARKCALL, cfg->featuremap->parkcall); + ast_cli(a->fd, HFS_FORMAT, "One Touch MixMonitor", DEFAULT_FEATUREMAP_AUTOMIXMON, cfg->featuremap->automixmon); + + ast_cli(a->fd, "\n"); + ast_cli(a->fd, HFS_FORMAT, "Dynamic Feature", "Default", "Current"); + ast_cli(a->fd, HFS_FORMAT, "---------------", "-------", "-------"); + if (!cfg->applicationmap || ao2_container_count(cfg->applicationmap) == 0) { + ast_cli(a->fd, "(none)\n"); + } else { + ao2_callback(cfg->applicationmap, 0, print_applicationmap, a); + } + + ast_cli(a->fd, "\nFeature Groups:\n"); + ast_cli(a->fd, "---------------\n"); + if (!cfg->featuregroups || ao2_container_count(cfg->featuregroups) == 0) { + ast_cli(a->fd, "(none)\n"); + } else { + ao2_callback(cfg->featuregroups, 0, print_featuregroups, a); + } + + ast_cli(a->fd, "\n"); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_features_config[] = { + AST_CLI_DEFINE(handle_feature_show, "Lists configured features"), +}; + +void ast_features_config_shutdown(void) +{ + ast_custom_function_unregister(&featuremap_function); + ast_custom_function_unregister(&feature_function); + ast_cli_unregister_multiple(cli_features_config, ARRAY_LEN(cli_features_config)); + aco_info_destroy(&cfg_info); + ao2_global_obj_release(globals); +} + +int ast_features_config_reload(void) +{ + return load_config(1); +} + +int ast_features_config_init(void) +{ + int res; + + res = load_config(0); + res |= __ast_custom_function_register(&feature_function, NULL); + res |= __ast_custom_function_register(&featuremap_function, NULL); + res |= ast_cli_register_multiple(cli_features_config, ARRAY_LEN(cli_features_config)); + + if (res) { + ast_features_config_shutdown(); + } + + return res; +} |