This file is indexed.

/usr/share/bash-completion/completions/tc is in iproute2 4.15.0-2ubuntu1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
# tc(8) completion                                         -*- shell-script -*-
# Copyright 2016 6WIND S.A.
# Copyright 2016 Quentin Monnet <quentin.monnet@6wind.com>

QDISC_KIND=' choke codel bfifo pfifo pfifo_head_drop fq fq_codel gred hhf \
            mqprio multiq netem pfifo_fast pie red rr sfb sfq tbf atm cbq drr \
            dsmark hfsc htb prio qfq '
FILTER_KIND=' basic bpf cgroup flow flower fw route rsvp tcindex u32 matchall '
ACTION_KIND=' gact mirred bpf sample '

# 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.
_tc_once_attr()
{
    local w subcword found
    for w in $*; do
        found=0
        for (( subcword=3; subcword < ${#words[@]}-1; subcword++ )); do
            if [[ $w == ${words[subcword]} ]]; then
                found=1
                break
            fi
        done
        [[ $found -eq 0 ]] && \
            COMPREPLY+=( $( compgen -W "$w" -- "$cur" ) )
    done
}

# 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 from the provided index. Returns
# no value.
_tc_once_attr_from()
{
    local w subcword found from=$1
    shift
    for w in $*; do
        found=0
        for (( subcword=$from; subcword < ${#words[@]}-1; subcword++ )); do
            if [[ $w == ${words[subcword]} ]]; then
                found=1
                break
            fi
        done
        [[ $found -eq 0 ]] && \
            COMPREPLY+=( $( compgen -W "$w" -- "$cur" ) )
    done
}

# 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.
_tc_one_of_list()
{
    local w subcword
    for w in $*; do
        for (( subcword=3; subcword < ${#words[@]}-1; subcword++ )); do
            [[ $w == ${words[subcword]} ]] && return 1
        done
    done
    COMPREPLY+=( $( compgen -W "$*" -- "$cur" ) )
}

# Takes a list of words in argument; adds them all to COMPREPLY if none of them
# is already present on the command line from the provided index. Returns no
# value.
_tc_one_of_list_from()
{
    local w subcword from=$1
    shift
    for w in $*; do
        for (( subcword=$from; subcword < ${#words[@]}-1; subcword++ )); do
            [[ $w == ${words[subcword]} ]] && return 1
        done
    done
    COMPREPLY+=( $( compgen -W "$*" -- "$cur" ) )
}

# Returns "$cur ${cur}arg1 ${cur}arg2 ..."
_tc_expand_units()
{
    [[ $cur =~ ^[0-9]+ ]] || return 1
    local value=${cur%%[^0-9]*}
    [[ $cur == $value ]] && echo $cur
    echo ${@/#/$value}
}

# Complete based on given word, usually $prev (or possibly the word before),
# for when an argument or an option name has but a few possible arguments (so
# tc does not take particular commands into account here).
# Returns 0 is completion should stop after running this function, 1 otherwise.
_tc_direct_complete()
{
    case $1 in
        # Command options
        dev)
            _available_interfaces
            return 0
            ;;
        classid)
            return 0
            ;;
        estimator)
            local list=$( _tc_expand_units 'secs' 'msecs' 'usecs' )
            COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) )
            return 0
            ;;
        handle)
            return 0
            ;;
        parent|flowid)
            local i iface ids cmd
            for (( i=3; i < ${#words[@]}-2; i++ )); do
                [[ ${words[i]} == dev ]] && iface=${words[i+1]}
                break
            done
            for cmd in qdisc class; do
                if [[ -n $iface ]]; then
                    ids+=$( tc $cmd show dev $iface 2>/dev/null | \
                        cut -d\  -f 3 )" "
                else
                    ids+=$( tc $cmd show 2>/dev/null | cut -d\  -f 3 )
                fi
            done
            [[ $ids != " " ]] && \
                COMPREPLY+=( $( compgen -W "$ids" -- "$cur" ) )
            return 0
            ;;
        protocol) # list comes from lib/ll_proto.c
            COMPREPLY+=( $( compgen -W ' 802.1Q 802.1ad 802_2 802_3 LLDP aarp \
                all aoe arp atalk atmfate atmmpoa ax25 bpq can control cust \
                ddcmp dec diag dna_dl dna_rc dna_rt econet ieeepup ieeepupat \
                ip ipv4 ipv6 ipx irda lat localtalk loop mobitex ppp_disc \
                ppp_mp ppp_ses ppptalk pup pupat rarp sca snap tipc tr_802_2 \
                wan_ppp x25' -- "$cur" ) )
            return 0
            ;;
        prio)
            return 0
            ;;
        stab)
            COMPREPLY+=( $( compgen -W 'mtu tsize mpu overhead
                linklayer' -- "$cur" ) )
            ;;

        # Qdiscs and classes options
        alpha|bands|beta|buckets|corrupt|debug|decrement|default|\
        default_index|depth|direct_qlen|divisor|duplicate|ewma|flow_limit|\
        flows|hh_limit|increment|indices|linklayer|non_hh_weight|num_tc|\
        penalty_burst|penalty_rate|prio|priomap|probability|queues|r2q|\
        reorder|vq|vqs)
            return 0
            ;;
        setup)
            COMPREPLY+=( $( compgen -W 'vqs' -- "$cur" ) )
            return 0
            ;;
        hw)
            COMPREPLY+=( $( compgen -W '1 0' -- "$cur" ) )
            return 0
            ;;
        distribution)
            COMPREPLY+=( $( compgen -W 'uniform normal pareto
                paretonormal' -- "$cur" ) )
            return 0
            ;;
        loss)
            COMPREPLY+=( $( compgen -W 'random state gmodel' -- "$cur" ) )
            return 0
            ;;

        # Qdiscs and classes options options
        gap|gmodel|state)
            return 0
            ;;

        # Filters options
        map)
            COMPREPLY+=( $( compgen -W 'key' -- "$cur" ) )
            return 0
            ;;
        hash)
            COMPREPLY+=( $( compgen -W 'keys' -- "$cur" ) )
            return 0
            ;;
        indev)
            _available_interfaces
            return 0
            ;;
        eth_type)
            COMPREPLY+=( $( compgen -W 'ipv4 ipv6' -- "$cur" ) )
            return 0
            ;;
        ip_proto)
            COMPREPLY+=( $( compgen -W 'tcp udp' -- "$cur" ) )
            return 0
            ;;

        # Filters options options
        key|keys)
            [[ ${words[@]} =~ graft ]] && return 1
            COMPREPLY+=( $( compgen -W 'src dst proto proto-src proto-dst iif \
                priority mark nfct nfct-src nfct-dst nfct-proto-src \
                nfct-proto-dst rt-classid sk-uid sk-gid vlan-tag rxhash' -- \
                "$cur" ) )
            return 0
            ;;

        # BPF options - used for filters, actions, and exec
        export|bytecode|bytecode-file|object-file)
            _filedir
            return 0
            ;;
        object-pinned|graft) # Pinned object is probably under /sys/fs/bpf/
            [[ -n "$cur" ]] && _filedir && return 0
            COMPREPLY=( $( compgen -G "/sys/fs/bpf/*" -- "$cur" ) ) || _filedir
            compopt -o nospace
            return 0
            ;;
        section)
            if (type objdump > /dev/null 2>&1) ; then
                local fword objfile section_list
                for (( fword=3; fword < ${#words[@]}-3; fword++ )); do
                    if [[ ${words[fword]} == object-file ]]; then
                        objfile=${words[fword+1]}
                        break
                    fi
                done
                section_list=$( objdump -h $objfile 2>/dev/null | \
                    sed -n 's/^ *[0-9]\+ \([^ ]*\) *.*/\1/p' )
                COMPREPLY+=( $( compgen -W "$section_list" -- "$cur" ) )
            fi
            return 0
            ;;
        import|run)
            _filedir
            return 0
            ;;
        type)
            COMPREPLY+=( $( compgen -W 'cls act' -- "$cur" ) )
            return 0
            ;;

        # Actions options
        random)
            _tc_one_of_list 'netrand determ'
            return 0
            ;;

        # Units for option arguments
        bandwidth|maxrate|peakrate|rate)
            local list=$( _tc_expand_units 'bit' \
                'kbit' 'kibit' 'kbps' 'kibps' \
                'mbit' 'mibit' 'mbps' 'mibps' \
                'gbit' 'gibit' 'gbps' 'gibps' \
                'tbit' 'tibit' 'tbps' 'tibps' )
            COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) )
            ;;
        admit_bytes|avpkt|burst|cell|initial_quantum|limit|max|min|mtu|mpu|\
        overhead|quantum|redflowlist)
            local list=$( _tc_expand_units \
                'b' 'kbit' 'k' 'mbit' 'm' 'gbit' 'g' )
            COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) )
            ;;
        db|delay|evict_timeout|interval|latency|perturb|rehash|reset_timeout|\
        target|tupdate)
            local list=$( _tc_expand_units 'secs' 'msecs' 'usecs' )
            COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) )
            ;;
    esac
    return 1
}

