All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] ethtool: Add bash-completion script
@ 2019-04-08  2:19 Kevin Locke
  2019-04-11 16:14 ` Jesse Brandeburg
  2019-04-11 17:39 ` [PATCH v2] " Kevin Locke
  0 siblings, 2 replies; 9+ messages in thread
From: Kevin Locke @ 2019-04-08  2:19 UTC (permalink / raw)
  To: John W. Linville; +Cc: netdev

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 <kevin@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


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH] ethtool: Add bash-completion script
  2019-04-08  2:19 [PATCH] ethtool: Add bash-completion script Kevin Locke
@ 2019-04-11 16:14 ` Jesse Brandeburg
  2019-04-11 16:47   ` Kevin Locke
  2019-04-11 17:39 ` [PATCH v2] " Kevin Locke
  1 sibling, 1 reply; 9+ messages in thread
From: Jesse Brandeburg @ 2019-04-11 16:14 UTC (permalink / raw)
  To: Kevin Locke; +Cc: John W. Linville, netdev, jesse.brandeburg

On Sun, 7 Apr 2019 20:19:37 -0600
Kevin Locke <kevin@kevinlocke.name> wrote:

> 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 <kevin@kevinlocke.name>

Thank you! I think this is super useful, and I agree FWIW that it
should be part of ethtool.  I suspect that it needs to be installed by
any package manager as part of ethtool install, into the right
directory.  Could be a followup patch?

And the only (minor) complaint I have about your patch is that the
commit message doesn't show how to install it (basically just copy
ethtool file from this patch to
f.e. /usr/share/bash-completion/completions/)

I did a quick touch test of it and it seemed to be completing ethtool
commands which made me really happy, especially the network interface
names which are so long.

Reviewed-by: Jesse Brandeburg <jesse.brandeburg@intel.com>

And now I'm going to go and tell everyone I work with about this
patch. :-)


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] ethtool: Add bash-completion script
  2019-04-11 16:14 ` Jesse Brandeburg
@ 2019-04-11 16:47   ` Kevin Locke
  0 siblings, 0 replies; 9+ messages in thread
From: Kevin Locke @ 2019-04-11 16:47 UTC (permalink / raw)
  To: Jesse Brandeburg; +Cc: John W. Linville, netdev

On Thu, 2019-04-11 at 09:14 -0700, Jesse Brandeburg wrote:
> On Sun, 7 Apr 2019 20:19:37 -0600 Kevin Locke <kevin@kevinlocke.name> wrote:
>> 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).
>> 
>> [...]
> 
> Thank you! I think this is super useful, and I agree FWIW that it
> should be part of ethtool.  I suspect that it needs to be installed by
> any package manager as part of ethtool install, into the right
> directory.  Could be a followup patch?

Great!  Glad to hear it.  Patched configure.ac and Makefile.am install
the script to `pkg-config --variable=completionsdir bash-completion`
(with fallback to $datadir/bash-completion/completions) by default.
It can be disabled by passing --without-bash-completion-dir or
overridden by --with-bash-completion-dir=$anypath.  I would expect
most distribution packages would install it by default, but if there
are followup patches (for ethtool or to the distributions), I'd be
happy to submit those as well.

> And the only (minor) complaint I have about your patch is that the
> commit message doesn't show how to install it (basically just copy
> ethtool file from this patch to
> f.e. /usr/share/bash-completion/completions/)

Good point.  I will document the install process and the configure
arguments mentioned above and send an updated patch shortly.

> I did a quick touch test of it and it seemed to be completing ethtool
> commands which made me really happy, especially the network interface
> names which are so long.
> 
> Reviewed-by: Jesse Brandeburg <jesse.brandeburg@intel.com>
> 
> And now I'm going to go and tell everyone I work with about this
> patch. :-)

Wonderful!  I'm very glad to hear that you found it useful and I hope
your colleagues appreciate it as well.  Let me know if you or they
find any improvements that I can include in a future version of the
patch.

Thanks for taking the time to review it!

Best,
Kevin

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2] ethtool: Add bash-completion script
  2019-04-08  2:19 [PATCH] ethtool: Add bash-completion script Kevin Locke
  2019-04-11 16:14 ` Jesse Brandeburg
@ 2019-04-11 17:39 ` Kevin Locke
  2019-04-16 18:37   ` John W. Linville
  2019-04-20  0:16   ` [PATCH v3] " Kevin Locke
  1 sibling, 2 replies; 9+ messages in thread
From: Kevin Locke @ 2019-04-11 17:39 UTC (permalink / raw)
  To: John W. Linville; +Cc: netdev, Jesse Brandeburg

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 named shell-completion/bash/ethtool, similar to [kmod],
and installed to `pkg-config --variable=completionsdir bash-completion`
(with fallback to $datadir/bash-completion/completions) by default.
This can be disabled by passing --without-bash-completion-dir or changed
by passing --with-bash-completion-dir=$anypath to ./configure.  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).

To install the script manually for the current user, copy or link
shell-completion/bash to $BASH_COMPLETION_USER_DIR/completions
(default $XDG_DATA_HOME/bash-completion/completions
(default ~/.local/share/bash-completion/completions)).
To install system-wide, copy shell-completion/bash to completionsdir
from pkg-config (default /usr/share/bash-completion/completions)
discussed above.  Restarting bash may be necessary to pick up changes to
the script (if a previous version had already been loaded).

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 <kevin@kevinlocke.name>
Reviewed-by: Jesse Brandeburg <jesse.brandeburg@intel.com>
---

Changes in v2:
* Describe manual install and ./configure arguments in commit message.

 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


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] ethtool: Add bash-completion script
  2019-04-11 17:39 ` [PATCH v2] " Kevin Locke
@ 2019-04-16 18:37   ` John W. Linville
  2019-04-17  2:53     ` Kevin Locke
  2019-04-20  0:16   ` [PATCH v3] " Kevin Locke
  1 sibling, 1 reply; 9+ messages in thread
From: John W. Linville @ 2019-04-16 18:37 UTC (permalink / raw)
  To: Kevin Locke; +Cc: netdev, Jesse Brandeburg

On Thu, Apr 11, 2019 at 11:39:32AM -0600, Kevin Locke wrote:
> 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 named shell-completion/bash/ethtool, similar to [kmod],
> and installed to `pkg-config --variable=completionsdir bash-completion`
> (with fallback to $datadir/bash-completion/completions) by default.
> This can be disabled by passing --without-bash-completion-dir or changed
> by passing --with-bash-completion-dir=$anypath to ./configure.  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).
> 
> To install the script manually for the current user, copy or link
> shell-completion/bash to $BASH_COMPLETION_USER_DIR/completions
> (default $XDG_DATA_HOME/bash-completion/completions
> (default ~/.local/share/bash-completion/completions)).
> To install system-wide, copy shell-completion/bash to completionsdir
> from pkg-config (default /usr/share/bash-completion/completions)
> discussed above.  Restarting bash may be necessary to pick up changes to
> the script (if a previous version had already been loaded).
> 
> 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 <kevin@kevinlocke.name>
> Reviewed-by: Jesse Brandeburg <jesse.brandeburg@intel.com>
> ---
> 
> Changes in v2:
> * Describe manual install and ./configure arguments in commit message.

Overall, it looks good to me. But when I build with "make distcheck",
I get this output:

...
make[3]: Leaving directory '/home/linville/git/ethtool/ethtool-5.0/_build/sub'
make[2]: Leaving directory '/home/linville/git/ethtool/ethtool-5.0/_build/sub'
make[1]: Leaving directory '/home/linville/git/ethtool/ethtool-5.0/_build/sub'
make[1]: Entering directory '/home/linville/git/ethtool/ethtool-5.0/_build/sub'
make[2]: Entering directory '/home/linville/git/ethtool/ethtool-5.0/_build/sub'
 /usr/bin/mkdir -p '/home/linville/git/ethtool/ethtool-5.0/_inst/sbin'
  /usr/bin/install -c ethtool '/home/linville/git/ethtool/ethtool-5.0/_inst/sbin'
 /usr/bin/mkdir -p '/usr/share/bash-completion/completions'
 /usr/bin/install -c -m 644 ../../shell-completion/bash/ethtool '/usr/share/bash-completion/completions'
/usr/bin/install: cannot create regular file '/usr/share/bash-completion/completions/ethtool': Permission denied
make[2]: *** [Makefile:2005: install-dist_bashcompletionDATA] Error 1
make[2]: Leaving directory '/home/linville/git/ethtool/ethtool-5.0/_build/sub'
make[1]: *** [Makefile:2438: install-am] Error 2
make[1]: Leaving directory '/home/linville/git/ethtool/ethtool-5.0/_build/sub'
make: *** [Makefile:2347: distcheck] Error 1

It looks like somewhere you are using "$(bashcompletiondir)" instead of
"$(DESTDIR)$(bashcompletiondir)", but I can't seem to find it. Perhaps
you can find the change required to avoid this build error?

Thanks,

John
-- 
John W. Linville		Someday the world will need a hero, and you
linville@tuxdriver.com			might be all we have.  Be ready.

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] ethtool: Add bash-completion script
  2019-04-16 18:37   ` John W. Linville
