diff --git a/AUTHORS b/AUTHORS index fd3749a48a0..fe79acd287b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -302,6 +302,7 @@ Pankaj Thakkar thakkar@nicira.com Pasi Kärkkäinen pasik@iki.fi Paulo Cravero pcravero@as2594.net Pawan Shukla shuklap@vmware.com +Peter Amidon peter@picnicpark.org Peter Balland peter@nicira.com Peter Phaal peter.phaal@inmon.com Prabina Pattnaik Prabina.Pattnaik@nechclst.in diff --git a/utilities/automake.mk b/utilities/automake.mk index 09d67022fba..5aae782b5db 100644 --- a/utilities/automake.mk +++ b/utilities/automake.mk @@ -30,6 +30,7 @@ docs += utilities/ovs-command-bashcomp.INSTALL.md EXTRA_DIST += \ utilities/ovs-check-dead-ifs.in \ utilities/ovs-appctl-bashcomp.bash \ + utilities/ovs-vsctl-bashcomp.bash \ utilities/ovs-command-bashcomp.INSTALL.md \ utilities/ovs-ctl.in \ utilities/ovs-dev.py \ diff --git a/utilities/ovs-vsctl-bashcomp.bash b/utilities/ovs-vsctl-bashcomp.bash new file mode 100755 index 00000000000..8f73ce1f815 --- /dev/null +++ b/utilities/ovs-vsctl-bashcomp.bash @@ -0,0 +1,775 @@ +SAVE_IFS=$IFS +IFS=" +" +_OVSDB_SERVER_LOCATION="" + +# Run ovs-vsctl and make sure that ovs-vsctl is always called with +# the correct --db argument. +_ovs_vsctl () { + local _db + + if [ -n "$_OVSDB_SERVER_LOCATION" ]; then + _db="--db=$_OVSDB_SERVER_LOCATION" + fi + ovs-vsctl ${_db} "$@" +} + +# ovs-vsctl --commands outputs in this format: +# +# main = ,, +# localopts = ([] )* +# localopt = --[^]]* +# name = [^,]* +# arguments = ((!argument|?argument|*argument|+argument) )* +# argument = ([^ ]*|argument\|argument) +# +# The [] characters in local options are just delimiters. The +# argument prefixes mean: +# !argument :: The argument is required +# ?argument :: The argument is optional +# *argument :: The argument may appear any number (0 or more) times +# +argument :: The argument may appear one or more times +# A bar (|) character in an argument means thing before bar OR thing +# after bar; for example, del-port can take a port or an interface. + +_OVS_VSCTL_COMMANDS="$(_ovs_vsctl --commands)" + +# This doesn't complete on short arguments, so it filters them out. +_OVS_VSCTL_OPTIONS="$(_ovs_vsctl --options | awk '/^--/ { print $0 }' \ + | sed -e 's/\(.*\)=ARG/\1=/')" +IFS=$SAVE_IFS + +declare -A _OVS_VSCTL_PARSED_ARGS +declare -A _OVS_VSCTL_NEW_RECORDS + +# This is a convenience function to make sure that user input is +# looked at as a fixed string when being compared to something. $1 is +# the input; this behaves like 'grep "^$1"' but deals with regex +# metacharacters in $1. +_ovs_vsctl_check_startswith_string () { + awk 'index($0, thearg)==1' thearg="$1" +} + +# $1 = word to complete on. +# Complete on global options. +_ovs_vsctl_bashcomp_globalopt () { + local options result + + options="" + result=$(printf "%s\n" "${_OVS_VSCTL_OPTIONS}" \ + | _ovs_vsctl_check_startswith_string "${1%=*}") + if [[ $result =~ "=" ]]; then + options="NOSPACE" + fi + printf -- "${options}\nEO\n${result}" +} + +# $1 = word to complete on. +# Complete on local options. +_ovs_vsctl_bashcomp_localopt () { + local options result possible_opts + + possible_opts=$(printf "%s\n" "${_OVS_VSCTL_COMMANDS}" | cut -f1 -d',') + # This finds all options that could go together with the + # already-seen ones + for prefix_arg in $1; do + possible_opts=$(printf "%s\n" "$possible_opts" \ + | grep -- "\[${prefix_arg%%=*}=\?\]") + done + result=$(printf "%s\n" "${possible_opts}" \ + | tr ' ' '\n' | tr -s '\n' | sort | uniq) + # This removes the already-seen options from the list so that + # users aren't completed for the same option twice. + for prefix_arg in $1; do + result=$(printf "%s\n" "${result}" \ + | grep -v -- "\[${prefix_arg%%=*}=\?\]") + done + result=$(printf "%s\n" "${result}" | sed -ne 's/\[\(.*\)\]/\1/p' \ + | _ovs_vsctl_check_startswith_string "$2") + if [[ $result =~ "=" ]]; then + options="NOSPACE" + fi + printf -- "${options}\nEO\n${result}" +} + +# $1 = given local options. +# $2 = word to complete on. +# Complete on command that could contain the given local options. +_ovs_vsctl_bashcomp_command () { + local result possible_cmds + + possible_cmds=$(printf "%s\n" "${_OVS_VSCTL_COMMANDS}") + for prefix_arg in $1; do + possible_cmds=$(printf "%s\n" "$possible_cmds" \ + | grep -- "\[$prefix_arg=\?\]") + done + result=$(printf "%s\n" "${possible_cmds}" \ + | cut -f2 -d',' \ + | _ovs_vsctl_check_startswith_string "$2") + printf -- "${result}" +} + +# $1 = completion result to check. +# Return 0 if the completion result is non-empty, otherwise return 1. +_ovs_vsctl_detect_nonzero_completions () { + local tmp newarg + + newarg=${1#*EO} + readarray tmp <<< "$newarg" + if [ "${#tmp[@]}" -eq 1 ] && [ "${#newarg}" -eq 0 ]; then + return 1 + fi + return 0 +} + +# $1 = argument format to expand. +# Expand '+ARGUMENT' in argument format to '!ARGUMENT *ARGUMENT'. +_ovs_vsctl_expand_command () { + result=$(printf "%s\n" "${_OVS_VSCTL_COMMANDS}" \ + | grep -- ",$1," | cut -f3 -d',' | tr ' ' '\n' \ + | awk '/\+.*/ { name=substr($0,2); + print "!"name; print "*"name; next; } + 1') + printf -- "${result}\n!--" +} + +# $1 = word to complete on. +# Complete on table. +_ovs_vsctl_complete_table () { + local result + + result=$(ovsdb-client --no-heading list-tables $_OVSDB_SERVER_LOCATION Open_vSwitch \ + | _ovs_vsctl_check_startswith_string "$1") + printf -- "EO\n%s\n" "${result}" +} + +# $1 = word to complete on. +# Complete on record. Provide both the name and uuid. +_ovs_vsctl_complete_record () { + local table uuids names new_record + + table="${_OVS_VSCTL_PARSED_ARGS[TABLE]}" + new_record="${_OVS_VSCTL_NEW_RECORDS[${table^^}]}" + # Tables should always have an _uuid column + uuids=$(_ovs_vsctl --no-heading -f table -d bare --columns=_uuid \ + list $table | _ovs_vsctl_check_startswith_string "$1") + # Names don't always exist, silently ignore if the name column is + # unavailable. + names=$(_ovs_vsctl --no-heading -f table -d bare \ + --columns=name list $table \ + 2>/dev/null \ + | _ovs_vsctl_check_startswith_string "$1") + printf -- "EO\n%s\n%s\n%s\n" "${uuids}" "${names}" "${new_record}" +} + +# $1 = word to complete on. +# Complete on bridge. +_ovs_vsctl_complete_bridge () { + local result + + result=$(_ovs_vsctl list-br | _ovs_vsctl_check_startswith_string "$1") + printf -- "EO\n%s\n" "${result}" +} + +# $1 = word to complete on. +# Complete on port. If a bridge has already been specified, +# just complete for that bridge. +_ovs_vsctl_complete_port () { + local ports result + + if [ -n "${_OVS_VSCTL_PARSED_ARGS[BRIDGE]}" ]; then + ports=$(_ovs_vsctl list-ports "${_OVS_VSCTL_PARSED_ARGS[BRIDGE]}") + else + local all_ports + all_ports=$(_ovs_vsctl --format=table \ + --no-headings \ + --columns=name \ + list Port) + ports=$(printf "$all_ports" | tr -d '" ' | sort -u) + fi + result=$(_ovs_vsctl_check_startswith_string "$1" <<< "$ports") + printf -- "EO\n%s\n" "${result}" +} + +# $1: Atom to complete (as usual) +# $2: Table to complete the key in +# $3: Column to find keys in +# $4: Prefix for each completion +# Complete on key based on given table and column info. +_ovs_vsctl_complete_key_given_table_column () { + local keys + + keys=$(_ovs_vsctl --no-heading --columns="$3" list \ + "$2" \ + | tr -d '{\"}' | tr -s ', ' '\n' | cut -d'=' -f1 \ + | xargs printf "$4%s\n" | _ovs_vsctl_check_startswith_string "$1") + result="${keys}" + printf -- "%s\n" "${result}" +} + +# $1 = word to complete on. +# Complete on key. +__complete_key () { + # KEY is used in both br-set-external-id/br-get-external id (in + # which case it is implicitly a key in the external-id column) and + # in remove, where it is a table key. This checks to see if table + # is set (the remove scenario), and then decides what to do. + local result + + if [ -n "${_OVS_VSCTL_PARSED_ARGS[TABLE]}" ]; then + local column=$(tr -d '\n' <<< ${_OVS_VSCTL_PARSED_ARGS["COLUMN"]}) + result=$(_ovs_vsctl_complete_key_given_table_column \ + "$1" \ + ${_OVS_VSCTL_PARSED_ARGS["TABLE"]} \ + $column \ + "") + else + result=$(_ovs_vsctl br-get-external-id \ + ${_OVS_VSCTL_PARSED_ARGS["BRIDGE"]} \ + | cut -d'=' -f1 | _ovs_vsctl_check_startswith_string "$1") + fi + printf -- "%s" "${result}" +} + +# $1 = word to complete on. +# Complete on key. +_ovs_vsctl_complete_key () { + # KEY is used in both br-set-external-id/br-get-external id (in + # which case it is implicitly a key in the external-id column) and + # in remove, where it is a table key. This checks to see if table + # is set (the remove scenario), and then decides what to do. + local result + + result="$(__complete_key $1)" + # If result is empty, just use user input as result. + if [ -z "$result" ]; then + result=$1 + fi + printf -- "EO\n%s\n" "${result}" +} + +# $1 = word to complete on. +# Complete on value. +_ovs_vsctl_complete_value () { + local result + + # Just use user input as result. + result=$1 + + printf -- "EO\n%s\n" "${result}" +} + +# $1 = word to complete on. +# Complete on key=value. +_ovs_vsctl_complete_key_value () { + local orig_completions new_completions + + orig_completions=$(__complete_key "$1") + for completion in ${orig_completions#*EO}; do + new_completions="${new_completions} ${completion}=" + done + # If 'new_completions' is empty, just use user input as result. + if [ -z "$new_completions" ]; then + new_completions=$1 + fi + printf -- "NOSPACE\nEO\n%s" "${new_completions}" +} + +# $1 = word to complete on. +# Complete on column. +_ovs_vsctl_complete_column () { + local columns result + + columns=$(ovsdb-client --no-headings list-columns $_OVSDB_SERVER_LOCATION \ + Open_vSwitch ${_OVS_VSCTL_PARSED_ARGS["TABLE"]}) + result=$(printf "%s\n" "${columns}" \ + | tr -d ':' | cut -d' ' -f1 \ + | _ovs_vsctl_check_startswith_string "$1" | sort | uniq) + printf -- "EO\n%s\n" "${result}" +} + +# Extract all system interfaces. +_ovs_vsctl_get_sys_intf () { + local result + + case "$(uname -o)" in + *Linux*) + result=$(ip -o link 2>/dev/null | cut -d':' -f2 \ + | sed -e 's/^ \(.*\)/\1/') + ;; + *) + result=$(ifconfig -a -s 2>/dev/null | cut -f1 -d' ' | tail -n +2) + ;; + esac + printf "%s\n" "${result}" +} + +# $1 = word to complete on. +# Complete on system interface. +_ovs_vsctl_complete_sysiface () { + local result + + result=$(_ovs_vsctl_get_sys_intf | _ovs_vsctl_check_startswith_string "$1") + printf -- "EO\n%s\n" "${result}" +} + +# $1 = word to complete on. +# Complete on interface. If a bridge has already been specified, +# just complete for that bridge. +_ovs_vsctl_complete_iface () { + local result + + if [ -n "${_OVS_VSCTL_PARSED_ARGS[BRIDGE]}" ]; then + result=$(_ovs_vsctl list-ifaces "${_OVS_VSCTL_PARSED_ARGS[BRIDGE]}") + else + for bridge in $(_ovs_vsctl list-br); do + local ifaces + + ifaces=$(_ovs_vsctl list-ifaces "${bridge}") + result="${result} ${ifaces}" + done + fi + printf "EO\n%s\n" "${result}" +} + +# $1 = word to complete on. +# Complete on COLUMN?:KEY=VALUE. +_ovs_vsctl_complete_column_optkey_value () { + local result column key value completion + + column=$(printf "%s\n" "$1" | cut -d '=' -f1 | cut -d':' -f1) + key=$(printf "%s\n" "$1" | cut -d '=' -f1 | cut -s -d':' -f2) + # The tr -d '\n' <<< makes sure that there are no leading or + # trailing accidental newlines. + table=$(tr -d '\n' <<< ${_OVS_VSCTL_PARSED_ARGS["TABLE"]}) + # This might also be called after add-port or add-bond; in those + # cases, the table should implicitly be assumed to be "Port". + # This is done by checking if a NEW- parameter has been + # encountered and, if it has, using that type without the NEW- as + # the table. + if [ -z "$table" ]; then + if [ -n ${_OVS_VSCTL_PARSED_ARGS["NEW-PORT"]} ] \ + || [ -n ${_OVS_VSCTL_PARSED_ARGS["NEW-BOND-PORT"]} ]; then + table="Port" + fi + fi + if [ -z "$key" ]; then + local columns=$(ovsdb-client --no-headings list-columns \ + $_OVSDB_SERVER_LOCATION Open_vSwitch $table) + + result=$(printf "%s\n" "${columns}" \ + | awk '/key.*value/ { print $1":"; next } + { print $1; next }' \ + | _ovs_vsctl_check_startswith_string "$1" | sort | uniq) + fi + if [[ $1 =~ ":" ]]; then + result=$(_ovs_vsctl_complete_key_given_table_column \ + "$key" "$table" "$column" "$column:") + fi + # If result is empty, just use user input as result. + if [ -z "$result" ]; then + result=$1 + fi + printf -- "NOSPACE\nEO\n%s\n" "${result}" +} + +# $1 = word to complete on. +# Complete on filename. +_ovs_vsctl_complete_filename () { + local result + + result=$(compgen -o filenames -A file "$1") + printf -- "EO\n%s\n" "${result}" +} + +_ovs_vsctl_complete_bridge_fail_mode () { + printf -- "EO\nstandalone\nsecure" +} + +# $1 = word to complete on. +# Complete on target. +_ovs_vsctl_complete_target () { + local result + + if [[ "$1" =~ ^p?u ]]; then + local protocol pathname expansion_base result + + protocol=$(cut -d':' -f1 <<< "$1") + pathname=$(cut -s -d':' -f2 <<< "$1") + expansion_base=$(compgen -W "unix punix" "$protocol") + expansion_base="$expansion_base:" + result=$(compgen -o filenames -A file \ + -P $expansion_base "${pathname}") + printf -- "NOSPACE\nEO\n%s\n" "${result}" + else + printf -- "NOSPACE\nEO\nssl:\ntcp:\nunix:\npssl:\nptcp:\npunix:" + fi +} + +# Extract PS1 prompt. +_ovs_vsctl_get_PS1 () { + if [ "$test" = "true" ]; then + printf -- "> " + return; + fi + + # Original inspiration from + # http://stackoverflow.com/questions/10060500/bash-how-to-evaluate-ps1-ps2, + # but changed quite a lot to make it more robust. + + # Make sure the PS1 used doesn't include any of the special + # strings used to identify the prompt + myPS1="$(sed 's/Begin prompt/\\Begin prompt/; s/End prompt/\\End prompt/' <<< "$PS1")" + # Export the current environment in case the prompt uses any + vars="$(env | cut -d'=' -f1)" + for var in $vars; do export $var; done + funcs="$(declare -F | cut -d' ' -f3)" + for func in $funcs; do export -f $func; done + # Get the prompt + v="$(bash --norc --noprofile -i 2>&1 <<< $'PS1=\"'"$myPS1"$'\" \n# Begin prompt\n# End prompt')" + v="${v##*# Begin prompt}" + printf -- "$(tail -n +2 <<< "${v%# End prompt*}" | sed 's/\\Begin prompt/Begin prompt/; s/\\End prompt/End prompt/')" + +} + +# Request a new value from user. Nothing to complete on. +_ovs_vsctl_complete_new () { + local two_word_type message result + + if [ ! "$1" = "--" ]; then + two_word_type="${2/-/ }" + message="\nEnter a ${two_word_type,,}:\n$(_ovs_vsctl_get_PS1)$COMP_LINE" + if [ -n "$1" ]; then + result="$1" + fi + printf -- "NOCOMP\nBM%sEM\nEO\n%s\n" "${message}" "${result}" + fi +} + +_ovs_vsctl_complete_dashdash () { + printf -- "EO\n%s\n" "--" +} + + +# These functions are given two arguments: +# +# $1 is the word being completed +# +# $2 is the type of completion --- only currently useful for the +# NEW-* functions. +# +# Note that the NEW-* functions actually are ``completed''; currently +# the completions are just used to save the fact that they have +# appeared for later use (i.e. implicit table calculation). +# +# The output is of the form EO, where EO stands +# for end options. Currently available options are: +# - NOSPACE: Do not add a space at the end of each completion +# - NOCOMP: Do not complete, but store the output of the completion +# func in _OVS_VSCTL_PARSED_ARGS for later usage. +# - BMEM: Print the +declare -A _OVS_VSCTL_ARG_COMPLETION_FUNCS=( + ["TABLE"]=_ovs_vsctl_complete_table + ["RECORD"]=_ovs_vsctl_complete_record + ["BRIDGE"]=_ovs_vsctl_complete_bridge + ["PARENT"]=_ovs_vsctl_complete_bridge + ["PORT"]=_ovs_vsctl_complete_port + ["KEY"]=_ovs_vsctl_complete_key + ["VALUE"]=_ovs_vsctl_complete_value + ["ARG"]=_ovs_vsctl_complete_value + ["IFACE"]=_ovs_vsctl_complete_iface + ["SYSIFACE"]=_ovs_vsctl_complete_sysiface + ["COLUMN"]=_ovs_vsctl_complete_column + ["COLUMN?:KEY"]=_ovs_vsctl_complete_column_optkey_value + ["COLUMN?:KEY=VALUE"]=_ovs_vsctl_complete_column_optkey_value + ["KEY=VALUE"]=_ovs_vsctl_complete_key_value + ["?KEY=VALUE"]=_ovs_vsctl_complete_key_value + ["PRIVATE-KEY"]=_ovs_vsctl_complete_filename + ["CERTIFICATE"]=_ovs_vsctl_complete_filename + ["CA-CERT"]=_ovs_vsctl_complete_filename + ["MODE"]=_ovs_vsctl_complete_bridge_fail_mode + ["TARGET"]=_ovs_vsctl_complete_target + ["NEW-BRIDGE"]=_ovs_vsctl_complete_new + ["NEW-PORT"]=_ovs_vsctl_complete_new + ["NEW-BOND-PORT"]=_ovs_vsctl_complete_new + ["NEW-VLAN"]=_ovs_vsctl_complete_new + ["--"]=_ovs_vsctl_complete_dashdash +) + +# $1: Argument type, may include vertical bars to mean OR +# $2: Beginning of completion +# +# Note that this checks for existance in +# _OVS_VSCTL_ARG_COMPLETION_FUNCS; if the argument type ($1) is not +# there it will fail gracefully. +_ovs_vsctl_possible_completions_of_argument () { + local possible_types completions tmp + + completions="EO" + + possible_types=$(printf "%s\n" "$1" | tr '|' '\n') + for type in $possible_types; do + if [ ${_OVS_VSCTL_ARG_COMPLETION_FUNCS["${type^^}"]} ]; then + tmp=$(${_OVS_VSCTL_ARG_COMPLETION_FUNCS["${type^^}"]} \ + "$2" "${type^^}") + tmp_noEO="${tmp#*EO}" + tmp_EO="${tmp%%EO*}" + completions=$(printf "%s%s\n%s" "${tmp_EO}" \ + "${completions}" "${tmp_noEO}") + fi + done + printf "%s\n" "${completions}" +} + +# $1 = List of argument types +# $2 = current pointer into said list +# $3 = word to complete on +# Outputs list of possible completions +# The return value is the index in the cmd_args($1) list that should +# next be matched, if only one of them did, or 254 if there are no +# matches, so it doesn't know what comes next. +_ovs_vsctl_complete_argument() { + local cmd_args arg expansion index + + new=$(printf "%s\n" "$1" | grep -- '.\+') + readarray -t cmd_args <<< "$new"; + arg=${cmd_args[$2]} + case ${arg:0:1} in + !) + expansion=$(_ovs_vsctl_possible_completions_of_argument \ + "${arg:1}" $3) + index=$(($2+1)) + ;; + \?|\*) + local tmp1 tmp2 arg2_index tmp2_noEO tmp2_EO + tmp1=$(_ovs_vsctl_possible_completions_of_argument "${arg:1}" $3) + tmp2=$(_ovs_vsctl_complete_argument "$1" "$(($2+1))" "$3") + arg2_index=$? + if _ovs_vsctl_detect_nonzero_completions "$tmp1" \ + && _ovs_vsctl_detect_nonzero_completions "$tmp2"; then + if [ "${arg:0:1}" = "*" ]; then + index=$2; + else + index=$(($2+1)); + fi + fi + if _ovs_vsctl_detect_nonzero_completions "$tmp1" \ + && (! _ovs_vsctl_detect_nonzero_completions "$tmp2"); then + if [ "${arg:0:1}" = "*" ]; then + index=$2; + else + index=$(($2+1)); + fi + fi + if (! _ovs_vsctl_detect_nonzero_completions "$tmp1") \ + && _ovs_vsctl_detect_nonzero_completions "$tmp2"; then + index=$arg2_index + fi + if (! _ovs_vsctl_detect_nonzero_completions "$tmp1") \ + && (! _ovs_vsctl_detect_nonzero_completions "$tmp2"); then + index=254 + fi + # Don't allow secondary completions to inhibit primary + # completions: + if [[ $tmp2 =~ ^([^E]|E[^O])*NOCOMP ]]; then + tmp2="" + fi + tmp2_noEO="${tmp2#*EO}" + tmp2_EO="${tmp2%%EO*}" + expansion=$(printf "%s%s\n%s" "${tmp2_EO}" \ + "${tmp1}" "${tmp2_noEO}") + ;; + esac + printf "%s\n" "$expansion" + return $index +} + +_ovs_vsctl_detect_nospace () { + if [[ $1 =~ ^([^E]|E[^O])*NOSPACE ]]; then + _OVS_VSCTL_COMP_NOSPACE=true + fi +} + +_ovs_vsctl_process_messages () { + local message + + message="${1#*BM}" + message="${message%%EM*}" + if [ "$test" = "true" ]; then + printf -- "--- BEGIN MESSAGE" + fi + printf "${message}" + if [ "$test" = "true" ]; then + printf -- "--- END MESSAGE" + fi +} + +# The general strategy here is that the same functions that decide +# completions can also capture the necessary context for later +# completions. This means that there is no distinction between the +# processing for words that are not the current word and words that +# are the current word. +# +# Parsing up until the command word happens starts with everything +# valid; as the syntax order of ovs-vsctl is fairly strict, when types +# of words that preclude other words from happending can turn them +# off; this is controlled by valid_globals, valid_opts, and +# valid_commands. given_opts is used to narrow down which commands +# are valid based on the previously given options. +# +# After the command has been detected, the parsing becomes more +# complicated. The cmd_pos variable is set to 0 when the command is +# detected; it is used as a pointer into an array of the argument +# types for that given command. The argument types are stored in both +# cmd_args and raw_cmd as the main loop uses properties of arrays to +# detect certain conditions, but arrays cannot be passed to functions. +# To be able to deal with optional or repeatable arguments, the exit +# status of the function _ovs_vsctl_complete_argument represents where +# it has determined that the next argument will be. +_ovs_vsctl_bashcomp () { + local cur valid_globals cmd_args raw_cmd cmd_pos valid_globals valid_opts + local test="false" + + # Prepare the COMP_* variables based on input. + if [ "$1" = "test" ]; then + test="true" + export COMP_LINE="ovs-vsctl $2" + tmp="ovs-vsctl"$'\n'"$(tr ' ' '\n' <<< "${COMP_LINE}x")" + tmp="${tmp%x}" + readarray -t COMP_WORDS \ + <<< "$tmp" + export COMP_WORDS + export COMP_CWORD="$((${#COMP_WORDS[@]}-1))" + fi + + # Extract the conf.db path. + db=$(sed -n 's/.*--db=\([^ ]*\).*/\1/p' <<< "$COMP_LINE") + if [ -n "$db" ]; then + _OVSDB_SERVER_LOCATION="$db" + fi + + # If having trouble accessing the database, return. + if ! _ovs_vsctl get-manager 2>/dev/null; then + return 1; + fi + + _OVS_VSCTL_PARSED_ARGS=() + _OVS_VSCTL_NEW_RECORDS=() + cmd_pos=-1 + cur=${COMP_WORDS[COMP_CWORD]} + valid_globals=true + valid_opts=true + valid_commands=true + given_opts="" + index=1 + export COMP_WORDBREAKS=" " + for word in "${COMP_WORDS[@]:1:${COMP_CWORD}} "; do + _OVS_VSCTL_COMP_NOSPACE=false + local completion + completion="" + if [ $cmd_pos -gt -1 ]; then + local tmp tmp_noop arg possible_newindex + tmp=$(_ovs_vsctl_complete_argument "$raw_cmd" "$cmd_pos" "$word") + possible_newindex=$? + # Check for nospace. + _ovs_vsctl_detect_nospace $tmp + # Remove all options. + tmp_noop="${tmp#*EO}" + + # Allow commands to specify that they should not be + # completed + if ! [[ $tmp =~ ^([^E]|E[^O])*NOCOMP ]]; then + # Directly assignment, since 'completion' is guaranteed to + # to be empty. + completion="$tmp_noop" + # If intermediate completion is empty, it means that the current + # argument is invalid. And we should not continue. + if [ $index -lt $COMP_CWORD ] \ + && (! _ovs_vsctl_detect_nonzero_completions "$completion"); then + _ovs_vsctl_process_messages "BM\nCannot complete \'${COMP_WORDS[$index]}\' at index ${index}:\n$(_ovs_vsctl_get_PS1)${COMP_LINE}EM\nEO\n" + return 1 + fi + else + # Only allow messages when there is no completion + # printout and when on the current word. + if [ $index -eq $COMP_CWORD ]; then + _ovs_vsctl_process_messages "${tmp}" + fi + # Append the new record to _OVS_VSCTL_NEW_RECORDS. + _OVS_VSCTL_NEW_RECORDS["${cmd_args[$cmd_pos]##*-}"]="${_OVS_VSCTL_NEW_RECORDS["${cmd_args[$cmd_pos]##*-}"]} $tmp_noop" + fi + if [[ $cmd_pos -lt ${#cmd_args} ]]; then + _OVS_VSCTL_PARSED_ARGS["${cmd_args[$cmd_pos]:1}"]=$word + fi + if [ $possible_newindex -lt 254 ]; then + cmd_pos=$possible_newindex + fi + fi + + if [ $valid_globals == true ]; then + tmp=$(_ovs_vsctl_bashcomp_globalopt $word) + _ovs_vsctl_detect_nospace $tmp + completion="${completion} ${tmp#*EO}" + fi + if [ $valid_opts == true ]; then + tmp=$(_ovs_vsctl_bashcomp_localopt "$given_opts" $word) + _ovs_vsctl_detect_nospace $tmp + completion="${completion} ${tmp#*EO}" + if [ $index -lt $COMP_CWORD ] \ + && _ovs_vsctl_detect_nonzero_completions "$tmp"; then + valid_globals=false + given_opts="${given_opts} ${word}" + fi + fi + if [ $valid_commands = true ]; then + tmp=$(_ovs_vsctl_bashcomp_command "$given_opts" $word) + _ovs_vsctl_detect_nospace $tmp + completion="${completion} ${tmp#*EO}" + if [ $index -lt $COMP_CWORD ] \ + && _ovs_vsctl_detect_nonzero_completions "$tmp"; then + valid_globals=false + valid_opts=false + valid_commands=false + cmd_pos=0 + raw_cmd=$(_ovs_vsctl_expand_command "$word") + readarray -t cmd_args <<< "$raw_cmd" + fi + fi + if [ "$word" = "--" ] && [ $index -lt $COMP_CWORD ]; then + # Empty the parsed args array. + _OVS_VSCTL_PARSED_AGS=() + cmd_pos=-1 + # No longer allow global options after '--'. + valid_globals=false + valid_opts=true + valid_commands=true + given_opts="" + fi + completion="$(sort -u <<< "$(tr ' ' '\n' <<< ${completion})")" + if [ $index -eq $COMP_CWORD ]; then + if [ "$test" = "true" ]; then + if [ "${_OVS_VSCTL_COMP_NOSPACE}" = "true" ]; then + printf "%s" "$completion" | sed -e '/^$/d' + else + printf "%s" "$completion" | sed -e '/^$/d; s/$/ /g' + fi + printf "\n" + else + if [ "${_OVS_VSCTL_COMP_NOSPACE}" = "true" ]; then + compopt -o nospace + COMPREPLY=( $(compgen -W "${completion}" -- $word) ) + else + compopt +o nospace + COMPREPLY=( $(compgen -W "${completion}" -- $word) ) + fi + fi + fi + index=$(($index+1)) + done +} + +if [ "$1" = "test" ]; then + _ovs_vsctl_bashcomp "$@" +else + complete -F _ovs_vsctl_bashcomp ovs-vsctl +fi diff --git a/utilities/ovs-vsctl.c b/utilities/ovs-vsctl.c index 84fca8a17d6..af1cfc0d539 100644 --- a/utilities/ovs-vsctl.c +++ b/utilities/ovs-vsctl.c @@ -63,6 +63,11 @@ struct vsctl_command_syntax { int min_args; /* Min number of arguments following name. */ int max_args; /* Max number of arguments following name. */ + /* Names that roughly describe the arguments that the command + * uses. These should be similar to the names displayed in the + * man page or in the help output. */ + const char *arguments; + /* If nonnull, calls ovsdb_idl_add_column() or ovsdb_idl_add_table() for * each column or table in ctx->idl that it uses. */ void (*prerequisites)(struct vsctl_context *ctx); @@ -85,6 +90,7 @@ struct vsctl_command_syntax { /* A comma-separated list of supported options, e.g. "--a,--b", or the * empty string if the command does not support any options. */ const char *options; + enum { RO, RW } mode; /* Does this command modify the database? */ }; @@ -141,6 +147,8 @@ OVS_NO_RETURN static void vsctl_exit(int status); OVS_NO_RETURN static void vsctl_fatal(const char *, ...) OVS_PRINTF_FORMAT(1, 2); static char *default_db(void); OVS_NO_RETURN static void usage(void); +OVS_NO_RETURN static void print_vsctl_commands(void); +OVS_NO_RETURN static void print_vsctl_options(const struct option *options); static void parse_options(int argc, char *argv[], struct shash *local_options); static bool might_write_to_db(char **argv); @@ -292,6 +300,8 @@ parse_options(int argc, char *argv[], struct shash *local_options) OPT_PEER_CA_CERT, OPT_LOCAL, OPT_RETRY, + OPT_COMMANDS, + OPT_OPTIONS, VLOG_OPTION_ENUMS, TABLE_OPTION_ENUMS }; @@ -304,6 +314,8 @@ parse_options(int argc, char *argv[], struct shash *local_options) {"timeout", required_argument, NULL, 't'}, {"retry", no_argument, NULL, OPT_RETRY}, {"help", no_argument, NULL, 'h'}, + {"commands", no_argument, NULL, OPT_COMMANDS}, + {"options", no_argument, NULL, OPT_OPTIONS}, {"version", no_argument, NULL, 'V'}, VLOG_LONG_OPTIONS, TABLE_LONG_OPTIONS, @@ -418,6 +430,12 @@ parse_options(int argc, char *argv[], struct shash *local_options) case 'h': usage(); + case OPT_COMMANDS: + print_vsctl_commands(); + + case OPT_OPTIONS: + print_vsctl_options(global_long_options); + case 'V': ovs_print_version(0, 0); printf("DB Schema %s\n", ovsrec_get_db_version()); @@ -734,6 +752,132 @@ Other options:\n\ exit(EXIT_SUCCESS); } +/* Converts the command arguments into format that can be parsed by + * bash completion script. + * + * Therein, arguments will be attached with following prefixes: + * + * !argument :: The argument is required + * ?argument :: The argument is optional + * *argument :: The argument may appear any number (0 or more) times + * +argument :: The argument may appear one or more times + * + */ +static void +print_command_arguments(const struct vsctl_command_syntax *command) +{ + /* + * The argument string is parsed in reverse. We use a stack 'oew_stack' to + * keep track of nested optionals. Whenever a ']' is encountered, we push + * a bit to 'oew_stack'. The bit is set to 1 if the ']' is not nested. + * Subsequently, we pop an entry everytime '[' is met. + * + * We use 'whole_word_is_optional' value to decide whether or not a ! or + + * should be added on encountering a space: if the optional surrounds the + * whole word then it shouldn't be, but if it is only a part of the word + * (i.e. [key=]value), it should be. + */ + uint32_t oew_stack = 0; + + const char *arguments = command->arguments; + int length = strlen(arguments); + if (!length) { + return; + } + + /* Output buffer, written backward from end. */ + char *output = xmalloc(2 * length); + char *outp = output + 2 * length; + *--outp = '\0'; + + bool in_repeated = false; + bool whole_word_is_optional = false; + + for (const char *inp = arguments + length; inp > arguments; ) { + switch (*--inp) { + case ']': + oew_stack <<= 1; + if (inp[1] == '\0' || inp[1] == ' ' || inp[1] == '.') { + oew_stack |= 1; + } + break; + case '[': + /* Checks if the whole word is optional, and sets the + * 'whole_word_is_optional' accordingly. */ + if ((inp == arguments || inp[-1] == ' ') && oew_stack & 1) { + *--outp = in_repeated ? '*' : '?'; + whole_word_is_optional = true; + } else { + *--outp = '?'; + whole_word_is_optional = false; + } + oew_stack >>= 1; + break; + case ' ': + if (!whole_word_is_optional) { + *--outp = in_repeated ? '+' : '!'; + } + *--outp = ' '; + in_repeated = false; + whole_word_is_optional = false; + break; + case '.': + in_repeated = true; + break; + default: + *--outp = *inp; + break; + } + } + if (arguments[0] != '[' && outp != output + 2 * length - 1) { + *--outp = in_repeated ? '+' : '!'; + } + printf("%s", outp); + free(output); +} + +static void +print_vsctl_commands(void) +{ + const struct vsctl_command_syntax *p; + + for (p = get_all_commands(); p->name; p++) { + char *options = xstrdup(p->options); + char *options_begin = options; + char *item; + + for (item = strsep(&options, ","); item != NULL; + item = strsep(&options, ",")) { + if (item[0] != '\0') { + printf("[%s] ", item); + } + } + printf(",%s,", p->name); + print_command_arguments(p); + printf("\n"); + + free(options_begin); + } + + exit(EXIT_SUCCESS); +} + +static void +print_vsctl_options(const struct option *options) +{ + for (; options->name; options++) { + const struct option *o = options; + + printf("--%s%s\n", o->name, o->has_arg ? "=ARG" : ""); + if (o->flag == NULL && o->val > 0 && o->val <= UCHAR_MAX) { + printf("-%c%s\n", o->val, o->has_arg ? " ARG" : ""); + } + } + + exit(EXIT_SUCCESS); +} + + static char * default_db(void) { @@ -3732,6 +3876,7 @@ cmd_remove(struct vsctl_context *ctx) rm_type.n_max = UINT_MAX; error = ovsdb_datum_from_string(&rm, &rm_type, ctx->argv[i], ctx->symtab); + if (error) { if (ovsdb_type_is_map(&rm_type)) { rm_type.value.type = OVSDB_TYPE_VOID; @@ -4448,82 +4593,125 @@ do_vsctl(const char *args, struct vsctl_command *commands, size_t n_commands, free(error); } +/* + * Developers who add new commands to the 'struct vsctl_command_syntax' must + * define the 'arguments' member of the struct. The following keywords are + * available for composing the argument format: + * + * TABLE RECORD BRIDGE PARENT PORT + * KEY VALUE ARG KEY=VALUE ?KEY=VALUE + * IFACE SYSIFACE COLUMN COLUMN?:KEY COLUMN?:KEY=VALUE + * MODE CA-CERT CERTIFICATE PRIVATE-KEY + * TARGET NEW-* (e.g. NEW-PORT) + * + * For argument types not listed above, just uses 'ARG' as place holder. + * + * Encloses the keyword with '[]' if it is optional. Appends '...' to + * keyword or enclosed keyword to indicate that the argument can be specified + * multiple times. + * + * */ static const struct vsctl_command_syntax all_commands[] = { /* Open vSwitch commands. */ - {"init", 0, 0, NULL, cmd_init, NULL, "", RW}, - {"show", 0, 0, pre_cmd_show, cmd_show, NULL, "", RO}, + {"init", 0, 0, "", NULL, cmd_init, NULL, "", RW}, + {"show", 0, 0, "", pre_cmd_show, cmd_show, NULL, "", RO}, /* Bridge commands. */ - {"add-br", 1, 3, pre_get_info, cmd_add_br, NULL, "--may-exist", RW}, - {"del-br", 1, 1, pre_get_info, cmd_del_br, NULL, "--if-exists", RW}, - {"list-br", 0, 0, pre_get_info, cmd_list_br, NULL, "--real,--fake", RO}, - {"br-exists", 1, 1, pre_get_info, cmd_br_exists, NULL, "", RO}, - {"br-to-vlan", 1, 1, pre_get_info, cmd_br_to_vlan, NULL, "", RO}, - {"br-to-parent", 1, 1, pre_get_info, cmd_br_to_parent, NULL, "", RO}, - {"br-set-external-id", 2, 3, pre_cmd_br_set_external_id, - cmd_br_set_external_id, NULL, "", RW}, - {"br-get-external-id", 1, 2, pre_cmd_br_get_external_id, + {"add-br", 1, 3, "NEW-BRIDGE [PARENT] [NEW-VLAN]", pre_get_info, + cmd_add_br, NULL, "--may-exist", RW}, + {"del-br", 1, 1, "BRIDGE", pre_get_info, cmd_del_br, + NULL, "--if-exists", RW}, + {"list-br", 0, 0, "", pre_get_info, cmd_list_br, NULL, "--real,--fake", + RO}, + {"br-exists", 1, 1, "BRIDGE", pre_get_info, cmd_br_exists, NULL, "", RO}, + {"br-to-vlan", 1, 1, "BRIDGE", pre_get_info, cmd_br_to_vlan, NULL, "", + RO}, + {"br-to-parent", 1, 1, "BRIDGE", pre_get_info, cmd_br_to_parent, NULL, + "", RO}, + {"br-set-external-id", 2, 3, "BRIDGE KEY [VALUE]", + pre_cmd_br_set_external_id, cmd_br_set_external_id, NULL, "", RW}, + {"br-get-external-id", 1, 2, "BRIDGE [KEY]", pre_cmd_br_get_external_id, cmd_br_get_external_id, NULL, "", RO}, /* Port commands. */ - {"list-ports", 1, 1, pre_get_info, cmd_list_ports, NULL, "", RO}, - {"add-port", 2, INT_MAX, pre_get_info, cmd_add_port, NULL, "--may-exist", - RW}, - {"add-bond", 4, INT_MAX, pre_get_info, cmd_add_bond, NULL, - "--may-exist,--fake-iface", RW}, - {"del-port", 1, 2, pre_get_info, cmd_del_port, NULL, + {"list-ports", 1, 1, "BRIDGE", pre_get_info, cmd_list_ports, NULL, "", + RO}, + {"add-port", 2, INT_MAX, "BRIDGE NEW-PORT [COLUMN[:KEY]=VALUE]...", + pre_get_info, cmd_add_port, NULL, "--may-exist", RW}, + {"add-bond", 4, INT_MAX, + "BRIDGE NEW-BOND-PORT SYSIFACE... [COLUMN[:KEY]=VALUE]...", pre_get_info, + cmd_add_bond, NULL, "--may-exist,--fake-iface", RW}, + {"del-port", 1, 2, "[BRIDGE] PORT|IFACE", pre_get_info, cmd_del_port, NULL, "--if-exists,--with-iface", RW}, - {"port-to-br", 1, 1, pre_get_info, cmd_port_to_br, NULL, "", RO}, + {"port-to-br", 1, 1, "PORT", pre_get_info, cmd_port_to_br, NULL, "", RO}, /* Interface commands. */ - {"list-ifaces", 1, 1, pre_get_info, cmd_list_ifaces, NULL, "", RO}, - {"iface-to-br", 1, 1, pre_get_info, cmd_iface_to_br, NULL, "", RO}, + {"list-ifaces", 1, 1, "BRIDGE", pre_get_info, cmd_list_ifaces, NULL, "", + RO}, + {"iface-to-br", 1, 1, "IFACE", pre_get_info, cmd_iface_to_br, NULL, "", + RO}, /* Controller commands. */ - {"get-controller", 1, 1, pre_controller, cmd_get_controller, NULL, "", RO}, - {"del-controller", 1, 1, pre_controller, cmd_del_controller, NULL, "", RW}, - {"set-controller", 1, INT_MAX, pre_controller, cmd_set_controller, NULL, + {"get-controller", 1, 1, "BRIDGE", pre_controller, cmd_get_controller, + NULL, "", RO}, + {"del-controller", 1, 1, "BRIDGE", pre_controller, cmd_del_controller, + NULL, "", RW}, + {"set-controller", 1, INT_MAX, "BRIDGE TARGET...", pre_controller, + cmd_set_controller, NULL, "", RW}, + {"get-fail-mode", 1, 1, "BRIDGE", pre_get_info, cmd_get_fail_mode, NULL, + "", RO}, + {"del-fail-mode", 1, 1, "BRIDGE", pre_get_info, cmd_del_fail_mode, NULL, "", RW}, - {"get-fail-mode", 1, 1, pre_get_info, cmd_get_fail_mode, NULL, "", RO}, - {"del-fail-mode", 1, 1, pre_get_info, cmd_del_fail_mode, NULL, "", RW}, - {"set-fail-mode", 2, 2, pre_get_info, cmd_set_fail_mode, NULL, "", RW}, + {"set-fail-mode", 2, 2, "BRIDGE MODE", pre_get_info, cmd_set_fail_mode, + NULL, "", RW}, /* Manager commands. */ - {"get-manager", 0, 0, pre_manager, cmd_get_manager, NULL, "", RO}, - {"del-manager", 0, 0, pre_manager, cmd_del_manager, NULL, "", RW}, - {"set-manager", 1, INT_MAX, pre_manager, cmd_set_manager, NULL, "", RW}, + {"get-manager", 0, 0, "", pre_manager, cmd_get_manager, NULL, "", RO}, + {"del-manager", 0, 0, "", pre_manager, cmd_del_manager, NULL, "", RW}, + {"set-manager", 1, INT_MAX, "TARGET...", pre_manager, cmd_set_manager, + NULL, "", RW}, /* SSL commands. */ - {"get-ssl", 0, 0, pre_cmd_get_ssl, cmd_get_ssl, NULL, "", RO}, - {"del-ssl", 0, 0, pre_cmd_del_ssl, cmd_del_ssl, NULL, "", RW}, - {"set-ssl", 3, 3, pre_cmd_set_ssl, cmd_set_ssl, NULL, "--bootstrap", RW}, + {"get-ssl", 0, 0, "", pre_cmd_get_ssl, cmd_get_ssl, NULL, "", RO}, + {"del-ssl", 0, 0, "", pre_cmd_del_ssl, cmd_del_ssl, NULL, "", RW}, + {"set-ssl", 3, 3, "PRIVATE-KEY CERTIFICATE CA-CERT", pre_cmd_set_ssl, + cmd_set_ssl, NULL, "--bootstrap", RW}, /* Auto Attach commands. */ - {"add-aa-mapping", 3, 3, pre_get_info, cmd_add_aa_mapping, NULL, "", RW}, - {"del-aa-mapping", 3, 3, pre_aa_mapping, cmd_del_aa_mapping, NULL, "", RW}, - {"get-aa-mapping", 1, 1, pre_aa_mapping, cmd_get_aa_mapping, NULL, "", RO}, + {"add-aa-mapping", 3, 3, "BRIDGE ARG ARG", pre_get_info, cmd_add_aa_mapping, + NULL, "", RW}, + {"del-aa-mapping", 3, 3, "BRIDGE ARG ARG", pre_aa_mapping, cmd_del_aa_mapping, + NULL, "", RW}, + {"get-aa-mapping", 1, 1, "BRIDGE", pre_aa_mapping, cmd_get_aa_mapping, + NULL, "", RO}, /* Switch commands. */ - {"emer-reset", 0, 0, pre_cmd_emer_reset, cmd_emer_reset, NULL, "", RW}, + {"emer-reset", 0, 0, "", pre_cmd_emer_reset, cmd_emer_reset, NULL, "", RW}, /* Database commands. */ - {"comment", 0, INT_MAX, NULL, NULL, NULL, "", RO}, - {"get", 2, INT_MAX, pre_cmd_get, cmd_get, NULL, "--if-exists,--id=", RO}, - {"list", 1, INT_MAX, pre_cmd_list, cmd_list, NULL, + {"comment", 0, INT_MAX, "[ARG]...", NULL, NULL, NULL, "", RO}, + {"get", 2, INT_MAX, "TABLE RECORD [COLUMN[:KEY]]...",pre_cmd_get, cmd_get, + NULL, "--if-exists,--id=", RO}, + {"list", 1, INT_MAX, "TABLE [RECORD]...", pre_cmd_list, cmd_list, NULL, "--if-exists,--columns=", RO}, - {"find", 1, INT_MAX, pre_cmd_find, cmd_find, NULL, "--columns=", RO}, - {"set", 3, INT_MAX, pre_cmd_set, cmd_set, NULL, "--if-exists", RW}, - {"add", 4, INT_MAX, pre_cmd_add, cmd_add, NULL, "--if-exists", RW}, - {"remove", 4, INT_MAX, pre_cmd_remove, cmd_remove, NULL, "--if-exists", - RW}, - {"clear", 3, INT_MAX, pre_cmd_clear, cmd_clear, NULL, "--if-exists", RW}, - {"create", 2, INT_MAX, pre_create, cmd_create, post_create, "--id=", RW}, - {"destroy", 1, INT_MAX, pre_cmd_destroy, cmd_destroy, NULL, - "--if-exists,--all", RW}, - {"wait-until", 2, INT_MAX, pre_cmd_wait_until, cmd_wait_until, NULL, "", - RO}, - - {NULL, 0, 0, NULL, NULL, NULL, NULL, RO}, + {"find", 1, INT_MAX, "TABLE [COLUMN[:KEY]=VALUE]...", pre_cmd_find, + cmd_find, NULL, "--columns=", RO}, + {"set", 3, INT_MAX, "TABLE RECORD COLUMN[:KEY]=VALUE...", pre_cmd_set, + cmd_set, NULL, "--if-exists", RW}, + {"add", 4, INT_MAX, "TABLE RECORD COLUMN [KEY=]VALUE...", pre_cmd_add, + cmd_add, NULL, "--if-exists", RW}, + {"remove", 4, INT_MAX, "TABLE RECORD COLUMN KEY|VALUE|KEY=VALUE...", + pre_cmd_remove, cmd_remove, NULL, "--if-exists", RW}, + {"clear", 3, INT_MAX, "TABLE RECORD COLUMN...", pre_cmd_clear, cmd_clear, + NULL, "--if-exists", RW}, + {"create", 2, INT_MAX, "TABLE COLUMN[:KEY]=VALUE...", pre_create, + cmd_create, post_create, "--id=", RW}, + {"destroy", 1, INT_MAX, "TABLE [RECORD]...", pre_cmd_destroy, cmd_destroy, + NULL, "--if-exists,--all", RW}, + {"wait-until", 2, INT_MAX, "TABLE RECORD [COLUMN[:KEY]=VALUE]...", + pre_cmd_wait_until, cmd_wait_until, NULL, "", RO}, + + {NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, RO}, }; static const struct vsctl_command_syntax *get_all_commands(void)