# Complete with options names for qdiscs. Each qdisc has its own set of options
# and it seems we cannot really parse it from anywhere, so we add it manually
# in this function.
# Returns 0 is completion should stop after running this function, 1 otherwise.
_tc_qdisc_options()
{
    case $1 in
        choke)
            _tc_once_attr 'limit bandwidth ecn min max burst'
            return 0
            ;;
        codel)
            _tc_once_attr 'limit target interval'
            _tc_one_of_list 'ecn noecn'
            return 0
            ;;
        bfifo|pfifo|pfifo_head_drop)
            _tc_once_attr 'limit'
            return 0
            ;;
        fq)
            _tc_once_attr 'limit flow_limit quantum initial_quantum maxrate \
                buckets'
            _tc_one_of_list 'pacing nopacing'
            return 0
            ;;
        fq_codel)
            _tc_once_attr 'limit flows target interval quantum'
            _tc_one_of_list 'ecn noecn'
            return 0
            ;;
        gred)
            _tc_once_attr 'setup vqs default grio vq prio limit min max avpkt \
                burst probability bandwidth'
            return 0
            ;;
        hhf)
            _tc_once_attr 'limit quantum hh_limit reset_timeout admit_bytes \
                evict_timeout non_hh_weight'
            return 0
            ;;
        mqprio)
            _tc_once_attr 'num_tc map queues hw'
            return 0
            ;;
        netem)
            _tc_once_attr 'delay distribution corrupt duplicate loss ecn \
                reorder rate'
            return 0
            ;;
        pie)
            _tc_once_attr 'limit target tupdate alpha beta'
            _tc_one_of_list 'bytemode nobytemode'
            _tc_one_of_list 'ecn noecn'
            return 0
            ;;
        red)
            _tc_once_attr 'limit min max avpkt burst adaptive probability \
                bandwidth ecn harddrop'
            return 0
            ;;
        rr|prio)
            _tc_once_attr 'bands priomap multiqueue'
            return 0
            ;;
        sfb)
            _tc_once_attr 'rehash db limit max target increment decrement \
                penalty_rate penalty_burst'
            return 0
            ;;
        sfq)
            _tc_once_attr 'limit perturb quantum divisor flows depth headdrop \
                redflowlimit min max avpkt burst probability ecn harddrop'
            return 0
            ;;
        tbf)
            _tc_once_attr 'limit burst rate mtu peakrate latency overhead \
                linklayer'
            return 0
            ;;
        cbq)
            _tc_once_attr 'bandwidth avpkt mpu cell ewma'
            return 0
            ;;
        dsmark)
            _tc_once_attr 'indices default_index set_tc_index'
            return 0
            ;;
        hfsc)
            _tc_once_attr 'default'
            return 0
            ;;
        htb)
            _tc_once_attr 'default r2q direct_qlen debug'
            return 0
            ;;
        multiq|pfifo_fast|atm|drr|qfq)
            return 0
            ;;
    esac
    return 1
}