@ 2019-04-17  2:53     ` Kevin Locke
  2019-04-18 16:05       ` John W. Linville
  0 siblings, 1 reply; 9+ messages in thread
From: Kevin Locke @ 2019-04-17  2:53 UTC (permalink / raw)
  To: John W. Linville; +Cc: netdev, Jesse Brandeburg

On Tue, 2019-04-16 at 14:37 -0400, John W. Linville wrote:
> Overall, it looks good to me. But when I build with "make distcheck",
> I get this output:
> 
> [...]
> 
> It looks like somewhere you are using "$(bashcompletiondir)" instead of
> "$(DESTDIR)$(bashcompletiondir)", but I can't seem to find it. Perhaps
> you can find the change required to avoid this build error?

Thanks for taking a look at it!  Good catch.  DESTDIR would have been
my guess as well.  Interestingly, the issue is that make distcheck
expects `./configure --prefix=foo && make install` to only install
files below foo, which fails because --with-bash-completion-dir
defaults to `pkg-config --variable=completionsdir bash-completion`
(usually /usr/share/bash-completion/completions).

I can think of a few different ways to fix this:

1. Add =--without-bash-completion-dir or
   --with-bashcompletiondir=$something to DISTCHECK_CONFIGURE_FLAGS
   in Makefile.am to avoid installing the script during distcheck.
2. Replace the prefix for bash-completion (using
   `pkg-config --variable=prefix bash-completion`) in
   $bashcompletiondir with --prefix passed to configure.
3. Stop using pkg-config and install to
   $datadir/bash-completion/completions by default.

Option 1 has the disadvantage that users may not expect files to be
installed outside of --prefix, that the script does not install to
/usr/local (with everything else) by default, and that
--with-bash-completion-dir= must be passed for non-root installs.
kmod passes
--with-bashcompletiondir=$$dc_install_base/$(bashcompletiondir)
to DISTCHECK_CONFIGURE_FLAGS, which has the additional disadvantage of
using the undocumented (AFAICT) Automake $$dc_install_base variable.

Options 2 and 3 have the disadvantage that passing --prefix= which is
not the prefix of $XDG_DATA_HOME or $XDG_DATA_DIRS will install the
script to a directory that bash-completion doesn't use.

Option 3 has the additional disadvantage of ignoring the upstream
recommendations, which could install the script to a directory not
used by bash-completion for customized or future versions.  It has the
advantage of being extremely simple and understandable.

My personal preference is #2, but I would defer to your judgement.
Let me know which you would prefer and I'll update the patch.

Best,
Kevin

P.S.  I noticed that the PKG_CHECK_MODULES macro does unnecessary work
handling BASH_COMPLETION_CFLAGS and BASH_COMPLETION_LIBS and adds them
to the configure --help text, so I will remove it in favor of calling
$PKG_CONFIG directly, unless you object.

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] ethtool: Add bash-completion script
  2019-04-17  2:53     ` Kevin Locke
@ 2019-04-18 16:05       ` John W. Linville
  0 siblings, 0 replies; 9+ messages in thread
From: John W. Linville @ 2019-04-18 16:05 UTC (permalink / raw)
  To: Kevin Locke, netdev, Jesse Brandeburg

