#!/bin/bash # **** License **** # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # A copy of the GNU General Public License is available as # `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution # or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'. # You can also obtain it by writing to the Free Software Foundation, # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, # MA 02110-1301, USA. # # Author: An-Cheng Huang # Description: bash completion for Vyatta configuration commands # # **** End License **** # Turn on history logging export HISTCONTROL= export HISTFILESIZE=10000 export HISTSIZE=10000 export HISTTIMEFORMAT='%FT%T%z ' export PROMPT_COMMAND='history -a' builtin set histappend=1 # only do this if we are going into configure mode if [ "$_OFR_CONFIGURE" != "ok" ]; then return 0 fi umask 0002 if [ -r /etc/default/vyatta ]; then source /etc/default/vyatta fi declare is_set=0 declare last_idx=0 declare -a comp_words=() # commands to unalias declare -a unalias_cmds=( clear configure date debug edit exit load merge \ no run set show save terminal undebug up top ) for cmd in "${unalias_cmds[@]}"; do unalias $cmd >& /dev/null done show () { local show_all='' local -a args=() for arg in "$@"; do if [ "$arg" == "-all" ]; then show_all='-all' else args[${#args[@]}]="$arg" fi done ${vyatta_sbindir}/vyatta-output-config.pl ${show_all} \ ${VYATTA_EDIT_LEVEL//\// } ${args[@]} \ | eval "${VYATTA_PAGER:-cat}" } save () { eval "sudo sg vyattacfg \"umask 0002 ; ${vyatta_sbindir}/vyatta-save-config.pl $@\"" } discard () { local changes if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then changes=1 else changes=0 fi sudo umount $VYATTA_TEMP_CONFIG_DIR sudo rm -fr $VYATTA_CHANGES_ONLY_DIR $VYATTA_TEMP_CONFIG_DIR make_vyatta_config_dir $VYATTA_CHANGES_ONLY_DIR make_vyatta_config_dir $VYATTA_TEMP_CONFIG_DIR sudo mount -t $UNIONFS \ -o dirs=${VYATTA_CHANGES_ONLY_DIR}=rw:${VYATTA_ACTIVE_CONFIGURATION_DIR}=ro \ $UNIONFS ${VYATTA_TEMP_CONFIG_DIR} if (( changes )); then echo "Changes have been discarded" else echo "No changes have been discarded" fi } reboot () { echo "Exit from configure mode before rebooting." } shutdown () { echo "Exit from configure mode before shutting down system." } declare vyatta_cfg_prompt_level='' set_config_ps1 () { local level=$1 vyatta_unescape level level if [ -z "$level" ]; then export PS1="[edit]\n\u@\h# " vyatta_cfg_prompt_level='' else export PS1="[edit $level]\n\u@\h# " vyatta_cfg_prompt_level="$level" fi } load () { # don't load if there are uncommitted changes. if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then echo "Cannot load: configuration modified." echo "Commit or discard the changes before loading a config file." return 1 fi # return to top level. export VYATTA_EDIT_LEVEL="/" export VYATTA_TEMPLATE_LEVEL="/" set_config_ps1 '' eval "${vyatta_sbindir}/vyatta-load-config.pl $@" } merge () { # don't load if there are uncommitted changes. if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then echo "Cannot load: configuration modified." echo "Commit or discard the changes before loading a config file." return 1 fi # return to top level. export VYATTA_EDIT_LEVEL="/" export VYATTA_TEMPLATE_LEVEL="/" set_config_ps1 '' eval "${vyatta_sbindir}/vyatta-load-config.pl $@ --merge" } top () { if [ "$VYATTA_EDIT_LEVEL" == "/" ]; then echo "Already at the top level" return 0 fi # go to the top level. export VYATTA_EDIT_LEVEL="/" export VYATTA_TEMPLATE_LEVEL="/" set_config_ps1 '' } rename() { mvcp rename Rename mv "$@" } copy() { mvcp copy Copy "cp -a" "$@" } mvcp () { local str=$1 shift local Str=$1 shift local cmd=$1 shift local _otag=$1 local _ovalu=$2 local _to=$3 local _ntag=$4 local _nvalu=$5 local _oval='' local _nval='' local _mpath=${VYATTA_TEMP_CONFIG_DIR}/${VYATTA_EDIT_LEVEL} local _tpath=${VYATTA_CONFIG_TEMPLATE}/${VYATTA_TEMPLATE_LEVEL} vyatta_escape _ovalu _oval vyatta_escape _nvalu _nval if [ "$_to" != 'to' ] || [ -z "$_ntag" ] || [ -z "$_nval" ]; then echo "Invalid $str command" return 1 fi if [ "$_otag" != "$_ntag" ]; then echo "Cannot $str from \"$_otag\" to \"$_ntag\"" return 1 fi if [ ! -d "$_tpath/$_otag/$VYATTA_TAG_NAME" ]; then echo "Cannot $str under \"$_otag\"" return 1 fi if [ ! -d "$_mpath/$_otag/$_oval" ]; then echo "Configuration \"$_otag $_ovalu\" does not exist" return 1 fi if [ -d "$_mpath/$_ntag/$_nval" ]; then echo "Configuration \"$_ntag $_nvalu\" already exists" return 1 fi if ! /opt/vyatta/sbin/my_set $_ntag "$_nvalu"; then echo "$Str failed" return 1 fi /opt/vyatta/sbin/my_delete $_ntag "$_nvalu" >&/dev/null 3>&1 $cmd "$_mpath/$_otag/$_oval" "$_mpath/$_ntag/$_nval" return 0 } edit () { local num_comp=${#@} local _mpath=${VYATTA_TEMP_CONFIG_DIR}/${VYATTA_EDIT_LEVEL} local _tpath=${VYATTA_CONFIG_TEMPLATE}/${VYATTA_TEMPLATE_LEVEL} local idx if ! /opt/vyatta/sbin/my_set $* >&/dev/null 3>&1; then echo "Invalid node \"$*\" for the 'edit' command" return 1 fi for (( idx=1; idx <= num_comp; idx++ )); do local comp eval "comp=\$$idx" vyatta_escape comp comp push_path _mpath $comp push_path _tpath $comp if [ ! -d $_mpath ]; then # "edit" only allows existing node break fi # check if it's not tag value if [ -d $_tpath ]; then continue fi # check if it's tag value pop_path _tpath push_path _tpath $VYATTA_TAG_NAME if [ -d $_tpath ]; then continue fi pop_path _tpath pop_path _mpath break done # "edit" only valid for # * "node.tag" level # * "node.def" level without "type:" if (( idx != ( num_comp + 1) )); then echo "Invalid node \"$*\" for the 'edit' command" return 1 fi if [ "${_tpath:((-9))}" != "/node.tag" ]; then # we are not at "node.tag" level. look for "type:". if [ ! -r "$_tpath/node.def" ]; then vyatta_cfg_type="" else vyatta_parse_tmpl "$_tpath/node.def" fi if [ -n "$vyatta_cfg_type" ]; then # "type:" present echo "The 'edit' command cannot be issued at the \"$*\" level" return 1 fi fi export VYATTA_EDIT_LEVEL="${_mpath#$VYATTA_TEMP_CONFIG_DIR}/" export VYATTA_TEMPLATE_LEVEL="${_tpath#$VYATTA_CONFIG_TEMPLATE}/" declare -a path_arr path_str2arr VYATTA_EDIT_LEVEL path_arr local path_str="${path_arr[*]}" set_config_ps1 "$path_str" } up () { if [ "$VYATTA_EDIT_LEVEL" == "/" ]; then echo "Already at the top level" return 0 fi if [[ $VYATTA_TEMPLATE_LEVEL == */node.tag/ ]]; then export VYATTA_EDIT_LEVEL="${VYATTA_EDIT_LEVEL%/*/*/}/" export VYATTA_TEMPLATE_LEVEL="${VYATTA_TEMPLATE_LEVEL%/*/*/}/" else export VYATTA_EDIT_LEVEL="${VYATTA_EDIT_LEVEL%/*/}/" export VYATTA_TEMPLATE_LEVEL="${VYATTA_TEMPLATE_LEVEL%/*/}/" fi declare -a path_arr path_str2arr VYATTA_EDIT_LEVEL path_arr local path_str="${path_arr[*]}" set_config_ps1 "$path_str" } really_exit() { sudo umount $VYATTA_TEMP_CONFIG_DIR sudo rm -rf $VYATTA_TEMP_CONFIG_DIR $VYATTA_CHANGES_ONLY_DIR \ $VYATTA_CONFIG_TMP unset _OFR_CONFIGURE builtin exit 0 } exit () { local discard if [ $# == 0 ]; then discard=0 elif [ $# == 1 ] && [ "$1" == "discard" ]; then discard=1 else echo "Invalid argument \"$*\" for 'exit'" return 1 fi if [ "$VYATTA_EDIT_LEVEL" == "/" ]; then # we are at the root level. check if we can really exit. if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then if (( ! discard )); then echo "Cannot exit: configuration modified." echo "Use 'exit discard' to discard the changes and exit." return 1 fi fi really_exit fi # "exit" to the root level. export VYATTA_EDIT_LEVEL="/" export VYATTA_TEMPLATE_LEVEL="/" set_config_ps1 '' } # run op mode commands run () { if [ $# == 0 ]; then echo Incomplete command return 1 fi _vyatta_op_run $@ } # do op mode completion vyatta_run_complete () { local restore_shopts=$( shopt -p extglob nullglob | tr \\n \; ) shopt -s extglob nullglob COMP_WORDS=( "${COMP_WORDS[@]:1}" ) (( COMP_CWORD -= 1 )) _vyatta_op_expand eval $restore_shopts } vyatta_loadsave_complete() { # Generate completion help for the "load" and "save" commands local restore_shopts=$( shopt -p extglob nullglob | tr \\n \; ) shopt -s extglob nullglob # Only provide completions after command name has been typed, but # before any characters of the command argument have been entered. # File name completion, and completion of the various URL formats # is not supported yet. # if [ $COMP_CWORD -eq 1 -a -z "${COMP_WORDS[1]}" ]; then echo echo "Possible completions:" if [ "${COMP_WORDS[0]}" = "load" ]; then echo -e " \t\t\t\tLoad from system config file" echo -e " \t\t\t\tLoad from file on local machine" echo -e " scp://:@/\tLoad from file on remote machine" echo -e " ftp://:@/\tLoad from file on remote machine" echo -e " http:///\t\t\tLoad from file on remote machine" echo -e " tftp:///\t\t\tLoad from file on remote machine" elif [ "${COMP_WORDS[0]}" = "merge" ]; then echo -e " \t\t\t\tMerge from system config file" echo -e " \t\t\t\tMerge from file on local machine" echo -e " scp://:@/\tMerge from file on remote machine" echo -e " ftp://:@/\tMerge from file on remote machine" echo -e " http:///\t\t\tMerge from file on remote machine" echo -e " tftp:///\t\t\tMerge from file on remote machine" elif [ "${COMP_WORDS[0]}" = "save" ]; then echo -e " \t\t\t\tSave to system config file" echo -e " \t\t\t\tSave to file on local machine" echo -e " scp://:@/\tSave to file on remote machine" echo -e " ftp://:@/\tSave to file on remote machine" echo -e " tftp:///\t\t\tSave to file on remote machine" fi COMPREPLY=( "" " " ) else COMPREPLY=() fi eval $restore_shopts } loadkey() { # don't load if there are uncommitted changes. if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then echo "Cannot load: configuration modified." echo "Commit or discard the changes before loading a config file." return 1 fi # return to top level. export VYATTA_EDIT_LEVEL="/" export VYATTA_TEMPLATE_LEVEL="/" set_config_ps1 '' eval "${vyatta_sbindir}/vyatta-load-user-key.pl $@" } comment() { if [ "$#" -eq "0" ]; then return 0 fi args=("$@") #now need to replace last element args[${#args[*]}-1]="\"${args[${#args[*]}-1]i}\""; eval "${vyatta_sbindir}/vyatta-comment-config.pl ${args[*]}" } activate() { #create or remove activate file eval "${vyatta_sbindir}/vyatta-activate-config.pl activate $@" } deactivate() { #create or remove activate file eval "${vyatta_sbindir}/vyatta-activate-config.pl deactivate $@" } vyatta_loadkey_complete() { case "$COMP_CWORD" in 1) if [ -z "${COMP_WORDS[1]}" ]; then COMPREPLY=( $(getent passwd | awk -F: '$7 == "/bin/vbash" { print $1}') ) else COMPREPLY=( $(compgen -u -- ${COMP_WORDS[1]} ) ) fi ;; 2) if [ -z "${COMP_WORDS[2]}" ]; then echo echo "Possible completions:" echo -e " \t\t\t\tLoad from file on local machine" echo -e " scp://@/\tLoad from file on remote machine" echo -e " ftp://@/\tLoad from file on remote machine" echo -e " http:///\t\t\tLoad from file on remote machine" echo -e " tftp:///\t\t\tLoad from file on remote machine" COMPREPLY=() else COMPREPLY=( $(compgen -f -- ${COMP_WORDS[2]} ) ) fi ;; esac } declare v_cfg_completion_debug=0 decho () { if (( v_cfg_completion_debug )); then echo -n "$*" fi } push_path_arr () { # $1: \@path_arr # $2: component eval "$1=( \"\${$1[@]}\" '$2' )" } pop_path_arr () { # $1: \@path_arr eval "$1=( \"\${$1[@]:0:((\${#$1[@]}-1))}\" )" } path_arr2str () { # $1: \@path_arr # $2: \$path_str eval "$2=\"\${$1[*]}\"" eval "$2=/\${$2// //}" } path_str2arr () { # $1: \$path_str # $2: \@path_arr local tmp eval "tmp=\${$1:1}" eval "$2=( \${tmp//\// } )" } push_path () { # $1: \$path_str # $2: component declare -a path_arr eval "path_str2arr $1 path_arr" eval "push_path_arr path_arr '$2'" eval "path_arr2str path_arr $1" } pop_path () { # $1: \$path_str declare -a path_arr eval "path_str2arr $1 path_arr" pop_path_arr path_arr eval "path_arr2str path_arr $1" } get_filtered_dir_listing () { # $1: path # $2: \@listing if [ ! -d $1 ]; then eval "$2=()" return fi local pattern='^node\.def$|^node\.tag$|^node\.val$|^\.modified$' patterh=$pattern'|^\.commit\.lck$|^\.wh\.' local cmd="ls $1 |egrep -v '$pattern'" declare -a listing=( $(eval $cmd) ) for enode in "${listing[@]}"; do local unode vyatta_unescape enode unode eval "$2[\${#$2[@]}]=$unode" done } filter_existing_nodes () { # $1: mpath # $2: \@orig # $3: \@filtered declare -a orig eval "orig=( \"\${$2[@]}\" )" for node in "${orig[@]}"; do if [ -d "$1/$node" ]; then eval "$3[\${#$3[@]}]=$node" fi done } get_prefix_filtered_list () { # $1: prefix # $2: \@list # $3: \@filtered declare -a olist local pfx=$1 pfx=${pfx#\"} eval "olist=( \"\${$2[@]}\" )" local idx=0 for elem in "${olist[@]}"; do local sub=${elem#$pfx} if [ "$elem" == "$sub" ] && [ -n "$pfx" ]; then continue fi eval "$3[$idx]=\"$elem\"" (( idx++ )) done } get_prefix_filtered_list2 () { # $1: prefix # $2: \@list # $3: \@filtered # $4: \@list2 # $5: \@filtered2 declare -a olist local pfx=$1 pfx=${pfx#\"} eval "olist=( \"\${$2[@]}\" )" eval "local orig_len=\${#$2[@]}" local orig_idx=0 local idx=0 for (( orig_idx = 0; orig_idx < orig_len; orig_idx++ )); do eval "local elem=\${$2[$orig_idx]}" eval "local elem2=\${$4[$orig_idx]}" local sub=${elem#$pfx} if [ "$elem" == "$sub" ] && [ -n "$pfx" ]; then continue fi eval "$3[$idx]=\"$elem\"" eval "$5[$idx]=\"$elem2\"" (( idx++ )) done } vyatta_parse_tmpl_comp_fields () { # $1: tmpl # $2: field name sed -n ' /^'"$2"':/,$ { s/^'"$2"':[ ]*// h :b $ { x; p; q } n /^\([-_a-z]\+:\|#\)/ { x; p; q } H bb } ' $1 } declare vyatta_cfg_help="" declare vyatta_cfg_type="" declare vyatta_cfg_tag=0 declare vyatta_cfg_multi=0 declare -a vyatta_cfg_allowed=() declare vyatta_cfg_comp_help="" vyatta_parse_tmpl () { # $1: tmpl vyatta_cfg_help="" vyatta_cfg_type="" vyatta_cfg_tag=0 vyatta_cfg_multi=0 vyatta_cfg_allowed=() vyatta_cfg_comp_help='' if [ ! -r $1 ]; then return fi eval `sed -n ' /^syntax:expression:[ ]\+\$VAR(@)[ ]\+in[ ]\+/ { s/^syntax:expression:[ ]\+\$VAR(@)[ ]\+in[ ]\+/vyatta_cfg_allowed=( / s/^\([^;]\+\);.*$/\1 )/ s/[ ]*,[ ]*/ /gp } s/^tag:.*/vyatta_cfg_tag=1/p s/^multi:.*/vyatta_cfg_multi=1/p s/^type:[ ]\+\([^ ;]\+\)\(;.*\)\?/vyatta_cfg_type=\1/p ' $1` vyatta_cfg_help=$(vyatta_parse_tmpl_comp_fields $1 "help") local acmd=$(vyatta_parse_tmpl_comp_fields $1 "allowed") vyatta_cfg_comp_help=$(vyatta_parse_tmpl_comp_fields $1 "comp_help") if (( ${#vyatta_cfg_allowed[@]} == 0 )); then astr=$(eval "$acmd") astr=${astr///\\>} eval "ares=( $astr )" for (( i=0 ; i<${#ares[@]} ; i++ )); do if [[ "${ares[i]}" != \<*\> ]]; then vyatta_cfg_allowed+=( "${ares[i]}" ) else vyatta_cfg_allowed+=( "" ) fi done fi if [ -z "$vyatta_cfg_help" ]; then vyatta_cfg_help='' fi } # this fills in $vyatta_help_text generate_help_text () { # $1: \@items # $2: \@help_strs declare -a items declare -a helps eval "items=( \"\${$1[@]}\" )" eval "helps=( \"\${$2[@]}\" )" if [ -n "$vyatta_cfg_comp_help" ]; then vyatta_help_text="\\n${vyatta_cfg_comp_help}" return 0 fi vyatta_help_text="\\nPossible completions:" for (( idx = 0; idx < ${#items[@]}; idx++ )); do vyatta_help_text="${vyatta_help_text}\\n\\x20\\x20" if [ ${#items[$idx]} -lt 6 ]; then vyatta_help_text="${vyatta_help_text}${items[$idx]}\\t\\t" elif [ ${#items[$idx]} -lt 14 ]; then vyatta_help_text="${vyatta_help_text}${items[$idx]}\\t" else vyatta_help_text="${vyatta_help_text}${items[$idx]}\\n\\x20\\x20\\t\\t" fi vyatta_help_text="${vyatta_help_text}${helps[$idx]}" done } # this fills in $vyatta_help_text get_tmpl_subdir_help () { # $1: path # $2: \@subdirs declare -a subdirs eval "subdirs=( \"\${$2[@]}\" )" if [ ${#subdirs[@]} == 0 ]; then vyatta_help_text="" return fi declare -a hitems=() declare -a hstrs=() for subdir in "${subdirs[@]}"; do if [ ! -r $1/$subdir/node.def ]; then vyatta_cfg_help="" else vyatta_parse_tmpl "$1/$subdir/node.def" # comp_help overrides the current help, so we reset it here since # it is from the subdir. vyatta_cfg_comp_help='' fi hitems[${#hitems[@]}]=$subdir hstrs[${#hstrs[@]}]=$vyatta_cfg_help done generate_help_text hitems hstrs } # return 0 if yes. 1 if no. item_in_list () { # $1: item # $2: \@list declare -a olist local item eval "olist=( \"\${$2[@]}\" )" for item in "${olist[@]}"; do if [ "$1" == "$item" ]; then return 0 fi done return 1 } append_allowed_values () { # $1: tmpl_path # $2: \@values if [ ! -r "$1/node.def" ]; then return fi vyatta_parse_tmpl "$1/node.def" local item for item in "${vyatta_cfg_allowed[@]}"; do if ! item_in_list "$item" $2; then eval "$2[\${#$2[@]}]=\"$item\"" fi done } # return 0 if yes. 1 if no. is_setting_new_leaf () { # $1: tmpl_path if [ $is_set == 0 ]; then return 1 fi vyatta_parse_tmpl "$1/node.def" if [ -z "$vyatta_cfg_type" ]; then return 1 fi return 0 } # this fills in $vyatta_help_text get_node_value_help () { # $1: path # $2: \@values declare -a vals eval "vals=( \"\${$2[@]}\" )" if [ $is_set == 0 -a ${#vals[@]} == 0 ]; then vyatta_help_text="" return fi if [ ! -r "$1/node.def" ]; then vyatta_cfg_help="" vyatta_cfg_type="" else vyatta_parse_tmpl "$1/node.def" fi if [ $is_set == 1 -a ! -z "$vyatta_cfg_type" ]; then # add a value local val="<$vyatta_cfg_type>" vals=( $val "${vals[@]}" ) fi if [ ${#vals[@]} == 0 ]; then vyatta_help_text="" return fi declare -a hitems=() declare -a hstrs=() for val in "${vals[@]}"; do hitems[${#hitems[@]}]=$val hstrs[${#hstrs[@]}]=$vyatta_cfg_help done generate_help_text hitems hstrs } get_value_list () { # $1: path # $2: \@listing local vfile=$1/node.val if [ ! -r $vfile ]; then eval "$2=()" return fi declare -a listing=() local cmd=$(sed 's/^\(.*\)$/listing[\${#listing[@]}]='\''\1'\''/' $vfile) eval "$cmd" eval "$2=( \"\${listing[@]}\" )" } vyatta_escape () { # $1: \$original # $2: \$escaped eval "$2=\${$1//\%/%25}" eval "$2=\${$2//\*/%2A}" eval "$2=\${$2//\//%2F}" } vyatta_unescape () { # $1: \$escaped # $2: \$original eval "$2=\${$1//\%2F/\/}" eval "$2=\${$2//\%2A/*}" eval "$2=\${$2//\%25/%}" } declare -a vyatta_completions declare vyatta_help_text="\\nNo help text available" declare vyatta_do_help=0 vyatta_do_complete () { # when this function is called, it is expected that: # * "vyatta_help_text" is filled with the help text. # * "vyatta_completions" is an array of "filtered" possible completions # (i.e., only those starting with the current last component). local do_help=$vyatta_do_help # we may not want to do the following <<'ENDCOMMENT' if [ ${#vyatta_completions[@]} == 1 ]; then # no ambiguous completions. do completion instead of help. do_help=0 fi # now check if we can auto-complete at least 1 more character. if (( do_help )); then local schar="" for comp in "${vyatta_completions[@]}"; do local sub=$comp if [ ! -z "${COMP_WORDS[COMP_CWORD]}" ]; then sub=${comp#${comp_words[$last_idx]}} if [ "$comp" == "$sub" ]; then # should not happen since vyatta_completions should be filtered. continue fi fi if [ -z "$schar" ]; then schar=${sub:0:1} else if [ "$schar" != "${sub:0:1}" ]; then schar="" break fi fi done if [ ! -z "$schar" ]; then do_help=0 fi fi ENDCOMMENT if (( do_help )); then printf "$vyatta_help_text" COMPREPLY=( "" " " ) else local -a f_comps=() local cword= if (( ${#COMP_WORDS[@]} > 0 )); then cword=${COMP_WORDS[COMP_CWORD]} fi get_prefix_filtered_list "$cword" vyatta_completions f_comps local estr="COMPREPLY=( " for w in "${f_comps[@]}"; do estr="$estr\"$w\" " done estr="${estr})" eval "$estr" fi vyatta_help_text="\\nNo help text available" } generate_pipe_help () { local -a hcomps=( "${_vyatta_pipe_completions[@]}" \ "${_vyatta_pipe_noncompletions[@]}" ) local -a hstrs=() for comp in "${hcomps[@]}"; do hstrs+=("$(_vyatta_pipe_help "$comp")") done generate_help_text hcomps hstrs } vyatta_config_complete () { local restore_shopts=$( shopt -p extglob nullglob | tr \\n \; ) shopt -s extglob nullglob if [ "$COMP_LINE" == "$VYATTA_COMP_LINE" ]; then VYATTA_COMP_LINE='' vyatta_do_help=1 else VYATTA_COMP_LINE=$COMP_LINE vyatta_do_help=0 fi # handle pipe if _vyatta_pipe_completion "${COMP_WORDS[@]}"; then generate_pipe_help vyatta_completions=( "${_vyatta_pipe_completions[@]}" ) vyatta_do_complete eval $restore_shopts return fi if (( ${#COMP_WORDS[@]} < 2 )); then declare -a hitems=( "activate" \ "comment" \ "commit" \ "copy" \ "deactivate" \ "delete" \ "discard" \ "edit" \ "exit" \ "load" \ "loadkey" \ "merge" \ "rename" \ "run" \ "save" \ "set" \ "show" ) declare -a hstrs=( \ "Enable this portion of the configuration" \ "Add comment to this configuration element" \ "Commit the current set of changes" \ "Copy a configuration element" \ "Inactivate this portion of the configuration" \ "Delete a configuration element" \ "Discard uncommitted changes" \ "Edit a sub-element" \ "Exit from this configuration level" \ "Load configuration from a file and replace running configuration" \ "Load configuration from a file and merge running configuration" \ "Rename a configuration element" \ "Run an operational-mode command" \ "Save configuration to a file" \ "Set the value of a parameter or create a new element" \ "Show the configuration (default values may be suppressed)" \ ) if (( ${#COMP_WORDS[@]} == 1 )); then declare -a fitems=() declare -a fstrs=() get_prefix_filtered_list2 "${COMP_WORDS[0]}" hitems fitems hstrs fstrs hitems=( "${fitems[@]}" ) hstrs=( "${fstrs[@]}" ) fi generate_help_text hitems hstrs vyatta_completions=( "${hitems[@]}" ) vyatta_do_complete eval $restore_shopts return fi local command=${COMP_WORDS[0]} # completion for "set"/"edit" is different from other commands is_set=0 if [ "$command" == "set" -o "$command" == "edit" ]; then is_set=1 fi local end_space=0 local num_comp=$COMP_CWORD if [ -z "${COMP_WORDS[$COMP_CWORD]}" ]; then end_space=1 (( num_comp -= 1 )) fi (( last_idx = num_comp - 1 )) comp_words=( ${COMP_WORDS[@]:1:$num_comp} ) # handle "exit" if [ "$command" == "exit" ]; then if (( num_comp > 1 || ( end_space && num_comp > 0 ) )); then COMPREPLY=() eval $restore_shopts return fi declare -a hitems=( "discard" ) declare -a hstrs=( "Discard any changes" ) generate_help_text hitems hstrs vyatta_completions=( "discard" ) vyatta_do_complete eval $restore_shopts return fi local start_idx=0 # handle "copy" and "rename" if [ "$command" == "copy" -o "$command" == "rename" ]; then # Syntax of copy and rename commands are: # # copy/rename to # # where and are configuration parameters # in the tree at the current edit level. # # If parsing index 1 or 2 (i.e. or ), # fall through this test to the parameter parsing code below. # if (( ( end_space && num_comp == 2 ) || ( !end_space && num_comp == 3 ) )); then # If parsing index 3, there's only one option. declare -a hitems=( "to" ) declare -a hstrs=( "Set destination" ) generate_help_text hitems hstrs vyatta_completions=( "to" ) vyatta_do_complete eval $restore_shopts return elif (( ( num_comp > 2 ) && ( ( num_comp < 5 ) || ( !end_space && num_comp == 5 ) ) )); then # If parsing index 4 or 5, we set start_idx so that # the parameter parsing code will start parsing at # . Parsing these parameters should follow # the same rules as the "set" command. start_idx=3 is_set=1 elif (( ( end_space && num_comp == 5 ) || ( num_comp > 5 ) )); then # If parsing after index 5, there are no more valid parameters COMPREPLY=() eval $restore_shopts return fi fi local _mpath=${VYATTA_TEMP_CONFIG_DIR}/${VYATTA_EDIT_LEVEL} local _tpath=${VYATTA_CONFIG_TEMPLATE}/${VYATTA_TEMPLATE_LEVEL} local last_tag=0 local idx=$start_idx for (( idx=$start_idx; idx < num_comp; idx++ )); do last_tag=0 local comp=${comp_words[$idx]} vyatta_escape comp comp push_path _mpath $comp push_path _tpath $comp if [ -d $_tpath ]; then if (( ! is_set )); then # we are not in "set" => only allow existing node if [ ! -d $_mpath ]; then break fi fi continue fi pop_path _tpath push_path _tpath $VYATTA_TAG_NAME if [ -d $_tpath ]; then if (( ! is_set && end_space )); then # we are not in "set" && last component is complete. # => only allow existing tag value. if [ ! -d $_mpath ]; then break fi fi if (( idx != last_idx )); then # TODO validate value # break if not valid # XXX is this validation necessary? (set will validate anyway) true fi last_tag=1 continue fi pop_path _tpath pop_path _mpath break done # at the end of the loop, 3 possibilities: # 1. (idx < last_idx): some component before the last is invalid # => invalid command # 2. (idx == last_idx): last component matches neither template nor node.tag # => if end_space, then invalid command # otherwise, may be an incomplete (non-tag) component, or incomplete # "leaf value" # => try matching dirs in _tpath or value(s) in _mpath/node.val # 3. (idx == num_comp): the whole command matches templates/tags if (( idx < last_idx || ( idx == last_idx && end_space ) )); then # TODO error message? COMPREPLY=() eval $restore_shopts return fi declare -a matches if (( idx == last_idx )); then # generate possibile matches (dirs in _tpath, and "help" from # node.def in each dir, or values in _mpath/node.val) declare -a fmatches if [ -f $_mpath/node.val ]; then decho {1a} get_value_list $_mpath matches get_prefix_filtered_list ${comp_words[$last_idx]} matches fmatches append_allowed_values $_tpath fmatches get_node_value_help $_tpath fmatches else decho {1b} # see if the last component is a new leaf node fmatches=() if is_setting_new_leaf $_tpath; then append_allowed_values $_tpath fmatches get_node_value_help $_tpath fmatches else # last component is a non-value node. look for child nodes. if (( ! is_set )); then # not "set". only complete existing nodes. declare -a amatches=() get_filtered_dir_listing $_tpath amatches filter_existing_nodes $_mpath amatches matches else get_filtered_dir_listing $_tpath matches fi get_prefix_filtered_list ${comp_words[$last_idx]} matches fmatches get_tmpl_subdir_help $_tpath fmatches fi fi vyatta_completions=( "${fmatches[@]}" ) vyatta_do_complete eval $restore_shopts return fi if (( last_tag && end_space )); then # if not "set", check _mpath (last component is the tag) is valid # generate possible matches (dirs in _tpath, and "help" from node.def # in each dir) decho {2} if [ $is_set == 1 -o -d $_mpath ]; then if (( ! is_set )); then # not "set". only complete existing nodes. declare -a fmatches=() get_filtered_dir_listing $_tpath fmatches filter_existing_nodes $_mpath fmatches matches else get_filtered_dir_listing $_tpath matches fi get_tmpl_subdir_help $_tpath matches vyatta_completions=( "${matches[@]}" ) vyatta_do_complete eval $restore_shopts return fi eval $restore_shopts return fi if (( last_tag && !end_space )); then # generate possible matches (dirs in _mpath, and "help" from node.def # in dirs in _tpath) decho {3} pop_path _mpath pop_path _tpath get_filtered_dir_listing $_mpath matches declare -a fmatches get_prefix_filtered_list ${comp_words[$last_idx]} matches fmatches append_allowed_values $_tpath fmatches get_node_value_help $_tpath fmatches vyatta_completions=( "${fmatches[@]}" ) vyatta_do_complete eval $restore_shopts return fi if (( !last_tag && end_space )); then # generate possible matches # 1. dirs in _tpath, and "help" from node.def in each dir # 2. value(s) in _mpath/node.val (only if _tpath/node.def is "multi:") # 3. dirs in _mpath (only if _tpath/node.def is "tag:") if [ -d $_tpath/node.tag ]; then # last component is a "tag name". look for tag values. decho {4a} get_filtered_dir_listing $_mpath matches append_allowed_values $_tpath matches get_node_value_help $_tpath matches elif [ -f $_mpath/node.val ]; then # last component is a leaf node. look for values. decho {4b} get_value_list $_mpath matches append_allowed_values $_tpath matches get_node_value_help $_tpath matches else decho {4c} # see if the last component is a new leaf node matches=() if is_setting_new_leaf $_tpath; then append_allowed_values $_tpath matches get_node_value_help $_tpath matches else # last component is a non-value node. look for child nodes. if (( ! is_set )); then # not "set". only complete existing nodes. declare -a fmatches=() get_filtered_dir_listing $_tpath fmatches filter_existing_nodes $_mpath fmatches matches else get_filtered_dir_listing $_tpath matches fi get_tmpl_subdir_help $_tpath matches fi fi vyatta_completions=( "${matches[@]}" ) vyatta_do_complete eval $restore_shopts return fi if (( !last_tag && !end_space )); then # generate possible matches (dirs in _tpath, and "help" from node.def # in each dir) decho {5} pop_path _tpath get_filtered_dir_listing $_tpath matches declare -a fmatches get_prefix_filtered_list ${comp_words[$last_idx]} matches fmatches get_tmpl_subdir_help $_tpath fmatches vyatta_completions=( "${fmatches[@]}" ) vyatta_do_complete eval $restore_shopts return fi eval $restore_shopts } DEF_GROUP=vyattacfg make_vyatta_config_dir () { sudo mkdir -m 0775 -p $1 sudo chgrp ${DEF_GROUP} $1 } if grep -q union=aufs /proc/cmdline || grep -q aufs /proc/filesystems ; then export UNIONFS=aufs else export UNIONFS=unionfs fi make_vyatta_config_dir $VYATTA_ACTIVE_CONFIGURATION_DIR make_vyatta_config_dir $VYATTA_CHANGES_ONLY_DIR make_vyatta_config_dir $VYATTA_CONFIG_TMP if [ ! -d $VYATTA_TEMP_CONFIG_DIR ]; then make_vyatta_config_dir $VYATTA_TEMP_CONFIG_DIR sudo mount -t $UNIONFS -o dirs=${VYATTA_CHANGES_ONLY_DIR}=rw:/opt/vyatta/config/active=ro $UNIONFS ${VYATTA_TEMP_CONFIG_DIR} fi # disallow 'Ctrl-D' exit, since we need special actions on 'exit' set -o ignoreeof 1 set_config_ps1 '' alias commit=/opt/vyatta/sbin/my_commit alias set=/opt/vyatta/sbin/my_set alias delete=/opt/vyatta/sbin/my_delete export VYATTA_COMP_LINE="" # readline bindings bind 'set show-all-if-ambiguous on' if ! bind -p |grep -q '\\C-x\\C-t'; then bind '"\C-x\C-t": kill-region' fi if ! bind -p |grep -q '\\C-x\\C-o'; then bind '"\C-x\C-o": copy-region-as-kill' fi complete -F vyatta_config_complete '' complete -F vyatta_config_complete set complete -F vyatta_config_complete delete complete -F vyatta_config_complete show complete -F vyatta_config_complete edit complete -F vyatta_config_complete exit complete -F vyatta_run_complete run complete -F vyatta_loadsave_complete save complete -F vyatta_loadsave_complete load complete -F vyatta_loadsave_complete merge complete -F vyatta_loadkey_complete loadkey complete -F vyatta_config_complete comment complete -F vyatta_config_complete activate complete -F vyatta_config_complete deactivate complete -F vyatta_config_complete copy complete -F vyatta_config_complete rename # Local Variables: # mode: shell-script # sh-indentation: 4 # End: