From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-14.0 required=3.0 tests=HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_PATCH,MAILING_LIST_MULTI, MENTIONS_GIT_HOSTING,SIGNED_OFF_BY,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 6872FC10F14 for ; Thu, 11 Apr 2019 17:40:55 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 27E0F2084D for ; Thu, 11 Apr 2019 17:40:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726664AbfDKRky (ORCPT ); Thu, 11 Apr 2019 13:40:54 -0400 Received: from vulcan.kevinlocke.name ([107.191.43.88]:36662 "EHLO vulcan.kevinlocke.name" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726646AbfDKRkx (ORCPT ); Thu, 11 Apr 2019 13:40:53 -0400 Received: from kevinolos (unknown [IPv6:2001:470:b:5c3:bc32:c700:cca9:b5d3]) (Authenticated sender: kevin@kevinlocke.name) by vulcan.kevinlocke.name (Postfix) with ESMTPSA id D34D9E7EFA7; Thu, 11 Apr 2019 17:40:50 +0000 (UTC) Received: by kevinolos (Postfix, from userid 1000) id 17A83130043B; Thu, 11 Apr 2019 11:40:47 -0600 (MDT) From: Kevin Locke To: "John W. Linville" Cc: netdev@vger.kernel.org, Jesse Brandeburg Subject: [PATCH v2] ethtool: Add bash-completion script Date: Thu, 11 Apr 2019 11:39:32 -0600 Message-Id: <4dedb8b19e812805abdd4ffbdfddac5df755b8a5.1555004349.git.kevin@kevinlocke.name> X-Mailer: git-send-email 2.20.1 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org 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 Reviewed-by: Jesse Brandeburg --- 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 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 </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 </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