On Tue, Apr 16, 2019 at 08:53:33PM -0600, Kevin Locke wrote:
> On Tue, 2019-04-16 at 14:37 -0400, John W. Linville wrote:
> > Overall, it looks good to me. But when I build with "make distcheck",
> > I get this output:
> > 
> > [...]
> > 
> > It looks like somewhere you are using "$(bashcompletiondir)" instead of
> > "$(DESTDIR)$(bashcompletiondir)", but I can't seem to find it. Perhaps
> > you can find the change required to avoid this build error?
> 
> Thanks for taking a look at it!  Good catch.  DESTDIR would have been
> my guess as well.  Interestingly, the issue is that make distcheck
> expects `./configure --prefix=foo && make install` to only install
> files below foo, which fails because --with-bash-completion-dir
> defaults to `pkg-config --variable=completionsdir bash-completion`
> (usually /usr/share/bash-completion/completions).
> 
> I can think of a few different ways to fix this:
> 
> 1. Add =--without-bash-completion-dir or
>    --with-bashcompletiondir=$something to DISTCHECK_CONFIGURE_FLAGS
>    in Makefile.am to avoid installing the script during distcheck.
> 2. Replace the prefix for bash-completion (using
>    `pkg-config --variable=prefix bash-completion`) in
>    $bashcompletiondir with --prefix passed to configure.
> 3. Stop using pkg-config and install to
>    $datadir/bash-completion/completions by default.
> 
> Option 1 has the disadvantage that users may not expect files to be
> installed outside of --prefix, that the script does not install to
> /usr/local (with everything else) by default, and that
> --with-bash-completion-dir= must be passed for non-root installs.
> kmod passes
> --with-bashcompletiondir=$$dc_install_base/$(bashcompletiondir)
> to DISTCHECK_CONFIGURE_FLAGS, which has the additional disadvantage of
> using the undocumented (AFAICT) Automake $$dc_install_base variable.
> 
> Options 2 and 3 have the disadvantage that passing --prefix= which is
> not the prefix of $XDG_DATA_HOME or $XDG_DATA_DIRS will install the
> script to a directory that bash-completion doesn't use.
> 
> Option 3 has the additional disadvantage of ignoring the upstream
> recommendations, which could install the script to a directory not
> used by bash-completion for customized or future versions.  It has the
> advantage of being extremely simple and understandable.
> 
> My personal preference is #2, but I would defer to your judgement.
> Let me know which you would prefer and I'll update the patch.
> 
> Best,
> Kevin
> 
> P.S.  I noticed that the PKG_CHECK_MODULES macro does unnecessary work
> handling BASH_COMPLETION_CFLAGS and BASH_COMPLETION_LIBS and adds them
> to the configure --help text, so I will remove it in favor of calling
> $PKG_CONFIG directly, unless you object.

Option #2 seems reasonable, and your "P.S." seems fine too.

Thanks,

John
-- 
John W. Linville		Someday the world will need a hero, and you
linville@tuxdriver.com			might be all we have.  Be ready.

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v3] ethtool: Add bash-completion script
  2019-04-11 17:39 ` [PATCH v2] " Kevin Locke
  2019-04-16 18:37   ` John W. Linville
@ 2019-04-20  0:16   ` Kevin Locke
  2019-04-24 19:54     ` John W. Linville
  1 sibling, 1 reply; 9+ messages in thread
From: Kevin Locke @ 2019-04-20  0:16 UTC (permalink / raw)
  To: John W. Linville; +Cc: netdev, Jesse Brandeburg

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 named shell-completion/bash/ethtool, similar to [kmod],
and installed to `pkg-config --variable=completionsdir bash-completion`
(with $prefix replaced by --prefix of ./configure and fallback to
$datadir/bash-completion/completions) by default.  This can be disabled
by passing --without-bash-completion-dir or changed by passing
--with-bash-completion-dir=$anypath to ./configure.  It requires
pkg-config 0.18 or later to be installed on the build system which runs
aclocal (for the PKG_PROG_PKG_CONFIG m4 macro).

To install the script manually for the current user, copy or link
shell-completion/bash to $BASH_COMPLETION_USER_DIR/completions
(default $XDG_DATA_HOME/bash-completion/completions
(default ~/.local/share/bash-completion/completions)).
To install system-wide, copy shell-completion/bash to completionsdir
from pkg-config (default /usr/share/bash-completion/completions)
discussed above.  Restarting bash may be necessary to pick up changes to
the script (if a previous version had already been loaded).

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 <kevin@kevinlocke.name>
Reviewed-by: Jesse Brandeburg <jesse.brandeburg@intel.com>
---

Changes in v3:
* Apply --prefix to completionsdir for `make distcheck` and expectations.
* Stop using PKG_CHECK_MODULES due to unnecessary _CFLAGS/LIBS vars.
  Use PKG_PROG_PKG_CONFIG then call $PKG_CONFIG directly.

Changes in v2:
* Describe manual install and ./configure arguments in commit message.

 Makefile.am                   |    5 +
 configure.ac                  |   26 +
 shell-completion/bash/ethtool | 1251 +++++++++++++++++++++++++++++++++
 3 files changed, 1282 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..3c0686c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -11,6 +11,7 @@ dnl Checks for programs.
 AC_PROG_CC
 AC_PROG_GCC_TRADITIONAL
 AM_PROG_CC_C_O
