xref: /spdk/scripts/bash-completion/spdk (revision c02c5e04b33c5c72693b843c1a43be5e2c38465d)
1# shellcheck disable=SC2016,SC2207
2
3_get_help() {
4	"$@" -h 2>&1
5}
6
7_get_help_opt() {
8	# Fetch all the optional parameters with help from _parse_help()
9	_parse_help - < <(printf '%s\n' "$@")
10}
11
12_get_help_pos() {
13	local pos
14
15	# Fetch all the positional parameters, i.e. get first word prefixed
16	# with 20h x 2. This may not be 100% accurate. Also, it won't return
17	# any usable strings, it's just meant to point out what type of
18	# mandatory argument given method depends on, like bdev_name, etc.
19	# TODO: separate completion for such arguments, e.g., get all bdevs
20	# for parameter like bdev_name?
21	while read -r; do
22		[[ $REPLY =~ ^\ {2}[^\ -] ]] || continue
23		read -r pos _ <<< "$REPLY" && echo "$pos"
24	done < <(printf '%s\n' "$@")
25}
26
27_get_default_rpc_methods() {
28	local aliases method names
29	# Don't squash whitespaces, slurp the entire line
30	while read -r; do
31		# Each method name seems to be prefixed with 20h x 4. Then it can
32		# be followed with list of aliases enclosed inside (). Example:
33		#    ioat_scan_accel_module
34		[[ $REPLY =~ ^\ {4}([a-z0-9]+(_[a-z0-9]+)*)(\ *\((.+)\))? ]] || continue
35
36		names=("${BASH_REMATCH[1]}")
37		if [[ $SPDK_RPC_ALIASES == yes ]] && [[ -n ${BASH_REMATCH[4]} ]]; then
38			IFS=", " read -ra aliases <<< "${BASH_REMATCH[4]}"
39			names+=("${aliases[@]}")
40		fi
41
42		for method in "${names[@]}"; do
43			rpc_methods["$method"]=1
44		done
45	done < <(_get_help "$1" 2> /dev/null)
46}
47
48_get_rpc_methods() {
49	local method methods
50
51	_get_default_rpc_methods "$1"
52	methods=($("$1" -s "$rpc_sock" rpc_get_methods 2> /dev/null)) || return 0
53	((${#methods[@]} > 0)) || return 0
54
55	# Kill the json flavor
56	methods=("${methods[@]//+(\"|,| )/}")
57	unset -v "methods[0]" "methods[-1]" # []
58
59	for method in "${methods[@]}"; do
60		rpc_methods["$method"]=1
61	done
62}
63
64_get_help_rpc_method() {
65	local rpc=$1
66	local method=$2
67	local rpc_help opt
68
69	mapfile -t rpc_help < <(_get_help "$rpc" "$method")
70
71	_get_help_pos "${rpc_help[@]}"
72	_get_help_opt "${rpc_help[@]}"
73}
74
75_is_rpc_method() {
76	local word=$1
77
78	[[ -n $word && -n ${rpc_methods["$word"]} ]]
79}
80
81_method_in_words() {
82	for word in "${words[@]}"; do
83		if _is_rpc_method "$word"; then
84			echo "$word"
85			return 0
86		fi
87	done
88	return 1
89}
90
91_set_rpc_sock() {
92	# Look for unix sock each app creates upon its execution. In
93	# first instance, check the cmdline for an -s arg, if it's
94	# followed by the path to the sock, use it.
95
96	local word
97	for ((word = 0; word < ${#words[@]}; word++)); do
98		if [[ ${words[word]} == -s && -S ${words[word + 1]} ]]; then
99			rpc_sock=${words[word + 1]}
100			return 0
101		fi
102	done
103
104	# default .sock
105	[[ -S /var/tmp/spdk.sock ]] && rpc_sock=/var/tmp/spdk.sock
106
107	return 0
108}
109
110_spdk_opt_to_complete() {
111	local opt=$1
112
113	case "$opt" in
114		--pci-blocked | -B | --pci-allowed | -A)
115			local pcis
116			if [[ -e /sys/bus/pci/devices ]]; then
117				pcis=(/sys/bus/pci/devices/*)
118				pcis=("${pcis[@]##*/}")
119			fi
120			COMPREPLY=($(compgen -W '${pcis[*]}' -- "$cur"))
121			compopt -o filenames
122			;;
123		--main-core | -p) # FIXME: Is this meant to be an actual core id or thread id? Assume the latter
124			local cpus
125			if [[ -e /sys/devices/system/cpu ]]; then
126				cpus=(/sys/devices/system/cpu/cpu+([0-9]))
127				cpus=("${cpus[@]##*cpu}")
128			fi
129			COMPREPLY=($(compgen -W '${cpus[*]}' -- "$cur"))
130			;;
131		--iova-mode)
132			COMPREPLY=($(compgen -W 'pa va' -- "$cur"))
133			;;
134		--tpoint-group | -e)
135			COMPREPLY=($(compgen -W '$(_get_tpoint_g_masks)' -- "$cur"))
136			compopt -o nosort
137			;;
138		--logflag)
139			COMPREPLY=($(compgen -W '$(_get_log_flags)' -- "$cur"))
140			;;
141		--huge-dir)
142			COMPREPLY=($(compgen -W '$(_get_fs_mounts "hugetlbfs")' -- "$cur"))
143			compopt -o filenames
144			;;
145		--iflag | --oflag) # spdk_dd specific
146			if [[ ${app##*/} == spdk_dd ]]; then
147				COMPREPLY=($(compgen -W '$(_get_help_pos "${app_help[@]}")' -- "$cur"))
148			fi
149			;;
150		*) return 1 ;;
151	esac 2> /dev/null
152	return 0
153}
154
155_get_fs_mounts() {
156	[[ $(< /proc/filesystems) == *"$1"* ]] || return 0
157
158	local mount fs mounts
159	while read -r _ mount fs _; do
160		[[ $fs == "$1" ]] && mounts+=("$mount")
161	done < /proc/mounts
162
163	if ((${#mounts[@]} > 0)); then
164		printf '%s\n' "${mounts[@]}"
165	fi
166}
167
168_get_from_spdk_help() {
169	_get_help "$app" |& grep "$1"
170}
171
172_get_tpoint_g_masks() {
173	local g_masks
174
175	g_masks=$(_get_from_spdk_help "tracepoint group mask for spdk trace buffers") || return 0
176	[[ $g_masks =~ \((.+)\) ]] || return 0
177
178	IFS=", " read -ra g_masks <<< "${BASH_REMATCH[1]}"
179	printf '%s\n' "${g_masks[@]}"
180}
181
182_get_log_flags() {
183	local logflags
184
185	logflags=$(_get_from_spdk_help "enable debug log flag") || return 0
186	[[ $logflags =~ \((.+)\) ]] || return 0
187
188	if [[ -n ${BASH_REMATCH[1]} && ${BASH_REMATCH[1]} != "not supported"* ]]; then
189		IFS=", " read -ra logflags <<< "${BASH_REMATCH[1]}"
190		printf '%s\n' "${logflags[@]}"
191	fi
192}
193
194_is_app() {
195	type -P "$1" > /dev/null
196}
197
198_rpc() {
199	local cur prev words
200
201	_init_completion || return
202	_is_app "$1" || return
203
204	local rpc=$1 method=""
205	local -g rpc_sock="" rpc_sock_ts old_rpc_sock_ts
206	local -gA rpc_methods
207	local -gA __comps
208
209	_set_rpc_sock
210	# Verify sock against the creation time - this is meant to cover cases of different
211	# applications re-creating the socket file at the same location.
212	rpc_sock_ts=$(stat --print="%W" "$rpc_sock" 2> /dev/null)
213	# Always try to refresh list of methods when sock changed or when the list
214	# is empty.
215	if [[ $old_rpc_sock_ts != "$rpc_sock_ts" ]] || ((${#rpc_methods[@]} == 0)); then
216		rpc_methods=()
217		_get_rpc_methods "$rpc"
218	fi
219	old_rpc_sock_ts=$rpc_sock_ts
220
221	if method=$(_method_in_words); then
222		if [[ -n ${__comps["$rpc:$method:$cur"]} ]]; then
223			COMPREPLY=(${__comps["$rpc:$method:$cur"]})
224		else
225			COMPREPLY=($(compgen -W '$(_get_help_rpc_method "$rpc" "$method")' -- "$cur"))
226			__comps["$rpc:$method:$cur"]=${COMPREPLY[*]}
227		fi
228		compopt -o nosort
229	elif [[ $cur == -* ]]; then
230		if [[ -n ${__comps["$rpc:$cur"]} ]]; then
231			COMPREPLY=(${__comps["$rpc:$cur"]})
232		else
233			COMPREPLY=($(compgen -W '$(_parse_help "$rpc")' -- "$cur"))
234			__comps["$rpc:$cur"]=${COMPREPLY[*]}
235		fi
236	elif [[ $prev == --verbose ]]; then
237		COMPREPLY=($(compgen -W 'DEBUG INFO ERROR' -- "$cur"))
238	elif [[ $prev == -s ]]; then
239		_filedir
240	else
241		COMPREPLY=($(compgen -W '${!rpc_methods[*]}' -- "$cur"))
242	fi 2> /dev/null
243}
244
245_spdk_app() {
246	local cur prev
247
248	_init_completion || return
249	_is_app "$1" || return
250
251	local app=$1 app_help
252
253	mapfile -t app_help < <(_get_help "$app")
254
255	if [[ $cur == -* ]]; then
256		COMPREPLY=($(compgen -W '$(_get_help_opt "${app_help[@]}")' -- "$cur"))
257	else
258		_spdk_opt_to_complete "$prev" || _filedir
259	fi
260}
261
262_setup() {
263	local cur prev
264
265	_init_completion || return
266	_is_app "$1" || return
267
268	COMPREPLY=($(compgen -W 'cleanup config help interactive reset status' -- "$cur"))
269}
270
271# Build simple completion for some common spdk apps|tools
272_spdk_apps() {
273	local apps
274
275	apps=(
276		iscsi_tgt
277		nvmf_tgt
278		spdk_dd
279		spdk_tgt
280		spdk_top
281		spdk_trace_record
282		vhost
283		create_vbox.sh
284		pkgdep.sh
285		run-autorun.sh
286		autotest_setup.sh
287	) # TODO: Add more?
288
289	complete -o default -F _spdk_app "${apps[@]}"
290	complete -o default -F _rpc rpc.py
291	complete -o default -F _setup setup.sh
292}
293
294_spdk_apps
295
296# Look for _configure(). If it exists, include default completions for path lookups
297if [[ $(type -t _configure) == function ]]; then
298	complete -o default -F _configure configure
299fi
300