All of lore.kernel.org
 help / color / mirror / Atom feed
From: Kevin Locke <kevin@kevinlocke.name>
To: "John W. Linville" <linville@tuxdriver.com>
Cc: netdev@vger.kernel.org
Subject: [PATCH] ethtool: Add bash-completion script
Date: Sun,  7 Apr 2019 20:19:37 -0600	[thread overview]
Message-ID: <deff591b261b01f1be90dec14eaffac468fed638.1554689977.git.kevin@kevinlocke.name> (raw)

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


             reply	other threads:[~2019-04-08  2:26 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-04-08  2:19 Kevin Locke [this message]
2019-04-11 16:14 ` [PATCH] ethtool: Add bash-completion script 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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=deff591b261b01f1be90dec14eaffac468fed638.1554689977.git.kevin@kevinlocke.name \
    --to=kevin@kevinlocke.name \
    --cc=linville@tuxdriver.com \
    --cc=netdev@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.