From d04ab3c6450f3d92aa004ae9d6e0e7da51f702a3 Mon Sep 17 00:00:00 2001 From: Matthew Jordan Date: Fri, 15 Feb 2013 13:38:12 +0000 Subject: [PATCH] Add CLI configuration documentation This patch allows a module to define its configuration in XML in source, such that it can be parsed by the XML documentation engine. Documentation is generated in a two-pass approach: 1. The documentation is first generated from the XML pulled from the source 2. The documentation is then enhanced by the registration of configuration options that use the configuration framework This patch include configuration documentation for the following modules: * chan_motif * res_xmpp * app_confbridge * app_skel * udptl Two new CLI commands have been added: * config show help - show configuration help by module, category, and item * xmldoc dump - dump the in-memory representation of the XML documentation to a new XML file. Review: https://reviewboard.asterisk.org/r/2278 Review: https://reviewboard.asterisk.org/r/2058 patches: on review 2058 uploaded by twilson git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@381527 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- Makefile | 2 +- apps/app_skel.c | 48 +++ apps/confbridge/conf_config_parser.c | 466 ++++++++++++++++++++++- channels/chan_motif.c | 140 +++++++ configs/motif.conf.sample | 27 +- configs/xmpp.conf.sample | 3 + doc/appdocsxml.dtd | 34 +- include/asterisk/_private.h | 1 + include/asterisk/config_options.h | 9 + include/asterisk/xml.h | 39 ++ include/asterisk/xmldoc.h | 28 ++ main/asterisk.c | 2 + main/config_options.c | 534 ++++++++++++++++++++++++++- main/named_acl.c | 51 +-- main/udptl.c | 37 +- main/xml.c | 39 ++ main/xmldoc.c | 254 ++++++++++++- res/res_xmpp.c | 122 ++++++ 18 files changed, 1762 insertions(+), 74 deletions(-) diff --git a/Makefile b/Makefile index 76e68c4162..02d6fa55da 100644 --- a/Makefile +++ b/Makefile @@ -456,7 +456,7 @@ doc/core-en_US.xml: makeopts $(foreach dir,$(MOD_SUBDIRS),$(shell $(GREP) -l "la @echo "" >> $@ @for x in $(MOD_SUBDIRS); do \ printf "$$x " ; \ - for i in $$x/*.c; do \ + for i in `find $$x -name *.c`; do \ $(AWK) -f build_tools/get_documentation $$i >> $@ ; \ done ; \ done diff --git a/apps/app_skel.c b/apps/app_skel.c index 6fdbebbf26..8a5b5bc750 100644 --- a/apps/app_skel.c +++ b/apps/app_skel.c @@ -86,6 +86,51 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") from. It shows you the basic structure to create your own Asterisk applications. + + + + + Options that apply globally to app_skel + + The number of games a single execution of SkelGuessNumber will play + + + Should the computer cheat? + If enabled, the computer will ignore winning guesses. + + + + Prompts for SkelGuessNumber to play + + A prompt directing the user to enter a number less than the max number + + + The sound file to play when a wrong guess is made + + + The sound file to play when a correct guess is made + + + The sound file to play when a guess is too low + + + The sound file to play when a guess is too high + + + The sound file to play when a player loses + + + + Defined levels for the SkelGuessNumber game + + The maximum in the range of numbers to guess (1 is the implied minimum) + + + The maximum number of guesses before a game is considered lost + + + + ***/ static char *app = "SkelGuessNumber"; @@ -197,6 +242,7 @@ static void *skel_level_find(struct ao2_container *tmp_container, const char *ca /*! \brief An aco_type structure to link the "general" category to the skel_global_config type */ static struct aco_type global_option = { .type = ACO_GLOBAL, + .name = "globals", .item_offset = offsetof(struct skel_config, global), .category_match = ACO_WHITELIST, .category = "^general$", @@ -207,6 +253,7 @@ struct aco_type *global_options[] = ACO_TYPES(&global_option); /*! \brief An aco_type structure to link the "sounds" category to the skel_global_config type */ static struct aco_type sound_option = { .type = ACO_GLOBAL, + .name = "sounds", .item_offset = offsetof(struct skel_config, global), .category_match = ACO_WHITELIST, .category = "^sounds$", @@ -217,6 +264,7 @@ struct aco_type *sound_options[] = ACO_TYPES(&sound_option); /*! \brief An aco_type structure to link the everything but the "general" and "sounds" categories to the skel_level type */ static struct aco_type level_option = { .type = ACO_ITEM, + .name = "level", .category_match = ACO_BLACKLIST, .category = "^(general|sounds)$", .item_alloc = skel_level_alloc, diff --git a/apps/confbridge/conf_config_parser.c b/apps/confbridge/conf_config_parser.c index c915b184c2..6c5ea0bb8e 100644 --- a/apps/confbridge/conf_config_parser.c +++ b/apps/confbridge/conf_config_parser.c @@ -40,6 +40,464 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stringfields.h" #include "asterisk/pbx.h" + +/*** DOCUMENTATION + + Conference Bridge Application + + + Unused, but reserved. + + + A named profile to apply to specific callers. + Callers in a ConfBridge have a profile associated with them + that determine their options. A configuration section is determined to be a + user_profile when the type parameter has a value + of user. + + + Define this configuration category as a user profile. + The type parameter determines how a context in the + configuration file is interpreted. + + Configure the context as a user_profile + Configure the context as a bridge_profile + Configure the context as a menu + + + + + Sets if the user is an admin or not + + + Sets if this is a marked user or not + + + Sets if all users should start out muted + + + Play MOH when user is alone or waiting on a marked user + + + Silence enter/leave prompts and user intros for this user + + + Sets if the number of users should be announced to the user + + + Announce user count to all the other users when this user joins + Sets if the number of users should be announced to all the other users + in the conference when this user joins. This option can be either set to 'yes' or + a number. When set to a number, the announcement will only occur once the user + count is above the specified number. + + + + Announce to a user when they join an empty conference + + + Sets if the user must wait for a marked user to enter before joining a conference + + + Kick the user from the conference when the last marked user leaves + + + Set whether or not notifications of when a user begins and ends talking should be sent out as events over AMI + + + Sets whether or not DTMF should pass through the conference + + + Prompt user for their name when joining a conference and play it to the conference when they enter + + + Sets a PIN the user must enter before joining the conference + + + The MOH class to use for this user + + + Sound file to play to the user when they join a conference + + + Apply a denoise filter to the audio before mixing + Sets whether or not a denoise filter should be applied + to the audio before mixing or not. Off by default. Requires + codec_speex to be built and installed. Do not confuse this option + with drop_silence. Denoise is useful if there is a lot of background + noise for a user as it attempts to remove the noise while preserving + the speech. This option does NOT remove silence from being mixed into + the conference and does come at the cost of a slight performance hit. + + + + Drop what Asterisk detects as silence from audio sent to the bridge + + This option drops what Asterisk detects as silence from + entering into the bridge. Enabling this option will drastically + improve performance and help remove the buildup of background + noise from the conference. Highly recommended for large conferences + due to its performance enhancements. + + + + The number of milliseconds of detected silence necessary to trigger silence detection + + The time in milliseconds of sound falling within the what + the dsp has established as baseline silence before a user + is considered be silent. This value affects several + operations and should not be changed unless the impact + on call quality is fully understood. + What this value affects internally: + + 1. When talk detection AMI events are enabled, this value + determines when the user has stopped talking after a + period of talking. If this value is set too low + AMI events indicating the user has stopped talking + may get falsely sent out when the user briefly pauses + during mid sentence. + + + 2. The drop_silence option depends on this value to + determine when the user's audio should begin to be + dropped from the conference bridge after the user + stops talking. If this value is set too low the user's + audio stream may sound choppy to the other participants. + This is caused by the user transitioning constantly from + silence to talking during mid sentence. + + + The best way to approach this option is to set it slightly above + the maximum amount of ms of silence a user may generate during + natural speech. + + By default this value is 2500ms. Valid values are 1 through 2^31. + + + + The number of milliseconds of detected non-silence necessary to triger talk detection + + The time in milliseconds of sound above what the dsp has + established as base line silence for a user before a user + is considered to be talking. This value affects several + operations and should not be changed unless the impact on + call quality is fully understood. + + What this value affects internally: + + + 1. Audio is only mixed out of a user's incoming audio stream + if talking is detected. If this value is set too + loose the user will hear themselves briefly each + time they begin talking until the dsp has time to + establish that they are in fact talking. + + + 2. When talk detection AMI events are enabled, this value + determines when talking has begun which results in + an AMI event to fire. If this value is set too tight + AMI events may be falsely triggered by variants in + room noise. + + + 3. The drop_silence option depends on this value to determine + when the user's audio should be mixed into the bridge + after periods of silence. If this value is too loose + the beginning of a user's speech will get cut off as they + transition from silence to talking. + + By default this value is 160 ms. Valid values are 1 through 2^31 + + + + Place a jitter buffer on the user's audio stream before audio mixing is performed + + Enabling this option places a jitterbuffer on the user's audio stream + before audio mixing is performed. This is highly recommended but will + add a slight delay to the audio. This option is using the JITTERBUFFER + dialplan function's default adaptive jitterbuffer. For a more fine tuned + jitterbuffer, disable this option and use the JITTERBUFFER dialplan function + on the user before entering the ConfBridge application. + + + + When using the CONFBRIDGE dialplan function, use a user profile as a template for creating a new temporary profile + + + + A named profile to apply to specific bridges. + ConfBridge bridges have a profile associated with them + that determine their options. A configuration section is determined to be a + bridge_profile when the type parameter has a value + of bridge. + + + Define this configuration category as a bridge profile + The type parameter determines how a context in the + configuration file is interpreted. + + Configure the context as a user_profile + Configure the context as a bridge_profile + Configure the context as a menu + + + + + Place a jitter buffer on the conference's audio stream + + + Set the internal native sample rate for mixing the conference + + Sets the internal native sample rate the + conference is mixed at. This is set to automatically + adjust the sample rate to the best quality by default. + Other values can be anything from 8000-192000. If a + sample rate is set that Asterisk does not support, the + closest sample rate Asterisk does support to the one requested + will be used. + + + + Sets the internal mixing interval in milliseconds for the bridge + + Sets the internal mixing interval in milliseconds for the bridge. This + number reflects how tight or loose the mixing will be for the conference. + In order to improve performance a larger mixing interval such as 40ms may + be chosen. Using a larger mixing interval comes at the cost of introducing + larger amounts of delay into the bridge. Valid values here are 10, 20, 40, + or 80. + + + + Record the conference starting with the first active user's entrance and ending with the last active user's exit + + Records the conference call starting when the first user + enters the room, and ending when the last user exits the room. + The default recorded filename is + 'confbridge-${name of conference bridge}-${start time}.wav + and the default format is 8khz slinear. This file will be + located in the configured monitoring directory in asterisk.conf. + + + + The filename of the conference recording + + When record_conference is set to yes, the specific name of the + record file can be set using this option. Note that since multiple + conferences may use the same bridge profile, this may cause issues + depending on the configuration. It is recommended to only use this + option dynamically with the CONFBRIDGE() dialplan function. This + allows the record name to be specified and a unique name to be chosen. + By default, the record_file is stored in Asterisk's spool/monitor directory + with a unique filename starting with the 'confbridge' prefix. + + + + Sets how confbridge handles video distribution to the conference participants + + Sets how confbridge handles video distribution to the conference participants. + Note that participants wanting to view and be the source of a video feed + _MUST_ be sharing the same video codec. Also, using video in conjunction with + with the jitterbuffer currently results in the audio being slightly out of sync + with the video. This is a result of the jitterbuffer only working on the audio + stream. It is recommended to disable the jitterbuffer when video is used. + + + No video sources are set by default in the conference. It is still + possible for a user to be set as a video source via AMI or DTMF action + at any time. + + + The video feed will follow whoever is talking and providing video. + + + The last marked user to join the conference with video capabilities + will be the single source of video distributed to all participants. + If multiple marked users are capable of video, the last one to join + is always the source, when that user leaves it goes to the one who + joined before them. + + + The first marked user to join the conference with video capabilities + is the single source of video distribution among all participants. If + that user leaves, the marked user to join after them becomes the source. + + + + + + Limit the maximum number of participants for a single conference + + This option limits the number of participants for a single + conference to a specific number. By default conferences + have no participant limit. After the limit is reached, the + conference will be locked until someone leaves. Note however + that an Admin user will always be alowed to join the conference + regardless if this limit is reached or not. + + + + Override the various conference bridge sound files + + All sounds in the conference are customizable using the bridge profile options below. + Simply state the option followed by the filename or full path of the filename after + the option. Example: sound_had_joined=conf-hasjoin This will play the conf-hasjoin + sound file found in the sounds directory when announcing someone's name is joining the + conference. + + The sound played to everyone when someone enters the conference. + The sound played to everyone when someone leaves the conference. + The sound played before announcing someone's name has + joined the conference. This is used for user intros. + Example "_____ has joined the conference" + The sound played when announcing someone's name has + left the conference. This is used for user intros. + Example "_____ has left the conference" + The sound played to a user who has been kicked from the conference. + The sound played when the mute option it toggled on. + The sound played when the mute option it toggled off. + The sound played when the user is the only person in the conference. + The sound played to a user when there is only one other + person is in the conference. + The sound played when announcing how many users there + are in a conference. + This file is used in conjunction with sound_there_are + when announcing how many users there are in the conference. + The sounds are stringed together like this. + "sound_there_are" ${number of participants} "sound_other_in_party" + The sound played when someone is placed into the conference + after waiting for a marked user. + The sound played when a user is placed into a conference that + can not start until a marked user enters. + The sound played when the last marked user leaves the conference. + The sound played when prompting for a conference pin number. + The sound played when an invalid pin is entered too many times. + The sound played to a user trying to join a locked conference. + The sound played to an admin after toggling the conference to locked mode. + The sound played to an admin after toggling the conference to unlocked mode. + The sound played when an invalid menu option is entered. + + + + + When using the CONFBRIDGE dialplan function, use a bridge profile as a template for creating a new temporary profile + + + + A conference user menu + + Conference users, as defined by a conf_user, + can have a DTMF menu assigned to their profile when they enter the + ConfBridge application. + + + Define this configuration category as a menu + The type parameter determines how a context in the + configuration file is interpreted. + + Configure the context as a user_profile + Configure the context as a bridge_profile + Configure the context as a menu + + + + + DTMF sequences to assign various confbridge actions to + --- ConfBridge Menu Options --- + The ConfBridge application also has the ability to apply custom DTMF menus to + each channel using the application. Like the User and Bridge profiles a menu + is passed in to ConfBridge as an argument in the dialplan. + Below is a list of menu actions that can be assigned to a DTMF sequence. + + A single DTMF sequence can have multiple actions associated with it. This is + accomplished by stringing the actions together and using a , as the + delimiter. Example: Both listening and talking volume is reset when 5 is + pressed. 5=reset_talking_volume, reset_listening_volume + + + playback will play back an audio file to a channel + and then immediately return to the conference. + This file can not be interupted by DTMF. + Multiple files can be chained together using the + & character. + + playback_and_continue will + play back a prompt while continuing to + collect the dtmf sequence. This is useful + when using a menu prompt that describes all + the menu options. Note however that any DTMF + during this action will terminate the prompts + playback. Prompt files can be chained together + using the & character as a delimiter. + + Toggle turning on and off mute. Mute will make the user silent + to everyone else, but the user will still be able to listen in. + continue to collect the dtmf sequence. + + This action does nothing (No Operation). Its only real purpose exists for + being able to reserve a sequence in the config as a menu exit sequence. + + Decreases the channel's listening volume. + + Increases the channel's listening volume. + + Reset channel's listening volume to default level. + + Decreases the channel's talking volume. + + Increases the channel's talking volume. + + Reset channel's talking volume to default level. + + The dialplan_exec action allows a user + to escape from the conference and execute + commands in the dialplan. Once the dialplan + exits the user will be put back into the + conference. The possibilities are endless! + + This action allows a user to exit the conference and continue + execution in the dialplan. + + This action allows an Admin to kick the last participant from the + conference. This action will only work for admins which allows + a single menu to be used for both users and admins. + + This action allows an Admin to toggle locking and + unlocking the conference. Non admins can not use + this action even if it is in their menu. + + This action allows any user to set themselves as the + single video source distributed to all participants. + This will make the video feed stick to them regardless + of what the video_mode is set to. + + This action allows a user to release themselves as + the video source. If video_mode is not set to none + this action will result in the conference returning to + whatever video mode the bridge profile is using. + Note that this action will have no effect if the user + is not currently the video source. Also, the user is + not guaranteed by using this action that they will not + become the video source again. The bridge will return + to whatever operation the video_mode option is set to + upon release of the video src. + + This action allows an administrator to toggle the mute + state for all non-admins within a conference. All + admin users are unaffected by this option. Note that all + users, regardless of their admin status, are notified + that the conference is muted. + + This action plays back the number of participants currently + in a conference + + + + + + +***/ + struct confbridge_cfg { struct ao2_container *bridge_profiles; struct ao2_container *user_profiles; @@ -81,6 +539,7 @@ static void *bridge_profile_find(struct ao2_container *container, const char *ca static struct aco_type bridge_type = { .type = ACO_ITEM, + .name = "bridge_profile", .category_match = ACO_BLACKLIST, .category = "^general$", .matchfield = "type", @@ -117,6 +576,7 @@ static void *user_profile_find(struct ao2_container *container, const char *cate static struct aco_type user_type = { .type = ACO_ITEM, + .name = "user_profile", .category_match = ACO_BLACKLIST, .category = "^general$", .matchfield = "type", @@ -147,6 +607,7 @@ static void *menu_find(struct ao2_container *container, const char *category) static struct aco_type menu_type = { .type = ACO_ITEM, + .name = "menu", .category_match = ACO_BLACKLIST, .category = "^general$", .matchfield = "type", @@ -164,6 +625,7 @@ static struct aco_type *user_types[] = ACO_TYPES(&user_type); /* The general category is reserved, but unused */ static struct aco_type general_type = { .type = ACO_GLOBAL, + .name = "global", .category_match = ACO_WHITELIST, .category = "^general$", }; @@ -1293,8 +1755,6 @@ int conf_load_config(int reload) /* User options */ aco_option_register(&cfg_info, "type", ACO_EXACT, user_types, NULL, OPT_NOOP_T, 0, 0); - aco_option_register(&cfg_info, "type", ACO_EXACT, bridge_types, NULL, OPT_NOOP_T, 0, 0); - aco_option_register(&cfg_info, "type", ACO_EXACT, menu_types, NULL, OPT_NOOP_T, 0, 0); aco_option_register(&cfg_info, "admin", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_ADMIN); aco_option_register(&cfg_info, "marked", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_MARKEDUSER); aco_option_register(&cfg_info, "startmuted", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_STARTMUTED); @@ -1321,6 +1781,7 @@ int conf_load_config(int reload) aco_option_register_custom(&cfg_info, "template", ACO_EXACT, user_types, NULL, user_template_handler, 0); /* Bridge options */ + aco_option_register(&cfg_info, "type", ACO_EXACT, bridge_types, NULL, OPT_NOOP_T, 0, 0); aco_option_register(&cfg_info, "jitterbuffer", ACO_EXACT, bridge_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct bridge_profile, flags), USER_OPT_JITTERBUFFER); /* "auto" will fail to parse as a uint, but we use PARSE_DEFAULT to set the value to 0 in that case, which is the value that auto resolves to */ aco_option_register(&cfg_info, "internal_sample_rate", ACO_EXACT, bridge_types, "0", OPT_UINT_T, PARSE_DEFAULT, FLDSET(struct bridge_profile, internal_sample_rate), 0); @@ -1334,6 +1795,7 @@ int conf_load_config(int reload) aco_option_register_custom(&cfg_info, "template", ACO_EXACT, bridge_types, NULL, bridge_template_handler, 0); /* Menu options */ + aco_option_register(&cfg_info, "type", ACO_EXACT, menu_types, NULL, OPT_NOOP_T, 0, 0); aco_option_register_custom(&cfg_info, "^[0-9A-D*#]+$", ACO_REGEX, menu_types, NULL, menu_option_handler, 0); if (aco_process_config(&cfg_info, reload) == ACO_PROCESS_ERROR) { diff --git a/channels/chan_motif.c b/channels/chan_motif.c index cc1e370fd1..4dbfb8e3a7 100644 --- a/channels/chan_motif.c +++ b/channels/chan_motif.c @@ -77,6 +77,145 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/abstract_jb.h" #include "asterisk/xmpp.h" +/*** DOCUMENTATION + + Jingle Channel Driver + + Transports + There are three different transports and protocol derivatives + supported by chan_motif. They are in order of + preference: Jingle using ICE-UDP, Google Jingle, and Google-V1. + Jingle as defined in XEP-0166 supports the widest range of + features. It is referred to as ice-udp. This is + the specification that Jingle clients implement. + Google Jingle follows the Jingle specification for signaling + but uses a custom transport for media. It is supported by the + Google Talk Plug-in in Gmail and by some other Jingle clients. It + is referred to as google in this file. + Google-V1 is the original Google Talk signaling protocol + which uses an initial preliminary version of Jingle. It also uses + the same custom transport as Google Jingle for media. It is + supported by Google Voice, some other Jingle clients, and the + Windows Google Talk client. It is referred to as google-v1 + in this file. + Incoming sessions will automatically switch to the correct + transport once it has been determined. + Outgoing sessions are capable of determining if the target + is capable of Jingle or a Google transport if the target is in the + roster. Unfortunately it is not possible to differentiate between + a Google Jingle or Google-V1 capable resource until a session + initiate attempt occurs. If a resource is determined to use a + Google transport it will initially use Google Jingle but will fall + back to Google-V1 if required. + If an outgoing session attempt fails due to failure to + support the given transport chan_motif will + fall back in preference order listed previously until all + transports have been exhausted. + Dialing and Resource Selection Strategy + Placing a call through an endpoint can be accomplished using the + following dial string: + Motif/[endpoint name]/[target] + When placing an outgoing call through an endpoint the requested + target is searched for in the roster list. If present the first Jingle + or Google Jingle capable resource is specifically targeted. Since the + capabilities of the resource are known the outgoing session initiation + will disregard the configured transport and use the determined one. + If the target is not found in the roster the target will be used + as-is and a session will be initiated using the transport specified + in this configuration file. If no transport has been specified the + endpoint defaults to ice-udp. + Video Support + Support for video does not need to be explicitly enabled. + Configuring any video codec on your endpoint will automatically enable + it. + DTMF + The only supported method for DTMF is RFC2833. This is always + enabled on audio streams and negotiated if possible. + Incoming Calls + Incoming calls will first look for the extension matching the + name of the endpoint in the configured context. If no such extension + exists the call will automatically fall back to the s extension. + CallerID + The incoming caller id number is populated with the username of + the caller and the name is populated with the full identity of the + caller. If you would like to perform authentication or filtering + of incoming calls it is recommended that you use these fields to do so. + Outgoing caller id can not be set. + + Multiple endpoints using the + same connection is NOT supported. Doing so + may result in broken calls. + + + + + The configuration for an endpoint. + + Default dialplan context that incoming sessions will be routed to + + + A callgroup to assign to this endpoint. + + + A pickup group to assign to this endpoint. + + + The default language for this endpoint. + + + Default music on hold class for this endpoint. + + + Default parking lot for this endpoint. + + + Accout code for CDR purposes + + + Codecs to allow + + + Codecs to disallow + + + Connection to accept traffic on and on which to send traffic out + + + The transport to use for the endpoint. + + The default outbound transport for this endpoint. Inbound + messages are inferred. Allowed transports are ice-udp, + google, or google-v1. Note + that chan_motif will fall back to transport + preference order if the transport value chosen here fails. + + + The Jingle protocol, as defined in XEP 0166. + + + The Google Jingle protocol, which follows the Jingle + specification for signaling but uses a custom transport for + media. + + + Google-V1 is the original Google Talk signaling + protocol which uses an initial preliminary version of Jingle. + It also uses the same custom transport as google for media. + + + + + + Maximum number of ICE candidates to offer + + + Maximum number of pyaloads to offer + + + + +***/ + /*! \brief Default maximum number of ICE candidates we will offer */ #define DEFAULT_MAX_ICE_CANDIDATES "10" @@ -406,6 +545,7 @@ static int jingle_endpoint_cmp(void *obj, void *arg, int flags) static struct aco_type endpoint_option = { .type = ACO_ITEM, + .name = "endpoint", .category_match = ACO_BLACKLIST, .category = "^general$", .item_alloc = jingle_endpoint_alloc, diff --git a/configs/motif.conf.sample b/configs/motif.conf.sample index ae3ab30d72..9d5bc808c9 100644 --- a/configs/motif.conf.sample +++ b/configs/motif.conf.sample @@ -75,18 +75,25 @@ context=incoming-motif ; Default context that incoming sessions will land in ;maxpayloads = 30 ; Maximum number of payloads we will offer ; Sample configuration entry for Jingle -[jingle-endpoint](default) -transport=ice-udp ; Change the default protocol of outgoing sessions to Jingle ICE-UDP -allow=g722 ; Add G.722 as an allowed format since the other side may support it -connection=local-jabber-account ; Connection to accept traffic on and send traffic out -accountcode=jingle ; Account code for CDR purposes +;[jingle-endpoint](default) +;transport=ice-udp ; Change the default protocol of outgoing sessions to Jingle ICE-UDP +;allow=g722 ; Add G.722 as an allowed format since the other side may support it +;connection=local-jabber-account ; Connection to accept traffic on and send traffic out +;accountcode=jingle ; Account code for CDR purposes ; Sample configuration entry for Google Talk [gtalk-endpoint](default) -transport=google ; Since this is a Google Talk endpoint we want to offer Google Jingle for outgoing sessions -connection=gtalk-account +;transport=google ; Since this is a Google Talk endpoint we want to offer Google Jingle for outgoing sessions +;connection=gtalk-account ; Sample configuration entry for Google Voice -[gvoice](default) -transport=google-v1 ; Google Voice uses the original Google Talk protocol -connection=gvoice-account +;[gvoice](default) +;transport=google-v1 ; Google Voice uses the original Google Talk protocol +;connection=gvoice-account + +; Additional options +; callgroup +; pickupgroup +; language +; musicclass +; parkinglot diff --git a/configs/xmpp.conf.sample b/configs/xmpp.conf.sample index a838568678..dad0f79ef2 100644 --- a/configs/xmpp.conf.sample +++ b/configs/xmpp.conf.sample @@ -37,3 +37,6 @@ ;sendtodialplan=yes ; Send incoming messages into the dialplan. Off by default. ;context=messages ; Dialplan context to send incoming messages to. If not set, ; "default" will be used. +;forceoldssl=no ; Force the use of old-style SSL. +;keepalive= + diff --git a/doc/appdocsxml.dtd b/doc/appdocsxml.dtd index 561e3d38cd..a475cd32f9 100644 --- a/doc/appdocsxml.dtd +++ b/doc/appdocsxml.dtd @@ -1,13 +1,13 @@ - + - @@ -39,6 +39,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -52,7 +76,7 @@ - + @@ -91,7 +115,7 @@ - + diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h index cded90ef7d..7e1ef13ec4 100644 --- a/include/asterisk/_private.h +++ b/include/asterisk/_private.h @@ -49,6 +49,7 @@ int ast_ssl_init(void); /*!< Provided by ssl.c */ int ast_test_init(void); /*!< Provided by test.c */ int ast_msg_init(void); /*!< Provided by message.c */ void ast_msg_shutdown(void); /*!< Provided by message.c */ +int aco_init(void); /*!< Provided by config_options.c */ /*! * \brief Reload asterisk modules. diff --git a/include/asterisk/config_options.h b/include/asterisk/config_options.h index 1b0beb206a..64d8d50893 100644 --- a/include/asterisk/config_options.h +++ b/include/asterisk/config_options.h @@ -109,6 +109,7 @@ typedef int (*aco_matchvalue_func)(const char *text); struct aco_type { /* common stuff */ enum aco_type_t type; /*!< Whether this is a global or item type */ + const char *name; /*!< The name of this type (must match XML documentation) */ const char *category; /*!< A regular expression for matching categories to be allowed or denied */ const char *matchfield; /*!< An option name to match for this type (i.e. a 'type'-like column) */ const char *matchvalue; /*!< The value of the option to require for matching (i.e. 'peer' for type= in sip.conf) */ @@ -202,6 +203,14 @@ static struct aco_info name = { \ __VA_ARGS__ \ }; +#define CONFIG_INFO_CORE(mod, name, arr, alloc, ...) \ +static struct aco_info name = { \ + .module = mod, \ + .global_obj = &arr, \ + .snapshot_alloc = alloc, \ + __VA_ARGS__ \ +}; + /*! \brief Initialize an aco_info structure * \note aco_info_destroy must be called if this succeeds * \param info The address of an aco_info struct to initialize diff --git a/include/asterisk/xml.h b/include/asterisk/xml.h index ddfcc25d90..063e8c0b39 100644 --- a/include/asterisk/xml.h +++ b/include/asterisk/xml.h @@ -23,6 +23,7 @@ struct ast_xml_node; struct ast_xml_doc; +struct ast_xml_xpath_results; /*! * \brief Initialize the XML library implementation. @@ -207,6 +208,44 @@ struct ast_xml_node *ast_xml_node_get_parent(struct ast_xml_node *node); * \brief Dump the specified document to a file. */ int ast_xml_doc_dump_file(FILE *output, struct ast_xml_doc *doc); +/*! + * \brief Free the XPath results + * \param results The XPath results object to dispose of + * + * \since 12 + */ +void ast_xml_xpath_results_free(struct ast_xml_xpath_results *results); + +/*! + * \brief Return the number of results from an XPath query + * \param results The XPath results object to count + * \retval The number of results in the XPath object + * + * \since 12 + */ +int ast_xml_xpath_num_results(struct ast_xml_xpath_results *results); + +/*! + * \brief Return the first result node of an XPath query + * \param results The XPath results object to get the first result from + * \retval The first result in the XPath object on success + * \retval NULL on error + * + * \since 12 + */ +struct ast_xml_node *ast_xml_xpath_get_first_result(struct ast_xml_xpath_results *results); + +/*! + * \brief Execute an XPath query on an XML document + * \param doc The XML document to query + * \param xpath_str The XPath query string to execute on the document + * \retval An object containing the results of the XPath query on success + * \retval NULL on failure + * + * \since 12 + */ +struct ast_xml_xpath_results *ast_xml_query(struct ast_xml_doc *doc, const char *xpath_str); + /* Features using ast_xml_ */ #ifdef HAVE_LIBXML2 #define AST_XML_DOCS diff --git a/include/asterisk/xmldoc.h b/include/asterisk/xmldoc.h index 9bf647612f..c09f693c88 100644 --- a/include/asterisk/xmldoc.h +++ b/include/asterisk/xmldoc.h @@ -35,6 +35,7 @@ enum ast_doc_src { #ifdef AST_XML_DOCS struct ao2_container; +struct ast_xml_node; /*! \brief Struct that contains the XML documentation for a particular item. Note * that this is an ao2 ref counted object. @@ -61,11 +62,27 @@ struct ast_xml_doc_item { AST_STRING_FIELD(name); /*! The type of the item */ AST_STRING_FIELD(type); + /*! Reference to another field */ + AST_STRING_FIELD(ref); ); + /*! The node that this item was created from. Note that the life time of + * the node is not tied to the lifetime of this object. + */ + struct ast_xml_node *node; /*! The next XML documentation item that matches the same name/item type */ struct ast_xml_doc_item *next; }; +/*! \brief Execute an XPath query on the loaded XML documentation + * \param query The XPath query string to execute + * \param ... Variable printf style format arguments + * \retval An XPath results object on success + * \retval NULL if no match found + * + * \since 12 + */ +struct ast_xml_xpath_results *__attribute__((format(printf, 1, 2))) ast_xmldoc_query(const char *fmt, ...); + /*! * \brief Get the syntax for a specified application or function. * \param type Application, Function or AGI ? @@ -138,6 +155,17 @@ char *ast_xmldoc_build_description(const char *type, const char *name, const cha */ struct ao2_container *ast_xmldoc_build_documentation(const char *type); +/*! + * \brief Regenerate the documentation for a particular item + * \param item The documentation item to regenerate + * + * \retval -1 on error + * \retval 0 on success + * + * \since 12 + */ +int ast_xmldoc_regenerate_doc_item(struct ast_xml_doc_item *item); + #endif /* AST_XML_DOCS */ #endif /* _ASTERISK_XMLDOC_H */ diff --git a/main/asterisk.c b/main/asterisk.c index 243321dda3..1d53719389 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -4170,6 +4170,8 @@ int main(int argc, char *argv[]) ast_xmldoc_load_documentation(); #endif + aco_init(); + if (astdb_init()) { printf("%s", term_quit()); exit(1); diff --git a/main/config_options.c b/main/config_options.c index 5e76a7a7be..7a65cc5553 100644 --- a/main/config_options.c +++ b/main/config_options.c @@ -31,11 +31,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include +#include "asterisk/_private.h" #include "asterisk/config.h" #include "asterisk/config_options.h" #include "asterisk/stringfields.h" #include "asterisk/acl.h" #include "asterisk/frame.h" +#include "asterisk/xmldoc.h" +#include "asterisk/cli.h" +#include "asterisk/term.h" #ifdef LOW_MEMORY #define CONFIG_OPT_BUCKETS 5 @@ -70,6 +74,26 @@ struct aco_option { intptr_t args[0]; }; +#ifdef AST_XML_DOCS +static struct ao2_container *xmldocs; +#endif /* AST_XML_DOCS */ + +/*! \brief Value of the aco_option_type enum as strings */ +static char *aco_option_type_string[] = { + "ACL", /* OPT_ACL_T, */ + "Boolean", /* OPT_BOOL_T, */ + "Boolean", /* OPT_BOOLFLAG_T, */ + "String", /* OPT_CHAR_ARRAY_T, */ + "Codec", /* OPT_CODEC_T, */ + "Custom", /* OPT_CUSTOM_T, */ + "Double", /* OPT_DOUBLE_T, */ + "Integer", /* OPT_INT_T, */ + "None", /* OPT_NOOP_T, */ + "IP Address", /* OPT_SOCKADDR_T, */ + "String", /* OPT_STRINGFIELD_T, */ + "Unsigned Integer", /* OPT_UINT_T, */ +}; + void *aco_pending_config(struct aco_info *info) { if (!(info && info->internal)) { @@ -100,6 +124,11 @@ static int codec_handler_fn(const struct aco_option *opt, struct ast_variable *v static int noop_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj); static int chararray_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj); +#ifdef AST_XML_DOCS +static int xmldoc_update_config_type(const char *module, const char *name, const char *category, const char *matchfield, const char *matchvalue, unsigned int matches); +static int xmldoc_update_config_option(struct aco_type **types, const char *module, const char *name, const char *object_name, const char *default_value, unsigned int regex, enum aco_option_type type); +#endif + static aco_option_handler ast_config_option_default_handler(enum aco_option_type type) { switch(type) { @@ -142,7 +171,7 @@ static regex_t *build_regex(const char *text) return regex; } -static int link_option_to_types(struct aco_type **types, struct aco_option *opt) +static int link_option_to_types(struct aco_info *info, struct aco_type **types, struct aco_option *opt) { size_t idx = 0; struct aco_type *type; @@ -152,7 +181,11 @@ static int link_option_to_types(struct aco_type **types, struct aco_option *opt) ast_log(LOG_ERROR, "Attempting to register option using uninitialized type\n"); return -1; } - if (!ao2_link(type->internal->opts, opt)) { + if (!ao2_link(type->internal->opts, opt) +#ifdef AST_XML_DOCS + || xmldoc_update_config_option(types, info->module, opt->name, type->name, opt->default_val, opt->match_type == ACO_REGEX, opt->type) +#endif /* AST_XML_DOCS */ + ) { while (--idx) { ao2_unlink(types[idx]->internal->opts, opt); } @@ -181,7 +214,7 @@ int aco_option_register_deprecated(struct aco_info *info, const char *name, stru opt->deprecated = 1; opt->match_type = ACO_EXACT; - if (link_option_to_types(types, opt)) { + if (link_option_to_types(info, types, opt)) { ao2_ref(opt, -1); return -1; } @@ -189,6 +222,53 @@ int aco_option_register_deprecated(struct aco_info *info, const char *name, stru return 0; } +#ifdef AST_XML_DOCS +/*! \internal + * \brief Find a particular ast_xml_doc_item from it's parent config_info, types, and name + */ +static struct ast_xml_doc_item *find_xmldoc_option(struct ast_xml_doc_item *config_info, struct aco_type **types, const char *name) +{ + struct ast_xml_doc_item *iter = config_info; + + if (!iter) { + return NULL; + } + /* First is just the configInfo, we can skip it */ + while ((iter = iter->next)) { + size_t x; + if (strcasecmp(iter->name, name)) { + continue; + } + for (x = 0; types[x]; x++) { + /* All we care about is that at least one type has the option */ + if (!strcasecmp(types[x]->name, iter->ref)) { + return iter; + } + } + } + return NULL; +} + +/*! \internal + * \brief Find a particular ast_xml_doc_item from it's parent config_info and name + */ +static struct ast_xml_doc_item *find_xmldoc_type(struct ast_xml_doc_item *config_info, const char *name) +{ + struct ast_xml_doc_item *iter = config_info; + if (!iter) { + return NULL; + } + /* First is just the config Info, skip it */ + while ((iter = iter->next)) { + if (!strcasecmp(iter->type, "configObject") && !strcasecmp(iter->name, name)) { + break; + } + } + return iter; +} + +#endif /* AST_XML_DOCS */ + int __aco_option_register(struct aco_info *info, const char *name, enum aco_matchtype matchtype, struct aco_type **types, const char *default_val, enum aco_option_type kind, aco_option_handler handler, unsigned int flags, size_t argc, ...) { @@ -235,7 +315,7 @@ int __aco_option_register(struct aco_info *info, const char *name, enum aco_matc return -1; }; - if (link_option_to_types(types, opt)) { + if (link_option_to_types(info, types, opt)) { ao2_ref(opt, -1); return -1; } @@ -535,10 +615,10 @@ try_alias: } if (res != ACO_PROCESS_OK) { - goto end; + goto end; } - if (info->pre_apply_config && (info->pre_apply_config())) { + if (info->pre_apply_config && (info->pre_apply_config())) { res = ACO_PROCESS_ERROR; goto end; } @@ -648,18 +728,26 @@ static int internal_type_init(struct aco_type *type) int aco_info_init(struct aco_info *info) { - size_t x, y; + size_t x = 0, y = 0; + struct aco_file *file; + struct aco_type *type; if (!(info->internal = ast_calloc(1, sizeof(*info->internal)))) { return -1; } - for (x = 0; info->files[x]; x++) { - for (y = 0; info->files[x]->types[y]; y++) { - if (internal_type_init(info->files[x]->types[y])) { + while ((file = info->files[x++])) { + while ((type = file->types[y++])) { + if (internal_type_init(type)) { + goto error; + } +#ifdef AST_XML_DOCS + if (xmldoc_update_config_type(info->module, type->name, type->category, type->matchfield, type->matchvalue, type->category_match == ACO_WHITELIST)) { goto error; } +#endif /* AST_XML_DOCS */ } + y = 0; } return 0; @@ -714,6 +802,428 @@ int aco_set_defaults(struct aco_type *type, const char *category, void *obj) return 0; } +#ifdef AST_XML_DOCS + +/*! \internal + * \brief Complete the name of the module the user is looking for + */ +static char *complete_config_module(const char *word, int pos, int state) +{ + char *c = NULL; + size_t wordlen = strlen(word); + int which = 0; + struct ao2_iterator i; + struct ast_xml_doc_item *cur; + + if (pos != 3) { + return NULL; + } + + i = ao2_iterator_init(xmldocs, 0); + while ((cur = ao2_iterator_next(&i))) { + if (!strncasecmp(word, cur->name, wordlen) && ++which > state) { + c = ast_strdup(cur->name); + ao2_ref(cur, -1); + break; + } + ao2_ref(cur, -1); + } + ao2_iterator_destroy(&i); + + return c; +} + +/*! \internal + * \brief Complete the name of the configuration type the user is looking for + */ +static char *complete_config_type(const char *module, const char *word, int pos, int state) +{ + char *c = NULL; + size_t wordlen = strlen(word); + int which = 0; + RAII_VAR(struct ast_xml_doc_item *, info, NULL, ao2_cleanup); + struct ast_xml_doc_item *cur; + + if (pos != 4) { + return NULL; + } + + if (!(info = ao2_find(xmldocs, module, OBJ_KEY))) { + return NULL; + } + + cur = info; + while ((cur = cur->next)) { + if (!strcasecmp(cur->type, "configObject") && !strncasecmp(word, cur->name, wordlen) && ++which > state) { + c = ast_strdup(cur->name); + break; + } + } + return c; +} + +/*! \internal + * \brief Complete the name of the configuration option the user is looking for + */ +static char *complete_config_option(const char *module, const char *option, const char *word, int pos, int state) +{ + char *c = NULL; + size_t wordlen = strlen(word); + int which = 0; + RAII_VAR(struct ast_xml_doc_item *, info, NULL, ao2_cleanup); + struct ast_xml_doc_item *cur; + + if (pos != 5) { + return NULL; + } + + if (!(info = ao2_find(xmldocs, module, OBJ_KEY))) { + return NULL; + } + + cur = info; + while ((cur = cur->next)) { + if (!strcasecmp(cur->type, "configOption") && !strcasecmp(cur->ref, option) && !strncasecmp(word, cur->name, wordlen) && ++which > state) { + c = ast_strdup(cur->name); + break; + } + } + return c; +} + +/* Define as 0 if we want to allow configurations to be registered without + * documentation + */ +#define XMLDOC_STRICT 1 + +/*! \internal + * \brief Update the XML documentation for a config type based on its registration + */ +static int xmldoc_update_config_type(const char *module, const char *name, const char *category, const char *matchfield, const char *matchvalue, unsigned int matches) +{ + RAII_VAR(struct ast_xml_xpath_results *, results, NULL, ast_xml_xpath_results_free); + RAII_VAR(struct ast_xml_doc_item *, config_info, ao2_find(xmldocs, module, OBJ_KEY), ao2_cleanup); + struct ast_xml_doc_item *config_type; + struct ast_xml_node *type, *syntax, *matchinfo, *tmp; + + /* If we already have a syntax element, bail. This isn't an error, since we may unload a module which + * has updated the docs and then load it again. */ + if ((results = ast_xmldoc_query("//configInfo[@name='%s']/*/configObject[@name='%s']/syntax", module, name))) { + return 0; + } + + if (!(results = ast_xmldoc_query("//configInfo[@name='%s']/*/configObject[@name='%s']", module, name))) { + ast_log(LOG_WARNING, "Cannot update type '%s' in module '%s' because it has no existing documentation!\n", name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + if (!(type = ast_xml_xpath_get_first_result(results))) { + ast_log(LOG_WARNING, "Could not retrieve documentation for type '%s' in module '%s'\n", name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + if (!(syntax = ast_xml_new_child(type, "syntax"))) { + ast_log(LOG_WARNING, "Could not create syntax node for type '%s' in module '%s'\n", name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + if (!(matchinfo = ast_xml_new_child(syntax, "matchInfo"))) { + ast_log(LOG_WARNING, "Could not create matchInfo node for type '%s' in module '%s'\n", name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + if (!(tmp = ast_xml_new_child(matchinfo, "category"))) { + ast_log(LOG_WARNING, "Could not create category node for type '%s' in module '%s'\n", name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + ast_xml_set_text(tmp, category); + ast_xml_set_attribute(tmp, "match", matches ? "true" : "false"); + + if (!ast_strlen_zero(matchfield) && !(tmp = ast_xml_new_child(matchinfo, "field"))) { + ast_log(LOG_WARNING, "Could not add %s attribute for type '%s' in module '%s'\n", matchfield, name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + ast_xml_set_attribute(tmp, "name", matchfield); + ast_xml_set_text(tmp, matchvalue); + + if (!config_info || !(config_type = find_xmldoc_type(config_info, name))) { + ast_log(LOG_WARNING, "Could not obtain XML documentation item for config type %s\n", name); + return XMLDOC_STRICT ? -1 : 0; + } + + if (ast_xmldoc_regenerate_doc_item(config_type)) { + ast_log(LOG_WARNING, "Could not update type '%s' with values from config type registration\n", name); + return XMLDOC_STRICT ? -1 : 0; + } + + return 0; +} + +/*! \internal + * \brief Update the XML documentation for a config option based on its registration + */ +static int xmldoc_update_config_option(struct aco_type **types, const char *module, const char *name, const char *object_name, const char *default_value, unsigned int regex, enum aco_option_type type) +{ + RAII_VAR(struct ast_xml_xpath_results *, results, NULL, ast_xml_xpath_results_free); + RAII_VAR(struct ast_xml_doc_item *, config_info, ao2_find(xmldocs, module, OBJ_KEY), ao2_cleanup); + struct ast_xml_doc_item * config_option; + struct ast_xml_node *option; + + ast_assert(ARRAY_LEN(aco_option_type_string) > type); + + if (!config_info || !(config_option = find_xmldoc_option(config_info, types, name))) { + ast_log(LOG_ERROR, "XML Documentation for option '%s' in modules '%s' not found!\n", name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + if (!(results = ast_xmldoc_query("//configInfo[@name='%s']/*/configObject[@name='%s']/configOption[@name='%s']", module, object_name, name))) { + ast_log(LOG_WARNING, "Could not find option '%s' with type '%s' in module '%s'\n", name, object_name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + if (!(option = ast_xml_xpath_get_first_result(results))) { + ast_log(LOG_WARNING, "Could obtain results for option '%s' with type '%s' in module '%s'\n", name, object_name, module); + return XMLDOC_STRICT ? -1 : 0; + } + ast_xml_set_attribute(option, "regex", regex ? "true" : "false"); + ast_xml_set_attribute(option, "default", default_value); + ast_xml_set_attribute(option, "type", aco_option_type_string[type]); + + if (ast_xmldoc_regenerate_doc_item(config_option)) { + ast_log(LOG_WARNING, "Could not update option '%s' with values from config option registration\n", name); + return XMLDOC_STRICT ? -1 : 0; + } + + return 0; +} + +/*! \internal + * \brief Show the modules with configuration information + */ +static void cli_show_modules(struct ast_cli_args *a) +{ + struct ast_xml_doc_item *item; + struct ao2_iterator it_items; + + ast_assert(a->argc == 3); + + if (ao2_container_count(xmldocs) == 0) { + ast_cli(a->fd, "No modules found.\n"); + return; + } + + it_items = ao2_iterator_init(xmldocs, 0); + ast_cli(a->fd, "The following modules have configuration information:\n"); + while ((item = ao2_iterator_next(&it_items))) { + ast_cli(a->fd, "\t%s\n", item->name); + ao2_ref(item, -1); + } + ao2_iterator_destroy(&it_items); +} + +/*! \internal + * \brief Show the configuration types for a module + */ +static void cli_show_module_types(struct ast_cli_args *a) +{ + RAII_VAR(struct ast_xml_doc_item *, item, NULL, ao2_cleanup); + struct ast_xml_doc_item *tmp; + + ast_assert(a->argc == 4); + + if (!(item = ao2_find(xmldocs, a->argv[3], OBJ_KEY))) { + ast_cli(a->fd, "Module %s not found.\n", a->argv[3]); + return; + } + + if (ast_str_strlen(item->synopsis)) { + ast_cli(a->fd, "%s\n\n", ast_xmldoc_printable(ast_str_buffer(item->synopsis), 1)); + } + if (ast_str_strlen(item->description)) { + ast_cli(a->fd, "%s\n\n", ast_xmldoc_printable(ast_str_buffer(item->description), 1)); + } + + tmp = item; + ast_cli(a->fd, "Configuration option types for %s:\n", tmp->name); + while ((tmp = tmp->next)) { + if (!strcasecmp(tmp->type, "configObject")) { + ast_cli(a->fd, "%-25s -- %-65.65s\n", tmp->name, + ast_str_buffer(tmp->synopsis)); + } + } +} + +/*! \internal + * \brief Show the information for a configuration type + */ +static void cli_show_module_type(struct ast_cli_args *a) +{ + RAII_VAR(struct ast_xml_doc_item *, item, NULL, ao2_cleanup); + struct ast_xml_doc_item *tmp; + char option_type[64]; + int match = 0; + + ast_assert(a->argc == 5); + + if (!(item = ao2_find(xmldocs, a->argv[3], OBJ_KEY))) { + ast_cli(a->fd, "Unknown module %s\n", a->argv[3]); + return; + } + + tmp = item; + while ((tmp = tmp->next)) { + if (!strcasecmp(tmp->type, "configObject") && !strcasecmp(tmp->name, a->argv[4])) { + match = 1; + term_color(option_type, tmp->name, COLOR_MAGENTA, COLOR_BLACK, sizeof(option_type)); + ast_cli(a->fd, "%s", option_type); + if (ast_str_strlen(tmp->syntax)) { + ast_cli(a->fd, ": [%s]\n\n", ast_xmldoc_printable(ast_str_buffer(tmp->syntax), 1)); + } else { + ast_cli(a->fd, "\n\n"); + } + if (ast_str_strlen(tmp->synopsis)) { + ast_cli(a->fd, "%s\n\n", ast_xmldoc_printable(ast_str_buffer(tmp->synopsis), 1)); + } + if (ast_str_strlen(tmp->description)) { + ast_cli(a->fd, "%s\n\n", ast_xmldoc_printable(ast_str_buffer(tmp->description), 1)); + } + } + } + + if (!match) { + ast_cli(a->fd, "Unknown configuration type %s\n", a->argv[4]); + return; + } + + /* Now iterate over the options for the type */ + tmp = item; + while ((tmp = tmp->next)) { + if (!strcasecmp(tmp->type, "configOption") && !strcasecmp(tmp->ref, a->argv[4])) { + ast_cli(a->fd, "%-25s -- %-65.65s\n", tmp->name, + ast_str_buffer(tmp->synopsis)); + } + } +} + +/*! \internal + * \brief Show detailed information for an option + */ +static void cli_show_module_options(struct ast_cli_args *a) +{ + RAII_VAR(struct ast_xml_doc_item *, item, NULL, ao2_cleanup); + struct ast_xml_doc_item *tmp; + char option_name[64]; + int match = 0; + + ast_assert(a->argc == 6); + + if (!(item = ao2_find(xmldocs, a->argv[3], OBJ_KEY))) { + ast_cli(a->fd, "Unknown module %s\n", a->argv[3]); + return; + } + tmp = item; + while ((tmp = tmp->next)) { + if (!strcasecmp(tmp->type, "configOption") && !strcasecmp(tmp->ref, a->argv[4]) && !strcasecmp(tmp->name, a->argv[5])) { + if (match) { + ast_cli(a->fd, "\n"); + } + term_color(option_name, tmp->ref, COLOR_MAGENTA, COLOR_BLACK, sizeof(option_name)); + ast_cli(a->fd, "[%s%s]\n", option_name, term_end()); + if (ast_str_strlen(tmp->syntax)) { + ast_cli(a->fd, "%s\n", ast_xmldoc_printable(ast_str_buffer(tmp->syntax), 1)); + } + ast_cli(a->fd, "%s\n\n", ast_xmldoc_printable(AS_OR(tmp->synopsis, "No information available"), 1)); + if (ast_str_strlen(tmp->description)) { + ast_cli(a->fd, "%s\n\n", ast_xmldoc_printable(ast_str_buffer(tmp->description), 1)); + } + + match = 1; + } + } + + if (!match) { + ast_cli(a->fd, "No option %s found for %s:%s\n", a->argv[5], a->argv[3], a->argv[4]); + } +} + +static char *cli_show_help(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "config show help"; + e->usage = + "Usage: config show help [ [ [