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