lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <4dedb8b19e812805abdd4ffbdfddac5df755b8a5.1555004349.git.kevin@kevinlocke.name>
Date:   Thu, 11 Apr 2019 11:39:32 -0600
From:   Kevin Locke <kevin@...inlocke.name>
To:     "John W. Linville" <linville@...driver.com>
Cc:     netdev@...r.kernel.org,
        Jesse Brandeburg <jesse.brandeburg@...el.com>
Subject: [PATCH v2] ethtool: Add bash-completion script

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@...inlocke.name>
Reviewed-by: Jesse Brandeburg <jesse.brandeburg@...el.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

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