# Complete with options names for BPF filters or actions.
# Returns 0 is completion should stop after running this function, 1 otherwise.
_tc_bpf_options()
{
    [[ ${words[${#words[@]}-3]} == object-file ]] && \
        _tc_once_attr 'section export'
    [[ ${words[${#words[@]}-5]} == object-file ]] && \
        [[ ${words[${#words[@]}-3]} =~ (section|export) ]] && \
        _tc_once_attr 'section export'
    _tc_one_of_list 'bytecode bytecode-file object-file object-pinned'
    _tc_once_attr 'verbose index direct-action action classid'
    return 0
}

# Complete with options names for filter actions.
# This function is recursive, thus allowing multiple actions statement to be
# parsed.
# Returns 0 is completion should stop after running this function, 1 otherwise.
_tc_filter_action_options()
{
    for ((acwd=$1; acwd < ${#words[@]}-1; acwd++));
    do
        if [[ action == ${words[acwd]} ]]; then
            _tc_filter_action_options $((acwd+1)) && return 0
        fi
    done

    local action acwd
    for ((acwd=$1; acwd < ${#words[@]}-1; acwd++)); do
        if [[ $ACTION_KIND =~ ' '${words[acwd]}' ' ]]; then
            _tc_one_of_list_from $acwd action
            _tc_action_options $acwd && return 0
        fi
    done
    _tc_one_of_list_from $acwd $ACTION_KIND
    return 0
}

# Complete with options names for filters.
# Returns 0 is completion should stop after running this function, 1 otherwise.
_tc_filter_options()
{

    for ((acwd=$1; acwd < ${#words[@]}-1; acwd++));
    do
        if [[ action == ${words[acwd]} ]]; then
            _tc_filter_action_options $((acwd+1)) && return 0
        fi
    done

    filter=${words[$1]}
    case $filter in
        basic)
            _tc_once_attr 'match action classid'
            return 0
            ;;
        bpf)
            _tc_bpf_options
            return 0
            ;;
        cgroup)
            _tc_once_attr 'match action'
            return 0
            ;;
        flow)
            local i
            for (( i=5; i < ${#words[@]}-1; i++ )); do
                if [[ ${words[i]} =~ ^keys?$ ]]; then
                    _tc_direct_complete 'key'
                    COMPREPLY+=( $( compgen -W 'or and xor rshift addend' -- \
                        "$cur" ) )
                    break
                fi
            done
            _tc_once_attr 'map hash divisor baseclass match action'
            return 0
            ;;
        matchall)
            _tc_once_attr 'action classid skip_sw skip_hw'
            return 0
            ;;
        flower)
            _tc_once_attr 'action classid indev dst_mac src_mac eth_type \
                ip_proto dst_ip src_ip dst_port src_port'
            return 0
            ;;
        fw)
            _tc_once_attr 'action classid'
            return 0
            ;;
        route)
            _tc_one_of_list 'from fromif'
            _tc_once_attr 'to classid action'
            return 0
            ;;
        rsvp)
            _tc_once_attr 'ipproto session sender classid action tunnelid \
                tunnel flowlabel spi/ah spi/esp u8 u16 u32'
            [[ ${words[${#words[@]}-3]} == tunnel ]] && \
                    COMPREPLY+=( $( compgen -W 'skip' -- "$cur" ) )
            [[ ${words[${#words[@]}-3]} =~ u(8|16|32) ]] && \
                    COMPREPLY+=( $( compgen -W 'mask' -- "$cur" ) )
            [[ ${words[${#words[@]}-3]} == mask ]] && \
                    COMPREPLY+=( $( compgen -W 'at' -- "$cur" ) )
            return 0
            ;;
        tcindex)
            _tc_once_attr 'hash mask shift classid action'
            _tc_one_of_list 'pass_on fall_through'
            return 0
            ;;
        u32)
            _tc_once_attr 'match link classid action offset ht hashkey sample'
            COMPREPLY+=( $( compgen -W 'ip ip6 udp tcp icmp u8 u16 u32 mark \
                divisor' -- "$cur" ) )
            return 0
            ;;
    esac
    return 1
}

# Complete with options names for actions.
# Returns 0 is completion should stop after running this function, 1 otherwise.
_tc_action_options()
{
    local from=$1
    local action=${words[from]}
    case $action in
        bpf)
            _tc_bpf_options
            return 0
            ;;
        mirred)
            _tc_one_of_list_from $from 'ingress egress'
            _tc_one_of_list_from $from 'mirror redirect'
            _tc_once_attr_from $from 'index dev'
            return 0
            ;;
        sample)
            _tc_once_attr_from $from 'rate'
            _tc_once_attr_from $from 'trunc'
            _tc_once_attr_from $from 'group'
            return 0
            ;;
        gact)
            _tc_one_of_list_from $from 'reclassify drop continue pass'
            _tc_once_attr_from $from 'random'
            return 0
            ;;
    esac
    return 1
}

# Complete with options names for exec.
# Returns 0 is completion should stop after running this function, 1 otherwise.
_tc_exec_options()
{
    case $1 in
        import)
            [[ ${words[${#words[@]}-3]} == import ]] && \
                _tc_once_attr 'run'
            return 0
            ;;
        graft)
            COMPREPLY+=( $( compgen -W 'key type' -- "$cur" ) )
            [[ ${words[${#words[@]}-3]} == object-file ]] && \
                _tc_once_attr 'type'
            _tc_bpf_options
            return 0
            ;;
    esac
    return 1
}

# Main completion function
# Logic is as follows:
#   1. Check if previous word is a global option; if so, propose arguments.
#   2. Check if current word is a global option; if so, propose completion.
#   3. Check for the presence of a main command (qdisc|class|filter|...). If
#      there is one, first call _tc_direct_complete to see if previous word is
#      waiting for a particular completion. If so, propose completion and exit.
#   4. Extract main command and -- if available -- its subcommand
#      (add|delete|show|...).
#   5. Propose completion based on main and sub- command in use. Additional
#      functions may be called for qdiscs, classes or filter options.
_tc()
{
    local cur prev words cword
    _init_completion || return

    case $prev in
        -V|-Version)
            return 0
            ;;
        -b|-batch|-cf|-conf)
            _filedir
            return 0
            ;;
        -force)
            COMPREPLY=( $( compgen -W '-batch' -- "$cur" ) )
            return 0
            ;;
        -nm|name)
            [[ -r /etc/iproute2/tc_cls ]] || \
                COMPREPLY=( $( compgen -W '-conf' -- "$cur" ) )
            return 0
            ;;
        -n|-net|-netns)
            local nslist=$( ip netns list 2>/dev/null )
            COMPREPLY+=( $( compgen -W "$nslist" -- "$cur" ) )
            return 0
            ;;
        -tshort)
            _tc_once_attr '-statistics'
            COMPREPLY+=( $( compgen -W 'monitor' -- "$cur" ) )
            return 0
            ;;
        -timestamp)
            _tc_once_attr '-statistics -tshort'
            COMPREPLY+=( $( compgen -W 'monitor' -- "$cur" ) )
            return 0
            ;;
    esac

    # Search for main commands
    local subcword cmd subcmd
    for (( subcword=1; subcword < ${#words[@]}-1; subcword++ )); do
        [[ ${words[subcword]} == -b?(atch) ]] && return 0
        [[ -n $cmd ]] && subcmd=${words[subcword]} && break
        [[ ${words[subcword]} != -* && \
            ${words[subcword-1]} != -@(n?(et?(ns))|c?(on)f) ]] && \
            cmd=${words[subcword]}
    done

    if [[ -z $cmd ]]; then
        case $cur in
            -*)
                local c='-Version -statistics -details -raw -pretty \
                    -iec -graphe -batch -name -netns -timestamp'
                [[ $cword -eq 1 ]] && c+=' -force'
                COMPREPLY=( $( compgen -W "$c" -- "$cur" ) )
                return 0
                ;;
            *)
                COMPREPLY=( $( compgen -W "help $( tc help 2>&1 | \
                    command sed \
                    -e '/OBJECT := /!d' \
                    -e 's/.*{//' \
                    -e 's/}.*//' \
                    -e \ 's/|//g' )" -- "$cur" ) )
                return 0
                ;;
        esac
    fi

    [[ $subcmd == help ]] && return 0

    # For this set of commands we may create COMPREPLY just by analysing the
    # previous word, if it expects for a specific list of options or values.
    if [[ $cmd =~ (qdisc|class|filter|action|exec) ]]; then
        _tc_direct_complete $prev && return 0
        if [[ ${words[${#words[@]}-3]} == estimator ]]; then
            local list=$( _tc_expand_units 'secs' 'msecs' 'usecs' )
            COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) ) && return 0
        fi
    fi

    # Completion depends on main command and subcommand in use.
    case $cmd in
        qdisc)
            case $subcmd in
                add|change|replace|link|del|delete)
                    if [[ $(($cword-$subcword)) -eq 1 ]]; then
                        COMPREPLY=( $( compgen -W 'dev' -- "$cur" ) )
                        return 0
                    fi
                    local qdisc qdwd
                    for ((qdwd=$subcword; qdwd < ${#words[@]}-1; qdwd++)); do
                        if [[ $QDISC_KIND =~ ' '${words[qdwd]}' ' ]]; then
                            qdisc=${words[qdwd]}
                            _tc_qdisc_options $qdisc && return 0
                        fi
                    done
                    _tc_one_of_list $QDISC_KIND
                    _tc_one_of_list 'root ingress parent clsact'
                    _tc_once_attr 'handle estimator stab'
                    ;;
                show)
                    _tc_once_attr 'dev'
                    _tc_one_of_list 'ingress clsact'
                    _tc_once_attr '-statistics -details -raw -pretty -iec \
                        -graph -name'
                    ;;
                help)
                    return 0
                    ;;
                *)
                    [[ $cword -eq $subcword ]] && \
                        COMPREPLY=( $( compgen -W 'help add delete change \
                            replace link show' -- "$cur" ) )
                    ;;
            esac
            ;;

        class)
            case $subcmd in
                add|change|replace|del|delete)
                    if [[ $(($cword-$subcword)) -eq 1 ]]; then
                        COMPREPLY=( $( compgen -W 'dev' -- "$cur" ) )
                        return 0
                    fi
                    local qdisc qdwd
                    for ((qdwd=$subcword; qdwd < ${#words[@]}-1; qdwd++)); do
                        if [[ $QDISC_KIND =~ ' '${words[qdwd]}' ' ]]; then
                            qdisc=${words[qdwd]}
                            _tc_qdisc_options $qdisc && return 0
                        fi
                    done
                    _tc_one_of_list $QDISC_KIND
                    _tc_one_of_list 'root parent'
                    _tc_once_attr 'classid'
                    ;;
                show)
                    _tc_once_attr 'dev'
                    _tc_one_of_list 'root parent'
                    _tc_once_attr '-statistics -details -raw -pretty -iec \
                        -graph -name'
                    ;;
                help)
                    return 0
                    ;;
                *)
                    [[ $cword -eq $subcword ]] && \
                        COMPREPLY=( $( compgen -W 'help add delete change \
                            replace show' -- "$cur" ) )
                    ;;
            esac
            ;;

        filter)
            case $subcmd in
                add|change|replace|del|delete)
                    if [[ $(($cword-$subcword)) -eq 1 ]]; then
                        COMPREPLY=( $( compgen -W 'dev' -- "$cur" ) )
                        return 0
                    fi
                    local filter fltwd
                    for ((fltwd=$subcword; fltwd < ${#words[@]}-1; fltwd++));
                    do
                        if [[ $FILTER_KIND =~ ' '${words[fltwd]}' ' ]]; then
                            _tc_filter_options $fltwd && return 0
                        fi
                    done
                    _tc_one_of_list $FILTER_KIND
                    _tc_one_of_list 'root ingress egress parent'
                    _tc_once_attr 'handle estimator pref protocol'
                    ;;
                show)
                    _tc_once_attr 'dev'
                    _tc_one_of_list 'root ingress egress parent'
                    _tc_once_attr '-statistics -details -raw -pretty -iec \
                        -graph -name'
                    ;;
                help)
                    return 0
                    ;;
                *)
                    [[ $cword -eq $subcword ]] && \
                        COMPREPLY=( $( compgen -W 'help add delete change \
                            replace show' -- "$cur" ) )
                    ;;
            esac
            ;;

        action)
            case $subcmd in
                add|change|replace)
                    local action acwd
                    for ((acwd=$subcword; acwd < ${#words[@]}-1; acwd++)); do
                        if [[ $ACTION_KIND =~ ' '${words[acwd]}' ' ]]; then
                            _tc_action_options $acwd && return 0
                        fi
                    done
                    _tc_one_of_list $ACTION_KIND
                    ;;
                get|del|delete)
                    _tc_once_attr 'index'
                    ;;
                lst|list|flush|show)
                    _tc_one_of_list $ACTION_KIND
                    ;;
                *)
                    [[ $cword -eq $subcword ]] && \
                        COMPREPLY=( $( compgen -W 'help add delete change \
                            replace show list flush action' -- "$cur" ) )
                    ;;
            esac
            ;;

        monitor)
            COMPREPLY=( $( compgen -W 'help' -- "$cur" ) )
            ;;

        exec)
            case $subcmd in
                bpf)
                    local excmd exwd EXEC_KIND=' import debug graft '
                    for ((exwd=$subcword; exwd < ${#words[@]}-1; exwd++)); do
                        if [[ $EXEC_KIND =~ ' '${words[exwd]}' ' ]]; then
                            excmd=${words[exwd]}
                            _tc_exec_options $excmd && return 0
                        fi
                    done
                    _tc_one_of_list $EXEC_KIND
                    ;;
                *)
                    [[ $cword -eq $subcword ]] && \
                        COMPREPLY=( $( compgen -W 'bpf' -- "$cur" ) )
                    ;;
            esac
            ;;
    esac
} &&
complete -F _tc tc

# ex: ts=4 sw=4 et filetype=sh