# vyatta configuration mode command interpreter functions

# **** 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: Vyatta
# Description: Command interpreter functions for config mode
# 
# **** End License ****

### Top level commands and help ###
_vyatta_cfg_cmds=( "confirm" \
                   "comment" \
                   "commit" \
                   "commit-confirm" \
                   "compare" \
                   "copy" \
                   "delete" \
                   "discard" \
                   "edit" \
                   "exit" \
                   "load" \
                   "loadkey" \
                   "merge" \
                   "rename" \
                   "rollback" \
                   "run" \
                   "save" \
                   "set" \
                   "show" )
_vyatta_cfg_helps=( \
      "Confirm prior commit-confirm" \
      "Add comment to this configuration element" \
      "Commit the current set of changes" \
      "Commit the current set of changes with 'confirm' required" \
      "Compare configuration revisions" \
      "Copy a configuration element" \
      "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 user SSH key from a file" \
      "Load configuration from a file and merge running configuration" \
      "Rename a configuration element" \
      "Rollback to a prior config revision (requires reboot)" \
      "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)" \
    )
###  End Top level commands and help ###

vyatta_cfg_expand_top_level () {
  local cmd=$1
  local -a filtered_cmds=()
  get_prefix_filtered_list ${cmd} _vyatta_cfg_cmds filtered_cmds
  local found
  is_elem_of "${cmd}" _vyatta_cfg_cmds
  found=$?
  local fcmd
  if [[ "${#filtered_cmds[@]}" == "1" || "$found" == "0" ]]; then
    if [[ "${#filtered_cmds[@]}" == "1" ]]; then
      fcmd=${filtered_cmds[0]}
    else
      fcmd=$cmd
    fi
  else
    fcmd=$cmd
  fi 
  echo $fcmd
}

### Top level command wrappers ###
vyatta_config_show ()
{
  local -a opts=()
  local -a args=()
  for arg in "$@"; do
    if [ "$arg" == "-all" ]; then
      opts+=('--show-show-defaults')
    else
      args+=("$arg")
    fi
  done
  cli-shell-api "${opts[@]}" -- showCfg "${args[@]}" \
    | eval "${VYATTA_PAGER:-cat}"
}

