1# bpftool(8) bash completion                               -*- shell-script -*-
2#
3# Copyright (C) 2017-2018 Netronome Systems, Inc.
4#
5# This software is dual licensed under the GNU General License
6# Version 2, June 1991 as shown in the file COPYING in the top-level
7# directory of this source tree or the BSD 2-Clause License provided
8# below.  You have the option to license this software under the
9# complete terms of either license.
10#
11# The BSD 2-Clause License:
12#
13#     Redistribution and use in source and binary forms, with or
14#     without modification, are permitted provided that the following
15#     conditions are met:
16#
17#      1. Redistributions of source code must retain the above
18#         copyright notice, this list of conditions and the following
19#         disclaimer.
20#
21#      2. Redistributions in binary form must reproduce the above
22#         copyright notice, this list of conditions and the following
23#         disclaimer in the documentation and/or other materials
24#         provided with the distribution.
25#
26# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
30# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
31# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
32# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33# SOFTWARE.
34#
35# Author: Quentin Monnet <quentin.monnet@netronome.com>
36
37# Takes a list of words in argument; each one of them is added to COMPREPLY if
38# it is not already present on the command line. Returns no value.
39_bpftool_once_attr()
40{
41    local w idx found
42    for w in $*; do
43        found=0
44        for (( idx=3; idx < ${#words[@]}-1; idx++ )); do
45            if [[ $w == ${words[idx]} ]]; then
46                found=1
47                break
48            fi
49        done
50        [[ $found -eq 0 ]] && \
51            COMPREPLY+=( $( compgen -W "$w" -- "$cur" ) )
52    done
53}
54
55# Takes a list of words as argument; if any of those words is present on the
56# command line, return 0. Otherwise, return 1.
57_bpftool_search_list()
58{
59    local w idx
60    for w in $*; do
61        for (( idx=3; idx < ${#words[@]}-1; idx++ )); do
62            [[ $w == ${words[idx]} ]] && return 0
63        done
64    done
65    return 1
66}
67
68# Takes a list of words in argument; adds them all to COMPREPLY if none of them
69# is already present on the command line. Returns no value.
70_bpftool_one_of_list()
71{
72    _bpftool_search_list $* && return 1
73    COMPREPLY+=( $( compgen -W "$*" -- "$cur" ) )
74}
75
76_bpftool_get_map_ids()
77{
78    COMPREPLY+=( $( compgen -W "$( bpftool -jp map  2>&1 | \
79        command sed -n 's/.*"id": \(.*\),$/\1/p' )" -- "$cur" ) )
80}
81
82_bpftool_get_perf_map_ids()
83{
84    COMPREPLY+=( $( compgen -W "$( bpftool -jp map  2>&1 | \
85        command grep -C2 perf_event_array | \
86        command sed -n 's/.*"id": \(.*\),$/\1/p' )" -- "$cur" ) )
87}
88
89
90_bpftool_get_prog_ids()
91{
92    COMPREPLY+=( $( compgen -W "$( bpftool -jp prog 2>&1 | \
93        command sed -n 's/.*"id": \(.*\),$/\1/p' )" -- "$cur" ) )
94}
95
96_bpftool_get_prog_tags()
97{
98    COMPREPLY+=( $( compgen -W "$( bpftool -jp prog 2>&1 | \
99        command sed -n 's/.*"tag": "\(.*\)",$/\1/p' )" -- "$cur" ) )
100}
101
102_bpftool_get_obj_map_names()
103{
104    local obj
105
106    obj=$1
107
108    maps=$(objdump -j maps -t $obj 2>/dev/null | \
109        command awk '/g     . maps/ {print $NF}')
110
111    COMPREPLY+=( $( compgen -W "$maps" -- "$cur" ) )
112}
113
114_bpftool_get_obj_map_idxs()
115{
116    local obj
117
118    obj=$1
119
120    nmaps=$(objdump -j maps -t $obj 2>/dev/null | grep -c 'g     . maps')
121
122    COMPREPLY+=( $( compgen -W "$(seq 0 $((nmaps - 1)))" -- "$cur" ) )
123}
124
125_sysfs_get_netdevs()
126{
127    COMPREPLY+=( $( compgen -W "$( ls /sys/class/net 2>/dev/null )" -- \
128        "$cur" ) )
129}
130
131# For bpftool map update: retrieve type of the map to update.
132_bpftool_map_update_map_type()
133{
134    local keyword ref
135    for (( idx=3; idx < ${#words[@]}-1; idx++ )); do
136        if [[ ${words[$((idx-2))]} == "update" ]]; then
137            keyword=${words[$((idx-1))]}
138            ref=${words[$((idx))]}
139        fi
140    done
141    [[ -z $ref ]] && return 0
142
143    local type
144    type=$(bpftool -jp map show $keyword $ref | \
145        command sed -n 's/.*"type": "\(.*\)",$/\1/p')
146    printf $type
147}
148
149_bpftool_map_update_get_id()
150{
151    # Is it the map to update, or a map to insert into the map to update?
152    # Search for "value" keyword.
153    local idx value
154    for (( idx=7; idx < ${#words[@]}-1; idx++ )); do
155        if [[ ${words[idx]} == "value" ]]; then
156            value=1
157            break
158        fi
159    done
160    [[ $value -eq 0 ]] && _bpftool_get_map_ids && return 0
161
162    # Id to complete is for a value. It can be either prog id or map id. This
163    # depends on the type of the map to update.
164    local type=$(_bpftool_map_update_map_type)
165    case $type in
166        array_of_maps|hash_of_maps)
167            _bpftool_get_map_ids
168            return 0
169            ;;
170        prog_array)
171            _bpftool_get_prog_ids
172            return 0
173            ;;
174        *)
175            return 0
176            ;;
177    esac
178}
179
180_bpftool()
181{
182    local cur prev words objword
183    _init_completion || return
184
185    # Deal with options
186    if [[ ${words[cword]} == -* ]]; then
187        local c='--version --json --pretty --bpffs'
188        COMPREPLY=( $( compgen -W "$c" -- "$cur" ) )
189        return 0
190    fi
191
192    # Deal with simplest keywords
193    case $prev in
194        help|hex|opcodes|visual)
195            return 0
196            ;;
197        tag)
198            _bpftool_get_prog_tags
199            return 0
200            ;;
201        file|pinned)
202            _filedir
203            return 0
204            ;;
205        batch)
206            COMPREPLY=( $( compgen -W 'file' -- "$cur" ) )
207            return 0
208            ;;
209    esac
210
211    # Remove all options so completions don't have to deal with them.
212    local i
213    for (( i=1; i < ${#words[@]}; )); do
214        if [[ ${words[i]::1} == - ]]; then
215            words=( "${words[@]:0:i}" "${words[@]:i+1}" )
216            [[ $i -le $cword ]] && cword=$(( cword - 1 ))
217        else
218            i=$(( ++i ))
219        fi
220    done
221    cur=${words[cword]}
222    prev=${words[cword - 1]}
223
224    local object=${words[1]} command=${words[2]}
225
226    if [[ -z $object || $cword -eq 1 ]]; then
227        case $cur in
228            *)
229                COMPREPLY=( $( compgen -W "$( bpftool help 2>&1 | \
230                    command sed \
231                    -e '/OBJECT := /!d' \
232                    -e 's/.*{//' \
233                    -e 's/}.*//' \
234                    -e 's/|//g' )" -- "$cur" ) )
235                COMPREPLY+=( $( compgen -W 'batch help' -- "$cur" ) )
236                return 0
237                ;;
238        esac
239    fi
240
241    [[ $command == help ]] && return 0
242
243    # Completion depends on object and command in use
244    case $object in
245        prog)
246            if [[ $command != "load" ]]; then
247                case $prev in
248                    id)
249                        _bpftool_get_prog_ids
250                        return 0
251                        ;;
252                esac
253            fi
254
255            local PROG_TYPE='id pinned tag'
256            case $command in
257                show|list)
258                    [[ $prev != "$command" ]] && return 0
259                    COMPREPLY=( $( compgen -W "$PROG_TYPE" -- "$cur" ) )
260                    return 0
261                    ;;
262                dump)
263                    case $prev in
264                        $command)
265                            COMPREPLY+=( $( compgen -W "xlated jited" -- \
266                                "$cur" ) )
267                            return 0
268                            ;;
269                        xlated|jited)
270                            COMPREPLY=( $( compgen -W "$PROG_TYPE" -- \
271                                "$cur" ) )
272                            return 0
273                            ;;
274                    *)
275                        _bpftool_once_attr 'file'
276                        if _bpftool_search_list 'xlated'; then
277                            COMPREPLY+=( $( compgen -W 'opcodes visual' -- \
278                                "$cur" ) )
279                        else
280                            COMPREPLY+=( $( compgen -W 'opcodes' -- \
281                                "$cur" ) )
282                        fi
283                        return 0
284                        ;;
285                    esac
286                    ;;
287                pin)
288                    if [[ $prev == "$command" ]]; then
289                        COMPREPLY=( $( compgen -W "$PROG_TYPE" -- "$cur" ) )
290                    else
291                        _filedir
292                    fi
293                    return 0
294                    ;;
295                load)
296                    local obj
297
298                    if [[ ${#words[@]} -lt 6 ]]; then
299                        _filedir
300                        return 0
301                    fi
302
303                    obj=${words[3]}
304
305                    if [[ ${words[-4]} == "map" ]]; then
306                        COMPREPLY=( $( compgen -W "id pinned" -- "$cur" ) )
307                        return 0
308                    fi
309                    if [[ ${words[-3]} == "map" ]]; then
310                        if [[ ${words[-2]} == "idx" ]]; then
311                            _bpftool_get_obj_map_idxs $obj
312                        elif [[ ${words[-2]} == "name" ]]; then
313                            _bpftool_get_obj_map_names $obj
314                        fi
315                        return 0
316                    fi
317                    if [[ ${words[-2]} == "map" ]]; then
318                        COMPREPLY=( $( compgen -W "idx name" -- "$cur" ) )
319                        return 0
320                    fi
321
322                    case $prev in
323                        type)
324                            COMPREPLY=( $( compgen -W "socket kprobe kretprobe classifier 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/post_bind4 cgroup/post_bind6" -- \
325                                                   "$cur" ) )
326                            return 0
327                            ;;
328                        id)
329                            _bpftool_get_map_ids
330                            return 0
331                            ;;
332                        pinned)
333                            _filedir
334                            return 0
335                            ;;
336                        dev)
337                            _sysfs_get_netdevs
338                            return 0
339                            ;;
340                        *)
341                            COMPREPLY=( $( compgen -W "map" -- "$cur" ) )
342                            _bpftool_once_attr 'type'
343                            _bpftool_once_attr 'dev'
344                            return 0
345                            ;;
346                    esac
347                    ;;
348                *)
349                    [[ $prev == $object ]] && \
350                        COMPREPLY=( $( compgen -W 'dump help pin load \
351                            show list' -- "$cur" ) )
352                    ;;
353            esac
354            ;;
355        map)
356            local MAP_TYPE='id pinned'
357            case $command in
358                show|list|dump)
359                    case $prev in
360                        $command)
361                            COMPREPLY=( $( compgen -W "$MAP_TYPE" -- "$cur" ) )
362                            return 0
363                            ;;
364                        id)
365                            _bpftool_get_map_ids
366                            return 0
367                            ;;
368                        *)
369                            return 0
370                            ;;
371                    esac
372                    ;;
373                lookup|getnext|delete)
374                    case $prev in
375                        $command)
376                            COMPREPLY=( $( compgen -W "$MAP_TYPE" -- "$cur" ) )
377                            return 0
378                            ;;
379                        id)
380                            _bpftool_get_map_ids
381                            return 0
382                            ;;
383                        key)
384                            COMPREPLY+=( $( compgen -W 'hex' -- "$cur" ) )
385                            ;;
386                        *)
387                            _bpftool_once_attr 'key'
388                            return 0
389                            ;;
390                    esac
391                    ;;
392                update)
393                    case $prev in
394                        $command)
395                            COMPREPLY=( $( compgen -W "$MAP_TYPE" -- "$cur" ) )
396                            return 0
397                            ;;
398                        id)
399                            _bpftool_map_update_get_id
400                            return 0
401                            ;;
402                        key)
403                            COMPREPLY+=( $( compgen -W 'hex' -- "$cur" ) )
404                            ;;
405                        value)
406                            # We can have bytes, or references to a prog or a
407                            # map, depending on the type of the map to update.
408                            case $(_bpftool_map_update_map_type) in
409                                array_of_maps|hash_of_maps)
410                                    local MAP_TYPE='id pinned'
411                                    COMPREPLY+=( $( compgen -W "$MAP_TYPE" \
412                                        -- "$cur" ) )
413                                    return 0
414                                    ;;
415                                prog_array)
416                                    local PROG_TYPE='id pinned tag'
417                                    COMPREPLY+=( $( compgen -W "$PROG_TYPE" \
418                                        -- "$cur" ) )
419                                    return 0
420                                    ;;
421                                *)
422                                    COMPREPLY+=( $( compgen -W 'hex' \
423                                        -- "$cur" ) )
424                                    return 0
425                                    ;;
426                            esac
427                            return 0
428                            ;;
429                        *)
430                            _bpftool_once_attr 'key'
431                            local UPDATE_FLAGS='any exist noexist'
432                            for (( idx=3; idx < ${#words[@]}-1; idx++ )); do
433                                if [[ ${words[idx]} == 'value' ]]; then
434                                    # 'value' is present, but is not the last
435                                    # word i.e. we can now have UPDATE_FLAGS.
436                                    _bpftool_one_of_list "$UPDATE_FLAGS"
437                                    return 0
438                                fi
439                            done
440                            for (( idx=3; idx < ${#words[@]}-1; idx++ )); do
441                                if [[ ${words[idx]} == 'key' ]]; then
442                                    # 'key' is present, but is not the last
443                                    # word i.e. we can now have 'value'.
444                                    _bpftool_once_attr 'value'
445                                    return 0
446                                fi
447                            done
448                            return 0
449                            ;;
450                    esac
451                    ;;
452                pin)
453                    if [[ $prev == "$command" ]]; then
454                        COMPREPLY=( $( compgen -W "$PROG_TYPE" -- "$cur" ) )
455                    else
456                        _filedir
457                    fi
458                    return 0
459                    ;;
460                event_pipe)
461                    case $prev in
462                        $command)
463                            COMPREPLY=( $( compgen -W "$MAP_TYPE" -- "$cur" ) )
464                            return 0
465                            ;;
466                        id)
467                            _bpftool_get_perf_map_ids
468                            return 0
469                            ;;
470                        cpu)
471                            return 0
472                            ;;
473                        index)
474                            return 0
475                            ;;
476                        *)
477                            _bpftool_once_attr 'cpu'
478                            _bpftool_once_attr 'index'
479                            return 0
480                            ;;
481                    esac
482                    ;;
483                *)
484                    [[ $prev == $object ]] && \
485                        COMPREPLY=( $( compgen -W 'delete dump getnext help \
486                            lookup pin event_pipe show list update' -- \
487                            "$cur" ) )
488                    ;;
489            esac
490            ;;
491        cgroup)
492            case $command in
493                show|list)
494                    _filedir
495                    return 0
496                    ;;
497		tree)
498		    _filedir
499		    return 0
500		    ;;
501                attach|detach)
502                    local ATTACH_TYPES='ingress egress sock_create sock_ops \
503                        device bind4 bind6 post_bind4 post_bind6 connect4 \
504                        connect6 sendmsg4 sendmsg6'
505                    local ATTACH_FLAGS='multi override'
506                    local PROG_TYPE='id pinned tag'
507                    case $prev in
508                        $command)
509                            _filedir
510                            return 0
511                            ;;
512                        ingress|egress|sock_create|sock_ops|device|bind4|bind6|\
513                        post_bind4|post_bind6|connect4|connect6|sendmsg4|\
514                        sendmsg6)
515                            COMPREPLY=( $( compgen -W "$PROG_TYPE" -- \
516                                "$cur" ) )
517                            return 0
518                            ;;
519                        id)
520                            _bpftool_get_prog_ids
521                            return 0
522                            ;;
523                        *)
524                            if ! _bpftool_search_list "$ATTACH_TYPES"; then
525                                COMPREPLY=( $( compgen -W "$ATTACH_TYPES" -- \
526                                    "$cur" ) )
527                            elif [[ "$command" == "attach" ]]; then
528                                # We have an attach type on the command line,
529                                # but it is not the previous word, or
530                                # "id|pinned|tag" (we already checked for
531                                # that). This should only leave the case when
532                                # we need attach flags for "attach" commamnd.
533                                _bpftool_one_of_list "$ATTACH_FLAGS"
534                            fi
535                            return 0
536                            ;;
537                    esac
538                    ;;
539                *)
540                    [[ $prev == $object ]] && \
541                        COMPREPLY=( $( compgen -W 'help attach detach \
542                            show list tree' -- "$cur" ) )
543                    ;;
544            esac
545            ;;
546        perf)
547            case $command in
548                *)
549                    [[ $prev == $object ]] && \
550                        COMPREPLY=( $( compgen -W 'help \
551                            show list' -- "$cur" ) )
552                    ;;
553            esac
554            ;;
555    esac
556} &&
557complete -F _bpftool bpftool
558
559# ex: ts=4 sw=4 et filetype=sh
560