summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Jordan <mjordan@digium.com>2013-02-15 13:38:12 +0000
committerMatthew Jordan <mjordan@digium.com>2013-02-15 13:38:12 +0000
commitd04ab3c6450f3d92aa004ae9d6e0e7da51f702a3 (patch)
tree821330ff71a4484afa46ade4a2bbd211c800a992
parentedf0483f4f0e73ded128f1e613b60f31925af102 (diff)
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
-rw-r--r--Makefile2
-rw-r--r--apps/app_skel.c48
-rw-r--r--apps/confbridge/conf_config_parser.c466
-rw-r--r--channels/chan_motif.c140
-rw-r--r--configs/motif.conf.sample27
-rw-r--r--configs/xmpp.conf.sample3
-rw-r--r--doc/appdocsxml.dtd34
-rw-r--r--include/asterisk/_private.h1
-rw-r--r--include/asterisk/config_options.h9
-rw-r--r--include/asterisk/xml.h39
-rw-r--r--include/asterisk/xmldoc.h28
-rw-r--r--main/asterisk.c2
-rw-r--r--main/config_options.c534
-rw-r--r--main/named_acl.c51
-rw-r--r--main/udptl.c37
-rw-r--r--main/xml.c39
-rw-r--r--main/xmldoc.c254
-rw-r--r--res/res_xmpp.c122
18 files changed, 1762 insertions, 74 deletions
diff --git a/Makefile b/Makefile
index 76e68c416..02d6fa55d 100644
--- a/Makefile
+++ b/Makefile
@@ -456,7 +456,7 @@ doc/core-en_US.xml: makeopts $(foreach dir,$(MOD_SUBDIRS),$(shell $(GREP) -l "la
@echo "<docs xmlns:xi=\"http://www.w3.org/2001/XInclude\">" >> $@
@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 6fdbebbf2..8a5b5bc75 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.</para>
</description>
</application>
+
+ <configInfo name="app_skel" language="en_US">
+ <configFile name="app_skel.conf">
+ <configObject name="globals">
+ <synopsis>Options that apply globally to app_skel</synopsis>
+ <configOption name="games">
+ <synopsis>The number of games a single execution of SkelGuessNumber will play</synopsis>
+ </configOption>
+ <configOption name="cheat">
+ <synopsis>Should the computer cheat?</synopsis>
+ <description><para>If enabled, the computer will ignore winning guesses.</para></description>
+ </configOption>
+ </configObject>
+ <configObject name="sounds">
+ <synopsis>Prompts for SkelGuessNumber to play</synopsis>
+ <configOption name="prompt" default="please-enter-your&amp;number&amp;queue-less-than">
+ <synopsis>A prompt directing the user to enter a number less than the max number</synopsis>
+ </configOption>
+ <configOption name="wrong_guess" default="vm-pls-try-again">
+ <synopsis>The sound file to play when a wrong guess is made</synopsis>
+ </configOption>
+ <configOption name="right_guess" default="auth-thankyou">
+ <synopsis>The sound file to play when a correct guess is made</synopsis>
+ </configOption>
+ <configOption name="too_low">
+ <synopsis>The sound file to play when a guess is too low</synopsis>
+ </configOption>
+ <configOption name="too_high">
+ <synopsis>The sound file to play when a guess is too high</synopsis>
+ </configOption>
+ <configOption name="lose" default="vm-goodbye">
+ <synopsis>The sound file to play when a player loses</synopsis>
+ </configOption>
+ </configObject>
+ <configObject name="level">
+ <synopsis>Defined levels for the SkelGuessNumber game</synopsis>
+ <configOption name="max_number">
+ <synopsis>The maximum in the range of numbers to guess (1 is the implied minimum)</synopsis>
+ </configOption>
+ <configOption name="max_guesses">
+ <synopsis>The maximum number of guesses before a game is considered lost</synopsis>
+ </configOption>
+ </configObject>
+ </configFile>
+ </configInfo>
***/
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 c915b184c..6c5ea0bb8 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
+ <configInfo name="app_confbridge" language="en_US">
+ <synopsis>Conference Bridge Application</synopsis>
+ <configFile name="confbridge.conf">
+ <configObject name="global">
+ <synopsis>Unused, but reserved.</synopsis>
+ </configObject>
+ <configObject name="user_profile">
+ <synopsis>A named profile to apply to specific callers.</synopsis>
+ <description><para>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 <literal>type</literal> parameter has a value
+ of <literal>user</literal>.
+ </para></description>
+ <configOption name="type">
+ <synopsis>Define this configuration category as a user profile.</synopsis>
+ <description><para>The type parameter determines how a context in the
+ configuration file is interpreted.</para>
+ <enumlist>
+ <enum name="user"><para>Configure the context as a <replaceable>user_profile</replaceable></para></enum>
+ <enum name="bridge"><para>Configure the context as a <replaceable>bridge_profile</replaceable></para></enum>
+ <enum name="menu"><para>Configure the context as a <replaceable>menu</replaceable></para></enum>
+ </enumlist>
+ </description>
+ </configOption>
+ <configOption name="admin">
+ <synopsis>Sets if the user is an admin or not</synopsis>
+ </configOption>
+ <configOption name="marked">
+ <synopsis>Sets if this is a marked user or not</synopsis>
+ </configOption>
+ <configOption name="startmuted">
+ <synopsis>Sets if all users should start out muted</synopsis>
+ </configOption>
+ <configOption name="music_on_hold_when_empty">
+ <synopsis>Play MOH when user is alone or waiting on a marked user</synopsis>
+ </configOption>
+ <configOption name="quiet">
+ <synopsis>Silence enter/leave prompts and user intros for this user</synopsis>
+ </configOption>
+ <configOption name="announce_user_count">
+ <synopsis>Sets if the number of users should be announced to the user</synopsis>
+ </configOption>
+ <configOption name="announce_user_count_all">
+ <synopsis>Announce user count to all the other users when this user joins</synopsis>
+ <description><para>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.
+ </para></description>
+ </configOption>
+ <configOption name="announce_only_user">
+ <synopsis>Announce to a user when they join an empty conference</synopsis>
+ </configOption>
+ <configOption name="wait_marked">
+ <synopsis>Sets if the user must wait for a marked user to enter before joining a conference</synopsis>
+ </configOption>
+ <configOption name="end_marked">
+ <synopsis>Kick the user from the conference when the last marked user leaves</synopsis>
+ </configOption>
+ <configOption name="talk_detection_events">
+ <synopsis>Set whether or not notifications of when a user begins and ends talking should be sent out as events over AMI</synopsis>
+ </configOption>
+ <configOption name="dtmf_passthrough">
+ <synopsis>Sets whether or not DTMF should pass through the conference</synopsis>
+ </configOption>
+ <configOption name="announce_join_leave">
+ <synopsis>Prompt user for their name when joining a conference and play it to the conference when they enter</synopsis>
+ </configOption>
+ <configOption name="pin">
+ <synopsis>Sets a PIN the user must enter before joining the conference</synopsis>
+ </configOption>
+ <configOption name="music_on_hold_class">
+ <synopsis>The MOH class to use for this user</synopsis>
+ </configOption>
+ <configOption name="announcement">
+ <synopsis>Sound file to play to the user when they join a conference</synopsis>
+ </configOption>
+ <configOption name="denoise">
+ <synopsis>Apply a denoise filter to the audio before mixing</synopsis>
+ <description><para>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.
+ </para></description>
+ </configOption>
+ <configOption name="dsp_drop_silence">
+ <synopsis>Drop what Asterisk detects as silence from audio sent to the bridge</synopsis>
+ <description><para>
+ 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.
+ </para></description>
+ </configOption>
+ <configOption name="dsp_silence_threshold">
+ <synopsis>The number of milliseconds of detected silence necessary to trigger silence detection</synopsis>
+ <description><para>
+ 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.</para>
+ <para>What this value affects internally:</para>
+ <para>
+ 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.
+ </para>
+ <para>
+ 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.
+ </para>
+ <para>
+ 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.
+ </para>
+ <para>By default this value is 2500ms. Valid values are 1 through 2^31.</para>
+ </description>
+ </configOption>
+ <configOption name="dsp_talking_threshold">
+ <synopsis>The number of milliseconds of detected non-silence necessary to triger talk detection</synopsis>
+ <description><para>
+ 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.</para>
+ <para>
+ What this value affects internally:
+ </para>
+ <para>
+ 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.
+ </para>
+ <para>
+ 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.
+ </para>
+ <para>
+ 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.
+ </para>
+ <para>By default this value is 160 ms. Valid values are 1 through 2^31</para>
+ </description>
+ </configOption>
+ <configOption name="jitterbuffer">
+ <synopsis>Place a jitter buffer on the user's audio stream before audio mixing is performed</synopsis>
+ <description><para>
+ 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 <literal>JITTERBUFFER</literal>
+ dialplan function's default adaptive jitterbuffer. For a more fine tuned
+ jitterbuffer, disable this option and use the <literal>JITTERBUFFER</literal> dialplan function
+ on the user before entering the ConfBridge application.
+ </para></description>
+ </configOption>
+ <configOption name="template">
+ <synopsis>When using the CONFBRIDGE dialplan function, use a user profile as a template for creating a new temporary profile</synopsis>
+ </configOption>
+ </configObject>
+ <configObject name="bridge_profile">
+ <synopsis>A named profile to apply to specific bridges.</synopsis>
+ <description><para>ConfBridge bridges have a profile associated with them
+ that determine their options. A configuration section is determined to be a
+ <literal>bridge_profile</literal> when the <literal>type</literal> parameter has a value
+ of <literal>bridge</literal>.
+ </para></description>
+ <configOption name="type">
+ <synopsis>Define this configuration category as a bridge profile</synopsis>
+ <description><para>The type parameter determines how a context in the
+ configuration file is interpreted.</para>
+ <enumlist>
+ <enum name="user"><para>Configure the context as a <replaceable>user_profile</replaceable></para></enum>
+ <enum name="bridge"><para>Configure the context as a <replaceable>bridge_profile</replaceable></para></enum>
+ <enum name="menu"><para>Configure the context as a <replaceable>menu</replaceable></para></enum>
+ </enumlist>
+ </description>
+ </configOption>
+ <configOption name="jitterbuffer">
+ <synopsis>Place a jitter buffer on the conference's audio stream</synopsis>
+ </configOption>
+ <configOption name="internal_sample_rate">
+ <synopsis>Set the internal native sample rate for mixing the conference</synopsis>
+ <description><para>
+ 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.
+ </para></description>
+ </configOption>
+ <configOption name="mixing_interval">
+ <synopsis>Sets the internal mixing interval in milliseconds for the bridge</synopsis>
+ <description><para>
+ 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.
+ </para></description>
+ </configOption>
+ <configOption name="record_conference">
+ <synopsis>Record the conference starting with the first active user's entrance and ending with the last active user's exit</synopsis>
+ <description><para>
+ 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
+ <filename>'confbridge-${name of conference bridge}-${start time}.wav</filename>
+ and the default format is 8khz slinear. This file will be
+ located in the configured monitoring directory in asterisk.conf.
+ </para></description>
+ </configOption>
+ <configOption name="record_file" default="confbridge-${name of conference bridge}-${start time}.wav">
+ <synopsis>The filename of the conference recording</synopsis>
+ <description><para>
+ 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 <literal>CONFBRIDGE()</literal> 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.
+ </para></description>
+ </configOption>
+ <configOption name="video_mode">
+ <synopsis>Sets how confbridge handles video distribution to the conference participants</synopsis>
+ <description><para>
+ 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.</para>
+ <enumlist>
+ <enum name="none">
+ <para>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.</para>
+ </enum>
+ <enum name="follow_talker">
+ <para>The video feed will follow whoever is talking and providing video.</para>
+ </enum>
+ <enum name="last_marked">
+ <para>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.</para>
+ </enum>
+ <enum name="first_marked">
+ <para>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.</para>
+ </enum>
+ </enumlist>
+ </description>
+ </configOption>
+ <configOption name="max_members">
+ <synopsis>Limit the maximum number of participants for a single conference</synopsis>
+ <description><para>
+ 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.
+ </para></description>
+ </configOption>
+ <configOption name="^sound_">
+ <synopsis>Override the various conference bridge sound files</synopsis>
+ <description><para>
+ 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: <literal>sound_had_joined=conf-hasjoin</literal> This will play the <literal>conf-hasjoin</literal>
+ sound file found in the sounds directory when announcing someone's name is joining the
+ conference.</para>
+ <enumlist>
+ <enum name="sound_join"><para>The sound played to everyone when someone enters the conference.</para></enum>
+ <enum name="sound_leave"><para>The sound played to everyone when someone leaves the conference.</para></enum>
+ <enum name="sound_has_joined"><para>The sound played before announcing someone's name has
+ joined the conference. This is used for user intros.
+ Example <literal>"_____ has joined the conference"</literal></para></enum>
+ <enum name="sound_has_left"><para>The sound played when announcing someone's name has
+ left the conference. This is used for user intros.
+ Example <literal>"_____ has left the conference"</literal></para></enum>
+ <enum name="sound_kicked"><para>The sound played to a user who has been kicked from the conference.</para></enum>
+ <enum name="sound_muted"><para>The sound played when the mute option it toggled on.</para></enum>
+ <enum name="sound_unmuted"><para>The sound played when the mute option it toggled off.</para></enum>
+ <enum name="sound_only_person"><para>The sound played when the user is the only person in the conference.</para></enum>
+ <enum name="sound_only_one"><para>The sound played to a user when there is only one other
+ person is in the conference.</para></enum>
+ <enum name="sound_there_are"><para>The sound played when announcing how many users there
+ are in a conference.</para></enum>
+ <enum name="sound_other_in_party"><para>This file is used in conjunction with <literal>sound_there_are</literal>
+ when announcing how many users there are in the conference.
+ The sounds are stringed together like this.
+ <literal>"sound_there_are" ${number of participants} "sound_other_in_party"</literal></para></enum>
+ <enum name="sound_place_into_conference"><para>The sound played when someone is placed into the conference
+ after waiting for a marked user.</para></enum>
+ <enum name="sound_wait_for_leader"><para>The sound played when a user is placed into a conference that
+ can not start until a marked user enters.</para></enum>
+ <enum name="sound_leader_has_left"><para>The sound played when the last marked user leaves the conference.</para></enum>
+ <enum name="sound_get_pin"><para>The sound played when prompting for a conference pin number.</para></enum>
+ <enum name="sound_invalid_pin"><para>The sound played when an invalid pin is entered too many times.</para></enum>
+ <enum name="sound_locked"><para>The sound played to a user trying to join a locked conference.</para></enum>
+ <enum name="sound_locked_now"><para>The sound played to an admin after toggling the conference to locked mode.</para></enum>
+ <enum name="sound_unlocked_now"><para>The sound played to an admin after toggling the conference to unlocked mode.</para></enum>
+ <enum name="sound_error_menu"><para>The sound played when an invalid menu option is entered.</para></enum>
+ </enumlist>
+ </description>
+ </configOption>
+ <configOption name="template">
+ <synopsis>When using the CONFBRIDGE dialplan function, use a bridge profile as a template for creating a new temporary profile</synopsis>
+ </configOption>
+ </configObject>
+ <configObject name="menu">
+ <synopsis>A conference user menu</synopsis>
+ <description>
+ <para>Conference users, as defined by a <literal>conf_user</literal>,
+ can have a DTMF menu assigned to their profile when they enter the
+ <literal>ConfBridge</literal> application.</para>
+ </description>
+ <configOption name="type">
+ <synopsis>Define this configuration category as a menu</synopsis>
+ <description><para>The type parameter determines how a context in the
+ configuration file is interpreted.</para>
+ <enumlist>
+ <enum name="user"><para>Configure the context as a <replaceable>user_profile</replaceable></para></enum>
+ <enum name="bridge"><para>Configure the context as a <replaceable>bridge_profile</replaceable></para></enum>
+ <enum name="menu"><para>Configure the context as a <replaceable>menu</replaceable></para></enum>
+ </enumlist>
+ </description>
+ </configOption>
+ <configOption name="^[0-9A-D*#]+$">
+ <synopsis>DTMF sequences to assign various confbridge actions to</synopsis>
+ <description><para>--- ConfBridge Menu Options ---</para>
+ <para>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.</para>
+ <para>Below is a list of menu actions that can be assigned to a DTMF sequence.</para>
+ <note><para>
+ A single DTMF sequence can have multiple actions associated with it. This is
+ accomplished by stringing the actions together and using a <literal>,</literal> as the
+ delimiter. Example: Both listening and talking volume is reset when <literal>5</literal> is
+ pressed. <literal>5=reset_talking_volume, reset_listening_volume</literal></para></note>
+ <enumlist>
+ <enum name="playback(filename&amp;filename2&amp;...)"><para>
+ <literal>playback</literal> 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
+ <literal>&amp;</literal> character.</para></enum>
+ <enum name="playback_and_continue(filename&amp;filename2&amp;...)"><para>
+ <literal>playback_and_continue</literal> 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 <literal>&amp;</literal> character as a delimiter.</para></enum>
+ <enum name="toggle_mute"><para>
+ 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.</para></enum>
+ <enum name="no_op"><para>
+ 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.</para></enum>
+ <enum name="decrease_listening_volume"><para>
+ Decreases the channel's listening volume.</para></enum>
+ <enum name="increase_listening_volume"><para>
+ Increases the channel's listening volume.</para></enum>
+ <enum name="reset_listening_volume"><para>
+ Reset channel's listening volume to default level.</para></enum>
+ <enum name="decrease_talking_volume"><para>
+ Decreases the channel's talking volume.</para></enum>
+ <enum name="increase_talking_volume"><para>
+ Increases the channel's talking volume.</para></enum>
+ <enum name="reset_talking_volume"><para>
+ Reset channel's talking volume to default level.</para></enum>
+ <enum name="dialplan_exec(context,exten,priority)"><para>
+ The <literal>dialplan_exec</literal> 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!</para></enum>
+ <enum name="leave_conference"><para>
+ This action allows a user to exit the conference and continue
+ execution in the dialplan.</para></enum>
+ <enum name="admin_kick_last"><para>
+ 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.</para></enum>
+ <enum name="admin_toggle_conference_lock"><para>
+ 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.</para></enum>
+ <enum name="set_as_single_video_src"><para>
+ 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 <literal>video_mode</literal> is set to.</para></enum>
+ <enum name="release_as_single_video_src"><para>
+ This action allows a user to release themselves as
+ the video source. If <literal>video_mode</literal> is not set to <literal>none</literal>
+ this action will result in the conference returning to
+ whatever video mode the bridge profile is using.</para>
+ <para>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 <literal>video_mode</literal> option is set to
+ upon release of the video src.</para></enum>
+ <enum name="admin_toggle_mute_participants"><para>
+ 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.</para></enum>
+ <enum name="participant_count"><para>
+ This action plays back the number of participants currently
+ in a conference</para></enum>
+ </enumlist>
+ </description>
+ </configOption>
+ </configObject>
+ </configFile>
+ </configInfo>
+***/
+
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 cc1e370fd..4dbfb8e3a 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
+ <configInfo name="chan_motif" language="en_US">
+ <synopsis>Jingle Channel Driver</synopsis>
+ <description>
+ <para><emphasis>Transports</emphasis></para>
+ <para>There are three different transports and protocol derivatives
+ supported by <literal>chan_motif</literal>. They are in order of
+ preference: Jingle using ICE-UDP, Google Jingle, and Google-V1.</para>
+ <para>Jingle as defined in XEP-0166 supports the widest range of
+ features. It is referred to as <literal>ice-udp</literal>. This is
+ the specification that Jingle clients implement.</para>
+ <para>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 <literal>google</literal> in this file.</para>
+ <para>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 <literal>google-v1</literal>
+ in this file.</para>
+ <para>Incoming sessions will automatically switch to the correct
+ transport once it has been determined.</para>
+ <para>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.</para>
+ <para>If an outgoing session attempt fails due to failure to
+ support the given transport <literal>chan_motif</literal> will
+ fall back in preference order listed previously until all
+ transports have been exhausted.</para>
+ <para><emphasis>Dialing and Resource Selection Strategy</emphasis></para>
+ <para>Placing a call through an endpoint can be accomplished using the
+ following dial string:</para>
+ <para><literal>Motif/[endpoint name]/[target]</literal></para>
+ <para>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.</para>
+ <para>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 <literal>ice-udp</literal>.</para>
+ <para><emphasis>Video Support</emphasis></para>
+ <para>Support for video does not need to be explicitly enabled.
+ Configuring any video codec on your endpoint will automatically enable
+ it.</para>
+ <para><emphasis>DTMF</emphasis></para>
+ <para>The only supported method for DTMF is RFC2833. This is always
+ enabled on audio streams and negotiated if possible.</para>
+ <para><emphasis>Incoming Calls</emphasis></para>
+ <para>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 <literal>s</literal> extension.</para>
+ <para><emphasis>CallerID</emphasis></para>
+ <para>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.</para>
+ <para>Outgoing caller id can <emphasis>not</emphasis> be set.</para>
+ <warning>
+ <para>Multiple endpoints using the
+ same connection is <emphasis>NOT</emphasis> supported. Doing so
+ may result in broken calls.</para>
+ </warning>
+ </description>
+ <configFile name="motif.conf">
+ <configObject name="endpoint">
+ <synopsis>The configuration for an endpoint.</synopsis>
+ <configOption name="context">
+ <synopsis>Default dialplan context that incoming sessions will be routed to</synopsis>
+ </configOption>
+ <configOption name="callgroup">
+ <synopsis>A callgroup to assign to this endpoint.</synopsis>
+ </configOption>
+ <configOption name="pickupgroup">
+ <synopsis>A pickup group to assign to this endpoint.</synopsis>
+ </configOption>
+ <configOption name="language">
+ <synopsis>The default language for this endpoint.</synopsis>
+ </configOption>
+ <configOption name="musicclass">
+ <synopsis>Default music on hold class for this endpoint.</synopsis>
+ </configOption>
+ <configOption name="parkinglot">
+ <synopsis>Default parking lot for this endpoint.</synopsis>
+ </configOption>
+ <configOption name="accountcode">
+ <synopsis>Accout code for CDR purposes</synopsis>
+ </configOption>
+ <configOption name="allow">
+ <synopsis>Codecs to allow</synopsis>
+ </configOption>
+ <configOption name="disallow">
+ <synopsis>Codecs to disallow</synopsis>
+ </configOption>
+ <configOption name="connection">
+ <synopsis>Connection to accept traffic on and on which to send traffic out</synopsis>
+ </configOption>
+ <configOption name="transport">
+ <synopsis>The transport to use for the endpoint.</synopsis>
+ <description>
+ <para>The default outbound transport for this endpoint. Inbound
+ messages are inferred. Allowed transports are <literal>ice-udp</literal>,
+ <literal>google</literal>, or <literal>google-v1</literal>. Note
+ that <literal>chan_motif</literal> will fall back to transport
+ preference order if the transport value chosen here fails.</para>
+ <enumlist>
+ <enum name="ice-udp">
+ <para>The Jingle protocol, as defined in XEP 0166.</para>
+ </enum>
+ <enum name="google">
+ <para>The Google Jingle protocol, which follows the Jingle
+ specification for signaling but uses a custom transport for
+ media.</para>
+ </enum>
+ <enum name="google-v1">
+ <para>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 <literal>google</literal> for media.</para>
+ </enum>
+ </enumlist>
+ </description>
+ </configOption>
+ <configOption name="maxicecandidates">
+ <synopsis>Maximum number of ICE candidates to offer</synopsis>
+ </configOption>
+ <configOption name="maxpayloads">
+ <synopsis>Maximum number of pyaloads to offer</synopsis>
+ </configOption>
+ </configObject>
+ </configFile>
+ </configInfo>
+***/
+
/*! \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 ae3ab30d7..9d5bc808c 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 a83856867..dad0f79ef 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 561e3d38c..a475cd32f 100644
--- a/doc/appdocsxml.dtd
+++ b/doc/appdocsxml.dtd
@@ -1,13 +1,13 @@
- <!ELEMENT docs (application|function|agi|manager|managerEvent|info)*>
+ <!ELEMENT docs (application|function|agi|manager|managerEvent|info|configInfo)*>
<!ATTLIST docs xmlns:xi CDATA #FIXED "http://www.w3.org/2001/XInclude">
<!ELEMENT xi:include (xi:fallback?) >
- <!ATTLIST xi:include
+ <!ATTLIST xi:include
xmlns:xi CDATA #FIXED "http://www.w3.org/2001/XInclude"
href CDATA #IMPLIED
parse (xml|text) "xml"
xpointer CDATA #IMPLIED
- encoding CDATA #IMPLIED
+ encoding CDATA #IMPLIED
accept CDATA #IMPLIED
accept-language CDATA #IMPLIED >
@@ -39,6 +39,30 @@
<!ELEMENT managerEventInstance (synopsis?,syntax?,description?,see-also?)*>
<!ATTLIST managerEventInstance class CDATA #REQUIRED>
+ <!ELEMENT configInfo (synopsis?,description?,configFile+)>
+ <!ATTLIST configInfo name CDATA #REQUIRED>
+ <!ATTLIST configInfo language CDATA #REQUIRED>
+
+ <!ELEMENT configFile (configObject+)>
+ <!ATTLIST configFile name CDATA #REQUIRED>
+
+ <!ELEMENT configObject (synopsis?|description?|syntax?|see-also?|configOption)*>
+ <!ATTLIST configObject name CDATA #REQUIRED>
+
+ <!ELEMENT configOption (synopsis,description?,syntax?,see-also?)*>
+ <!ATTLIST configOption name CDATA #REQUIRED>
+ <!ATTLIST configOption regex (yes|no|true|false) "false">
+ <!ATTLIST configOption default CDATA #IMPLIED>
+ <!ATTLIST configOption type CDATA #IMPLIED>
+
+ <!ELEMENT matchInfo (category|field?)>
+
+ <!ELEMENT category (#PCDATA)>
+ <!ATTLIST category match (yes|no|true|false) #REQUIRED>
+
+ <!ELEMENT field (#PCDATA)>
+ <!ATTLIST field name CDATA #REQUIRED>
+
<!ELEMENT info (para|note|warning|variablelist|enumlist|info|xi:include)*>
<!ATTLIST info name CDATA #REQUIRED>
<!ATTLIST info language CDATA #REQUIRED>
@@ -52,7 +76,7 @@
<!ELEMENT synopsis (#PCDATA)>
- <!ELEMENT syntax (parameter|xi:include)*>
+ <!ELEMENT syntax (parameter|dataType|category|matchInfo|xi:include)*>
<!ATTLIST syntax argsep CDATA ",">
<!ELEMENT description (para|note|warning|variablelist|enumlist|info|xi:include)*>
@@ -91,7 +115,7 @@
<!ELEMENT replaceable (#PCDATA)>
<!ELEMENT directory (#PCDATA)>
<!ELEMENT astcli (#PCDATA)>
-
+
<!ELEMENT note (para+|xi:include*)>
<!ELEMENT warning (para+|xi:include*)>
diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h
index cded90ef7..7e1ef13ec 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 1b0beb206..64d8d5089 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 ddfcc25d9..063e8c0b3 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 9bf647612..c09f693c8 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 243321dda..1d5371938 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 5e76a7a7b..7a65cc555 100644
--- a/main/config_options.c
+++ b/main/config_options.c
@@ -31,11 +31,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include <regex.h>
+#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 [<module> [<type> [<option>]]]\n"
+ " Display detailed information about module configuration.\n"
+ " * If nothing is specified, the modules that have\n"
+ " configuration information are listed.\n"
+ " * If <module> is specified, the configuration types\n"
+ " for that module will be listed, along with brief\n"
+ " information about that type.\n"
+ " * If <module> and <type> are specified, detailed\n"
+ " information about the type is displayed, as well\n"
+ " as the available options.\n"
+ " * If <module>, <type>, and <option> are specified,\n"
+ " detailed information will be displayed about that\n"
+ " option.\n"
+ " NOTE: the help documentation is partially generated at run\n"
+ " time when a module is loaded. If a module is not loaded,\n"
+ " configuration help for that module may be incomplete.\n";
+ return NULL;
+ case CLI_GENERATE:
+ switch(a->pos) {
+ case 3: return complete_config_module(a->word, a->pos, a->n);
+ case 4: return complete_config_type(a->argv[3], a->word, a->pos, a->n);
+ case 5: return complete_config_option(a->argv[3], a->argv[4], a->word, a->pos, a->n);
+ default: return NULL;
+ }
+ }
+
+ switch (a->argc) {
+ case 3:
+ cli_show_modules(a);
+ break;
+ case 4:
+ cli_show_module_types(a);
+ break;
+ case 5:
+ cli_show_module_type(a);
+ break;
+ case 6:
+ cli_show_module_options(a);
+ break;
+ default:
+ return CLI_SHOWUSAGE;
+ }
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_aco[] = {
+ AST_CLI_DEFINE(cli_show_help, "Show configuration help for a module"),
+};
+
+static void aco_deinit(void)
+{
+ ast_cli_unregister(cli_aco);
+ ao2_cleanup(xmldocs);
+}
+#endif /* AST_XML_DOCS */
+
+int aco_init(void)
+{
+#ifdef AST_XML_DOCS
+ ast_register_atexit(aco_deinit);
+ if (!(xmldocs = ast_xmldoc_build_documentation("configInfo"))) {
+ ast_log(LOG_ERROR, "Couldn't build config documentation\n");
+ return -1;
+ }
+ ast_cli_register_multiple(cli_aco, ARRAY_LEN(cli_aco));
+#endif /* AST_XML_DOCS */
+ return 0;
+}
+
/* Default config option handlers */
/*! \brief Default option handler for signed integers
@@ -730,7 +1240,7 @@ static int int_handler_fn(const struct aco_option *opt, struct ast_variable *var
ast_parse_arg(var->value, flags, field, (int) opt->args[1], (int) opt->args[2]);
if (res) {
if (opt->flags & PARSE_RANGE_DEFAULTS) {
- ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]);
+ ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]);
res = 0;
} else if (opt->flags & PARSE_DEFAULT) {
ast_log(LOG_WARNING, "Failed to set %s=%s, Set to default value %d instead.\n", var->name, var->value, *field);
@@ -760,7 +1270,7 @@ static int uint_handler_fn(const struct aco_option *opt, struct ast_variable *va
ast_parse_arg(var->value, flags, field, (unsigned int) opt->args[1], (unsigned int) opt->args[2]);
if (res) {
if (opt->flags & PARSE_RANGE_DEFAULTS) {
- ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]);
+ ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]);
res = 0;
} else if (opt->flags & PARSE_DEFAULT) {
ast_log(LOG_WARNING, "Failed to set %s=%s, Set to default value %d instead.\n", var->name, var->value, *field);
diff --git a/main/named_acl.c b/main/named_acl.c
index 074f4c57f..142693194 100644
--- a/main/named_acl.c
+++ b/main/named_acl.c
@@ -44,11 +44,21 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#define NACL_CONFIG "acl.conf"
#define ACL_FAMILY "acls"
-struct named_acl_global_config {
- AST_DECLARE_STRING_FIELDS(
- /* Nothing here yet. */
- );
-};
+/*** DOCUMENTATION
+ <configInfo name="named_acl" language="en_US">
+ <configFile name="named_acl.conf">
+ <configObject name="named_acl">
+ <synopsis>Options for configuring a named ACL</synopsis>
+ <configOption name="permit">
+ <synopsis>An address/subnet from which to allow access</synopsis>
+ </configOption>
+ <configOption name="deny">
+ <synopsis>An address/subnet from which to disallow access</synopsis>
+ </configOption>
+ </configObject>
+ </configFile>
+ </configInfo>
+***/
/*
* Configuration structure - holds pointers to ao2 containers used for configuration
@@ -56,7 +66,6 @@ struct named_acl_global_config {
* time, it's really a config options friendly wrapper for the named ACL container
*/
struct named_acl_config {
- struct named_acl_global_config *global;
struct ao2_container *named_acl_list;
};
@@ -70,6 +79,7 @@ static void *named_acl_find(struct ao2_container *container, const char *cat);
/* Config type for named ACL profiles (must not be named general) */
static struct aco_type named_acl_type = {
.type = ACO_ITEM, /*!< named_acls are items stored in containers, not individual global objects */
+ .name = "named_acl",
.category_match = ACO_BLACKLIST,
.category = "^general$", /*!< Match everything but "general" */
.item_alloc = named_acl_alloc, /*!< A callback to allocate a new named_acl based on category */
@@ -77,26 +87,16 @@ static struct aco_type named_acl_type = {
.item_offset = offsetof(struct named_acl_config, named_acl_list), /*!< Could leave this out since 0 */
};
-/* Config type for the general part of the ACL profile (must be named general) */
-static struct aco_type global_option = {
- .type = ACO_GLOBAL,
- .item_offset = offsetof(struct named_acl_config, global),
- .category_match = ACO_WHITELIST,
- .category = "^general$",
-};
-
/* This array of aco_type structs is necessary to use aco_option_register */
struct aco_type *named_acl_types[] = ACO_TYPES(&named_acl_type);
-struct aco_type *global_options[] = ACO_TYPES(&global_option);
-
struct aco_file named_acl_conf = {
.filename = "acl.conf",
- .types = ACO_TYPES(&named_acl_type, &global_option),
+ .types = ACO_TYPES(&named_acl_type),
};
/* Create a config info struct that describes the config processing for named ACLs. */
-CONFIG_INFO_STANDARD(cfg_info, globals, named_acl_config_alloc,
+CONFIG_INFO_CORE("named_acl", cfg_info, globals, named_acl_config_alloc,
.files = ACO_FILES(&named_acl_conf),
);
@@ -124,13 +124,6 @@ static void named_acl_config_destructor(void *obj)
{
struct named_acl_config *cfg = obj;
ao2_cleanup(cfg->named_acl_list);
- ao2_cleanup(cfg->global);
-}
-
-static void named_acl_global_config_destructor(void *obj)
-{
- struct named_acl_global_config *global = obj;
- ast_string_field_free_memory(global);
}
/*! \brief allocator callback for named_acl_config. Notice it returns void * since it is used by
@@ -144,14 +137,6 @@ static void *named_acl_config_alloc(void)
return NULL;
}
- if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), named_acl_global_config_destructor))) {
- goto error;
- }
-
- if (ast_string_field_init(cfg->global, 128)) {
- goto error;
- }
-
if (!(cfg->named_acl_list = ao2_container_alloc(37, named_acl_hash_fn, named_acl_cmp_fn))) {
goto error;
}
diff --git a/main/udptl.c b/main/udptl.c
index 37f04d530..8ae8334a9 100644
--- a/main/udptl.c
+++ b/main/udptl.c
@@ -80,6 +80,40 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/cli.h"
#include "asterisk/unaligned.h"
+/*** DOCUMENTATION
+ <configInfo name="udptl" language="en_US">
+ <configFile name="udptl.conf">
+ <configObject name="global">
+ <synopsis>Global options for configuring UDPTL</synopsis>
+ <configOption name="udptlstart">
+ <synopsis>The start of the UDPTL port range</synopsis>
+ </configOption>
+ <configOption name="udptlend">
+ <synopsis>The end of the UDPTL port range</synopsis>
+ </configOption>
+ <configOption name="udptlchecksums">
+ <synopsis>Whether to enable or disable UDP checksums on UDPTL traffic</synopsis>
+ </configOption>
+ <configOption name="udptlfecentries">
+ <synopsis>The number of error correction entries in a UDPTL packet</synopsis>
+ </configOption>
+ <configOption name="udptlfecspan">
+ <synopsis>The span over which parity is calculated for FEC in a UDPTL packet</synopsis>
+ </configOption>
+ <configOption name="use_even_ports">
+ <synopsis>Whether to only use even-numbered UDPTL ports</synopsis>
+ </configOption>
+ <configOption name="t38faxudpec">
+ <synopsis>Removed</synopsis>
+ </configOption>
+ <configOption name="t38faxmaxdatagram">
+ <synopsis>Removed</synopsis>
+ </configOption>
+ </configObject>
+ </configFile>
+ </configInfo>
+***/
+
#define UDPTL_MTU 1200
#if !defined(FALSE)
@@ -206,6 +240,7 @@ static int udptl_pre_apply_config(void);
static struct aco_type general_option = {
.type = ACO_GLOBAL,
+ .name = "global",
.category_match = ACO_WHITELIST,
.item_offset = offsetof(struct udptl_config, general),
.category = "^general$",
@@ -218,7 +253,7 @@ static struct aco_file udptl_conf = {
.types = ACO_TYPES(&general_option),
};
-CONFIG_INFO_STANDARD(cfg_info, globals, udptl_snapshot_alloc,
+CONFIG_INFO_CORE("udptl", cfg_info, globals, udptl_snapshot_alloc,
.files = ACO_FILES(&udptl_conf),
.pre_apply_config = udptl_pre_apply_config,
);
diff --git a/main/xml.c b/main/xml.c
index bdf983e51..5ca4d4ff1 100644
--- a/main/xml.c
+++ b/main/xml.c
@@ -28,6 +28,7 @@
#include "asterisk.h"
#include "asterisk/xml.h"
#include "asterisk/logger.h"
+#include "asterisk/utils.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
@@ -35,6 +36,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xinclude.h>
+#include <libxml/xpath.h>
/* libxml2 ast_xml implementation. */
@@ -316,5 +318,42 @@ struct ast_xml_node *ast_xml_node_get_parent(struct ast_xml_node *node)
return (struct ast_xml_node *) ((xmlNode *) node)->parent;
}
+struct ast_xml_node *ast_xml_xpath_get_first_result(struct ast_xml_xpath_results *results)
+{
+ return (struct ast_xml_node *) ((xmlXPathObjectPtr) results)->nodesetval->nodeTab[0];
+}
+
+void ast_xml_xpath_results_free(struct ast_xml_xpath_results *results)
+{
+ xmlXPathFreeObject((xmlXPathObjectPtr) results);
+}
+
+int ast_xml_xpath_num_results(struct ast_xml_xpath_results *results)
+{
+ return ((xmlXPathObjectPtr) results)->nodesetval->nodeNr;
+}
+
+struct ast_xml_xpath_results *ast_xml_query(struct ast_xml_doc *doc, const char *xpath_str)
+{
+ xmlXPathContextPtr context;
+ xmlXPathObjectPtr result;
+ if (!(context = xmlXPathNewContext((xmlDoc *) doc))) {
+ ast_log(LOG_ERROR, "Could not create XPath context!\n");
+ return NULL;
+ }
+ result = xmlXPathEvalExpression((xmlChar *) xpath_str, context);
+ xmlXPathFreeContext(context);
+ if (!result) {
+ ast_log(LOG_WARNING, "Error for query: %s\n", xpath_str);
+ return NULL;
+ }
+ if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
+ xmlXPathFreeObject(result);
+ ast_debug(5, "No results for query: %s\n", xpath_str);
+ return NULL;
+ }
+ return (struct ast_xml_xpath_results *) result;
+}
+
#endif /* defined(HAVE_LIBXML2) */
diff --git a/main/xmldoc.c b/main/xmldoc.c
index 225ed6611..a38b59852 100644
--- a/main/xmldoc.c
+++ b/main/xmldoc.c
@@ -38,6 +38,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/term.h"
#include "asterisk/astobj2.h"
#include "asterisk/xmldoc.h"
+#include "asterisk/cli.h"
#ifdef AST_XML_DOCS
@@ -979,6 +980,10 @@ static char *xmldoc_get_syntax_cmd(struct ast_xml_node *fixnode, const char *nam
const char *paramtype, *attrname, *literal;
int required, isenum, first = 1, isliteral;
+ if (!fixnode) {
+ return NULL;
+ }
+
syntax = ast_str_create(128);
if (!syntax) {
/* at least try to return something... */
@@ -1078,6 +1083,10 @@ static char *xmldoc_get_syntax_manager(struct ast_xml_node *fixnode, const char
int required;
char *ret;
+ if (!fixnode) {
+ return NULL;
+ }
+
syntax = ast_str_create(128);
if (!syntax) {
return ast_strdup(name);
@@ -1118,11 +1127,76 @@ static char *xmldoc_get_syntax_manager(struct ast_xml_node *fixnode, const char
return ret;
}
+static char *xmldoc_get_syntax_config_object(struct ast_xml_node *fixnode, const char *name)
+{
+ struct ast_xml_node *matchinfo, *tmp;
+ int match;
+ const char *attr_value;
+ const char *text;
+ RAII_VAR(struct ast_str *, syntax, ast_str_create(128), ast_free);
+
+ if (!syntax || !fixnode) {
+ return NULL;
+ }
+ if (!(matchinfo = ast_xml_find_element(ast_xml_node_get_children(fixnode), "matchInfo", NULL, NULL))) {
+ return NULL;
+ }
+ if (!(tmp = ast_xml_find_element(ast_xml_node_get_children(matchinfo), "category", NULL, NULL))) {
+ return NULL;
+ }
+ attr_value = ast_xml_get_attribute(tmp, "match");
+ if (attr_value) {
+ match = ast_true(attr_value);
+ text = ast_xml_get_text(tmp);
+ ast_str_set(&syntax, 0, "category %s /%s/", match ? "=~" : "!~", text);
+ ast_xml_free_attr(attr_value);
+ }
+
+ if ((tmp = ast_xml_find_element(ast_xml_node_get_children(matchinfo), "field", NULL, NULL))) {
+ text = ast_xml_get_text(tmp);
+ attr_value = ast_xml_get_attribute(tmp, "name");
+ ast_str_append(&syntax, 0, " matchfield: %s = %s", S_OR(attr_value, "Unknown"), text);
+ ast_xml_free_attr(attr_value);
+ }
+ return ast_strdup(ast_str_buffer(syntax));
+}
+
+static char *xmldoc_get_syntax_config_option(struct ast_xml_node *fixnode, const char *name)
+{
+ const char *type;
+ const char *default_value;
+ const char *regex;
+ RAII_VAR(struct ast_str *, syntax, ast_str_create(128), ast_free);
+
+ if (!syntax || !fixnode) {
+ return NULL;
+ }
+ type = ast_xml_get_attribute(fixnode, "type");
+ default_value = ast_xml_get_attribute(fixnode, "default");
+
+ regex = ast_xml_get_attribute(fixnode, "regex");
+ ast_str_set(&syntax, 0, "%s = [%s] (Default: %s) (Regex: %s)\n",
+ name,
+ type,
+ default_value,
+ regex ? regex : "False");
+
+ ast_xml_free_attr(type);
+ ast_xml_free_attr(default_value);
+ ast_xml_free_attr(regex);
+
+ return ast_strdup(ast_str_buffer(syntax));
+}
+
/*! \brief Types of syntax that we are able to generate. */
enum syntaxtype {
FUNCTION_SYNTAX,
MANAGER_SYNTAX,
MANAGER_EVENT_SYNTAX,
+ CONFIG_INFO_SYNTAX,
+ CONFIG_FILE_SYNTAX,
+ CONFIG_OPTION_SYNTAX,
+ CONFIG_OBJECT_SYNTAX,
COMMAND_SYNTAX
};
@@ -1131,11 +1205,15 @@ static struct strsyntaxtype {
const char *type;
enum syntaxtype stxtype;
} stxtype[] = {
- { "function", FUNCTION_SYNTAX },
- { "application", FUNCTION_SYNTAX },
- { "manager", MANAGER_SYNTAX },
- { "managerEvent", MANAGER_EVENT_SYNTAX },
- { "agi", COMMAND_SYNTAX }
+ { "function", FUNCTION_SYNTAX },
+ { "application", FUNCTION_SYNTAX },
+ { "manager", MANAGER_SYNTAX },
+ { "managerEvent", MANAGER_EVENT_SYNTAX },
+ { "configInfo", CONFIG_INFO_SYNTAX },
+ { "configFile", CONFIG_FILE_SYNTAX },
+ { "configOption", CONFIG_OPTION_SYNTAX },
+ { "configObject", CONFIG_OBJECT_SYNTAX },
+ { "agi", COMMAND_SYNTAX },
};
/*! \internal
@@ -1170,9 +1248,10 @@ static enum syntaxtype xmldoc_get_syntax_type(const char *type)
*
* \since 11
*/
-static char *_ast_xmldoc_build_syntax(struct ast_xml_node *node, const char *type, const char *name)
+static char *_ast_xmldoc_build_syntax(struct ast_xml_node *root_node, const char *type, const char *name)
{
char *syntax = NULL;
+ struct ast_xml_node *node = root_node;
for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
if (!strcasecmp(ast_xml_node_get_name(node), "syntax")) {
@@ -1180,10 +1259,6 @@ static char *_ast_xmldoc_build_syntax(struct ast_xml_node *node, const char *typ
}
}
- if (!node) {
- return syntax;
- }
-
switch (xmldoc_get_syntax_type(type)) {
case FUNCTION_SYNTAX:
syntax = xmldoc_get_syntax_fun(node, name, "parameter", 1, 1);
@@ -1197,6 +1272,12 @@ static char *_ast_xmldoc_build_syntax(struct ast_xml_node *node, const char *typ
case MANAGER_EVENT_SYNTAX:
syntax = xmldoc_get_syntax_manager(node, name, "Event");
break;
+ case CONFIG_OPTION_SYNTAX:
+ syntax = xmldoc_get_syntax_config_option(root_node, name);
+ break;
+ case CONFIG_OBJECT_SYNTAX:
+ syntax = xmldoc_get_syntax_config_object(node, name);
+ break;
default:
syntax = xmldoc_get_syntax_fun(node, name, "parameter", 1, 1);
}
@@ -2198,6 +2279,7 @@ static struct ast_xml_doc_item *xmldoc_build_documentation_item(struct ast_xml_n
if (!(item = ast_xml_doc_item_alloc(name, type))) {
return NULL;
}
+ item->node = node;
syntax = _ast_xmldoc_build_syntax(node, type, name);
seealso = _ast_xmldoc_build_seealso(node);
@@ -2230,6 +2312,105 @@ static struct ast_xml_doc_item *xmldoc_build_documentation_item(struct ast_xml_n
return item;
}
+struct ast_xml_xpath_results *__attribute__((format(printf, 1, 2))) ast_xmldoc_query(const char *fmt, ...)
+{
+ struct ast_xml_xpath_results *results = NULL;
+ struct documentation_tree *doctree;
+ RAII_VAR(struct ast_str *, xpath_str, ast_str_create(128), ast_free);
+ va_list ap;
+
+ if (!xpath_str) {
+ return NULL;
+ }
+
+ va_start(ap, fmt);
+ ast_str_set_va(&xpath_str, 0, fmt, ap);
+ va_end(ap);
+
+ AST_RWLIST_RDLOCK(&xmldoc_tree);
+ AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) {
+ if (!(results = ast_xml_query(doctree->doc, ast_str_buffer(xpath_str)))) {
+ continue;
+ }
+ break;
+ }
+ AST_RWLIST_UNLOCK(&xmldoc_tree);
+
+ return results;
+}
+
+static void build_config_docs(struct ast_xml_node *cur, struct ast_xml_doc_item **tail)
+{
+ struct ast_xml_node *iter;
+ struct ast_xml_doc_item *item;
+
+ for (iter = ast_xml_node_get_children(cur); iter; iter = ast_xml_node_get_next(iter)) {
+ if (strncasecmp(ast_xml_node_get_name(iter), "config", 6)) {
+ continue;
+ }
+ /* Now add all of the child config-related items to the list */
+ if (!(item = xmldoc_build_documentation_item(iter, ast_xml_get_attribute(iter, "name"), ast_xml_node_get_name(iter)))) {
+ ast_log(LOG_ERROR, "Could not build documentation for '%s:%s'\n", ast_xml_node_get_name(iter), ast_xml_get_attribute(iter, "name"));
+ break;
+ }
+ if (!strcasecmp(ast_xml_node_get_name(iter), "configOption")) {
+ ast_string_field_set(item, ref, ast_xml_get_attribute(cur, "name"));
+ }
+ (*tail)->next = item;
+ *tail = (*tail)->next;
+ build_config_docs(iter, tail);
+ }
+}
+
+int ast_xmldoc_regenerate_doc_item(struct ast_xml_doc_item *item)
+{
+ const char *name;
+ char *syntax;
+ char *seealso;
+ char *arguments;
+ char *synopsis;
+ char *description;
+
+ if (!item || !item->node) {
+ return -1;
+ }
+
+ name = ast_xml_get_attribute(item->node, "name");
+ if (!name) {
+ return -1;
+ }
+
+ syntax = _ast_xmldoc_build_syntax(item->node, item->type, name);
+ seealso = _ast_xmldoc_build_seealso(item->node);
+ arguments = _ast_xmldoc_build_arguments(item->node);
+ synopsis = _ast_xmldoc_build_synopsis(item->node);
+ description = _ast_xmldoc_build_description(item->node);
+
+ if (syntax) {
+ ast_str_set(&item->syntax, 0, "%s", syntax);
+ }
+ if (seealso) {
+ ast_str_set(&item->seealso, 0, "%s", seealso);
+ }
+ if (arguments) {
+ ast_str_set(&item->arguments, 0, "%s", arguments);
+ }
+ if (synopsis) {
+ ast_str_set(&item->synopsis, 0, "%s", synopsis);
+ }
+ if (description) {
+ ast_str_set(&item->description, 0, "%s", description);
+ }
+
+ ast_free(syntax);
+ ast_free(seealso);
+ ast_free(arguments);
+ ast_free(synopsis);
+ ast_free(description);
+ ast_xml_free_attr(name);
+ return 0;
+}
+
struct ao2_container *ast_xmldoc_build_documentation(const char *type)
{
struct ao2_container *docs;
@@ -2282,6 +2463,19 @@ struct ao2_container *ast_xmldoc_build_documentation(const char *type)
}
item = root;
break;
+ case CONFIG_INFO_SYNTAX:
+ {
+ struct ast_xml_doc_item *tail;
+ if (item || !ast_xml_node_get_children(node) || strcasecmp(ast_xml_node_get_name(node), "configInfo")) {
+ break;
+ }
+ if (!(item = xmldoc_build_documentation_item(node, ast_xml_get_attribute(node, "name"), "configInfo"))) {
+ break;
+ }
+ tail = item;
+ build_config_docs(node, &tail);
+ break;
+ }
default:
item = xmldoc_build_documentation_item(node, name, type);
}
@@ -2299,6 +2493,9 @@ struct ao2_container *ast_xmldoc_build_documentation(const char *type)
return docs;
}
+int ast_xmldoc_regenerate_doc_item(struct ast_xml_doc_item *item);
+
+
#if !defined(HAVE_GLOB_NOMAGIC) || !defined(HAVE_GLOB_BRACE) || defined(DEBUG_NONGNU)
static int xml_pathmatch(char *xmlpattern, int xmlpattern_maxlen, glob_t *globbuf)
{
@@ -2342,11 +2539,47 @@ static int xml_pathmatch(char *xmlpattern, int xmlpattern_maxlen, glob_t *globbu
}
#endif
+static char *handle_dump_docs(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct documentation_tree *doctree;
+ FILE *f;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "xmldoc dump";
+ e->usage =
+ "Usage: xmldoc dump <filename>\n"
+ " Dump XML documentation to a file\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+ if (!(f = fopen(a->argv[2], "w"))) {
+ ast_log(LOG_ERROR, "Could not open file '%s': %s\n", a->argv[2], strerror(errno));
+ return CLI_FAILURE;
+ }
+ AST_RWLIST_RDLOCK(&xmldoc_tree);
+ AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) {
+ ast_xml_doc_dump_file(f, doctree->doc);
+ }
+ AST_RWLIST_UNLOCK(&xmldoc_tree);
+ fclose(f);
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_dump_xmldocs = AST_CLI_DEFINE(handle_dump_docs, "Dump the XML docs to the specified file");
+
/*! \brief Close and unload XML documentation. */
static void xmldoc_unload_documentation(void)
{
struct documentation_tree *doctree;
+ ast_cli_unregister(&cli_dump_xmldocs);
+
AST_RWLIST_WRLOCK(&xmldoc_tree);
while ((doctree = AST_RWLIST_REMOVE_HEAD(&xmldoc_tree, entry))) {
ast_free(doctree->filename);
@@ -2390,6 +2623,7 @@ int ast_xmldoc_load_documentation(void)
/* initialize the XML library. */
ast_xml_init();
+ ast_cli_register(&cli_dump_xmldocs);
/* register function to be run when asterisk finish. */
ast_register_atexit(xmldoc_unload_documentation);
diff --git a/res/res_xmpp.c b/res/res_xmpp.c
index 3ba849e56..966432dc0 100644
--- a/res/res_xmpp.c
+++ b/res/res_xmpp.c
@@ -288,6 +288,126 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
account defined in <literal>xmpp.conf</literal> to send the message from.
Note that this field is required for XMPP messages.</para>
</info>
+ <configInfo name="res_xmpp" language="en_US">
+ <synopsis>XMPP Messaging</synopsis>
+ <configFile name="xmpp.conf">
+ <configObject name="global">
+ <synopsis>Global configuration settings</synopsis>
+ <configOption name="debug">
+ <synopsis>Enable/disable XMPP message debugging</synopsis>
+ </configOption>
+ <configOption name="autoprune">
+ <synopsis>Auto-remove users from buddy list.</synopsis>
+ <description><para>Auto-remove users from buddy list. Depending on the setup
+ (e.g., using your personal Gtalk account for a test) this could cause loss of
+ the contact list.
+ </para></description>
+ </configOption>
+ <configOption name="autoregister">
+ <synopsis>Auto-register users from buddy list</synopsis>
+ </configOption>
+ <configOption name="collection_nodes">
+ <synopsis>Enable support for XEP-0248 for use with distributed device state</synopsis>
+ </configOption>
+ <configOption name="pubsub_autocreate">
+ <synopsis>Whether or not the PubSub server supports/is using auto-create for nodes</synopsis>
+ </configOption>
+ <configOption name="auth_policy">
+ <synopsis>Whether to automatically accept or deny users' subscription requests</synopsis>
+ </configOption>
+ </configObject>
+ <configObject name="client">
+ <synopsis>Configuration options for an XMPP client</synopsis>
+ <configOption name="username">
+ <synopsis>XMPP username with optional resource</synopsis>
+ </configOption>
+ <configOption name="secret">
+ <synopsis>XMPP password</synopsis>
+ </configOption>
+ <configOption name="serverhost">
+ <synopsis>Route to server, e.g. talk.google.com</synopsis>
+ </configOption>
+ <configOption name="statusmessage">
+ <synopsis>Custom status message</synopsis>
+ </configOption>
+ <configOption name="pubsub_node">
+ <synopsis>Node for publishing events via PubSub</synopsis>
+ </configOption>
+ <configOption name="context">
+ <synopsis>Dialplan context to send incoming messages to</synopsis>
+ </configOption>
+ <configOption name="priority">
+ <synopsis>XMPP resource priority</synopsis>
+ </configOption>
+ <configOption name="port">
+ <synopsis>XMPP server port</synopsis>
+ </configOption>
+ <configOption name="timeout">
+ <synopsis>Timeout in seconds to hold incoming messages</synopsis>
+ <description><para>Timeout (in seconds) on the message stack. Messages stored longer
+ than this value will be deleted by Asterisk. This option applies to incoming messages only
+ which are intended to be processed by the <literal>JABBER_RECEIVE</literal> dialplan function.
+ </para></description>
+ </configOption>
+ <configOption name="debug">
+ <synopsis>Enable debugging</synopsis>
+ </configOption>
+ <configOption name="type">
+ <synopsis>Connection is either a client or a component</synopsis>
+ </configOption>
+ <configOption name="distribute_events">
+ <synopsis>Whether or not to distribute events using this connection</synopsis>
+ </configOption>
+ <configOption name="usetls">
+ <synopsis>Whether to use TLS for the connection or not</synopsis>
+ </configOption>
+ <configOption name="usesasl">
+ <synopsis>Whether to use SASL for the connection or not</synopsis>
+ </configOption>
+ <configOption name="forceoldssl">
+ <synopsis>Force the use of old-style SSL for the connection</synopsis>
+ </configOption>
+ <configOption name="keepalive">
+ <synopsis>If enabled, periodically send an XMPP message from this client with an empty message</synopsis>
+ </configOption>
+ <configOption name="autoprune">
+ <synopsis>Auto-remove users from buddy list.</synopsis>
+ <description><para>Auto-remove users from buddy list. Depending on the setup
+ (e.g., using your personal Gtalk account for a test) this could cause loss of
+ the contact list.
+ </para></description>
+ </configOption>
+ <configOption name="autoregister">
+ <synopsis>Auto-register users bfrom buddy list</synopsis>
+ </configOption>
+ <configOption name="auth_policy">
+ <synopsis>Whether to automatically accept or deny users' subscription requests</synopsis>
+ </configOption>
+ <configOption name="sendtodialplan">
+ <synopsis>Send incoming messages into the dialplan</synopsis>
+ </configOption>
+ <configOption name="status">
+ <synopsis>Default XMPP status for the client</synopsis>
+ <description><para>Can be one of the following XMPP statuses:</para>
+ <enumlist>
+ <enum name="chat"/>
+ <enum name="available"/>
+ <enum name="away"/>
+ <enum name="xaway"/>
+ <enum name="dnd"/>
+ </enumlist>
+ </description>
+ </configOption>
+ <configOption name="buddy">
+ <synopsis>Manual addition of buddy to list</synopsis>
+ <description><para>
+ Manual addition of buddy to the buddy list. For distributed events, these budies are
+ automatically added in the whitelist as 'owners' of the node(s).
+ </para></description>
+ </configOption>
+ </configObject>
+ </configFile>
+ </configInfo>
***/
/*! \brief Supported general configuration flags */
@@ -658,6 +778,7 @@ static void xmpp_config_post_apply(void)
static struct aco_type global_option = {
.type = ACO_GLOBAL,
+ .name = "global",
.item_offset = offsetof(struct xmpp_config, global),
.category_match = ACO_WHITELIST,
.category = "^general$",
@@ -667,6 +788,7 @@ struct aco_type *global_options[] = ACO_TYPES(&global_option);
static struct aco_type client_option = {
.type = ACO_ITEM,
+ .name = "client",
.category_match = ACO_BLACKLIST,
.category = "^(general)$",
.item_alloc = ast_xmpp_client_config_alloc,