vyatta_config_commit ()
{
  if ! vyatta_cli_shell_api sessionChanged; then
    echo "No configuration changes to commit"
    return 1;
  fi
  local comment="commit"
  local next=0
  local -a args=()
  for arg in "$@"; do
    if [ "$next" == "1" ]; then
      comment=$arg
      next=0;
    elif [ "$arg" == "comment" ]; then
      next=1
    elif [ "$arg" == "confirm" ]; then
      echo Use commit-confirm command
      return 1;
    else
      args[${#args[@]}]="$arg"    
    fi
  done

  export COMMIT_COMMENT="$comment"
  export COMMIT_VIA=cli
  /opt/vyatta/sbin/my_commit "${args[@]}" 2>&1
  unset COMMIT_VIA
  unset COMMIT_COMMENT
}

vyatta_config_commit-confirm ()
{
  if ! vyatta_cli_shell_api sessionChanged; then
    echo "No configuration changes to commit"
    return 1;
  fi
  local -a args=()
  local first=1
  local minutes=10
  for arg in "$@"; do
    if [ "$first" = "1" ]; then
      if [[ $arg = *[[:digit:]]* ]]; then
        minutes=$arg
      else
        args[${#args[@]}]="$arg"    
      fi
      first=0
    else
      args[${#args[@]}]="$arg"    
    fi
  done
  cmd="${vyatta_sbindir}/vyatta-config-mgmt.pl --action=commit-confirm \
                                               --minutes=$minutes"
  eval "sudo sg vyattacfg \"$cmd\" "
  if [ $? = 0 ]; then
    vyatta_config_commit "$@"
  fi
}

vyatta_config_confirm ()
{
  ${vyatta_sbindir}/vyatta-config-mgmt.pl --action=confirm
}

vyatta_config_compare ()
{
  local -a comp=( "saved" )
  local -a filtered=()
  get_prefix_filtered_list $1 comp filtered
  if [[ "${filtered[0]}" == "saved" ]]; then
     cli-shell-api showConfig --show-cfg1 /config/config.boot --show-cfg2 @WORKING --show-context-diff
  else  
     ${vyatta_sbindir}/vyatta-config-mgmt.pl --action=diff "$@" | eval "${VYATTA_PAGER:-cat}"
  fi 
}

vyatta_config_save ()
{
  if vyatta_cli_shell_api sessionChanged; then
    echo -e "Warning: you have uncommitted changes that will not be saved.\n"
  fi
  # return to top level.
  reset_edit_level
  # transform individual args into quoted strings
  local arg=''
  local save_cmd="${vyatta_sbindir}/vyatta-save-config.pl"
  for arg in "$@"; do
    save_cmd+=" '$arg'"
  done
  eval "sudo sg vyattacfg \"umask 0002 ; $save_cmd\""
  vyatta_cli_shell_api unmarkSessionUnsaved
}

reboot ()
{
  echo "Exit from configure mode before rebooting."
}

vyatta_config_rollback ()
{
  if [ $# != 1 ]; then
    echo "Error: must include a revision # to rollback to"
    return 1;
  fi
  ${vyatta_sbindir}/vyatta-config-mgmt.pl --action=rollback --revnum "$@"
}

shutdown ()
{
  echo "Exit from configure mode before shutting down system."
}

reset_edit_level ()
{
  vyatta_cli_shell_api getEditResetEnv
  return $?
}

vyatta_config_load ()
{
  # don't load if there are uncommitted changes.
  if vyatta_cli_shell_api sessionChanged; then
    echo "Cannot load: configuration modified."
    echo "Commit or discard the changes before loading a config file."
    return 1
  fi
  # return to top level.
  reset_edit_level
  ${vyatta_sbindir}/vyatta-load-config.pl "$@"
}

vyatta_config_merge ()
{
  # don't load if there are uncommitted changes.
  if vyatta_cli_shell_api sessionChanged; then
    echo "Cannot load: configuration modified."
    echo "Commit or discard the changes before loading a config file."
    return 1
  fi
  # return to top level.
  reset_edit_level
  ${vyatta_sbindir}/vyatta-load-config.pl "$@" --merge
}

top ()
{
  if vyatta_cli_shell_api editLevelAtRoot; then
    echo "Already at the top level"
    return 0
  fi

  # go to the top level.
  reset_edit_level
}

vyatta_config_edit ()
{
  vyatta_cli_shell_api getEditEnv "$@"
  return $?
}

up ()
{
  vyatta_cli_shell_api getEditUpEnv "$@"
  return $?
}

really_exit()
{

  if vyatta_cli_shell_api sessionUnsaved; then
    echo "Warning: configuration changes have not been saved."
  fi
  vyatta_cli_shell_api teardownSession
  unset _OFR_CONFIGURE
  builtin exit 0
}

vyatta_config_exit ()
{
  local discard
  local -a comp=( "discard" )
  local -a filtered=()
  get_prefix_filtered_list $1 comp filtered
  if [ $# == 0 ]; then
    discard=0
  elif [ $# == 1 ] && [ "${filtered[0]}" == "discard" ]; then
    discard=1
  else
    echo "Invalid argument \"$*\" for 'exit'"
    return 1
  fi

  if vyatta_cli_shell_api editLevelAtRoot; then
    # we are at the root level. check if we can really exit.
    if vyatta_cli_shell_api sessionChanged; 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.
  reset_edit_level
}

# run op mode commands
vyatta_config_run ()
{
  if [ $# == 0 ]; then
    echo -e "\n  Incomplete command: run\n"
    return 1
  fi
  if [[ "set" =~ "$1" ]]; then
    _vyatta_op_run "$@"
  elif [[ "$1" =~ "/" ]]; then
    local args=("$@")
    ${args[0]} "${args[@]:1}"
  else
    /opt/vyatta/bin/vyatta-op-cmd-wrapper "$@"
  fi
}

### End Top level command wrappers ###

### Top level wrappers ###
vyatta_cfg_cmd_run ()
{
    local cmd=$1
    local output=''
    if [[ "$cmd" == "edit" ]]; then 
      vyatta_config_edit "${@:2}"
    elif [[ "$cmd" == "show" ]]; then 
      vyatta_config_show "${@:2}"
    else 
      cmd="/opt/vyatta/sbin/my_$cmd"
      output=$($cmd "${@:2}")
    fi   
    vyatta_cfg_print_output "$output"
}

vyatta_cfg_print_output ()
{
  local output=$1
  if [[ ! -z "${output}" ]];then
    output=$(echo "$output" | sed -e 's/^/  /')
    echo -ne "\n${output}\n\n" | eval "${VYATTA_PAGER:-cat}"
  fi 
}

vyatta_cfg_validate_cmd ()
{
    local cmd=$1 
    local -a expanded_api_args=( "$@" )
    local editlvl=$(cli-shell-api getEditLevelStr)
    local path=''
    local opath=''
    for arg in "${expanded_api_args[@]:1}"; do
      if [[ "$path" == '' ]]; then 
        path="$arg"
      else 
        path="$path $arg"
      fi   
      if ! cli-shell-api validateTmplPath -- ${editlvl} ${path}; then 
        _cli_shell_api_comp_values=()
        vyatta_cli_shell_api getCompletionEnv $cmd ${path} 
        if [[ "${#_cli_shell_api_comp_values[@]}" != "1"  
           && "${#_cli_shell_api_comp_values[@]}" != "0" ]]; then 
          local -a _get_help_text_items=( "${_cli_shell_api_hitems[@]}" )
          local -a _get_help_text_helps=( "${_cli_shell_api_hstrs[@]}" )
          local vyatta_help_text=''
          if [[ $opath == '' ]]; then 
            echo -ne "\n  Configuration path: [$arg] is ambiguous\n" >&2
          else
            echo -ne "\n  Configuration path: $opath [$arg] is ambiguous\n" >&2
          fi
          get_help_text
          echo -e "$vyatta_help_text\n" | sed 's/^P/  P/'
          echo -e "  ${cmd^} failed\n"
          break
        else
          if [[ $opath == '' ]]; then 
            echo -ne "\n  Configuration path: [$arg] is not valid\n  ${cmd^} failed\n\n" >&2
          else
            echo -ne "\n  Configuration path: $opath [$arg] is not valid\n  ${cmd^} failed\n\n" >&2
          fi
          break
        fi
      else 
        opath=$path
      fi   
    done 
}

vyatta_config_copy ()
{
    local cmd=$1 
    if [[ "${#@}" == "1" ]]; then
      vyatta_cfg_cmd_run $cmd
      return
    fi
    local -a args=( "$@" )
    local -a param1=( "$cmd" ${args[@]:1:2} )
    local -a param2=( "$cmd" ${args[@]:4:5} )
    local editlvl=$(cli-shell-api getEditLevelStr)
    expanded_api_args=( )
    vyatta_config_expand_compwords "${param1[@]}"
    param1=( "${expanded_api_args[@]}" )
    expanded_api_args=( )
    vyatta_config_expand_compwords "${param2[@]}"
    param2=( "${expanded_api_args[@]}" )
    if [[ "${args[3]}" != "to" ]]; then
        echo -ne "\n  Invalid command: $cmd ${param1[@]:1} ${args[3]} ${param2[@]:1}\n\n" >&2
    elif cli-shell-api validateTmplPath -- ${editlvl[*]} "${param1[@]:1}" &&
       cli-shell-api validateTmplPath -- ${editlvl[*]} "${param2[@]:1}" ; then
      cmd="/opt/vyatta/sbin/my_$cmd"
      output=$(eval "$cmd ${param1[@]:1} to ${param2[@]:1} | sed -e 's/^/  /'")
      if [[ ! -z "${output}" ]];then
        echo -ne "\n${output}\n\n"
      fi
    else
      if ! cli-shell-api validateTmplPath -- ${editlvl[*]} "${param1[@]:1}"; then
        _cli_shell_api_comp_values=()
        vyatta_cli_shell_api getCompletionEnv $cmd ${param1[1]}
        if [[ "${#_cli_shell_api_comp_values[@]}" != "1" 
           && "${#_cli_shell_api_comp_values[@]}" != "0" ]]; then
          echo -ne "\n  Ambiguous command: $cmd [${param1[1]}]\n" >&2
          echo -ne "\n  Possible completions: ${_cli_shell_api_comp_values[@]}\n\n" >&2
        else
          echo -ne "\n  Invalid command: $cmd [${param1[1]}]\n\n" >&2
        fi
      elif ! cli-shell-api validateTmplPath -- ${editlvl[*]} "${param1[@]:2}"; then
        _cli_shell_api_comp_values=()
        vyatta_cli_shell_api getCompletionEnv $cmd "${param2[1]}" 
        if [[ "${#_cli_shell_api_comp_values[@]}" != "1" 
           && "${#_cli_shell_api_comp_values[@]}" != "0" ]]; then
          echo -ne "\n  Ambiguous command: $cmd ${param2[@]:1} to [${param2[1]}]\n" >&2
          echo -ne "\n  Possible completions: ${_cli_shell_api_comp_values[@]}\n\n" >&2
        else
          echo -ne "\n  Invalid command: $cmd ${param1[@]:1} to [${param2[1]}]\n\n" >&2
        fi
      else
        echo -ne "\n  Invalid command: $cmd ${param1[@]:1} to ${param2[@]:1}\n\n" >&2
      fi
    fi
}

vyatta_config_comment ()
{
  local cmd=$1
  if [[ "${#@}" == "1" ]]; then
    vyatta_cfg_cmd_run $cmd
    return
  fi
  # change the ifs so we can extract the entire comment
  local -a args=( "$@" )
  # extract the comment
  local comment="'${args[$[${#args[@]}-1]]}'"
  args=( "${args[@]:0:$[${#args[@]}-1]}" )
  local -a expanded_api_args=()
  # expand the comment command
  local editlvl=$(cli-shell-api getEditLevelStr)
  vyatta_config_expand_compwords "${args[@]}"
  if [[ "$#" != "${#expanded_api_args[@]}" ]]; then
     expanded_api_args+=( $comment )
  fi
  # use the standard run function with the comment expansion
  output=$(eval "/opt/vyatta/sbin/my_${expanded_api_args[0]} ${expanded_api_args[@]:1}")
  vyatta_cfg_print_output "$output"
}

vyatta_cfg_cmd () 
{ 
  # commands that need expanded paths get called through here
  local cmd=$1
  if [[ "$#" == "1" ]]; then
    vyatta_cfg_cmd_run $cmd
    return
  fi
  local -a args=( "$@" )
  local -a expanded_api_args=()
  local editlvl=$(cli-shell-api getEditLevelStr)
  vyatta_config_expand_compwords "${args[@]}"
  if [[ "${expanded_api_args[@]:$[${#expanded_api_args[@]}-1]}" == "-all" ]] &&
     [[ "${expanded_api_args[0]}" == "show" ]]; then
    if [[ $[${#expanded_api_args[@]}-2] -eq 0 ]]; then
      vyatta_cfg_cmd_run "${expanded_api_args[@]}"
    elif cli-shell-api validateTmplPath -- ${editlvl[*]} \
         "${expanded_api_args[@]:1:$[${#expanded_api_args[@]}-2]}"; then
      vyatta_cfg_cmd_run "${expanded_api_args[@]}"
    else
      vyatta_cfg_validate_cmd "${expanded_api_args[@]}"
    fi
  elif cli-shell-api validateTmplPath -- ${editlvl[*]} "${expanded_api_args[@]:1}"; then
    vyatta_cfg_cmd_run "${expanded_api_args[@]}"
  else
    # find broken portion of command
    vyatta_cfg_validate_cmd "${expanded_api_args[@]}"
  fi
}
### Top level wrappers ###

### Main run command ###
vyatta_cfg_run ()
{
  # validate top level command and execute proper function
  local cmd=$1
  local -a args=( "$@" )
  local -a filtered_cmds
  get_prefix_filtered_list $cmd _vyatta_cfg_cmds filtered_cmds
  local found
  is_elem_of "${cmd}" _vyatta_cfg_cmds
  found=$?
  stty echo 2> /dev/null # turn echo on, this is a workaround for bug 7570
                         # not a fix we need to look at why the readline library 
                         # is getting confused on paged help text.
  if [[ "${#filtered_cmds[@]}" == "0" ]]; then
    echo -ne "\n  Invalid command: [$cmd]\n\n" >&2
    return 1
  elif [[ "${#filtered_cmds[@]}" != "1" && "$found" == "1" ]];  then
    echo -ne "\n  Ambiguous command: [$cmd]\n" >&2
    local -a fitems=()
    local -a fstrs=()
    local -a _get_help_text_items=( "${_vyatta_cfg_cmds[@]}" )
    local -a _get_help_text_helps=( "${_vyatta_cfg_helps[@]}" )
    get_prefix_filtered_list2 "$cmd" \
      _get_help_text_items fitems _get_help_text_helps fstrs
    _get_help_text_items=( "${fitems[@]}" )
    _get_help_text_helps=( "${fstrs[@]}" )
    get_help_text
    echo -e "$vyatta_help_text\n" | sed 's/^P/  P/'
    return 1
  fi
  local fcmd;
  if is_elem_of "${cmd}" _vyatta_cfg_cmds; then
    fcmd=$cmd
  else
    fcmd=${filtered_cmds[0]}
  fi
  case $fcmd in
    compare) vyatta_config_compare "${@:2}" ;;
    comment) vyatta_config_comment "${args[@]}" ;; # comment is a special case
    copy|rename) vyatta_config_copy $fcmd ${@:2} ;; # copy is a special case
    exit) vyatta_config_exit "${@:2}" ;; 
    run) vyatta_config_run "${@:2}" ;; 
    load) vyatta_config_load "${@:2}" ;; 
    commit) vyatta_config_commit "${@:2}";;
    confirm) vyatta_config_confirm "${@:2}";;
    rollback) vyatta_config_rollback "${@:2}";;
    commit-confirm) vyatta_config_commit-confirm "${@:2}";;
    compare) vyatta_config_compare "${@:2}";;
    save) vyatta_config_save "${@:2}" ;;
    merge) vyatta_config_merge "${@:2}" ;;
    loadkey) vyatta_config_loadkey "${@:2}";;
    *) vyatta_cfg_cmd $fcmd "${@:2}" ;; # commands requiring path expansion must go through here
  esac
}

