2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2013, Digium, Inc.
6 * Mark Michelson <mmichelson@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 #include "asterisk/features_config.h"
22 #include "asterisk/config_options.h"
23 #include "asterisk/datastore.h"
24 #include "asterisk/channel.h"
25 #include "asterisk/pbx.h"
26 #include "asterisk/app.h"
27 #include "asterisk/cli.h"
29 /* BUGBUG XML Documentation is still needed for configuration options */
31 <function name="FEATURE" language="en_US">
33 Get or set a feature option on a channel.
36 <parameter name="option_name" required="true">
37 <para>The allowed values are:</para>
39 <enum name="inherit"><para>Inherit feature settings made in FEATURE or FEATUREMAP to child channels.</para></enum>
40 <enum name="featuredigittimeout"><para>Milliseconds allowed between digits when entering a feature code.</para></enum>
41 <enum name="transferdigittimeout"><para>Milliseconds allowed between digits when dialing a transfer destination</para></enum>
42 <enum name="atxfernoanswertimeout"><para>Milliseconds to wait for transfer destination to answer</para></enum>
43 <enum name="atxferdropcall"><para>Hang up the call entirely if the attended transfer fails</para></enum>
44 <enum name="atxferloopdelay"><para>Milliseconds to wait between attempts to re-dial transfer destination</para></enum>
45 <enum name="atxfercallbackretries"><para>Number of times to re-attempt dialing a transfer destination</para></enum>
46 <enum name="xfersound"><para>Sound to play to a transferee when a transfer completes</para></enum>
47 <enum name="xferfailsound"><para>Sound to play to a transferee when a transfer fails</para></enum>
48 <enum name="atxferabort"><para>Digits to dial to abort an attended transfer attempt</para></enum>
49 <enum name="atxfercomplete"><para>Digits to dial to complete an attended transfer</para></enum>
50 <enum name="atxferthreeway"><para>Digits to dial to change an attended transfer into a three-way call</para></enum>
51 <enum name="pickupexten"><para>Digits used for picking up ringing calls</para></enum>
52 <enum name="pickupsound"><para>Sound to play to picker when a call is picked up</para></enum>
53 <enum name="pickupfailsound"><para>Sound to play to picker when a call cannot be picked up</para></enum>
54 <enum name="courtesytone"><para>Sound to play when automon or automixmon is activated</para></enum>
59 <para>When this function is used as a read, it will get the current
60 value of the specified feature option for this channel. It will be
61 the value of this option configured in features.conf if a channel specific
62 value has not been set. This function can also be used to set a channel
63 specific value for the supported feature options.</para>
66 <ref type="function">FEATUREMAP</ref>
69 <function name="FEATUREMAP" language="en_US">
71 Get or set a feature map to a given value on a specific channel.
74 <parameter name="feature_name" required="true">
75 <para>The allowed values are:</para>
77 <enum name="atxfer"><para>Attended Transfer</para></enum>
78 <enum name="blindxfer"><para>Blind Transfer</para></enum>
79 <enum name="automon"><para>Auto Monitor</para></enum>
80 <enum name="disconnect"><para>Call Disconnect</para></enum>
81 <enum name="parkcall"><para>Park Call</para></enum>
82 <enum name="automixmon"><para>Auto MixMonitor</para></enum>
87 <para>When this function is used as a read, it will get the current
88 digit sequence mapped to the specified feature for this channel. This
89 value will be the one configured in features.conf if a channel specific
90 value has not been set. This function can also be used to set a channel
91 specific value for a feature mapping.</para>
94 <ref type="function">FEATURE</ref>
98 /*! Default general options */
99 #define DEFAULT_FEATURE_DIGIT_TIMEOUT 1000
101 /*! Default xfer options */
102 #define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000
103 #define DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER 15000
104 #define DEFAULT_ATXFER_DROP_CALL 0
105 #define DEFAULT_ATXFER_LOOP_DELAY 10000
106 #define DEFAULT_ATXFER_CALLBACK_RETRIES 2
107 #define DEFAULT_XFERSOUND "beep"
108 #define DEFAULT_XFERFAILSOUND "beeperr"
109 #define DEFAULT_ATXFER_ABORT "*1"
110 #define DEFAULT_ATXFER_COMPLETE "*2"
111 #define DEFAULT_ATXFER_THREEWAY "*3"
113 /*! Default pickup options */
114 #define DEFAULT_PICKUPEXTEN "*8"
115 #define DEFAULT_PICKUPSOUND ""
116 #define DEFAULT_PICKUPFAILSOUND ""
118 /*! Default featuremap options */
119 #define DEFAULT_FEATUREMAP_BLINDXFER "#"
120 #define DEFAULT_FEATUREMAP_DISCONNECT "*"
121 #define DEFAULT_FEATUREMAP_AUTOMON ""
122 #define DEFAULT_FEATUREMAP_ATXFER ""
123 #define DEFAULT_FEATUREMAP_PARKCALL ""
124 #define DEFAULT_FEATUREMAP_AUTOMIXMON ""
127 * \brief Configuration from the "general" section of features.conf
129 struct features_global_config {
130 struct ast_features_general_config *general;
131 struct ast_features_xfer_config *xfer;
132 struct ast_features_pickup_config *pickup;
135 static void ast_applicationmap_item_destructor(void *obj)
137 struct ast_applicationmap_item *item = obj;
139 ast_string_field_free_memory(item);
142 static int applicationmap_sort(const void *obj, const void *arg, int flags)
144 const struct ast_applicationmap_item *item1 = obj;
145 const struct ast_applicationmap_item *item2;
148 switch(flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
151 return strcasecmp(item1->name, key2);
152 case OBJ_PARTIAL_KEY:
154 return strncasecmp(item1->name, key2, strlen(key2));
158 return strcasecmp(item1->name, item2->name);
163 * \brief Entry in the container of featuregroups
165 struct featuregroup_item {
166 AST_DECLARE_STRING_FIELDS(
167 /*! The name of the applicationmap item that we are referring to */
168 AST_STRING_FIELD(appmap_item_name);
169 /*! Custom DTMF override to use instead of the default for the applicationmap item */
170 AST_STRING_FIELD(dtmf_override);
172 /*! The applicationmap item that is being referred to */
173 struct ast_applicationmap_item *appmap_item;
176 static void featuregroup_item_destructor(void *obj)
178 struct featuregroup_item *item = obj;
180 ast_string_field_free_memory(item);
181 ao2_cleanup(item->appmap_item);
184 static int group_item_sort(const void *obj, const void *arg, int flags)
186 const struct featuregroup_item *item1 = obj;
187 const struct featuregroup_item *item2;
190 switch(flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
193 return strcasecmp(item1->appmap_item_name, key2);
194 case OBJ_PARTIAL_KEY:
196 return strncasecmp(item1->appmap_item_name, key2, strlen(key2));
199 return strcasecmp(item1->appmap_item_name, item2->appmap_item_name);
206 * \brief Featuregroup representation
208 struct featuregroup {
209 /*! The name of the featuregroup */
211 /*! A container of featuregroup_items */
212 struct ao2_container *items;
215 static int featuregroup_hash(const void *obj, int flags)
217 const struct featuregroup *group;
220 switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
223 return ast_str_case_hash(key);
224 case OBJ_PARTIAL_KEY:
230 return ast_str_case_hash(group->name);
234 static int featuregroup_cmp(void *obj, void *arg, int flags)
236 struct featuregroup *group1 = obj;
237 struct featuregroup *group2;
240 switch(flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
243 return strcasecmp(group1->name, key2) ? 0 : CMP_MATCH;
244 case OBJ_PARTIAL_KEY:
246 return strncasecmp(group1->name, key2, strlen(key2)) ? 0 : CMP_MATCH;
249 return strcasecmp(group1->name, group2->name) ? 0 : CMP_MATCH;
255 static void *featuregroup_find(struct ao2_container *group_container, const char *category)
257 return ao2_find(group_container, category, OBJ_KEY);
260 static void featuregroup_destructor(void *obj)
262 struct featuregroup *group = obj;
264 ast_free((char *) group->name);
265 ao2_cleanup(group->items);
268 static void *featuregroup_alloc(const char *cat)
270 struct featuregroup *group;
272 group = ao2_alloc(sizeof(*group), featuregroup_destructor);
277 group->name = ast_strdup(cat);
283 group->items = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK,
284 AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, group_item_sort, NULL);
293 struct features_config {
294 struct features_global_config *global;
295 struct ast_featuremap_config *featuremap;
296 struct ao2_container *applicationmap;
297 struct ao2_container *featuregroups;
300 static struct aco_type global_option = {
303 .category_match = ACO_WHITELIST,
304 .category = "^general$",
305 .item_offset = offsetof(struct features_config, global),
308 static struct aco_type featuremap_option = {
310 .name = "featuremap",
311 .category_match = ACO_WHITELIST,
312 .category = "^featuremap$",
313 .item_offset = offsetof(struct features_config, featuremap),
316 static struct aco_type applicationmap_option = {
318 .name = "applicationmap",
319 .category_match = ACO_WHITELIST,
320 .category = "^applicationmap$",
321 .item_offset = offsetof(struct features_config, applicationmap),
324 static struct aco_type featuregroup_option = {
326 .name = "featuregroup",
327 .category_match = ACO_BLACKLIST,
328 .category = "^(general|featuremap|applicationmap|parkinglot_.*)$",
329 .item_offset = offsetof(struct features_config, featuregroups),
330 .item_alloc = featuregroup_alloc,
331 .item_find = featuregroup_find,
334 static struct aco_type *global_options[] = ACO_TYPES(&global_option);
335 static struct aco_type *featuremap_options[] = ACO_TYPES(&featuremap_option);
336 static struct aco_type *applicationmap_options[] = ACO_TYPES(&applicationmap_option);
337 static struct aco_type *featuregroup_options[] = ACO_TYPES(&featuregroup_option);
339 static struct aco_file features_conf = {
340 .filename = "features.conf",
341 .types = ACO_TYPES(&global_option, &featuremap_option, &applicationmap_option, &featuregroup_option),
344 AO2_GLOBAL_OBJ_STATIC(globals);
346 static void features_config_destructor(void *obj)
348 struct features_config *cfg = obj;
350 ao2_cleanup(cfg->global);
351 ao2_cleanup(cfg->featuremap);
352 ao2_cleanup(cfg->applicationmap);
353 ao2_cleanup(cfg->featuregroups);
356 static void featuremap_config_destructor(void *obj)
358 struct ast_featuremap_config *cfg = obj;
360 ast_string_field_free_memory(cfg);
363 static void global_config_destructor(void *obj)
365 struct features_global_config *cfg = obj;
367 ao2_cleanup(cfg->general);
368 ao2_cleanup(cfg->xfer);
369 ao2_cleanup(cfg->pickup);
372 static void general_destructor(void *obj)
374 struct ast_features_general_config *cfg = obj;
376 ast_string_field_free_memory(cfg);
379 static void xfer_destructor(void *obj)
381 struct ast_features_xfer_config *cfg = obj;
383 ast_string_field_free_memory(cfg);
386 static void pickup_destructor(void *obj)
388 struct ast_features_pickup_config *cfg = obj;
390 ast_string_field_free_memory(cfg);
393 static struct features_global_config *global_config_alloc(void)
395 RAII_VAR(struct features_global_config *, cfg, NULL, ao2_cleanup);
397 cfg = ao2_alloc(sizeof(*cfg), global_config_destructor);
402 cfg->general = ao2_alloc(sizeof(*cfg->general), general_destructor);
403 if (!cfg->general || ast_string_field_init(cfg->general, 32)) {
407 cfg->xfer = ao2_alloc(sizeof(*cfg->xfer), xfer_destructor);
408 if (!cfg->xfer || ast_string_field_init(cfg->xfer, 32)) {
412 cfg->pickup = ao2_alloc(sizeof(*cfg->pickup), pickup_destructor);
413 if (!cfg->pickup || ast_string_field_init(cfg->pickup, 32)) {
421 static struct ao2_container *applicationmap_alloc(int replace_duplicates)
423 return ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK,
424 replace_duplicates ? AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE : AO2_CONTAINER_ALLOC_OPT_DUPS_ALLOW,
425 applicationmap_sort, NULL);
430 * \brief Allocate the major configuration structure
432 * The parameter is used to determine if the applicationmap and featuregroup
433 * structures should be allocated. We only want to allocate these structures for
434 * the global features_config structure. For the datastores on channels, we don't
435 * need to allocate these structures because they are not used.
437 * \param allocate_applicationmap See previous explanation
438 * \retval NULL Failed to alloate configuration
439 * \retval non-NULL Allocated configuration
441 static struct features_config *__features_config_alloc(int allocate_applicationmap)
443 RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
445 cfg = ao2_alloc(sizeof(*cfg), features_config_destructor);
450 cfg->global = global_config_alloc();;
455 cfg->featuremap = ao2_alloc(sizeof(*cfg->featuremap), featuremap_config_destructor);
456 if (!cfg->featuremap || ast_string_field_init(cfg->featuremap, 32)) {
460 if (allocate_applicationmap) {
461 cfg->applicationmap = applicationmap_alloc(1);
462 if (!cfg->applicationmap) {
466 cfg->featuregroups = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 11, featuregroup_hash,
468 if (!cfg->featuregroups) {
478 static void *features_config_alloc(void)
480 return __features_config_alloc(1);
483 static void general_copy(struct ast_features_general_config *dest, const struct ast_features_general_config *src)
485 ast_string_fields_copy(dest, src);
486 dest->featuredigittimeout = src->featuredigittimeout;
489 static void xfer_copy(struct ast_features_xfer_config *dest, const struct ast_features_xfer_config *src)
491 ast_string_fields_copy(dest, src);
492 dest->transferdigittimeout = src->transferdigittimeout;
493 dest->atxfernoanswertimeout = src->atxfernoanswertimeout;
494 dest->atxferloopdelay = src->atxferloopdelay;
495 dest->atxfercallbackretries = src->atxfercallbackretries;
496 dest->atxferdropcall = src->atxferdropcall;
499 static void pickup_copy(struct ast_features_pickup_config *dest, const struct ast_features_pickup_config *src)
501 ast_string_fields_copy(dest, src);
504 static void global_copy(struct features_global_config *dest, const struct features_global_config *src)
506 general_copy(dest->general, src->general);
507 xfer_copy(dest->xfer, src->xfer);
508 pickup_copy(dest->pickup, src->pickup);
511 static void featuremap_copy(struct ast_featuremap_config *dest, const struct ast_featuremap_config *src)
513 ast_string_fields_copy(dest, src);
516 static void features_copy(struct features_config *dest, const struct features_config *src)
518 global_copy(dest->global, src->global);
519 featuremap_copy(dest->featuremap, src->featuremap);
521 /* applicationmap and featuregroups are purposely not copied. A channel's applicationmap
522 * is produced on the fly when ast_get_chan_applicationmap() is called
526 static struct features_config *features_config_dup(const struct features_config *orig)
528 struct features_config *dup;
530 dup = __features_config_alloc(0);
535 features_copy(dup, orig);
540 static int general_set(struct ast_features_general_config *general, const char *name,
545 if (!strcasecmp(name, "featuredigittimeout")) {
546 res = ast_parse_arg(value, PARSE_INT32, &general->featuredigittimeout);
547 } else if (!strcasecmp(name, "courtesytone")) {
548 ast_string_field_set(general, courtesytone, value);
550 /* Unrecognized option */
557 static int general_get(struct ast_features_general_config *general, const char *field,
558 char *buf, size_t len)
562 if (!strcasecmp(field, "featuredigittimeout")) {
563 snprintf(buf, len, "%u", general->featuredigittimeout);
564 } else if (!strcasecmp(field, "courtesytone")) {
565 ast_copy_string(buf, general->courtesytone, len);
567 /* Unrecognized option */
574 static int xfer_set(struct ast_features_xfer_config *xfer, const char *name,
579 if (!strcasecmp(name, "transferdigittimeout")) {
580 res = ast_parse_arg(value, PARSE_INT32, &xfer->transferdigittimeout);
581 } else if (!strcasecmp(name, "atxfernoanswertimeout")) {
582 res = ast_parse_arg(value, PARSE_INT32, &xfer->atxfernoanswertimeout);
583 } else if (!strcasecmp(name, "atxferloopdelay")) {
584 res = ast_parse_arg(value, PARSE_INT32, &xfer->atxferloopdelay);
585 } else if (!strcasecmp(name, "atxfercallbackretries")) {
586 res = ast_parse_arg(value, PARSE_INT32, &xfer->atxfercallbackretries);
587 } else if (!strcasecmp(name, "atxferdropcall")) {
588 xfer->atxferdropcall = ast_true(value);
589 } else if (!strcasecmp(name, "xfersound")) {
590 ast_string_field_set(xfer, xfersound, value);
591 } else if (!strcasecmp(name, "xferfailsound")) {
592 ast_string_field_set(xfer, xferfailsound, value);
593 } else if (!strcasecmp(name, "atxferabort")) {
594 ast_string_field_set(xfer, atxferabort, value);
595 } else if (!strcasecmp(name, "atxfercomplete")) {
596 ast_string_field_set(xfer, atxfercomplete, value);
597 } else if (!strcasecmp(name, "atxferthreeway")) {
598 ast_string_field_set(xfer, atxferthreeway, value);
600 /* Unrecognized option */
607 static int xfer_get(struct ast_features_xfer_config *xfer, const char *field,
608 char *buf, size_t len)
612 if (!strcasecmp(field, "transferdigittimeout")) {
613 snprintf(buf, len, "%u", xfer->transferdigittimeout);
614 } else if (!strcasecmp(field, "atxfernoanswertimeout")) {
615 snprintf(buf, len, "%u", xfer->atxfernoanswertimeout);
616 } else if (!strcasecmp(field, "atxferloopdelay")) {
617 snprintf(buf, len, "%u", xfer->atxferloopdelay);
618 } else if (!strcasecmp(field, "atxfercallbackretries")) {
619 snprintf(buf, len, "%u", xfer->atxfercallbackretries);
620 } else if (!strcasecmp(field, "atxferdropcall")) {
621 snprintf(buf, len, "%u", xfer->atxferdropcall);
622 } else if (!strcasecmp(field, "xfersound")) {
623 ast_copy_string(buf, xfer->xfersound, len);
624 } else if (!strcasecmp(field, "xferfailsound")) {
625 ast_copy_string(buf, xfer->xferfailsound, len);
626 } else if (!strcasecmp(field, "atxferabort")) {
627 ast_copy_string(buf, xfer->atxferabort, len);
628 } else if (!strcasecmp(field, "atxfercomplete")) {
629 ast_copy_string(buf, xfer->atxfercomplete, len);
630 } else if (!strcasecmp(field, "atxferthreeway")) {
631 ast_copy_string(buf, xfer->atxferthreeway, len);
633 /* Unrecognized option */
640 static int pickup_set(struct ast_features_pickup_config *pickup, const char *name,
645 if (!strcasecmp(name, "pickupsound")) {
646 ast_string_field_set(pickup, pickupsound, value);
647 } else if (!strcasecmp(name, "pickupfailsound")) {
648 ast_string_field_set(pickup, pickupfailsound, value);
649 } else if (!strcasecmp(name, "pickupexten")) {
650 ast_string_field_set(pickup, pickupexten, value);
652 /* Unrecognized option */
659 static int pickup_get(struct ast_features_pickup_config *pickup, const char *field,
660 char *buf, size_t len)
664 if (!strcasecmp(field, "pickupsound")) {
665 ast_copy_string(buf, pickup->pickupsound, len);
666 } else if (!strcasecmp(field, "pickupfailsound")) {
667 ast_copy_string(buf, pickup->pickupfailsound, len);
668 } else if (!strcasecmp(field, "pickupexten")) {
669 ast_copy_string(buf, pickup->pickupexten, len);
671 /* Unrecognized option */
678 static int featuremap_set(struct ast_featuremap_config *featuremap, const char *name,
683 if (!strcasecmp(name, "blindxfer")) {
684 ast_string_field_set(featuremap, blindxfer, value);
685 } else if (!strcasecmp(name, "disconnect")) {
686 ast_string_field_set(featuremap, disconnect, value);
687 } else if (!strcasecmp(name, "automon")) {
688 ast_string_field_set(featuremap, automon, value);
689 } else if (!strcasecmp(name, "atxfer")) {
690 ast_string_field_set(featuremap, atxfer, value);
691 } else if (!strcasecmp(name, "automixmon")) {
692 ast_string_field_set(featuremap, automixmon, value);
693 } else if (!strcasecmp(name, "parkcall")) {
694 ast_string_field_set(featuremap, parkcall, value);
696 /* Unrecognized option */
703 static int featuremap_get(struct ast_featuremap_config *featuremap, const char *field,
704 char *buf, size_t len)
708 if (!strcasecmp(field, "blindxfer")) {
709 ast_copy_string(buf, featuremap->blindxfer, len);
710 } else if (!strcasecmp(field, "disconnect")) {
711 ast_copy_string(buf, featuremap->disconnect, len);
712 } else if (!strcasecmp(field, "automon")) {
713 ast_copy_string(buf, featuremap->automon, len);
714 } else if (!strcasecmp(field, "atxfer")) {
715 ast_copy_string(buf, featuremap->atxfer, len);
716 } else if (!strcasecmp(field, "automixmon")) {
717 ast_copy_string(buf, featuremap->automixmon, len);
718 } else if (!strcasecmp(field, "parkcall")) {
719 ast_copy_string(buf, featuremap->parkcall, len);
721 /* Unrecognized option */
728 static void feature_ds_destroy(void *data)
730 struct features_config *cfg = data;
734 static void *feature_ds_duplicate(void *data)
736 struct features_config *old_cfg = data;
738 return features_config_dup(old_cfg);
741 static const struct ast_datastore_info feature_ds_info = {
743 .destroy = feature_ds_destroy,
744 .duplicate = feature_ds_duplicate,
749 * \brief Find or create feature datastore on a channel
751 * \pre chan is locked
753 * \return the data on the FEATURE datastore, or NULL on error
755 static struct features_config *get_feature_ds(struct ast_channel *chan)
757 RAII_VAR(struct features_config *, orig, NULL, ao2_cleanup);
758 struct features_config *cfg;
759 struct ast_datastore *ds;
761 if ((ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL))) {
767 orig = ao2_global_obj_ref(globals);
772 cfg = features_config_dup(orig);
777 if (!(ds = ast_datastore_alloc(&feature_ds_info, NULL))) {
782 /* Give the datastore a reference to the config */
786 ast_channel_datastore_add(chan, ds);
791 static struct ast_datastore *get_feature_chan_ds(struct ast_channel *chan)
793 struct ast_datastore *ds;
795 if (!(ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL))) {
796 /* Hasn't been created yet. Trigger creation. */
797 RAII_VAR(struct features_config *, cfg, get_feature_ds(chan), ao2_cleanup);
798 ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL);
804 struct ast_features_general_config *ast_get_chan_features_general_config(struct ast_channel *chan)
806 RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
809 cfg = get_feature_ds(chan);
811 cfg = ao2_global_obj_ref(globals);
818 ast_assert(cfg->global && cfg->global->general);
820 ao2_ref(cfg->global->general, +1);
821 return cfg->global->general;
824 struct ast_features_xfer_config *ast_get_chan_features_xfer_config(struct ast_channel *chan)
826 RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
829 cfg = get_feature_ds(chan);
831 cfg = ao2_global_obj_ref(globals);
838 ast_assert(cfg->global && cfg->global->xfer);
840 ao2_ref(cfg->global->xfer, +1);
841 return cfg->global->xfer;
844 struct ast_features_pickup_config *ast_get_chan_features_pickup_config(struct ast_channel *chan)
846 RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
849 cfg = get_feature_ds(chan);
851 cfg = ao2_global_obj_ref(globals);
858 ast_assert(cfg->global && cfg->global->pickup);
860 ao2_ref(cfg->global->pickup, +1);
861 return cfg->global->pickup;
864 struct ast_featuremap_config *ast_get_chan_featuremap_config(struct ast_channel *chan)
866 RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
869 cfg = get_feature_ds(chan);
871 cfg = ao2_global_obj_ref(globals);
878 ast_assert(cfg->featuremap != NULL);
880 ao2_ref(cfg->featuremap, +1);
881 return cfg->featuremap;
884 int ast_get_builtin_feature(struct ast_channel *chan, const char *feature, char *buf, size_t len)
886 RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
889 cfg = get_feature_ds(chan);
891 cfg = ao2_global_obj_ref(globals);
898 return featuremap_get(cfg->featuremap, feature, buf, len);
901 int ast_get_feature(struct ast_channel *chan, const char *feature, char *buf, size_t len)
903 RAII_VAR(struct ao2_container *, applicationmap, NULL, ao2_cleanup);
904 RAII_VAR(struct ast_applicationmap_item *, item, NULL, ao2_cleanup);
906 if (!ast_get_builtin_feature(chan, feature, buf, len)) {
910 /* Dang, must be in the application map */
911 applicationmap = ast_get_chan_applicationmap(chan);
912 if (!applicationmap) {
916 item = ao2_find(applicationmap, feature, OBJ_KEY);
921 ast_copy_string(buf, item->dtmf, len);
925 static struct ast_applicationmap_item *applicationmap_item_alloc(const char *name,
926 const char *app, const char *app_data, const char *moh_class, const char *dtmf,
927 unsigned int activate_on_self)
929 struct ast_applicationmap_item *item;
931 item = ao2_alloc(sizeof(*item), ast_applicationmap_item_destructor);
933 if (!item || ast_string_field_init(item, 64)) {
937 ast_string_field_set(item, name, name);
938 ast_string_field_set(item, app, app);
939 ast_string_field_set(item, app_data, app_data);
940 ast_string_field_set(item, moh_class, moh_class);
941 ast_copy_string(item->dtmf, dtmf, sizeof(item->dtmf));
942 item->activate_on_self = activate_on_self;
947 static int add_item(void *obj, void *arg, int flags)
949 struct featuregroup_item *fg_item = obj;
950 struct ao2_container *applicationmap = arg;
951 RAII_VAR(struct ast_applicationmap_item *, appmap_item, NULL, ao2_cleanup);
953 /* If there's no DTMF override, then we can just link
954 * the applicationmap item directly. Otherwise, we need
955 * to create a copy with the DTMF override in place and
958 if (ast_strlen_zero(fg_item->dtmf_override)) {
959 ao2_ref(fg_item->appmap_item, +1);
960 appmap_item = fg_item->appmap_item;
962 appmap_item = applicationmap_item_alloc(fg_item->appmap_item_name,
963 fg_item->appmap_item->app, fg_item->appmap_item->app_data,
964 fg_item->appmap_item->moh_class, fg_item->dtmf_override,
965 fg_item->appmap_item->activate_on_self);
972 ao2_link(applicationmap, appmap_item);
976 struct ao2_container *ast_get_chan_applicationmap(struct ast_channel *chan)
978 RAII_VAR(struct features_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
979 struct ao2_container *applicationmap;
988 if (!cfg->applicationmap || ao2_container_count(cfg->applicationmap) == 0) {
991 ao2_ref(cfg->applicationmap, +1);
992 return cfg->applicationmap;
995 group_names = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES"), ""));
996 if (ast_strlen_zero(group_names)) {
1000 applicationmap = applicationmap_alloc(0);
1001 if (!applicationmap) {
1005 while ((name = strsep(&group_names, "#"))) {
1006 RAII_VAR(struct featuregroup *, group, ao2_find(cfg->featuregroups, name, OBJ_KEY), ao2_cleanup);
1009 RAII_VAR(struct ast_applicationmap_item *, item, ao2_find(cfg->applicationmap, name, OBJ_KEY), ao2_cleanup);
1012 ao2_link(applicationmap, item);
1014 ast_log(LOG_WARNING, "Unknown DYNAMIC_FEATURES item '%s' on channel %s.\n",
1015 name, ast_channel_name(chan));
1018 ao2_callback(group->items, 0, add_item, applicationmap);
1022 if (ao2_container_count(applicationmap) == 0) {
1023 ao2_cleanup(applicationmap);
1027 return applicationmap;
1030 static int applicationmap_handler(const struct aco_option *opt,
1031 struct ast_variable *var, void *obj)
1033 RAII_VAR(struct ast_applicationmap_item *, item, NULL, ao2_cleanup);
1034 struct ao2_container *applicationmap = obj;
1035 AST_DECLARE_APP_ARGS(args,
1037 AST_APP_ARG(activate_on);
1039 AST_APP_ARG(app_data);
1040 AST_APP_ARG(moh_class);
1042 char *parse = ast_strdupa(var->value);
1045 unsigned int activate_on_self;
1047 AST_STANDARD_APP_ARGS(args, parse);
1049 if (ast_strlen_zero(args.dtmf) ||
1050 ast_strlen_zero(args.activate_on) ||
1051 ast_strlen_zero(args.app)) {
1052 ast_log(LOG_WARNING, "Invalid applicationmap syntax for '%s'. Missing required argument\n", var->name);
1056 /* features.conf used to have an "activated_by" portion
1057 * in addition to activate_on. Get rid of whatever may be
1060 slash = strchr(args.activate_on, '/');
1065 /* Two syntaxes allowed for applicationmap:
1066 * Old: foo = *1,self,NoOp,Boo!,default
1067 * New: foo = *1,self,NoOp(Boo!),default
1069 * We need to handle both
1071 paren = strchr(args.app, '(');
1076 args.moh_class = args.app_data;
1078 close_paren = strrchr(paren, ')');
1080 *close_paren = '\0';
1082 args.app_data = paren;
1084 /* Re-check that the application is not empty */
1085 if (ast_strlen_zero(args.app)) {
1086 ast_log(LOG_WARNING, "Applicationmap item '%s' does not contain an application name.\n", var->name);
1089 } else if (strchr(args.app_data, '"')) {
1090 args.app_data = ast_strip_quoted(args.app_data, "\"", "\"");
1093 /* Allow caller and callee to be specified for backwards compatibility */
1094 if (!strcasecmp(args.activate_on, "self") || !strcasecmp(args.activate_on, "caller")) {
1095 activate_on_self = 1;
1096 } else if (!strcasecmp(args.activate_on, "peer") || !strcasecmp(args.activate_on, "callee")) {
1097 activate_on_self = 0;
1099 ast_log(LOG_WARNING, "Invalid 'activate_on' value %s for applicationmap item %s\n",
1100 args.activate_on, var->name);
1104 ast_debug(1, "Allocating applicationmap item: dtmf = %s, app = %s, app_data = %s, moh_class = %s\n",
1105 args.dtmf, args.app, args.app_data, args.moh_class);
1107 item = applicationmap_item_alloc(var->name, args.app, args.app_data,
1108 args.moh_class, args.dtmf, activate_on_self);
1114 if (!ao2_link(applicationmap, item)) {
1121 static int featuregroup_handler(const struct aco_option *opt,
1122 struct ast_variable *var, void *obj)
1124 RAII_VAR(struct featuregroup_item *, item, NULL, ao2_cleanup);
1125 struct featuregroup *group = obj;
1127 item = ao2_alloc(sizeof(*item), featuregroup_item_destructor);
1128 if (!item || ast_string_field_init(item, 32)) {
1132 ast_string_field_set(item, appmap_item_name, var->name);
1133 ast_string_field_set(item, dtmf_override, var->value);
1135 if (!ao2_link(group->items, item)) {
1139 /* We wait to look up the application map item in the preapply callback */
1144 static int general_handler(const struct aco_option *opt,
1145 struct ast_variable *var, void *obj)
1147 struct features_global_config *global = obj;
1148 struct ast_features_general_config *general = global->general;
1150 return general_set(general, var->name, var->value);
1153 static int xfer_handler(const struct aco_option *opt,
1154 struct ast_variable *var, void *obj)
1156 struct features_global_config *global = obj;
1157 struct ast_features_xfer_config *xfer = global->xfer;
1159 return xfer_set(xfer, var->name, var->value);
1162 static int pickup_handler(const struct aco_option *opt,
1163 struct ast_variable *var, void *obj)
1165 struct features_global_config *global = obj;
1166 struct ast_features_pickup_config *pickup = global->pickup;
1168 return pickup_set(pickup, var->name, var->value);
1171 static int unsupported_handler(const struct aco_option *opt,
1172 struct ast_variable *var, void *obj)
1174 ast_log(LOG_WARNING, "The option '%s' is no longer configurable in features.conf.\n", var->name);
1178 static int featuremap_handler(const struct aco_option *opt,
1179 struct ast_variable *var, void *obj)
1181 struct ast_featuremap_config *featuremap = obj;
1183 return featuremap_set(featuremap, var->name, var->value);
1186 static int check_featuregroup_item(void *obj, void *arg, void *data, int flags)
1188 struct ast_applicationmap_item *appmap_item;
1189 struct featuregroup_item *fg_item = obj;
1191 struct ao2_container *applicationmap = data;
1193 appmap_item = ao2_find(applicationmap, fg_item->appmap_item_name, OBJ_KEY);
1199 fg_item->appmap_item = appmap_item;
1204 static int check_featuregroup(void *obj, void *arg, void *data, int flags)
1206 struct featuregroup *group = obj;
1209 ao2_callback_data(group->items, 0, check_featuregroup_item, arg, data);
1212 ast_log(LOG_WARNING, "Featuregroup %s refers to non-existent applicationmap item\n",
1216 return *err ? CMP_STOP : 0;
1219 static int features_pre_apply_config(void);
1221 CONFIG_INFO_CORE("features", cfg_info, globals, features_config_alloc,
1222 .files = ACO_FILES(&features_conf),
1223 .pre_apply_config = features_pre_apply_config,
1226 static int features_pre_apply_config(void)
1228 struct features_config *cfg = aco_pending_config(&cfg_info);
1231 /* Now that the entire config has been processed, we can check that the featuregroup
1232 * items refer to actual applicationmap items.
1235 ao2_callback_data(cfg->featuregroups, 0, check_featuregroup, &err, cfg->applicationmap);
1240 static int feature_read(struct ast_channel *chan, const char *cmd, char *data,
1241 char *buf, size_t len)
1244 RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
1245 SCOPED_CHANNELLOCK(lock, chan);
1247 if (!strcasecmp(data, "inherit")) {
1248 struct ast_datastore *ds = get_feature_chan_ds(chan);
1249 unsigned int inherit = ds ? ds->inheritance : 0;
1251 snprintf(buf, len, "%s", inherit ? "yes" : "no");
1255 cfg = get_feature_ds(chan);
1260 res = general_get(cfg->global->general, data, buf, len) &&
1261 xfer_get(cfg->global->xfer, data, buf, len) &&
1262 pickup_get(cfg->global->pickup, data, buf, len);
1265 ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data);
1271 static int feature_write(struct ast_channel *chan, const char *cmd, char *data,
1275 RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
1276 SCOPED_CHANNELLOCK(lock, chan);
1278 if (!strcasecmp(data, "inherit")) {
1279 struct ast_datastore *ds = get_feature_chan_ds(chan);
1281 ds->inheritance = ast_true(value) ? DATASTORE_INHERIT_FOREVER : 0;
1286 if (!(cfg = get_feature_ds(chan))) {
1290 res = general_set(cfg->global->general, data, value) &&
1291 xfer_set(cfg->global->xfer, data, value) &&
1292 pickup_set(cfg->global->pickup, data, value);
1295 ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data);
1301 static int featuremap_read(struct ast_channel *chan, const char *cmd, char *data,
1302 char *buf, size_t len)
1305 SCOPED_CHANNELLOCK(lock, chan);
1307 res = ast_get_builtin_feature(chan, data, buf, len);
1310 ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data);
1316 static int featuremap_write(struct ast_channel *chan, const char *cmd, char *data,
1320 RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
1321 SCOPED_CHANNELLOCK(lock, chan);
1323 if (!(cfg = get_feature_ds(chan))) {
1327 res = featuremap_set(cfg->featuremap, data, value);
1329 ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data);
1336 static struct ast_custom_function feature_function = {
1338 .read = feature_read,
1339 .write = feature_write
1342 static struct ast_custom_function featuremap_function = {
1343 .name = "FEATUREMAP",
1344 .read = featuremap_read,
1345 .write = featuremap_write
1348 static int load_config(int reload)
1350 if (!reload && aco_info_init(&cfg_info)) {
1351 ast_log(LOG_ERROR, "Unable to initialize configuration info for features\n");
1355 aco_option_register_custom(&cfg_info, "featuredigittimeout", ACO_EXACT, global_options,
1356 __stringify(DEFAULT_FEATURE_DIGIT_TIMEOUT), general_handler, 0);
1357 aco_option_register_custom(&cfg_info, "courtesytone", ACO_EXACT, global_options,
1358 __stringify(DEFAULT_COURTESY_TONE), general_handler, 0);
1360 aco_option_register_custom(&cfg_info, "transferdigittimeout", ACO_EXACT, global_options,
1361 __stringify(DEFAULT_TRANSFER_DIGIT_TIMEOUT), xfer_handler, 0)
1362 aco_option_register_custom(&cfg_info, "atxfernoanswertimeout", ACO_EXACT, global_options,
1363 __stringify(DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER), xfer_handler, 0);
1364 aco_option_register_custom(&cfg_info, "atxferdropcall", ACO_EXACT, global_options,
1365 __stringify(DEFAULT_ATXFER_DROP_CALL), xfer_handler, 0);
1366 aco_option_register_custom(&cfg_info, "atxferloopdelay", ACO_EXACT, global_options,
1367 __stringify(DEFAULT_ATXFER_LOOP_DELAY), xfer_handler, 0);
1368 aco_option_register_custom(&cfg_info, "atxfercallbackretries", ACO_EXACT, global_options,
1369 __stringify(DEFAULT_ATXFER_CALLBACK_RETRIES), xfer_handler, 0);
1370 aco_option_register_custom(&cfg_info, "xfersound", ACO_EXACT, global_options,
1371 DEFAULT_XFERSOUND, xfer_handler, 0);
1372 aco_option_register_custom(&cfg_info, "xferfailsound", ACO_EXACT, global_options,
1373 DEFAULT_XFERFAILSOUND, xfer_handler, 0);
1374 aco_option_register_custom(&cfg_info, "atxferabort", ACO_EXACT, global_options,
1375 DEFAULT_ATXFER_ABORT, xfer_handler, 0);
1376 aco_option_register_custom(&cfg_info, "atxfercomplete", ACO_EXACT, global_options,
1377 DEFAULT_ATXFER_COMPLETE, xfer_handler, 0);
1378 aco_option_register_custom(&cfg_info, "atxferthreeway", ACO_EXACT, global_options,
1379 DEFAULT_ATXFER_THREEWAY, xfer_handler, 0);
1381 aco_option_register_custom(&cfg_info, "pickupexten", ACO_EXACT, global_options,
1382 DEFAULT_PICKUPEXTEN, pickup_handler, 0);
1383 aco_option_register_custom(&cfg_info, "pickupsound", ACO_EXACT, global_options,
1384 DEFAULT_PICKUPSOUND, pickup_handler, 0);
1385 aco_option_register_custom(&cfg_info, "pickupfailsound", ACO_EXACT, global_options,
1386 DEFAULT_PICKUPFAILSOUND, pickup_handler, 0);
1388 aco_option_register_custom(&cfg_info, "context", ACO_EXACT, global_options,
1389 "", unsupported_handler, 0);
1390 aco_option_register_custom(&cfg_info, "parkext", ACO_EXACT, global_options,
1391 "", unsupported_handler, 0);
1392 aco_option_register_custom(&cfg_info, "parkext_exclusive", ACO_EXACT, global_options,
1393 "", unsupported_handler, 0);
1394 aco_option_register_custom(&cfg_info, "parkinghints", ACO_EXACT, global_options,
1395 "", unsupported_handler, 0);
1396 aco_option_register_custom(&cfg_info, "parkedmusicclass", ACO_EXACT, global_options,
1397 "", unsupported_handler, 0);
1398 aco_option_register_custom(&cfg_info, "parkingtime", ACO_EXACT, global_options,
1399 "", unsupported_handler, 0);
1400 aco_option_register_custom(&cfg_info, "parkpos", ACO_EXACT, global_options,
1401 "", unsupported_handler, 0);
1402 aco_option_register_custom(&cfg_info, "findslot", ACO_EXACT, global_options,
1403 "", unsupported_handler, 0);
1404 aco_option_register_custom(&cfg_info, "parkedcalltransfers", ACO_EXACT, global_options,
1405 "", unsupported_handler, 0);
1406 aco_option_register_custom(&cfg_info, "parkedcallreparking", ACO_EXACT, global_options,
1407 "", unsupported_handler, 0);
1408 aco_option_register_custom(&cfg_info, "parkedcallhangup", ACO_EXACT, global_options,
1409 "", unsupported_handler, 0);
1410 aco_option_register_custom(&cfg_info, "parkedcallrecording", ACO_EXACT, global_options,
1411 "", unsupported_handler, 0);
1412 aco_option_register_custom(&cfg_info, "comebackcontext", ACO_EXACT, global_options,
1413 "", unsupported_handler, 0);
1414 aco_option_register_custom(&cfg_info, "comebacktoorigin", ACO_EXACT, global_options,
1415 "", unsupported_handler, 0);
1416 aco_option_register_custom(&cfg_info, "comebackdialtime", ACO_EXACT, global_options,
1417 "", unsupported_handler, 0);
1418 aco_option_register_custom(&cfg_info, "parkeddynamic", ACO_EXACT, global_options,
1419 "", unsupported_handler, 0);
1420 aco_option_register_custom(&cfg_info, "adsipark", ACO_EXACT, global_options,
1421 "", unsupported_handler, 0);
1423 aco_option_register_custom(&cfg_info, "blindxfer", ACO_EXACT, featuremap_options,
1424 DEFAULT_FEATUREMAP_BLINDXFER, featuremap_handler, 0);
1425 aco_option_register_custom(&cfg_info, "disconnect", ACO_EXACT, featuremap_options,
1426 DEFAULT_FEATUREMAP_DISCONNECT, featuremap_handler, 0);
1427 aco_option_register_custom(&cfg_info, "automon", ACO_EXACT, featuremap_options,
1428 DEFAULT_FEATUREMAP_AUTOMON, featuremap_handler, 0);
1429 aco_option_register_custom(&cfg_info, "atxfer", ACO_EXACT, featuremap_options,
1430 DEFAULT_FEATUREMAP_ATXFER, featuremap_handler, 0);
1431 aco_option_register_custom(&cfg_info, "parkcall", ACO_EXACT, featuremap_options,
1432 DEFAULT_FEATUREMAP_PARKCALL, featuremap_handler, 0);
1433 aco_option_register_custom(&cfg_info, "automixmon", ACO_EXACT, featuremap_options,
1434 DEFAULT_FEATUREMAP_AUTOMIXMON, featuremap_handler, 0);
1436 aco_option_register_custom(&cfg_info, "^.*$", ACO_REGEX, applicationmap_options,
1437 "", applicationmap_handler, 0);
1439 aco_option_register_custom(&cfg_info, "^.*$", ACO_REGEX, featuregroup_options,
1440 "", featuregroup_handler, 0);
1442 if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
1443 ast_log(LOG_ERROR, "Failed to process features.conf configuration!\n");
1445 aco_info_destroy(&cfg_info);
1446 ao2_global_obj_release(globals);
1454 static int print_featuregroup(void *obj, void *arg, int flags)
1456 struct featuregroup_item *item = obj;
1457 struct ast_cli_args *a = arg;
1459 ast_cli(a->fd, "===> --> %s (%s)\n", item->appmap_item_name,
1460 S_OR(item->dtmf_override, item->appmap_item->dtmf));
1465 static int print_featuregroups(void *obj, void *arg, int flags)
1467 struct featuregroup *group = obj;
1468 struct ast_cli_args *a = arg;
1470 ast_cli(a->fd, "===> Group: %s\n", group->name);
1472 ao2_callback(group->items, 0, print_featuregroup, a);
1476 #define HFS_FORMAT "%-25s %-7s %-7s\n"
1478 static int print_applicationmap(void *obj, void *arg, int flags)
1480 struct ast_applicationmap_item *item = obj;
1481 struct ast_cli_args *a = arg;
1483 ast_cli(a->fd, HFS_FORMAT, item->name, "no def", item->dtmf);
1488 * \brief CLI command to list configured features
1493 * \retval CLI_SUCCESS on success.
1494 * \retval NULL when tab completion is used.
1496 static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1498 RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
1503 e->command = "features show";
1505 "Usage: features show\n"
1506 " Lists configured features\n";
1512 cfg = ao2_global_obj_ref(globals);
1514 ast_cli(a->fd, HFS_FORMAT, "Builtin Feature", "Default", "Current");
1515 ast_cli(a->fd, HFS_FORMAT, "---------------", "-------", "-------");
1517 ast_cli(a->fd, HFS_FORMAT, "Pickup", DEFAULT_PICKUPEXTEN, cfg->global->pickup->pickupexten);
1518 ast_cli(a->fd, HFS_FORMAT, "Blind Transfer", DEFAULT_FEATUREMAP_BLINDXFER, cfg->featuremap->blindxfer);
1519 ast_cli(a->fd, HFS_FORMAT, "Attended Transfer", DEFAULT_FEATUREMAP_ATXFER, cfg->featuremap->atxfer);
1520 ast_cli(a->fd, HFS_FORMAT, "One Touch Monitor", DEFAULT_FEATUREMAP_AUTOMON, cfg->featuremap->automon);
1521 ast_cli(a->fd, HFS_FORMAT, "Disconnect Call", DEFAULT_FEATUREMAP_DISCONNECT, cfg->featuremap->disconnect);
1522 ast_cli(a->fd, HFS_FORMAT, "Park Call", DEFAULT_FEATUREMAP_PARKCALL, cfg->featuremap->parkcall);
1523 ast_cli(a->fd, HFS_FORMAT, "One Touch MixMonitor", DEFAULT_FEATUREMAP_AUTOMIXMON, cfg->featuremap->automixmon);
1525 ast_cli(a->fd, "\n");
1526 ast_cli(a->fd, HFS_FORMAT, "Dynamic Feature", "Default", "Current");
1527 ast_cli(a->fd, HFS_FORMAT, "---------------", "-------", "-------");
1528 if (!cfg->applicationmap || ao2_container_count(cfg->applicationmap) == 0) {
1529 ast_cli(a->fd, "(none)\n");
1531 ao2_callback(cfg->applicationmap, 0, print_applicationmap, a);
1534 ast_cli(a->fd, "\nFeature Groups:\n");
1535 ast_cli(a->fd, "---------------\n");
1536 if (!cfg->featuregroups || ao2_container_count(cfg->featuregroups) == 0) {
1537 ast_cli(a->fd, "(none)\n");
1539 ao2_callback(cfg->featuregroups, 0, print_featuregroups, a);
1542 ast_cli(a->fd, "\n");
1547 static struct ast_cli_entry cli_features_config[] = {
1548 AST_CLI_DEFINE(handle_feature_show, "Lists configured features"),
1551 void ast_features_config_shutdown(void)
1553 ast_custom_function_unregister(&featuremap_function);
1554 ast_custom_function_unregister(&feature_function);
1555 ast_cli_unregister_multiple(cli_features_config, ARRAY_LEN(cli_features_config));
1556 aco_info_destroy(&cfg_info);
1557 ao2_global_obj_release(globals);
1560 int ast_features_config_reload(void)
1562 return load_config(1);
1565 int ast_features_config_init(void)
1569 res = load_config(0);
1570 res |= __ast_custom_function_register(&feature_function, NULL);
1571 res |= __ast_custom_function_register(&featuremap_function, NULL);
1572 res |= ast_cli_register_multiple(cli_features_config, ARRAY_LEN(cli_features_config));
1575 ast_features_config_shutdown();