# bpftool(8) bash completion -*- shell-script -*- # # SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) # Copyright (C) 2017-2018 Netronome Systems, Inc. # # Author: Quentin Monnet # Takes a list of words in argument; each one of them is added to COMPREPLY if # it is not already present on the command line. Returns no value. _bpftool_once_attr() { local w idx found for w in $*; do found=0 for (( idx=3; idx < ${#words[@]}-1; idx++ )); do if [[ $w == ${words[idx]} ]]; then found=1 break fi done [[ $found -eq 0 ]] && \ COMPREPLY+=( $( compgen -W "$w" -- "$cur" ) ) done } # Takes a list of words as argument; if any of those words is present on the # command line, return 0. Otherwise, return 1. _bpftool_search_list() { local w idx for w in $*; do for (( idx=3; idx < ${#words[@]}-1; idx++ )); do [[ $w == ${words[idx]} ]] && return 0 done done return 1 } # Takes a list of words in argument; adds them all to COMPREPLY if none of them # is already present on the command line. Returns no value. _bpftool_one_of_list() { _bpftool_search_list $* && return 1 COMPREPLY+=( $( compgen -W "$*" -- "$cur" ) ) } _bpftool_get_map_ids() { COMPREPLY+=( $( compgen -W "$( bpftool -jp map 2>&1 | \ command sed -n 's/.*"id": \(.*\),$/\1/p' )" -- "$cur" ) ) } # Takes map type and adds matching map ids to the list of suggestions. _bpftool_get_map_ids_for_type() { local type="$1" COMPREPLY+=( $( compgen -W "$( bpftool -jp map 2>&1 | \ command grep -C2 "$type" | \ command sed -n 's/.*"id": \(.*\),$/\1/p' )" -- "$cur" ) ) } _bpftool_get_prog_ids() { COMPREPLY+=( $( compgen -W "$( bpftool -jp prog 2>&1 | \ command sed -n 's/.*"id": \(.*\),$/\1/p' )" -- "$cur" ) ) } _bpftool_get_prog_tags() { COMPREPLY+=( $( compgen -W "$( bpftool -jp prog 2>&1 | \ command sed -n 's/.*"tag": "\(.*\)",$/\1/p' )" -- "$cur" ) ) } _bpftool_get_btf_ids() { COMPREPLY+=( $( compgen -W "$( bpftool -jp prog 2>&1 | \ command sed -n 's/.*"btf_id": \(.*\),\?$/\1/p' )" -- "$cur" ) ) } _bpftool_get_obj_map_names() { local obj obj=$1 maps=$(objdump -j maps -t $obj 2>/dev/null | \ command awk '/g . maps/ {print $NF}') COMPREPLY+=( $( compgen -W "$maps" -- "$cur" ) ) } _bpftool_get_obj_map_idxs() { local obj obj=$1 nmaps=$(objdump -j maps -t $obj 2>/dev/null | grep -c 'g . maps') COMPREPLY+=( $( compgen -W "$(seq 0 $((nmaps - 1)))" -- "$cur" ) ) } _sysfs_get_netdevs() { COMPREPLY+=( $( compgen -W "$( ls /sys/class/net 2>/dev/null )" -- \ "$cur" ) ) } # Retrieve type of the map that we are operating on. _bpftool_map_guess_map_type() { local keyword ref for (( idx=3; idx < ${#words[@]}-1; idx++ )); do case "${words[$((idx-2))]}" in lookup|update) keyword=${words[$((idx-1))]} ref=${words[$((idx))]} ;; push) printf "stack" return 0 ;; enqueue) printf "queue" return 0 ;; esac done [[ -z $ref ]] && return 0 local type type=$(bpftool -jp map show $keyword $ref | \ command sed -n 's/.*"type": "\(.*\)",$/\1/p') [[ -n $type ]] && printf $type } _bpftool_map_update_get_id() { local command="$1" # Is it the map to update, or a map to insert into the map to update? # Search for "value" keyword. local idx value for (( idx=7; idx < ${#words[@]}-1; idx++ )); do if [[ ${words[idx]} == "value" ]]; then value=1 break fi done if [[ $value -eq 0 ]]; then case "$command" in push) _bpftool_get_map_ids_for_type stack ;; enqueue) _bpftool_get_map_ids_for_type queue ;; *) _bpftool_get_map_ids ;; esac return 0 fi # Id to complete is for a value. It can be either prog id or map id. This # depends on the type of the map to update. local type=$(_bpftool_map_guess_map_type) case $type in array_of_maps|hash_of_maps) _bpftool_get_map_ids return 0 ;; prog_array) _bpftool_get_prog_ids return 0 ;; *) return 0 ;; esac } _bpftool() { local cur prev words objword _init_completion || return # Deal with options if [[ ${words[cword]} == -* ]]; then local c='--version --json --pretty --bpffs --mapcompat --debug' COMPREPLY=( $( compgen -W "$c" -- "$cur" ) ) return 0 fi # Deal with simplest keywords case $prev in help|hex|opcodes|visual|linum) return 0 ;; tag) _bpftool_get_prog_tags return 0 ;; file|pinned) _filedir return 0 ;; batch) COMPREPLY=( $( compgen -W 'file' -- "$cur" ) ) return 0 ;; esac # Remove all options so completions don't have to deal with them. local i for (( i=1; i < ${#words[@]}; )); do if [[ ${words[i]::1} == - ]]; then words=( "${words[@]:0:i}" "${words[@]:i+1}" ) [[ $i -le $cword ]] && cword=$(( cword - 1 )) else i=$(( ++i )) fi done cur=${words[cword]} prev=${words[cword - 1]} pprev=${words[cword - 2]} local object=${words[1]} command=${words[2]} if [[ -z $object || $cword -eq 1 ]]; then case $cur in *) COMPREPLY=( $( compgen -W "$( bpftool help 2>&1 | \ command sed \ -e '/OBJECT := /!d' \ -e 's/.*{//' \ -e 's/}.*//' \ -e 's/|//g' )" -- "$cur" ) ) COMPREPLY+=( $( compgen -W 'batch help' -- "$cur" ) ) return 0 ;; esac fi [[ $command == help ]] && return 0 # Completion depends on object and command in use case $object in prog) # Complete id, only for subcommands that use prog (but no map) ids case $command in show|list|dump|pin) case $prev in id) _bpftool_get_prog_ids return 0 ;; esac ;; esac local PROG_TYPE='id pinned tag' local MAP_TYPE='id pinned' case $command in show|list) [[ $prev != "$command" ]] && return 0 COMPREPLY=( $( compgen -W "$PROG_TYPE" -- "$cur" ) ) return 0 ;; dump) case $prev in $command) COMPREPLY+=( $( compgen -W "xlated jited" -- \ "$cur" ) ) return 0 ;; xlated|jited) COMPREPLY=( $( compgen -W "$PROG_TYPE" -- \ "$cur" ) ) return 0 ;; *) _bpftool_once_attr 'file' if _bpftool_search_list 'xlated'; then COMPREPLY+=( $( compgen -W 'opcodes visual linum' -- \ "$cur" ) ) else COMPREPLY+=( $( compgen -W 'opcodes linum' -- \ "$cur" ) ) fi return 0 ;; esac ;; pin) if [[ $prev == "$command" ]]; then COMPREPLY=( $( compgen -W "$PROG_TYPE" -- "$cur" ) ) else _filedir fi return 0 ;; attach|detach) case $cword in 3) COMPREPLY=( $( compgen -W "$PROG_TYPE" -- "$cur" ) ) return 0 ;; 4) case $prev in id) _bpftool_get_prog_ids ;; pinned) _filedir ;; esac return 0 ;; 5) COMPREPLY=( $( compgen -W 'msg_verdict stream_verdict \ stream_parser flow_dissector' -- "$cur" ) ) return 0 ;; 6) COMPREPLY=( $( compgen -W "$MAP_TYPE" -- "$cur" ) ) return 0 ;; 7) case $prev in id) _bpftool_get_map_ids ;; pinned) _filedir ;; esac return 0 ;; esac ;; load|loadall) local obj # Propose "load/loadall" to complete "bpftool prog load", # or bash tries to complete "load" as a filename below. if [[ ${#words[@]} -eq 3 ]]; then COMPREPLY=( $( compgen -W "load loadall" -- "$cur" ) ) return 0 fi if [[ ${#words[@]} -lt 6 ]]; then _filedir return 0 fi obj=${words[3]} if [[ ${words[-4]} == "map" ]]; then COMPREPLY=( $( compgen -W "id pinned" -- "$cur" ) ) return 0 fi if [[ ${words[-3]} == "map" ]]; then if [[ ${words[-2]} == "idx" ]]; then _bpftool_get_obj_map_idxs $obj elif [[ ${words[-2]} == "name" ]]; then _bpftool_get_obj_map_names $obj fi return 0 fi if [[ ${words[-2]} == "map" ]]; then COMPREPLY=( $( compgen -W "idx name" -- "$cur" ) ) return 0 fi case $prev in type) COMPREPLY=( $( compgen -W "socket kprobe \ kretprobe classifier flow_dissector \ action tracepoint raw_tracepoint \ xdp perf_event cgroup/skb cgroup/sock \ cgroup/dev lwt_in lwt_out lwt_xmit \ lwt_seg6local sockops sk_skb sk_msg \ lirc_mode2 cgroup/bind4 cgroup/bind6 \ cgroup/connect4 cgroup/connect6 \ cgroup/sendmsg4 cgroup/sendmsg6 \ cgroup/recvmsg4 cgroup/recvmsg6 \ cgroup/post_bind4 cgroup/post_bind6 \ cgroup/sysctl cgroup/getsockopt \ cgroup/setsockopt" -- \ "$cur" ) ) return 0 ;; id) _bpftool_get_map_ids return 0 ;; pinned|pinmaps) _filedir return 0 ;; dev) _sysfs_get_netdevs return 0 ;; *) COMPREPLY=( $( compgen -W "map" -- "$cur" ) ) _bpftool_once_attr 'type' _bpftool_once_attr 'dev' _bpftool_once_attr 'pinmaps' return 0 ;; esac ;; tracelog) return 0 ;; run) if [[ ${#words[@]} -lt 5 ]]; then _filedir return 0 fi case $prev in id) _bpftool_get_prog_ids return 0 ;; data_in|data_out|ctx_in|ctx_out) _filedir return 0 ;; repeat|data_size_out|ctx_size_out) return 0 ;; *) _bpftool_once_attr 'data_in data_out data_size_out \ ctx_in ctx_out ctx_size_out repeat' return 0 ;; esac ;; *) [[ $prev == $object ]] && \ COMPREPLY=( $( compgen -W 'dump help pin attach detach \ load loadall show list tracelog run' -- "$cur" ) ) ;; esac ;; map) local MAP_TYPE='id pinned' case $command in show|list|dump|peek|pop|dequeue) case $prev in $command) COMPREPLY=( $( compgen -W "$MAP_TYPE" -- "$cur" ) ) return 0 ;; id) case "$command" in peek) _bpftool_get_map_ids_for_type stack _bpftool_get_map_ids_for_type queue ;; pop) _bpftool_get_map_ids_for_type stack ;; dequeue) _bpftool_get_map_ids_for_type queue ;; *) _bpftool_get_map_ids ;; esac return 0 ;; *) return 0 ;; esac ;; create) case $prev in $command) _filedir return 0 ;; type) COMPREPLY=( $( compgen -W 'hash array prog_array \ perf_event_array percpu_hash percpu_array \ stack_trace cgroup_array lru_hash \ lru_percpu_hash lpm_trie array_of_maps \ hash_of_maps devmap devmap_hash sockmap cpumap \ xskmap sockhash cgroup_storage reuseport_sockarray \ percpu_cgroup_storage queue stack' -- \ "$cur" ) ) return 0 ;; key|value|flags|name|entries) return 0 ;; dev) _sysfs_get_netdevs return 0 ;; *) _bpftool_once_attr 'type' _bpftool_once_attr 'key' _bpftool_once_attr 'value' _bpftool_once_attr 'entries' _bpftool_once_attr 'name' _bpftool_once_attr 'flags' _bpftool_once_attr 'dev' return 0 ;; esac ;; lookup|getnext|delete) case $prev in $command) COMPREPLY=( $( compgen -W "$MAP_TYPE" -- "$cur" ) ) return 0 ;; id) _bpftool_get_map_ids return 0 ;; key) COMPREPLY+=( $( compgen -W 'hex' -- "$cur" ) ) ;; *) case $(_bpftool_map_guess_map_type) in queue|stack) return 0 ;; esac _bpftool_once_attr 'key' return 0 ;; esac ;; update|push|enqueue) case $prev in $command) COMPREPLY=( $( compgen -W "$MAP_TYPE" -- "$cur" ) ) return 0 ;; id) _bpftool_map_update_get_id $command return 0 ;; key) COMPREPLY+=( $( compgen -W 'hex' -- "$cur" ) ) ;; value) # We can have bytes, or references to a prog or a # map, depending on the type of the map to update. case "$(_bpftool_map_guess_map_type)" in array_of_maps|hash_of_maps) local MAP_TYPE='id pinned' COMPREPLY+=( $( compgen -W "$MAP_TYPE" \ -- "$cur" ) ) return 0 ;; prog_array) local PROG_TYPE='id pinned tag' COMPREPLY+=( $( compgen -W "$PROG_TYPE" \ -- "$cur" ) ) return 0 ;; *) COMPREPLY+=( $( compgen -W 'hex' \ -- "$cur" ) ) return 0 ;; esac return 0 ;; *) case $(_bpftool_map_guess_map_type) in queue|stack) _bpftool_once_attr 'value' return 0; ;; esac _bpftool_once_attr 'key' local UPDATE_FLAGS='any exist noexist' for (( idx=3; idx < ${#words[@]}-1; idx++ )); do if [[ ${words[idx]} == 'value' ]]; then # 'value' is present, but is not the last # word i.e. we can now have UPDATE_FLAGS. _bpftool_one_of_list "$UPDATE_FLAGS" return 0 fi done for (( idx=3; idx < ${#words[@]}-1; idx++ )); do if [[ ${words[idx]} == 'key' ]]; then # 'key' is present, but is not the last # word i.e. we can now have 'value'. _bpftool_once_attr 'value' return 0 fi done return 0 ;; esac ;; pin) if [[ $prev == "$command" ]]; then COMPREPLY=( $( compgen -W "$PROG_TYPE" -- "$cur" ) ) else _filedir fi return 0 ;; event_pipe) case $prev in $command) COMPREPLY=( $( compgen -W "$MAP_TYPE" -- "$cur" ) ) return 0 ;; id) _bpftool_get_map_ids_for_type perf_event_array return 0 ;; cpu) return 0 ;; index) return 0 ;; *) _bpftool_once_attr 'cpu' _bpftool_once_attr 'index' return 0 ;; esac ;; *) [[ $prev == $object ]] && \ COMPREPLY=( $( compgen -W 'delete dump getnext help \ lookup pin event_pipe show list update create \ peek push enqueue pop dequeue' -- \ "$cur" ) ) ;; esac ;; btf) local PROG_TYPE='id pinned tag' local MAP_TYPE='id pinned' case $command in dump) case $prev in $command) COMPREPLY+=( $( compgen -W "id map prog file" -- \ "$cur" ) ) return 0 ;; prog) COMPREPLY=( $( compgen -W "$PROG_TYPE" -- "$cur" ) ) return 0 ;; map) COMPREPLY=( $( compgen -W "$MAP_TYPE" -- "$cur" ) ) return 0 ;; id) case $pprev in prog) _bpftool_get_prog_ids ;; map) _bpftool_get_map_ids ;; dump) _bpftool_get_btf_ids ;; esac return 0 ;; format) COMPREPLY=( $( compgen -W "c raw" -- "$cur" ) ) ;; *) # emit extra options case ${words[3]} in id|file) _bpftool_once_attr 'format' ;; map|prog) if [[ ${words[3]} == "map" ]] && [[ $cword == 6 ]]; then COMPREPLY+=( $( compgen -W "key value kv all" -- "$cur" ) ) fi _bpftool_once_attr 'format' ;; *) ;; esac return 0 ;; esac ;; *) [[ $prev == $object ]] && \ COMPREPLY=( $( compgen -W 'dump help' -- "$cur" ) ) ;; esac ;; cgroup) case $command in show|list|tree) case $cword in 3) _filedir ;; 4) COMPREPLY=( $( compgen -W 'effective' -- "$cur" ) ) ;; esac return 0 ;; attach|detach) local ATTACH_TYPES='ingress egress sock_create sock_ops \ device bind4 bind6 post_bind4 post_bind6 connect4 \ connect6 sendmsg4 sendmsg6 recvmsg4 recvmsg6 sysctl \ getsockopt setsockopt' local ATTACH_FLAGS='multi override' local PROG_TYPE='id pinned tag' case $prev in $command) _filedir return 0 ;; ingress|egress|sock_create|sock_ops|device|bind4|bind6|\ post_bind4|post_bind6|connect4|connect6|sendmsg4|\ sendmsg6|recvmsg4|recvmsg6|sysctl|getsockopt|\ setsockopt) COMPREPLY=( $( compgen -W "$PROG_TYPE" -- \ "$cur" ) ) return 0 ;; id) _bpftool_get_prog_ids return 0 ;; *) if ! _bpftool_search_list "$ATTACH_TYPES"; then COMPREPLY=( $( compgen -W "$ATTACH_TYPES" -- \ "$cur" ) ) elif [[ "$command" == "attach" ]]; then # We have an attach type on the command line, # but it is not the previous word, or # "id|pinned|tag" (we already checked for # that). This should only leave the case when # we need attach flags for "attach" commamnd. _bpftool_one_of_list "$ATTACH_FLAGS" fi return 0 ;; esac ;; *) [[ $prev == $object ]] && \ COMPREPLY=( $( compgen -W 'help attach detach \ show list tree' -- "$cur" ) ) ;; esac ;; perf) case $command in *) [[ $prev == $object ]] && \ COMPREPLY=( $( compgen -W 'help \ show list' -- "$cur" ) ) ;; esac ;; net) case $command in *) [[ $prev == $object ]] && \ COMPREPLY=( $( compgen -W 'help \ show list' -- "$cur" ) ) ;; esac ;; feature) case $command in probe) [[ $prev == "dev" ]] && _sysfs_get_netdevs && return 0 [[ $prev == "prefix" ]] && return 0 if _bpftool_search_list 'macros'; then COMPREPLY+=( $( compgen -W 'prefix' -- "$cur" ) ) else COMPREPLY+=( $( compgen -W 'macros' -- "$cur" ) ) fi _bpftool_one_of_list 'kernel dev' return 0 ;; *) [[ $prev == $object ]] && \ COMPREPLY=( $( compgen -W 'help probe' -- "$cur" ) ) ;; esac ;; esac } && complete -F _bpftool bpftool # ex: ts=4 sw=4 et filetype=sh