]>
Commit | Line | Data |
---|---|---|
fccaea45 | 1 | # Copyright (c) 2010-2016, Aneurin Price <aneurin.price@gmail.com> |
1ffe90c5 TF |
2 | |
3 | # Permission is hereby granted, free of charge, to any person | |
4 | # obtaining a copy of this software and associated documentation | |
5 | # files (the "Software"), to deal in the Software without | |
6 | # restriction, including without limitation the rights to use, | |
7 | # copy, modify, merge, publish, distribute, sublicense, and/or sell | |
8 | # copies of the Software, and to permit persons to whom the | |
9 | # Software is furnished to do so, subject to the following | |
10 | # conditions: | |
11 | ||
12 | # The above copyright notice and this permission notice shall be | |
13 | # included in all copies or substantial portions of the Software. | |
14 | ||
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |
17 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
19 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
21 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
22 | # OTHER DEALINGS IN THE SOFTWARE. | |
23 | ||
24 | if [[ -w /dev/zfs ]]; then | |
25 | __ZFS_CMD="zfs" | |
26 | __ZPOOL_CMD="zpool" | |
27 | else | |
28 | __ZFS_CMD="sudo zfs" | |
29 | __ZPOOL_CMD="sudo zpool" | |
30 | fi | |
31 | ||
fccaea45 JCML |
32 | # Disable bash's built-in hostname completion, as this makes it impossible to |
33 | # provide completions containing an @-sign, which is necessary for completing | |
34 | # snapshot names. If bash_completion is in use, this will already be disabled | |
35 | # and replaced with better completions anyway. | |
36 | shopt -u hostcomplete | |
37 | ||
1ffe90c5 TF |
38 | __zfs_get_commands() |
39 | { | |
40 | $__ZFS_CMD 2>&1 | awk '/^\t[a-z]/ {print $1}' | cut -f1 -d '|' | uniq | |
41 | } | |
42 | ||
43 | __zfs_get_properties() | |
44 | { | |
45 | $__ZFS_CMD get 2>&1 | awk '$2 == "YES" || $2 == "NO" {print $1}'; echo all name space | |
46 | } | |
47 | ||
48 | __zfs_get_editable_properties() | |
49 | { | |
50 | $__ZFS_CMD get 2>&1 | awk '$2 == "YES" {print $1"="}' | |
51 | } | |
52 | ||
53 | __zfs_get_inheritable_properties() | |
54 | { | |
55 | $__ZFS_CMD get 2>&1 | awk '$3 == "YES" {print $1}' | |
56 | } | |
57 | ||
58 | __zfs_list_datasets() | |
59 | { | |
fccaea45 | 60 | $__ZFS_CMD list -H -o name -s name -t filesystem,volume "$@" |
1ffe90c5 TF |
61 | } |
62 | ||
63 | __zfs_list_filesystems() | |
64 | { | |
fccaea45 | 65 | $__ZFS_CMD list -H -o name -s name -t filesystem |
1ffe90c5 TF |
66 | } |
67 | ||
68 | __zfs_match_snapshot() | |
69 | { | |
70 | local base_dataset=${cur%@*} | |
71 | if [[ $base_dataset != $cur ]] | |
72 | then | |
fccaea45 | 73 | $__ZFS_CMD list -H -o name -s name -t snapshot -d 1 $base_dataset |
1ffe90c5 | 74 | else |
fccaea45 JCML |
75 | if [[ $cur != "" ]] && __zfs_list_datasets $cur &> /dev/null |
76 | then | |
77 | $__ZFS_CMD list -H -o name -s name -t filesystem -r $cur | tail -n +2 | |
78 | # We output the base dataset name even though we might be | |
79 | # completing a command that can only take a snapshot, because it | |
80 | # prevents bash from considering the completion finished when it | |
81 | # ends in the bare @. | |
82 | echo $cur | |
83 | echo $cur@ | |
84 | else | |
85 | local datasets=$(__zfs_list_datasets) | |
86 | # As above | |
87 | echo $datasets | |
88 | if [[ "$cur" == */ ]] | |
89 | then | |
90 | # If the current command ends with a slash, then the only way | |
91 | # it can be completed with a single tab press (ie. in this pass) | |
92 | # is if it has exactly one child, so that's the only time we | |
93 | # need to offer a suggestion with an @ appended. | |
94 | local num_children | |
95 | # This is actually off by one as zfs list includes the named | |
96 | # dataset in addition to its children | |
97 | num_children=$(__zfs_list_datasets -d 1 ${cur%/} 2> /dev/null | wc -l) | |
98 | if [[ $num_children != 2 ]] | |
99 | then | |
100 | return 0 | |
101 | fi | |
102 | fi | |
103 | echo "$datasets" | awk '{print $1"@"}' | |
104 | fi | |
1ffe90c5 TF |
105 | fi |
106 | } | |
107 | ||
fccaea45 | 108 | __zfs_match_snapshot_or_bookmark() |
1ffe90c5 | 109 | { |
fccaea45 | 110 | local base_dataset=${cur%[#@]*} |
1ffe90c5 TF |
111 | if [[ $base_dataset != $cur ]] |
112 | then | |
fccaea45 JCML |
113 | if [[ $cur == *@* ]] |
114 | then | |
115 | $__ZFS_CMD list -H -o name -s name -t snapshot -d 1 $base_dataset | |
116 | else | |
117 | $__ZFS_CMD list -H -o name -s name -t bookmark -d 1 $base_dataset | |
118 | fi | |
119 | else | |
120 | $__ZFS_CMD list -H -o name -s name -t filesystem,volume | |
121 | if [[ $cur != "" ]] && $__ZFS_CMD list -H -o name -s name -t filesystem,volume $cur &> /dev/null | |
122 | then | |
123 | echo $cur@ | |
124 | echo $cur# | |
125 | fi | |
1ffe90c5 TF |
126 | fi |
127 | } | |
128 | ||
129 | __zfs_match_multiple_snapshots() | |
130 | { | |
131 | local existing_opts=$(expr "$cur" : '\(.*\)[%,]') | |
132 | if [[ $existing_opts ]] | |
133 | then | |
134 | local base_dataset=${cur%@*} | |
135 | if [[ $base_dataset != $cur ]] | |
136 | then | |
137 | local cur=${cur##*,} | |
138 | if [[ $cur =~ ^%|%.*% ]] | |
139 | then | |
140 | # correct range syntax is start%end | |
141 | return 1 | |
142 | fi | |
143 | local range_start=$(expr "$cur" : '\(.*%\)') | |
fccaea45 | 144 | $__ZFS_CMD list -H -o name -s name -t snapshot -d 1 $base_dataset | sed 's$.*@$'$range_start'$g' |
1ffe90c5 TF |
145 | fi |
146 | else | |
fccaea45 | 147 | __zfs_match_snapshot_or_bookmark |
1ffe90c5 TF |
148 | fi |
149 | } | |
150 | ||
151 | __zfs_list_volumes() | |
152 | { | |
fccaea45 | 153 | $__ZFS_CMD list -H -o name -s name -t volume |
1ffe90c5 TF |
154 | } |
155 | ||
156 | __zfs_argument_chosen() | |
157 | { | |
158 | local word property | |
159 | for word in $(seq $((COMP_CWORD-1)) -1 2) | |
160 | do | |
161 | local prev="${COMP_WORDS[$word]}" | |
162 | if [[ ${COMP_WORDS[$word-1]} != -[tos] ]] | |
163 | then | |
fccaea45 | 164 | if [[ "$prev" == [^,]*,* ]] || [[ "$prev" == *[@:\#]* ]] |
1ffe90c5 TF |
165 | then |
166 | return 0 | |
167 | fi | |
168 | for property in $@ | |
169 | do | |
fccaea45 | 170 | if [[ $prev == "$property"* ]] |
1ffe90c5 TF |
171 | then |
172 | return 0 | |
173 | fi | |
174 | done | |
175 | fi | |
176 | done | |
177 | return 1 | |
178 | } | |
179 | ||
180 | __zfs_complete_ordered_arguments() | |
181 | { | |
182 | local list1=$1 | |
183 | local list2=$2 | |
184 | local cur=$3 | |
185 | local extra=$4 | |
186 | if __zfs_argument_chosen $list1 | |
187 | then | |
188 | COMPREPLY=($(compgen -W "$list2 $extra" -- "$cur")) | |
189 | else | |
190 | COMPREPLY=($(compgen -W "$list1 $extra" -- "$cur")) | |
191 | fi | |
192 | } | |
193 | ||
194 | __zfs_complete_multiple_options() | |
195 | { | |
196 | local options=$1 | |
197 | local cur=$2 | |
198 | ||
199 | COMPREPLY=($(compgen -W "$options" -- "${cur##*,}")) | |
200 | local existing_opts=$(expr "$cur" : '\(.*,\)') | |
201 | if [[ $existing_opts ]] | |
202 | then | |
203 | COMPREPLY=( "${COMPREPLY[@]/#/${existing_opts}}" ) | |
204 | fi | |
205 | } | |
206 | ||
207 | __zfs_complete_switch() | |
208 | { | |
209 | local options=$1 | |
210 | if [[ ${cur:0:1} == - ]] | |
211 | then | |
212 | COMPREPLY=($(compgen -W "-{$options}" -- "$cur")) | |
213 | return 0 | |
214 | else | |
215 | return 1 | |
216 | fi | |
217 | } | |
218 | ||
fccaea45 JCML |
219 | __zfs_complete_nospace() |
220 | { | |
221 | # Google indicates that there may still be bash versions out there that | |
222 | # don't have compopt. | |
223 | if type compopt &> /dev/null | |
224 | then | |
225 | compopt -o nospace | |
226 | fi | |
227 | } | |
228 | ||
1ffe90c5 TF |
229 | __zfs_complete() |
230 | { | |
231 | local cur prev cmd cmds | |
232 | COMPREPLY=() | |
fccaea45 JCML |
233 | if type _get_comp_words_by_ref &> /dev/null |
234 | then | |
235 | # Don't split on colon | |
236 | _get_comp_words_by_ref -n : -c cur -p prev -w COMP_WORDS -i COMP_CWORD | |
237 | else | |
238 | cur="${COMP_WORDS[COMP_CWORD]}" | |
239 | prev="${COMP_WORDS[COMP_CWORD-1]}" | |
240 | fi | |
1ffe90c5 TF |
241 | cmd="${COMP_WORDS[1]}" |
242 | ||
243 | if [[ ${prev##*/} == zfs ]] | |
244 | then | |
245 | cmds=$(__zfs_get_commands) | |
246 | COMPREPLY=($(compgen -W "$cmds -?" -- "$cur")) | |
247 | return 0 | |
248 | fi | |
249 | ||
250 | case "${cmd}" in | |
fccaea45 JCML |
251 | bookmark) |
252 | if __zfs_argument_chosen | |
253 | then | |
254 | COMPREPLY=($(compgen -W "${prev%@*}# ${prev/@/#}" -- "$cur")) | |
255 | else | |
256 | COMPREPLY=($(compgen -W "$(__zfs_match_snapshot)" -- "$cur")) | |
257 | fi | |
258 | ;; | |
1ffe90c5 TF |
259 | clone) |
260 | case "${prev}" in | |
261 | -o) | |
262 | COMPREPLY=($(compgen -W "$(__zfs_get_editable_properties)" -- "$cur")) | |
fccaea45 | 263 | __zfs_complete_nospace |
1ffe90c5 TF |
264 | ;; |
265 | *) | |
266 | if ! __zfs_complete_switch "o,p" | |
267 | then | |
268 | if __zfs_argument_chosen | |
269 | then | |
270 | COMPREPLY=($(compgen -W "$(__zfs_list_datasets)" -- "$cur")) | |
271 | else | |
272 | COMPREPLY=($(compgen -W "$(__zfs_match_snapshot)" -- "$cur")) | |
273 | fi | |
274 | fi | |
275 | ;; | |
276 | esac | |
277 | ;; | |
278 | get) | |
279 | case "${prev}" in | |
280 | -d) | |
281 | COMPREPLY=($(compgen -W "" -- "$cur")) | |
282 | ;; | |
283 | -t) | |
2bc07c6d | 284 | __zfs_complete_multiple_options "filesystem volume snapshot bookmark all" "$cur" |
1ffe90c5 TF |
285 | ;; |
286 | -s) | |
2bc07c6d | 287 | __zfs_complete_multiple_options "local default inherited temporary received none" "$cur" |
1ffe90c5 TF |
288 | ;; |
289 | -o) | |
290 | __zfs_complete_multiple_options "name property value source received all" "$cur" | |
291 | ;; | |
292 | *) | |
293 | if ! __zfs_complete_switch "H,r,p,d,o,t,s" | |
294 | then | |
295 | if __zfs_argument_chosen $(__zfs_get_properties) | |
296 | then | |
fccaea45 | 297 | COMPREPLY=($(compgen -W "$(__zfs_match_snapshot)" -- "$cur")) |
1ffe90c5 TF |
298 | else |
299 | __zfs_complete_multiple_options "$(__zfs_get_properties)" "$cur" | |
300 | fi | |
301 | fi | |
302 | ;; | |
303 | esac | |
304 | ;; | |
305 | inherit) | |
306 | if ! __zfs_complete_switch "r" | |
307 | then | |
fccaea45 | 308 | __zfs_complete_ordered_arguments "$(__zfs_get_inheritable_properties)" "$(__zfs_match_snapshot)" $cur |
1ffe90c5 TF |
309 | fi |
310 | ;; | |
311 | list) | |
312 | case "${prev}" in | |
313 | -d) | |
314 | COMPREPLY=($(compgen -W "" -- "$cur")) | |
315 | ;; | |
316 | -t) | |
2bc07c6d | 317 | __zfs_complete_multiple_options "filesystem volume snapshot bookmark all" "$cur" |
1ffe90c5 TF |
318 | ;; |
319 | -o) | |
320 | __zfs_complete_multiple_options "$(__zfs_get_properties)" "$cur" | |
321 | ;; | |
322 | -s|-S) | |
323 | COMPREPLY=($(compgen -W "$(__zfs_get_properties)" -- "$cur")) | |
324 | ;; | |
325 | *) | |
326 | if ! __zfs_complete_switch "H,r,d,o,t,s,S" | |
327 | then | |
fccaea45 | 328 | COMPREPLY=($(compgen -W "$(__zfs_match_snapshot)" -- "$cur")) |
1ffe90c5 TF |
329 | fi |
330 | ;; | |
331 | esac | |
332 | ;; | |
333 | promote) | |
334 | COMPREPLY=($(compgen -W "$(__zfs_list_filesystems)" -- "$cur")) | |
335 | ;; | |
336 | rollback) | |
337 | if ! __zfs_complete_switch "r,R,f" | |
338 | then | |
339 | COMPREPLY=($(compgen -W "$(__zfs_match_snapshot)" -- "$cur")) | |
340 | fi | |
341 | ;; | |
342 | send) | |
fccaea45 | 343 | if ! __zfs_complete_switch "D,n,P,p,R,v,e,L,i,I" |
1ffe90c5 | 344 | then |
fccaea45 JCML |
345 | if __zfs_argument_chosen |
346 | then | |
347 | COMPREPLY=($(compgen -W "$(__zfs_match_snapshot)" -- "$cur")) | |
348 | else | |
349 | if [[ $prev == -*i* ]] | |
350 | then | |
351 | COMPREPLY=($(compgen -W "$(__zfs_match_snapshot_or_bookmark)" -- "$cur")) | |
352 | else | |
353 | COMPREPLY=($(compgen -W "$(__zfs_match_snapshot)" -- "$cur")) | |
354 | fi | |
355 | fi | |
1ffe90c5 TF |
356 | fi |
357 | ;; | |
358 | snapshot) | |
359 | case "${prev}" in | |
360 | -o) | |
361 | COMPREPLY=($(compgen -W "$(__zfs_get_editable_properties)" -- "$cur")) | |
fccaea45 | 362 | __zfs_complete_nospace |
1ffe90c5 TF |
363 | ;; |
364 | *) | |
365 | if ! __zfs_complete_switch "o,r" | |
366 | then | |
fccaea45 JCML |
367 | COMPREPLY=($(compgen -W "$(__zfs_match_snapshot)" -- "$cur")) |
368 | __zfs_complete_nospace | |
1ffe90c5 TF |
369 | fi |
370 | ;; | |
371 | esac | |
372 | ;; | |
373 | set) | |
fccaea45 JCML |
374 | __zfs_complete_ordered_arguments "$(__zfs_get_editable_properties)" "$(__zfs_match_snapshot)" $cur |
375 | __zfs_complete_nospace | |
1ffe90c5 TF |
376 | ;; |
377 | upgrade) | |
378 | case "${prev}" in | |
379 | -a|-V|-v) | |
380 | COMPREPLY=($(compgen -W "" -- "$cur")) | |
381 | ;; | |
382 | *) | |
383 | if ! __zfs_complete_switch "a,V,v,r" | |
384 | then | |
385 | COMPREPLY=($(compgen -W "$(__zfs_list_filesystems)" -- "$cur")) | |
386 | fi | |
387 | ;; | |
388 | esac | |
389 | ;; | |
390 | destroy) | |
391 | if ! __zfs_complete_switch "d,f,n,p,R,r,v" | |
392 | then | |
393 | __zfs_complete_multiple_options "$(__zfs_match_multiple_snapshots)" $cur | |
fccaea45 | 394 | __zfs_complete_nospace |
1ffe90c5 TF |
395 | fi |
396 | ;; | |
397 | *) | |
fccaea45 | 398 | COMPREPLY=($(compgen -W "$(__zfs_match_snapshot)" -- "$cur")) |
1ffe90c5 TF |
399 | ;; |
400 | esac | |
fccaea45 JCML |
401 | if type __ltrim_colon_completions &> /dev/null |
402 | then | |
403 | __ltrim_colon_completions "$cur" | |
404 | fi | |
1ffe90c5 TF |
405 | return 0 |
406 | } | |
407 | ||
408 | __zpool_get_commands() | |
409 | { | |
410 | $__ZPOOL_CMD 2>&1 | awk '/^\t[a-z]/ {print $1}' | uniq | |
411 | } | |
412 | ||
413 | __zpool_get_properties() | |
414 | { | |
415 | $__ZPOOL_CMD get 2>&1 | awk '$2 == "YES" || $2 == "NO" {print $1}'; echo all | |
416 | } | |
417 | ||
418 | __zpool_get_editable_properties() | |
419 | { | |
420 | $__ZPOOL_CMD get 2>&1 | awk '$2 == "YES" {print $1"="}' | |
421 | } | |
422 | ||
423 | __zpool_list_pools() | |
424 | { | |
425 | $__ZPOOL_CMD list -H -o name | |
426 | } | |
427 | ||
428 | __zpool_complete() | |
429 | { | |
430 | local cur prev cmd cmds | |
431 | COMPREPLY=() | |
432 | cur="${COMP_WORDS[COMP_CWORD]}" | |
433 | prev="${COMP_WORDS[COMP_CWORD-1]}" | |
434 | cmd="${COMP_WORDS[1]}" | |
435 | ||
436 | if [[ ${prev##*/} == zpool ]] | |
437 | then | |
438 | cmds=$(__zpool_get_commands) | |
439 | COMPREPLY=($(compgen -W "$cmds" -- "$cur")) | |
440 | return 0 | |
441 | fi | |
442 | ||
443 | case "${cmd}" in | |
444 | get) | |
445 | __zfs_complete_ordered_arguments "$(__zpool_get_properties)" "$(__zpool_list_pools)" $cur | |
446 | return 0 | |
447 | ;; | |
448 | import) | |
449 | if [[ $prev == -d ]] | |
450 | then | |
451 | _filedir -d | |
452 | else | |
453 | COMPREPLY=($(compgen -W "$(__zpool_list_pools) -d" -- "$cur")) | |
454 | fi | |
455 | return 0 | |
456 | ;; | |
457 | set) | |
458 | __zfs_complete_ordered_arguments "$(__zpool_get_editable_properties)" "$(__zpool_list_pools)" $cur | |
fccaea45 | 459 | __zfs_complete_nospace |
1ffe90c5 TF |
460 | return 0 |
461 | ;; | |
462 | add|attach|clear|create|detach|offline|online|remove|replace) | |
463 | local pools="$(__zpool_list_pools)" | |
464 | if __zfs_argument_chosen $pools | |
465 | then | |
466 | _filedir | |
467 | else | |
468 | COMPREPLY=($(compgen -W "$pools" -- "$cur")) | |
469 | fi | |
470 | return 0 | |
471 | ;; | |
472 | *) | |
473 | COMPREPLY=($(compgen -W "$(__zpool_list_pools)" -- "$cur")) | |
474 | return 0 | |
475 | ;; | |
476 | esac | |
477 | ||
478 | } | |
479 | ||
480 | complete -F __zfs_complete zfs | |
481 | complete -F __zpool_complete zpool |