### Initalize top level command alias and completion functions
_vyatta_cfg_init ()
{
    # empty and default line compeletion
    complete -E -F vyatta_config_complete 
    complete -D -F vyatta_config_default_complete

    # create the top level aliases for the unambiguous portions of the commands
    # this is the only place we need an entire enumerated list of the subcommands
    for cmd in "${_vyatta_cfg_cmds[@]}"; do
      for pos in $(seq 1 ${#cmd}); do
        case ${cmd:0:$pos} in
          for|do|done|if|fi|case|while|tr )
            continue ;;
          *) ;;
        esac
        local -a filtered_cmds=()
        get_prefix_filtered_list ${cmd:0:$pos} _vyatta_cfg_cmds filtered_cmds
        local found
        is_elem_of "${cmd:0:$pos}" _vyatta_cfg_cmds
        found=$?
        if [[ "${#filtered_cmds[@]}" == "1" || "${cmd:0:$pos}" == "$cmd"  || "$found" == "0" ]]; then
          local fcmd
          if [[ "${#filtered_cmds[@]}" == "1" ]]; then
            fcmd=${filtered_cmds[0]}
          elif is_elem_of "${cmd:0:$pos}" _vyatta_cfg_cmds; then
            fcmd=${cmd:0:$pos}
          else
            fcmd=$cmd
          fi
          case $fcmd in
            save|load|merge)
              complete -F vyatta_loadsave_complete ${cmd:0:$pos} ;;
            discard|confirm)
               complete -F vyatta_single_word_complete ${cmd:0:$pos} ;; 
            run)
              complete -F vyatta_run_complete ${cmd:0:$pos} ;;
            loadkey)
              complete -F vyatta_loadkey_complete ${cmd:0:$pos} ;;
            compare)
              complete -F vyatta_compare_complete ${cmd:0:$pos} ;;
            rollback)
              complete -F vyatta_rollback_complete ${cmd:0:$pos} ;;
            commit|commit-confirm)
               complete -F vyatta_commit_complete ${cmd:0:$pos} ;;
            *)
               complete -F vyatta_config_complete ${cmd:0:$pos} ;;
          esac
        else
          complete -F vyatta_config_complete ${cmd:0:$pos} 
        fi
        eval alias ${cmd:0:$pos}=\'vyatta_cfg_run ${cmd:0:$pos}\'
      done
    done
    shopt -s histverify
}