To aid users constructing a valid ethtool invocation, create a
[bash-completion] script to provide [programmable completion] of ethtool
arguments.  It supports all current command options.

The script is placed in shell-completion/bash and installed to
completionsdir from pkg-config for bash-completion, similar to [kmod].
It requires pkg-config 0.18 or later to be installed on the build
system which runs aclocal (for the PKG_CHECK_MODULES m4 macro).

Note: In [scop/bash-completion#289] the bash-completion maintainer
suggested shipping this completion with ethtool rather than
bash-completion, due to assumptions about the ethtool command-line
format made by the script.  That pull request also contains an extensive
test suite in Python which is not included in this commit, but may be
ported to a format suitable for inclusion if there is sufficient
interest and agreement about how to achieve that.

[bash-completion]: https://github.com/scop/bash-completion
[programmable completion]: 
https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html
[kmod]: https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git/tree/
[scop/bash-completion#289]: https://github.com/scop/bash-completion/pull/289

Signed-off-by: Kevin Locke <ke...@kevinlocke.name>
---
 Makefile.am                   |    5 +
 configure.ac                  |   15 +
 shell-completion/bash/ethtool | 1251 +++++++++++++++++++++++++++++++++
 3 files changed, 1271 insertions(+)
 create mode 100644 shell-completion/bash/ethtool

diff --git a/Makefile.am b/Makefile.am
index 0a2fd29..3af4d4c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -17,6 +17,11 @@ ethtool_SOURCES += \
                  ixgbevf.c tse.c vmxnet3.c qsfp.c qsfp.h fjes.c lan78xx.c
 endif
 
+if ENABLE_BASH_COMPLETION
+bashcompletiondir = $(BASH_COMPLETION_DIR)
+dist_bashcompletion_DATA = shell-completion/bash/ethtool
+endif
+
 TESTS = test-cmdline test-features
 check_PROGRAMS = test-cmdline test-features
 test_cmdline_SOURCES = test-cmdline.c test-common.c $(ethtool_SOURCES) 
diff --git a/configure.ac b/configure.ac
index 4e5477a..f84540a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -40,5 +40,20 @@ if test x$enable_pretty_dump = xyes; then
 fi
 AM_CONDITIONAL([ETHTOOL_ENABLE_PRETTY_DUMP], [test x$enable_pretty_dump = 
xyes])
 
+AC_ARG_WITH([bash-completion-dir],
+           AS_HELP_STRING([--with-bash-completion-dir[=PATH]],
+                          [Install the bash-completion script in this 
directory. @<:@default=yes@:>@]),
+           [],
+           [with_bash_completion_dir=yes])
+AS_IF([test "x$with_bash_completion_dir" = xyes],
+      [PKG_CHECK_MODULES([BASH_COMPLETION],
+                        [bash-completion],
+                        [BASH_COMPLETION_DIR="`$PKG_CONFIG 
--variable=completionsdir bash-completion`"],
+                        
[BASH_COMPLETION_DIR="$datadir/bash-completion/completions"])],
+      [BASH_COMPLETION_DIR="$with_bash_completion_dir"])
+AC_SUBST([BASH_COMPLETION_DIR])
+AM_CONDITIONAL([ENABLE_BASH_COMPLETION],
+              [test "x$with_bash_completion_dir" != xno])
+
 AC_CONFIG_FILES([Makefile ethtool.spec ethtool.8])
 AC_OUTPUT
diff --git a/shell-completion/bash/ethtool b/shell-completion/bash/ethtool
new file mode 100644
index 0000000..5305559
--- /dev/null
+++ b/shell-completion/bash/ethtool
@@ -0,0 +1,1251 @@
+# bash completion for ethtool(8)                          -*- shell-script -*-
+# shellcheck shell=bash disable=SC2207
+
+# Complete a word representing a set of characters.
+# @param $@ chars      Characters which may be present in completed set.
+_ethtool_compgen_letterset()
+{
+       local char
+       for char; do
+               case "$cur" in
+                       *"$char"*)
+                               # $cur already contains $char
+                               ;;
+                       *)
+                               COMPREPLY+=( "$cur$char" )
+                               ;;
+               esac
+       done
+}
+
+# Generate completions for words matched case-insensitively
+# @param $@ choices    Completion choices.
+_ethtool_compgen_nocase()
+{
+       local reset
+       reset=$( shopt -p nocasematch )
+       shopt -s nocasematch
+
+       local choice
+       for choice; do
+               case "$choice" in
+                       "$cur"*) COMPREPLY+=( "$choice" ) ;;
+               esac
+       done
+
+       $reset
+}
+
+# Gets names from a section of ethtool output.
+# @param $1 section_bre        POSIX BRE matching section heading (without : 
at end).
+# @param $@            ethtool arguments
+_ethtool_get_names_in_section()
+{
+       local section_bre="$1"
+       shift
+
+       PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin" \
+               ethtool "$@" 2>/dev/null |
+               command sed -n "
+# Line is section heading iff it ends with :
+# From requested section heading to next section heading
+/^$section_bre:$/,/:$/ {
+       # If line is section heading, ignore it
+       /:$/d
+       # Remove value and separator, if present
+       s/[[:space:]]*:.*//
+       # Remove leading space, if present
+       s/^[[:space:]]*//
+       # Print the line
+       p
+}"
+}
+
+# Complete an RSS Context ID
+_ethtool_context()
+{
+       COMPREPLY=(
+               $(PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin" \
+                       ethtool --show-nfc "${words[2]}" 2>/dev/null |
+                       command sed -n 's/^[[:space:]]*RSS Context 
ID:[[:space:]]*\([0-9]*\)$/\1/p' |
+                       sort -u) )
+}
+
+# Complete a network flow traffic type
+# Available OPTIONS:
+#       --hash  Complete only types suitable for rx hashing
+_ethtool_flow_type()
+{
+       local types='ah4 ah6 esp4 esp6 ether sctp4 sctp6 tcp4 tcp6 udp4 udp6'
+       if [ "${1-}" != --hash ]; then
+               types="$types ip4 ip6"
+       fi
+       COMPREPLY=( $( compgen -W "$types" -- "$cur" ) )
+}
+
+# Completion for ethtool --change
+_ethtool_change()
+{
+       local -A settings=(
+               [advertise]=notseen
+               [autoneg]=notseen
+               [duplex]=notseen
+               [mdix]=notseen
+               [msglvl]=notseen
+               [port]=notseen
+               [phyad]=notseen
+               [speed]=notseen
+               [wol]=notseen
+               [xcvr]=notseen
+       )
+
+       local -A msgtypes=(
+               [drv]=notseen
+               [hw]=notseen
+               [ifdown]=notseen
+               [ifup]=notseen
+               [intr]=notseen
+               [link]=notseen
+               [pktdata]=notseen
+               [probe]=notseen
+               [rx_err]=notseen
+               [rx_status]=notseen
+               [timer]=notseen
+               [tx_done]=notseen
+               [tx_err]=notseen
+               [tx_queued]=notseen
+               [wol]=notseen
+       )
+
+       # Mark seen settings and msgtypes, and whether in msglvl sub-command
+       local in_msglvl=
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               if [ "$in_msglvl" ] && [ "${msgtypes[$word]+set}" ]; then
+                       msgtypes[$word]=seen
+               elif [ "${settings[$word]+set}" ]; then
+                       settings[$word]=seen
+                       if [ "$word" = msglvl ]; then
+                               in_msglvl=1
+                       else
+                               in_msglvl=
+                       fi
+               fi
+       done
+
+       if [ "$in_msglvl" ] && [ "${msgtypes[$prev]+set}" ]; then
+               # All msgtypes take an on/off argument
+               COMPREPLY=( $( compgen -W 'on off' -- "$cur" ) )
+               return
+       fi
+
+       case "$prev" in
+               advertise)
+                       # Hex number
+                       return ;;
+               autoneg)
+                       COMPREPLY=( $( compgen -W 'on off' -- "$cur" ) )
+                       return ;;
+               duplex)
+                       COMPREPLY=( $( compgen -W 'half full' -- "$cur" ) )
+                       return ;;
+               mdix)
+                       COMPREPLY=( $( compgen -W 'auto on off' -- "$cur" ) )
+                       return ;;
+               msglvl)
+                       # Unsigned integer or msgtype
+                       COMPREPLY=( $( compgen -W "${!msgtypes[*]}" -- "$cur" ) 
)
+                       return ;;
+               port)
+                       COMPREPLY=( $( compgen -W 'aui bnc fibre mii tp' -- 
"$cur" ) )
+                       return ;;
+               phyad)
+                       # Integer
+                       return ;;
+               sopass)
+                       _mac_addresses
+                       return ;;
+               speed)
+                       # Number
+                       return ;;
+               wol)
+                       # $cur is a set of wol type characters.
+                       _ethtool_compgen_letterset p u m b a g s f d
+                       return ;;
+               xcvr)
+                       COMPREPLY=( $( compgen -W 'internal external' -- "$cur" 
) )
+                       return ;;
+       esac
+
+       local -a comp_words=()
+
+       # Add settings not seen to completions
+       local setting
+       for setting in "${!settings[@]}"; do
+               if [ "${settings[$setting]}" = notseen ]; then
+                       comp_words+=( "$setting" )
+               fi
+       done
+
+       # Add settings not seen to completions
+       if [ "$in_msglvl" ]; then
+               local msgtype
+               for msgtype in "${!msgtypes[@]}"; do
+                       if [ "${msgtypes[$msgtype]}" = notseen ]; then
+                               comp_words+=( "$msgtype" )
+                       fi
+               done
+       fi
+
+       COMPREPLY=( $( compgen -W "${comp_words[*]}" -- "$cur" ) )
+}
+
+# Completion for ethtool --change-eeprom
+_ethtool_change_eeprom()
+{
+       local -A settings=(
+               [length]=1
+               [magic]=1
+               [offset]=1
+               [value]=1
+       )
+
+       if [ "${settings[$prev]+set}" ]; then
+               # All settings take an unsigned integer argument
+               return
+       fi
+
+       # Remove settings which have been seen
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               unset "settings[$word]"
+       done
+
+       COMPREPLY=( $( compgen -W "${!settings[*]}" -- "$cur" ) )
+}
+
+# Completion for ethtool --coalesce
+_ethtool_coalesce()
+{
+       local -A settings=(
+               [adaptive-rx]=1
+               [adaptive-tx]=1
+               [pkt-rate-high]=1
+               [pkt-rate-low]=1
+               [rx-frames]=1
+               [rx-frames-high]=1
+               [rx-frames-irq]=1
+               [rx-frames-low]=1
+               [rx-usecs]=1
+               [rx-usecs-high]=1
+               [rx-usecs-irq]=1
+               [rx-usecs-low]=1
+               [sample-interval]=1
+               [stats-block-usecs]=1
+               [tx-frames]=1
+               [tx-frames-high]=1
+               [tx-frames-irq]=1
+               [tx-frames-low]=1
+               [tx-usecs]=1
+               [tx-usecs-high]=1
+               [tx-usecs-irq]=1
+               [tx-usecs-low]=1
+       )
+
+       case "$prev" in
+               adaptive-rx|\
+               adaptive-tx)
+                       COMPREPLY=( $( compgen -W 'on off' -- "$cur" ) )
+                       return ;;
+       esac
+
+       if [ "${settings[$prev]+set}" ]; then
+               # Unsigned integer
+               return
+       fi
+
+       # Remove settings which have been seen
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               unset "settings[$word]"
+       done
+
+       COMPREPLY=( $( compgen -W "${!settings[*]}" -- "$cur" ) )
+}
+
+# Completion for ethtool --config-nfc <devname> flow-type
+_ethtool_config_nfc_flow_type()
+{
+       if [ "$cword" -eq 4 ]; then
+               _ethtool_flow_type --spec
+               return
+       fi
+
+       case "$prev" in
+               context)
+                       _ethtool_context
+                       return ;;
+               dst|\
+               dst-mac|\
+               src)
+                       # TODO: Complete only local for dst and remote for src
+                       _mac_addresses
+                       return ;;
+               dst-ip)
+                       # Note: RX classification, so dst is usually local
+                       case "${words[4]}" in
+                               *4) _ip_addresses -4 return ;;
+                               *6) _ip_addresses -6 return ;;
+                       esac
+                       return ;;
+               src-ip)
+                       # Note: RX classification, so src is usually remote
+                       # TODO: Remote IP addresses (ARP cache + /etc/hosts + ?)
+                       return ;;
+               m|\
+               *-mask)
+                       # MAC, IP, or integer bitmask
+                       return ;;
+       esac
+
+       local -A settings=(
+               [action]=1
+               [context]=1
+               [loc]=1
+               [queue]=1
+               [vf]=1
+       )
+
+       if [ "${settings[$prev]+set}" ]; then
+               # Integer
+               return
+       fi
+
+       case "${words[4]}" in
+               ah4|\
+               esp4)
+                       local -A fields=(
+                               [dst-ip]=1
+                               [dst-mac]=1
+                               [spi]=1
+                               [src-ip]=1
+                               [tos]=1
+                               [user-def]=1
+                               [vlan-etype]=1
+                               [vlan]=1
+                       )
+                       ;;
+               ah6|\
+               esp6)
+                       local -A fields=(
+                               [dst-ip]=1
+                               [dst-mac]=1
+                               [spi]=1
+                               [src-ip]=1
+                               [tclass]=1
+                               [user-def]=1
+                               [vlan-etype]=1
+                               [vlan]=1
+                       )
+                       ;;
+               ether)
+                       local -A fields=(
+                               [dst]=1
+                               [proto]=1
+                               [src]=1
+                               [user-def]=1
+                               [vlan-etype]=1
+                               [vlan]=1
+                       )
+                       ;;
+               ip4)
+                       local -A fields=(
+                               [dst-ip]=1
+                               [dst-mac]=1
+                               [dst-port]=1
+                               [l4data]=1
+                               [l4proto]=1
+                               [spi]=1
+                               [src-ip]=1
+                               [src-port]=1
+                               [tos]=1
+                               [user-def]=1
+                               [vlan-etype]=1
+                               [vlan]=1
+                       )
+                       ;;
+               ip6)
+                       local -A fields=(
+                               [dst-ip]=1
+                               [dst-mac]=1
+                               [dst-port]=1
+                               [l4data]=1
+                               [l4proto]=1
+                               [spi]=1
+                               [src-ip]=1
+                               [src-port]=1
+                               [tclass]=1
+                               [user-def]=1
+                               [vlan-etype]=1
+                               [vlan]=1
+                       )
+                       ;;
+               sctp4|\
+               tcp4|\
+               udp4)
+                       local -A fields=(
+                               [dst-ip]=1
+                               [dst-mac]=1
+                               [dst-port]=1
+                               [src-ip]=1
+                               [src-port]=1
+                               [tos]=1
+                               [user-def]=1
+                               [vlan-etype]=1
+                               [vlan]=1
+                       )
+                       ;;
+               sctp6|\
+               tcp6|\
+               udp6)
+                       local -A fields=(
+                               [dst-ip]=1
+                               [dst-mac]=1
+                               [dst-port]=1
+                               [src-ip]=1
+                               [src-port]=1
+                               [tclass]=1
+                               [user-def]=1
+                               [vlan-etype]=1
+                               [vlan]=1
+                       )
+                       ;;
+               *)
+                       return ;;
+       esac
+
+       if [ "${fields[$prev]+set}" ]; then
+               # Integer
+               return
+       fi
+
+       # If the previous 2 words were a field+value, suggest a mask
+       local mask=
+       if [ "${fields[${words[$cword-2]}]+set}" ]; then
+               mask="m ${words[$cword-2]}-mask"
+       fi
+
+       # Remove fields and settings which have been seen
+       local word
+       for word in "${words[@]:5:${#words[@]}-6}"; do
+               unset "fields[$word]" "settings[$word]"
+       done
+
+       # Remove mutually-exclusive options
+       if ! [ "${settings[action]+set}" ]; then
+               unset 'settings[queue]' 'settings[vf]'
+       fi
+       if ! [ "${settings[queue]+set}" ]; then
+               unset 'settings[action]'
+       fi
+       if ! [ "${settings[vf]+set}" ]; then
+               unset 'settings[action]'
+       fi
+
+       COMPREPLY=( $( compgen -W "$mask ${!fields[*]} ${!settings[*]}" -- 
"$cur" ) )
+}
+
+# Completion for ethtool --config-nfc
+_ethtool_config_nfc()
+{
+       if [ "$cword" -eq 3 ]; then
+               COMPREPLY=( $( compgen -W 'delete flow-type rx-flow-hash' -- 
"$cur" ) )
+               return
+       fi
+
+       case "${words[3]}" in
+               delete)
+                       # Unsigned integer
+                       return ;;
+               flow-type)
+                       _ethtool_config_nfc_flow_type
+                       return ;;
+               rx-flow-hash)
+                       case "$cword" in
+                               4)
+                                       _ethtool_flow_type --hash
+                                       return ;;
+                               5)
+                                       _ethtool_compgen_letterset m v t s d f 
n r
+                                       return ;;
+                               6)
+                                       COMPREPLY=( $( compgen -W context -- 
"$cur" ) )
+                                       return ;;
+                               7)
+                                       _ethtool_context
+                                       return ;;
+                       esac
+                       return ;;
+       esac
+}
+
+# Completion for ethtool --eeprom-dump
+_ethtool_eeprom_dump()
+{
+       local -A settings=(
+               [length]=1
+               [offset]=1
+               [raw]=1
+       )
+
+       if [ "$prev" = raw ]; then
+               COMPREPLY=( $( compgen -W 'on off' -- "$cur" ) )
+               return
+       fi
+
+       if [ "${settings[$prev]+set}" ]; then
+               # Unsigned integer argument
+               return
+       fi
+
+       # Remove settings which have been seen
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               unset "settings[$word]"
+       done
+
+       COMPREPLY=( $( compgen -W "${!settings[*]}" -- "$cur" ) )
+}
+
+# Completion for ethtool --features
+_ethtool_features()
+{
+       local -A abbreviations=(
+               [generic-receive-offload]=gro
+               [generic-segmentation-offload]=gso
+               [large-receive-offload]=lro
+               [ntuple-filters]=ntuple
+               [receive-hashing]=rxhash
+               [rx-checksumming]=rx
+               [rx-vlan-offload]=rxvlan
+               [scatter-gather]=sg
+               [tcp-segmentation-offload]=tso
+               [tx-checksumming]=tx
+               [tx-vlan-offload]=txvlan
+               [udp-fragmentation-offload]=ufo
+       )
+
+       local -A features=()
+       local feature status fixed
+       # shellcheck disable=SC2034
+       while read -r feature status fixed; do
+               if [ -z "$feature" ]; then
+                       # Ignore blank line from empty expansion in 
here-document
+                       continue
+               fi
+
+               if [ "$feature" = Features ]; then
+                       # Ignore heading
+                       continue
+               fi
+
+               if [ "$fixed" = '[fixed]' ]; then
+                       # Fixed features can't be changed
+                       continue
+               fi
+
+               feature=${feature%:}
+               if [ "${abbreviations[$feature]+set}" ]; then
+                       features[${abbreviations[$feature]}]=1
+               else
+                       features[$feature]=1
+               fi
+       done <<ETHTOOL_FEATURES
+$(PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin" \
+       ethtool --show-features "${words[2]}" 2>/dev/null)
+ETHTOOL_FEATURES
+
+       if [ "${features[$prev]+set}" ]; then
+               COMPREPLY=( $( compgen -W 'on off' -- "$cur" ) )
+               return
+       fi
+
+       # Remove features which have been seen
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               unset "features[$word]"
+       done
+
+       COMPREPLY=( $( compgen -W "${!features[*]}" -- "$cur" ) )
+}
+
+# Complete the current word as a kernel firmware file (for request_firmware)
+# See https://www.kernel.org/doc/html/latest/driver-api/firmware/core.html
+_ethtool_firmware()
+{
+       local -a firmware_paths=(
+               /lib/firmware/updates/
+               /lib/firmware/
+       )
+
+       local release
+       if release=$( uname -r 2>/dev/null ); then
+               firmware_paths+=(
+                       "/lib/firmware/updates/$release/"
+                       "/lib/firmware/$release/"
+               )
+       fi
+
+       local fw_path_para
+       if fw_path_para=$( cat /sys/module/firmware_class/parameters/path 
2>/dev/null ) \
+                       && [ -n "$fw_path_para" ]; then
+               firmware_paths+=( "$fw_path_para" )
+       fi
+
+       local -A firmware_files=()
+
+       local firmware_path
+       for firmware_path in "${firmware_paths[@]}"; do
+               local firmware_file
+               for firmware_file in "$firmware_path"*; do
+                       if [ -f "$firmware_file" ]; then
+                               firmware_files[${firmware_file##*/}]=1
+                       fi
+               done
+       done
+
+       local IFS='
+'
+       COMPREPLY=( $( compgen -W "${!firmware_files[*]}" -- "$cur" ) )
+}
+
+# Completion for ethtool --flash
+_ethtool_flash()
+{
+       if [ "$cword" -eq 3 ]; then
+               _ethtool_firmware
+               return
+       fi
+}
+
+# Completion for ethtool --get-dump
+_ethtool_get_dump()
+{
+       case "$cword" in
+               3)
+                       COMPREPLY=( $( compgen -W data -- "$cur" ) )
+                       return ;;
+               4)
+                       # Output filename
+                       local IFS='
+'
+                       COMPREPLY=( $( compgen -f -- "$cur" ) )
+                       return ;;
+       esac
+}
+
+# Completion for ethtool --get-phy-tunable
+_ethtool_get_phy_tunable()
+{
+       if [ "$cword" -eq 3 ]; then
+               COMPREPLY=( $( compgen -W downshift -- "$cur" ) )
+               return
+       fi
+}
+
+# Completion for ethtool --module-info
+_ethtool_module_info()
+{
+       local -A settings=(
+               [hex]=1
+               [length]=1
+               [offset]=1
+               [raw]=1
+       )
+
+       case "$prev" in
+               hex|\
+               raw)
+                       COMPREPLY=( $( compgen -W 'on off' -- "$cur" ) )
+                       return ;;
+       esac
+
+       if [ "${settings[$prev]+set}" ]; then
+               # Unsigned integer argument
+               return
+       fi
+
+       # Remove settings which have been seen
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               unset "settings[$word]"
+       done
+
+       COMPREPLY=( $( compgen -W "${!settings[*]}" -- "$cur" ) )
+}
+
+# Completion for ethtool --pause
+_ethtool_pause()
+{
+       local -A settings=(
+               [autoneg]=1
+               [rx]=1
+               [tx]=1
+       )
+
+       if [ "${settings[$prev]+set}" ]; then
+               COMPREPLY=( $( compgen -W 'on off' -- "$cur" ) )
+               return
+       fi
+
+       # Remove settings which have been seen
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               unset "settings[$word]"
+       done
+
+       COMPREPLY=( $( compgen -W "${!settings[*]}" -- "$cur" ) )
+}
+
+# Completion for ethtool --per-queue
+_ethtool_per_queue()
+{
+       local -a subcommands=(
+               --coalesce
+               --show-coalesce
+       )
+
+       if [ "$cword" -eq 3 ]; then
+               COMPREPLY=( $( compgen -W "queue_mask ${subcommands[*]}" -- 
"$cur" ) )
+               return
+       fi
+
+       local sc_start=3
+       if [ "${words[3]}" = queue_mask ] ; then
+               case "$cword" in
+                       4)
+                               # Hex number
+                               return ;;
+                       5)
+                               COMPREPLY=( $( compgen -W "${subcommands[*]}" 
-- "$cur" ) )
+                               return ;;
+               esac
+
+               sc_start=5
+       fi
+
+       case "${words[$sc_start]}" in
+               --coalesce)
+                       # Remove --per-queue args to match normal --coalesce 
invocation
+                       local words=(
+                               "${words[0]}"
+                               --coalesce
+                               "${words[2]}"
+                               
"${words[@]:$sc_start+1:${#words[@]}-$sc_start-1}"
+                       )
+                       _ethtool_coalesce
+                       return ;;
+               --show-coalesce)
+                       # No args
+                       return ;;
+       esac
+}
+
+# Completion for ethtool --register-dump
+_ethtool_register_dump()
+{
+       local -A settings=(
+               [file]=1
+               [hex]=1
+               [raw]=1
+       )
+
+       case "$prev" in
+               hex|\
+               raw)
+                       COMPREPLY=( $( compgen -W 'on off' -- "$cur" ) )
+                       return ;;
+               file)
+                       local IFS='
+'
+                       COMPREPLY=( $( compgen -f -- "$cur" ) )
+                       return ;;
+       esac
+
+       # Remove settings which have been seen
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               unset "settings[$word]"
+       done
+
+       COMPREPLY=( $( compgen -W "${!settings[*]}" -- "$cur" ) )
+}
+
+# Completion for ethtool --reset
+_ethtool_reset()
+{
+       if [ "$prev" = flags ]; then
+               # Unsigned integer
+               return
+       fi
+
+       local -A flag_names=(
+               [ap]=1
+               [dma]=1
+               [filter]=1
+               [irq]=1
+               [mac]=1
+               [mgmt]=1
+               [offload]=1
+               [phy]=1
+               [ram]=1
+       )
+
+       local -A all_flag_names=()
+       local flag_name
+       for flag_name in "${!flag_names[@]}"; do
+               all_flag_names[$flag_name]=1
+               all_flag_names[$flag_name-shared]=1
+       done
+
+       # Remove all_flag_names which have been seen
+       local any_dedicated=
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               case "$word" in
+                       all)
+                               # Flags are always additive.
+                               # Nothing to add after "all".
+                               return ;;
+                       dedicated)
+                               any_dedicated=1
+                               # "dedicated" sets all non-shared flags
+                               for flag_name in "${!flag_names[@]}"; do
+                                       unset "all_flag_names[$flag_name]"
+                               done
+                               continue ;;
+               esac
+
+               if [ "${flag_names[$word]+set}" ]; then
+                       any_dedicated=1
+               fi
+
+               unset "all_flag_names[$word]"
+       done
+
+       COMPREPLY=( $( compgen -W "${!all_flag_names[*]}" -- "$cur" ) )
+
+       # Although it is permitted to mix named and un-named flags or duplicate
+       # flags with "all" or "dedicated", it's not likely intentional.
+       # Reconsider if a real use-case (or good consistency argument) is found.
+       if [ "$cword" -eq 3 ]; then
+               COMPREPLY+=( all dedicated flags )
+       elif [ -z "$any_dedicated" ]; then
+               COMPREPLY+=( dedicated )
+       fi
+}
+
+# Completion for ethtool --rxfh
+_ethtool_rxfh()
+{
+       local -A settings=(
+               [context]=1
+               [default]=1
+               [delete]=1
+               [equal]=1
+               [hfunc]=1
+               [hkey]=1
+               [weight]=1
+       )
+
+       case "$prev" in
+               context)
+                       _ethtool_context
+                       # "new" to create a new context
+                       COMPREPLY+=( new )
+                       return ;;
+               equal)
+                       # Positive integer
+                       return ;;
+               hfunc)
+                       # Complete available RSS hash functions
+                       COMPREPLY=(
+                               $(_ethtool_get_names_in_section 'RSS hash 
function' \
+                                       --show-rxfh "${words[2]}")
+                       )
+                       return ;;
+               hkey)
+                       # Pairs of hex digits separated by :
+                       return ;;
+               weight)
+                       # Non-negative integer
+                       return ;;
+       esac
+
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               # Remove settings which have been seen
+               unset "settings[$word]"
+
+               # Remove settings which are mutually-exclusive with seen 
settings
+               case "$word" in
+                       context)
+                               unset 'settings[default]'
+                               ;;
+                       default)
+                               unset \
+                                       'settings[context]' \
+                                       'settings[delete]' \
+                                       'settings[equal]' \
+                                       'settings[weight]'
+                               ;;
+                       delete)
+                               unset \
+                                       'settings[default]' \
+                                       'settings[equal]' \
+                                       'settings[hkey]' \
+                                       'settings[weight]'
+                               ;;
+                       equal)
+                               unset \
+                                       'settings[default]' \
+                                       'settings[delete]' \
+                                       'settings[weight]'
+                               ;;
+                       hkey)
+                               unset 'settings[delete]'
+                               ;;
+                       weight)
+                               unset \
+                                       'settings[default]' \
+                                       'settings[delete]' \
+                                       'settings[equal]'
+                               ;;
+               esac
+       done
+
+
+       COMPREPLY=( $( compgen -W "${!settings[*]}" -- "$cur" ) )
+}
+
+# Completion for ethtool --set-channels
+_ethtool_set_channels()
+{
+       local -A settings=(
+               [combined]=1
+               [other]=1
+               [rx]=1
+               [tx]=1
+       )
+
+       if [ "${settings[$prev]+set}" ]; then
+               # Unsigned integer argument
+               return
+       fi
+
+       # Remove settings which have been seen
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               unset "settings[$word]"
+       done
+
+       COMPREPLY=( $( compgen -W "${!settings[*]}" -- "$cur" ) )
+}
+
+# Completion for ethtool --set-eee
+_ethtool_set_eee()
+{
+       local -A settings=(
+               [advertise]=1
+               [eee]=1
+               [tx-lpi]=1
+               [tx-timer]=1
+       )
+
+       case "$prev" in
+               advertise|\
+               tx-timer)
+                       # Unsigned integer
+                       return ;;
+               eee|\
+               tx-lpi)
+                       COMPREPLY=( $( compgen -W 'on off' -- "$cur" ) )
+                       return ;;
+       esac
+
+       # Remove settings which have been seen
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               unset "settings[$word]"
+       done
+
+       COMPREPLY=( $( compgen -W "${!settings[*]}" -- "$cur" ) )
+}
+
+# Completion for ethtool --set-fec
+_ethtool_set_fec()
+{
+       if [ "$cword" -eq 3 ]; then
+               COMPREPLY=( $( compgen -W encoding -- "$cur" ) )
+               return
+       fi
+
+       local -A modes=(
+               [auto]=auto
+               [rs]=RS
+               [off]=off
+               [baser]=BaseR
+       )
+
+       # Remove modes which have been seen
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               # ethtool recognizes modes case-insensitively
+               unset "modes[${word,,}]"
+       done
+
+       _ethtool_compgen_nocase "${modes[@]}"
+}
+
+# Completion for ethtool --set-phy-tunable
+_ethtool_set_phy_tunable()
+{
+       case "$cword" in
+               3)
+                       COMPREPLY=( $( compgen -W downshift -- "$cur" ) )
+                       return ;;
+               4)
+                       COMPREPLY=( $( compgen -W 'on off' -- "$cur" ) )
+                       return ;;
+               5)
+                       COMPREPLY=( $( compgen -W count -- "$cur" ) )
+                       return ;;
+       esac
+}
+
+# Completion for ethtool --set-priv-flags
+_ethtool_set_priv_flags()
+{
+       if [ $(( cword % 2 )) -eq 0 ]; then
+               COMPREPLY=( $( compgen -W 'on off' -- "$cur" ) )
+               return
+       fi
+
+       # Get available private flags
+       local -A flags=()
+       local flag
+       while IFS= read -r flag; do
+               # Ignore blank line from empty here-document
+               if [ -n "$flag" ]; then
+                       flags[$flag]=1
+               fi
+       done <<ETHTOOL_PRIV_FLAGS
+$(_ethtool_get_names_in_section \
+       'Private flags for [[:graph:]]*' --show-priv-flags "${words[2]}")
+ETHTOOL_PRIV_FLAGS
+
+       # Remove flags which have been seen
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               unset "flags[$word]"
+       done
+
+       COMPREPLY=( $( compgen -W "${!flags[*]}" -- "$cur" ) )
+}
+
+# Completion for ethtool --set-ring
+_ethtool_set_ring()
+{
+       local -A settings=(
+               [rx-jumbo]=1
+               [rx-mini]=1
+               [rx]=1
+               [tx]=1
+       )
+
+       if [ "${settings[$prev]+set}" ]; then
+               # Unsigned integer argument
+               return
+       fi
+
+       # Remove settings which have been seen
+       local word
+       for word in "${words[@]:3:${#words[@]}-4}"; do
+               unset "settings[$word]"
+       done
+
+       COMPREPLY=( $( compgen -W "${!settings[*]}" -- "$cur" ) )
+}
+
+# Completion for ethtool --show-nfc
+_ethtool_show_nfc()
+{
+       if [ "$cword" -eq 3 ]; then
+               COMPREPLY=( $( compgen -W 'rule rx-flow-hash' -- "$cur" ) )
+               return
+       fi
+
+       case "${words[3]}" in
+               rule)
+                       if [ "$cword" -eq 4 ]; then
+                               COMPREPLY=(
+                                       
$(PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin" \
+                                               ethtool --show-nfc 
"${words[2]}" 2>/dev/null |
+                                               command sed -n 
's/^Filter:[[:space:]]*\([0-9]*\)$/\1/p')
+                               )
+                       fi
+                       return ;;
+               rx-flow-hash)
+                       case "$cword" in
+                               4)
+                                       _ethtool_flow_type --hash
+                                       return ;;
+                               5)
+                                       COMPREPLY=( $( compgen -W context -- 
"$cur" ) )
+                                       return ;;
+                               6)
+                                       _ethtool_context
+                                       return ;;
+                       esac
+                       ;;
+       esac
+}
+
+# Completion for ethtool --show-rxfh
+_ethtool_show_rxfh()
+{
+       case "$cword" in
+               3)
+                       COMPREPLY=( $( compgen -W context -- "$cur" ) )
+                       return ;;
+               4)
+                       _ethtool_context
+                       return ;;
+       esac
+}
+
+# Completion for ethtool --test
+_ethtool_test()
+{
+       if [ "$cword" -eq 3 ]; then
+               COMPREPLY=( $( compgen -W 'external_lb offline online' -- 
"$cur" ) )
+               return
+       fi
+}
+
+
+# Complete any ethtool command
+_ethtool()
+{
+       local cur prev words cword
+       _init_completion || return
+
+       # Per "Contributing to bash-completion", complete non-duplicate long 
opts
+       local -A suggested_funcs=(
+               [--change-eeprom]=change_eeprom
+               [--change]=change
+               [--coalesce]=coalesce
+               [--config-nfc]=config_nfc
+               [--driver]=devname
+               [--dump-module-eeprom]=module_info
+               [--eeprom-dump]=eeprom_dump
+               [--features]=features
+               [--flash]=flash
+               [--get-dump]=get_dump
+               [--get-phy-tunable]=get_phy_tunable
+               [--identify]=devname
+               [--module-info]=module_info
+               [--negotiate]=devname
+               [--offload]=features
+               [--pause]=pause
+               [--per-queue]=per_queue
+               [--phy-statistics]=devname
+               [--register-dump]=register_dump
+               [--reset]=reset
+               [--set-channels]=set_channels
+               [--set-dump]=devname
+               [--set-eee]=set_eee
+               [--set-fec]=set_fec
+               [--set-phy-tunable]=set_phy_tunable
+               [--set-priv-flags]=set_priv_flags
+               [--set-ring]=set_ring
+               [--set-rxfh-indir]=rxfh
+               [--show-channels]=devname
+               [--show-coalesce]=devname
+               [--show-eee]=devname
+               [--show-features]=devname
+               [--show-fec]=devname
+               [--show-nfc]=show_nfc
+               [--show-offload]=devname
+               [--show-pause]=devname
+               [--show-permaddr]=devname
+               [--show-priv-flags]=devname
+               [--show-ring]=devname
+               [--show-rxfh]=show_rxfh
+               [--show-time-stamping]=devname
+               [--statistics]=devname
+               [--test]=test
+       )
+       local -A other_funcs=(
+               [--config-ntuple]=config_nfc
+               [--rxfh]=rxfh
+               [--show-ntuple]=show_nfc
+               [--show-rxfh-indir]=devname
+               [-A]=pause
+               [-C]=coalesce
+               [-E]=change_eeprom
+               [-G]=set_ring
+               [-K]=features
+               [-L]=set_channels
+               [-N]=config_nfc
+               [-P]=devname
+               [-Q]=per_queue
+               [-S]=devname
+               [-T]=devname
+               [-U]=config_nfc
+               [-W]=devname
+               [-X]=rxfh
+               [-a]=devname
+               [-c]=devname
+               [-d]=register_dump
+               [-e]=eeprom_dump
+               [-f]=flash
+               [-g]=devname
+               [-i]=devname
+               [-k]=devname
+               [-l]=devname
+               [-m]=module_info
+               [-n]=show_nfc
+               [-p]=devname
+               [-r]=devname
+               [-s]=change
+               [-t]=test
+               [-u]=show_nfc
+               [-w]=get_dump
+               [-x]=devname
+       )
+
+       if [ "$cword" -le 1 ]; then
+               _available_interfaces
+               COMPREPLY+=(
+                       $( compgen -W "--help --version ${!suggested_funcs[*]}" 
-- "$cur" )
+               )
+               return
+       fi
+
+       local func=${suggested_funcs[${words[1]}]-${other_funcs[${words[1]}]-}}
+       if [ "$func" ]; then
+               # All sub-commands have devname as their first argument
+               if [ "$cword" -eq 2 ]; then
+                       _available_interfaces
+                       return
+               fi
+
+               if [ "$func" != devname ]; then
+                       "_ethtool_$func"
+               fi
+       fi
+} &&
+complete -F _ethtool ethtool
+
+# ex: filetype=sh sts=8 sw=8 ts=8 noet
-- 
2.20.1

Reply via email to