+PKG_PROG_PKG_CONFIG
 
 dnl Checks for libraries.
 
@@ -40,5 +41,30 @@ 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],
+      [AC_MSG_CHECKING([for bash-completion directory])
+       dnl Attempt to use pkg-config completionsdir variable with given $prefix.
+       dnl This matches distcheck expectation that all files install to $prefix.
+       dnl It works with /usr and /usr/local (for default $XDG_DATA_DIRS) but
+       dnl may install to directory not used by bash-completion in other cases.
+       dnl See: https://lore.kernel.org/netdev/20190417025333.GA28674@kevinolos/
+       AS_IF([test "x$PKG_CONFIG" != x \
+	      && bash_completion_prefix=`"$PKG_CONFIG" --print-errors --variable=prefix bash-completion 2>&AS_MESSAGE_LOG_FD` \
+	      && bash_completion_dir=`"$PKG_CONFIG" --print-errors --variable=completionsdir bash-completion 2>&AS_MESSAGE_LOG_FD`],
+	     [bash_completion_dir="${bash_completion_dir#"$bash_completion_prefix"}"
+	      bash_completion_dir="${bash_completion_dir#/}"
+	      BASH_COMPLETION_DIR='${prefix}'/"$bash_completion_dir"],
+	     [BASH_COMPLETION_DIR='${datadir}/bash-completion/completions'])
+       AC_MSG_RESULT([$BASH_COMPLETION_DIR])],
+      [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


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH v3] ethtool: Add bash-completion script
  2019-04-20  0:16   ` [PATCH v3] " Kevin Locke
@ 2019-04-24 19:54     ` John W. Linville
  0 siblings, 0 replies; 9+ messages in thread
From: John W. Linville @ 2019-04-24 19:54 UTC (permalink / raw)
  To: Kevin Locke; +Cc: netdev, Jesse Brandeburg

On Fri, Apr 19, 2019 at 06:16:21PM -0600, Kevin Locke wrote:
> 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 named shell-completion/bash/ethtool, similar to [kmod],
> and installed to `pkg-config --variable=completionsdir bash-completion`
> (with $prefix replaced by --prefix of ./configure and fallback to
> $datadir/bash-completion/completions) by default.  This can be disabled
> by passing --without-bash-completion-dir or changed by passing
> --with-bash-completion-dir=$anypath to ./configure.  It requires
> pkg-config 0.18 or later to be installed on the build system which runs
> aclocal (for the PKG_PROG_PKG_CONFIG m4 macro).
> 
> To install the script manually for the current user, copy or link
> shell-completion/bash to $BASH_COMPLETION_USER_DIR/completions
> (default $XDG_DATA_HOME/bash-completion/completions
> (default ~/.local/share/bash-completion/completions)).
> To install system-wide, copy shell-completion/bash to completionsdir
> from pkg-config (default /usr/share/bash-completion/completions)
> discussed above.  Restarting bash may be necessary to pick up changes to
> the script (if a previous version had already been loaded).
> 
> 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 <kevin@kevinlocke.name>
> Reviewed-by: Jesse Brandeburg <jesse.brandeburg@intel.com>
> ---
> 
> Changes in v3:
> * Apply --prefix to completionsdir for `make distcheck` and expectations.
> * Stop using PKG_CHECK_MODULES due to unnecessary _CFLAGS/LIBS vars.
>   Use PKG_PROG_PKG_CONFIG then call $PKG_CONFIG directly.
> 
> Changes in v2:
> * Describe manual install and ./configure arguments in commit message.

LGTM -- queued for next release.

Thanks,

John
-- 
John W. Linville		Someday the world will need a hero, and you
linville@tuxdriver.com			might be all we have.  Be ready.

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2019-04-24 20:00 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-04-08  2:19 [PATCH] ethtool: Add bash-completion script Kevin Locke
2019-04-11 16:14 ` Jesse Brandeburg
2019-04-11 16:47   ` Kevin Locke
2019-04-11 17:39 ` [PATCH v2] " Kevin Locke
2019-04-16 18:37   ` John W. Linville
2019-04-17  2:53     ` Kevin Locke
2019-04-18 16:05       ` John W. Linville
2019-04-20  0:16   ` [PATCH v3] " Kevin Locke
2019-04-24 19:54     ` John W. Linville

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.