Skip to content
Snippets Groups Projects
features_config.c 68.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	} 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. */
    
    		ao2_cleanup(get_feature_ds(chan));
    
    
    		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;
    }
    
    
    char *ast_get_chan_features_xferfailsound(struct ast_channel *chan)
    {
    	char *res;
    	struct ast_features_xfer_config *cfg = ast_get_chan_features_xfer_config(chan);
    
    	if (!cfg) {
    		return NULL;
    	}
    
    	res = ast_strdup(cfg->xferfailsound);
    	ao2_ref(cfg, -1);
    
    	return res;
    }
    
    
    char *ast_get_chan_features_atxferabort(struct ast_channel *chan)
    {
    	char *res;
    	struct ast_features_xfer_config *cfg = ast_get_chan_features_xfer_config(chan);
    
    	if (!cfg) {
    		return NULL;
    	}
    
    	res = ast_strdup(cfg->atxferabort);
    	ao2_ref(cfg, -1);
    
    	return res;
    }
    
    
    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;
    	}
    
    
    	ao2_link(applicationmap, appmap_item);
    
    	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(0);
    
    	if (!applicationmap) {
    		return NULL;
    	}
    
    
    	/* global config must be initialized */
    	ast_assert(cfg->featuregroups != NULL);
    	ast_assert(cfg->applicationmap != 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);
    			} else {
    				ast_log(LOG_WARNING, "Unknown DYNAMIC_FEATURES item '%s' on channel %s.\n",
    					name, ast_channel_name(chan));
    
    			}
    		} 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';
    	}
    
    
    	/* Some applications do not require arguments. */
    	if (!args.app_data) {
    		args.app_data = "";
    	}
    
    
    	/* 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)) {
    
    	}
    
    	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)) {
    
    	}
    
    	/* 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 unsupported_handler(const struct aco_option *opt,
    		struct ast_variable *var, void *obj)
    {
    
    	if (!parking_warning) {
    		ast_log(LOG_WARNING, "Parkinglots are no longer configurable in features.conf; "
    			"parking is now handled by res_parking.conf\n");
    		parking_warning = 1;
    	}
    
    	ast_log(LOG_WARNING, "The option '%s' is no longer configurable in features.conf.\n", var->name);
    	return 0;
    }
    
    
    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.
    	 */
    
    
    	/* global config must be initialized */
    	ast_assert(cfg->featuregroups != NULL);
    	ast_assert(cfg->applicationmap != NULL);
    
    	ao2_callback_data(cfg->featuregroups, 0, check_featuregroup, &err, cfg->applicationmap);
    
    	return err;
    }
    
    
    static int internal_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 internal_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 internal_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 internal_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 int feature_read(struct ast_channel *chan, const char *cmd, char *data,
    		char *buf, size_t len)
    {
    	if (!chan) {
    		ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
    		return -1;
    	}
    
    	return internal_feature_read(chan, cmd, data, buf, len);
    }
    
    static int feature_write(struct ast_channel *chan, const char *cmd, char *data,
    		const char *value)
    {
    	if (!chan) {
    		ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
    		return -1;
    	}
    
    	return internal_feature_write(chan, cmd, data, value);
    }
    
    static int featuremap_read(struct ast_channel *chan, const char *cmd, char *data,
    		char *buf, size_t len)
    {
    	if (!chan) {
    		ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
    		return -1;
    	}
    
    	return internal_featuremap_read(chan, cmd, data, buf, len);
    }
    
    static int featuremap_write(struct ast_channel *chan, const char *cmd, char *data,
    		const char *value)
    {
    	if (!chan) {
    		ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
    		return -1;
    	}
    
    	return internal_featuremap_write(chan, cmd, data, value);
    }
    
    
    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(void)
    
    	if (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, "recordingfailsound", ACO_EXACT, global_options,
    			DEFAULT_RECORDING_FAIL_SOUND, general_handler, 0);
    
    	aco_option_register_custom(&cfg_info, "courtesytone", ACO_EXACT, global_options,
    
    			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, "atxferswap", ACO_EXACT, global_options,
    			DEFAULT_ATXFER_SWAP, xfer_handler, 0);
    
    	aco_option_register_custom(&cfg_info, "transferdialattempts", ACO_EXACT, global_options,
    			__stringify(DEFAULT_TRANSFER_DIAL_ATTEMPTS), xfer_handler, 0);
    	aco_option_register_custom(&cfg_info, "transferretrysound", ACO_EXACT, global_options,
    			DEFAULT_TRANSFER_RETRY_SOUND, xfer_handler, 0);
    	aco_option_register_custom(&cfg_info, "transferinvalidsound", ACO_EXACT, global_options,
    			DEFAULT_TRANSFER_INVALID_SOUND, 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_nodoc(&cfg_info, "context", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "parkext", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "parkext_exclusive", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "parkinghints", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "parkedmusicclass", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "parkingtime", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "parkpos", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "findslot", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "parkedcalltransfers", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "parkedcallreparking", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "parkedcallhangup", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "parkedcallrecording", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "comebackcontext", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "comebacktoorigin", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "comebackdialtime", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "parkeddynamic", ACO_EXACT, global_options,
    
    	aco_option_register_custom_nodoc(&cfg_info, "adsipark", ACO_EXACT, global_options,
    
    	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_PREFIX, applicationmap_options,
    
    			"", applicationmap_handler, 0);
    
    
    	aco_option_register_custom(&cfg_info, "", ACO_PREFIX, featuregroup_options,
    
    			"", featuregroup_handler, 0);
    
    
    	aco_option_register_custom_nodoc(&cfg_info, "", ACO_PREFIX, parkinglot_options,
    
    	if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
    
    		RAII_VAR(struct features_config *, features_cfg, features_config_alloc(), ao2_cleanup);
    
    
    		if (aco_set_defaults(&global_option, "general", features_cfg->global) ||
    			aco_set_defaults(&featuremap_option, "featuremap", features_cfg->featuremap)) {
    			ast_log(LOG_ERROR, "Failed to load features.conf and failed to initialize defaults.\n");
    			return -1;
    		}
    
    		ast_log(LOG_NOTICE, "Could not load features config; using defaults\n");
    
    		ao2_global_obj_replace_unref(globals, features_cfg);
    
    	}
    
    	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);
    	}
    
    	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)
    {
    
    	/* Rearm the parking config options have moved warning. */
    	parking_warning = 0;
    
    
    	if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
    		return -1;
    	}
    
    }
    
    int ast_features_config_init(void)
    {
    	int res;
    
    
    	res = load_config();
    
    	res |= __ast_custom_function_register(&feature_function, NULL);
    	res |= __ast_custom_function_register(&featuremap_function, NULL);