+_lustre_cmds()
+{
+ local cmd="$1"
+ local sub="$2"
+
+ # "--list-command" prints commands in columns and truncates long ones
+ "$cmd" "$sub" --non-existent-option |
+ sed -e 1d -e '$d' -e 's/"//g' -e /=/d -e /exit/d -e /quit/d
+}
+
+_lustre_long_opts()
+{
+ local cmd="$1"
+ local sub="$2"
+ local subsub="$3"
+
+ # strip off usage message decoration and leave long opts
+ if [[ -n "$subsub" ]]; then
+ "$cmd" "$sub" help "$subsub" |& grep -owE -- '--[-a-zA-Z0]*'
+ else
+ "$cmd" help "$sub" |& sed -e 1d | grep -owE -- '--[-a-zA-Z0]*'
+ fi
+ # several commands take the same options as setstripe, except --delete
+ case "$sub$subsub" in
+ migrate|mirrorcreate|mirrorextend)
+ _lustre_long_opts "$cmd" setstripe | grep -v -- --delete
+ esac
+}
+
+_lustre_short_opts()
+{
+ local cmd="$1"
+ local sub="$2"
+ local subsub="$3"
+
+ # strip off usage message decoration and leave short opts
+ if [[ -n "$subsub" ]]; then
+ "$cmd" "$sub" help "$subsub" |& grep -owE -- '-[-a-zA-Z0]'
+ else
+ "$cmd" help "$sub" |& grep -owE -- '-[-a-zA-Z0]'
+ fi
+ # several commands take the same options as setstripe, except -d
+ case "$sub$subsub" in
+ migrate|mirrorextend|mirrorextend)
+ _lustre_short_opts "$cmd" setstripe | grep -v -- -d
+ esac
+}
+
+_lustre_comp_flags()
+{
+ local cmd=$1
+ local flags
+
+ flags=$("$cmd" help find |& tr "<>[],}" " " |
+ grep -- '--component-flags {' | cut -d\{ -f2)
+ if [[ -z "$flags" ]]; then
+ local version=($("$cmd" --version))
+
+ case "${version[1]}" in
+ 2.13*) flags="init stale prefer offline nosync extension";;
+ 2.12*|2.11*) flags="init stale prefer offline nosync";;
+ *) flags="init";;
+ esac
+ fi
+ echo $flags
+}
+
+_lustre_mountpoints()
+{
+ findmnt --list -t lustre -n -o TARGET
+}
+
+_lustre_mount_fsnames()
+{
+ local mountpoint
+
+ # FIXME: will fail if newlines in $mountpoint, why would anyone do that?
+ _lustre_mountpoints | while read mountpoint; do
+ lfs getname -n "$mountpoint" 2> /dev/null
+ done
+}
+
+_lustre_devices()
+{
+ lctl device_list | awk '{ print $4 }'
+}
+
+_lustre_fsnames()
+{
+ local mountpoint="${1:-'.'}"
+
+ local fsname=$(lfs getname -n "$mountpoint" 2>/dev/null)
+
+ [[ -n "$fsname" ]] && echo "$fsname" || _lustre_mount_fsnames
+}
+
+_lustre_layouts()
+{
+ "$cmd" help find |& tr "[]," " " | grep -- --layout | sed "s/.*-L //"
+}
+
+_lustre_mdts()
+{
+ lfs mdts $1 | grep _UUID | sed -e "s/[0-9]*: //" -e "s/_UUID.*//"
+}
+
+_lustre_osts()
+{
+ lfs osts $1 | grep _UUID | sed -e "s/[0-9]*: //" -e "s/_UUID.*//"
+}
+
+_lustre_pools()
+{
+ if [[ -d "$1" ]]; then
+ "$cmd" pool_list $1 2> /dev/null | grep -v "[Pp]ools from" |
+ cut -d. -f2
+ return 0
+ fi
+
+ for fsname in $(_lustre_fsnames $1); do
+ "$cmd" pool_list $fsname 2> /dev/null | grep -v "[Pp]ools from"
+ done
+}
+
+_lfs()
+{
+ local cur prev words cword
+ local mountpoint cmd sub find_opts
+
+ COMPREPLY=()
+ # allow different versions of bash_completion to work
+ if declare -F _init_completion > /dev/null; then
+ # this provides more functionality, but is only in v2.x
+ _init_completion || return
+ else
+ # this is compatible with both v1.3 and v2.x
+ _get_comp_words_by_ref cur prev words cword
+ fi
+
+ cmd="${words[0]}"
+ sub="${words[1]}"
+ [[ "$sub" == "mirror" || "$sub" == "pcc" ]] && subsub="${words[2]}"
+ if [[ "$cword" == "1" || "$prev" == "help" ]]; then
+ COMPREPLY+=($(compgen -W '$(_lustre_cmds "$cmd")' -- "$cur"))
+ return 0
+ fi
+
+ case "$cur" in
+ --*)
+ COMPREPLY+=($(compgen -W '$(_lustre_long_opts "$cmd" "$sub" "$subsub")' -- "$cur"))
+ return 0
+ ;;
+ -*)
+ # lfs find allows "-longopt" for compatibility with find(1)
+ [[ "$sub" == "find" ]] && find_opts=$(_lustre_long_opts "$cmd" find)
+ COMPREPLY+=($(compgen -W '$(_lustre_short_opts "$cmd" "$sub" "$subsub") ${find_opts//--/-}' -- "$cur"))
+ return 0
+ ;;
+ esac
+
+ case "$sub" in
+ check)
+ [[ -n "$cur" ]] && return 0
+ COMPREPLY+=($(compgen -W '$("$cmd" help check |& grep usage |
+ sed -e "s/[<>|]/ /g" \
+ -e "s/.*check //")' -- "$cur"))
+ return 0
+ ;;
+ df)
+ mapfile -t COMPREPLY < <(
+ _lustre_mountpoints | grep -- "^$cur" | sed 's/ /\\ /g'
+ )
+ return 0
+ ;;
+ find)
+ [[ -d "${words[2]}" ]] && mountpoint="${words[2]}"
+ case "${prev/--/-}" in
+ -component-flags|-comp-flags)
+ # FIXME: this should allow a comma-separated list
+ COMPREPLY+=($(compgen -W '$(_lustre_comp_flags)' -- "$cur"))
+ return 0
+ ;;
+ -g|-group)
+ COMPREPLY+=($(compgen -g -- "$cur"))
+ return 0
+ ;;
+ -L|-layout)
+ COMPREPLY+=($(compgen -W '$(_lustre_layouts)' -- "$cur"))
+ return 0
+ ;;
+ -m|-mdt)
+ # FIXME: this should allow a comma-separated list
+ COMPREPLY+=($(compgen -W '$(_lustre_mdts "$mountpoint")' -- "$cur"))
+ return 0
+ ;;
+ -O|-ost)
+ # FIXME: this should allow a comma-separated list
+ COMPREPLY+=($(compgen -W '$(_lustre_osts "$mountpoint")' -- "$cur"))
+ return 0
+ ;;
+ -pool)
+ COMPREPLY+=($(compgen -W '$(_lustre_pools "$mountpoint")' -- "$cur"))
+ return 0
+ ;;
+ -t|-type)
+ COMPREPLY+=($(compgen -W 'b c d f l p s' -- "$cur"))
+ return 0
+ ;;
+ -u|-user)
+ COMPREPLY+=($(compgen -u -- "$cur"))
+ return 0
+ ;;
+ esac
+ if [ -z "$mountpoint" ]; then
+ mapfile -t COMPREPLY < <(
+ _lustre_mountpoints | grep -- "^$cur" |
+ sed -e 's/ /\\ /g'
+ )
+ return 0
+ fi
+ ;;
+ mirror)
+ if [[ "$prev" == "$sub" ]]; then
+ COMPREPLY+=($(compgen -W '$(_lustre_cmds "$cmd" "$sub")' -- "$cur"))
+ return 0
+ fi
+ ;;
+ pcc)
+ if [[ "$prev" == "$sub" ]]; then
+ COMPREPLY+=($(compgen -W '$(_lustre_cmds "$cmd" "$sub")' -- "$cur"))
+ return 0
+ fi
+ ;;
+ pool_list)
+ COMPREPLY+=($(compgen -W '$(_lustre_fsnames
+ _lustre_pools)' -- "$cur"))
+ return 0
+ ;;
+ setstripe)
+ case "$prev" in
+ --component-flags|--comp-flags)
+ # only subset allowed, easier to list than exclude for now
+ # COMPREPLY+=($(compgen -W '$(_lustre_comp_flags)' -- "$cur"))
+ # FIXME: this should allow a comma-separated list
+ COMPREPLY+=($(compgen -W 'nosync prefer' -- "$cur"))
+ return 0
+ ;;
+ -p|--pool)
+ COMPREPLY+=($(compgen -W '$(_lustre_pools)' -- "$cur"))
+ return 0
+ ;;
+ esac
+ ;;
+ esac
+
+ _filedir
+ return 0
+} &&
+complete -F _lfs lfs
+
+_lctl()
+{
+ local cur prev words cword
+
+ COMPREPLY=()
+ # allow different versions of bash_completion to work
+ if declare -F _init_completion > /dev/null; then
+ # this provides more functionality, but is only in v2.x
+ _init_completion || return
+ else
+ # this is compatible with both v1.3 and v2.x
+ _get_comp_words_by_ref cur prev words cword
+ fi
+
+ cmd="${words[0]}"
+ sub="${words[1]}"
+ [[ "$sub" == "--device" && $cword -ge 4 ]] && sub="${words[3]}"
+
+ if [[ "$cword" == "1" || "$prev" == "help" ]]; then
+ COMPREPLY+=($(compgen -W '$(_lustre_cmds "$cmd")' -- "$cur"))
+ return 0
+ fi
+
+ case "$cur" in
+ --*)
+ COMPREPLY+=($(compgen -W '$(_lustre_long_opts "$cmd" "$sub")' -- "$cur"))
+ return 0
+ ;;
+ -*)
+ COMPREPLY+=($(compgen -W '$(_lustre_short_opts "$cmd" "$sub")' -- "$cur"))
+ return 0
+ ;;
+ esac
+
+ case "$sub" in
+ --device)
+ if [[ "$cword" == "2" ]]; then
+ COMPREPLY+=($(compgen -W '$(_lustre_devices)' -- "$cur"))
+ elif [[ "$cword" == "3" ]]; then
+ COMPREPLY+=($(compgen -W '$(_lustre_cmds "$cmd")' -- "$cur"))
+ fi
+ return 0
+ ;;
+ get_param|list_param|set_param)
+ local filter="s/=$//"
+ [[ "$sub" == "set_param" ]] && filter="/[^=/]$/d"
+ mapfile -t COMPREPLY < <(
+ "$cmd" list_param -F "${cur#[\"\']}*" 2>/dev/null |
+ sed -e "$filter" -e 's#/$#.#' \
+ -e "s#^${cur//\*/[^.]*}#$cur#"
+ )
+ compopt -o nospace
+
+ return 0
+ ;;
+ pcc)
+ if [[ "$prev" == "$sub" ]]; then
+ COMPREPLY+=($(compgen -W '$(_lustre_cmds "$cmd" "$sub")' -- "$cur"))
+ return 0
+ fi
+ ;;
+ pool_list)
+ if [[ "$cword" == "2" ]]; then
+ COMPREPLY+=($(compgen -W '$(_lustre_mountpoints
+ _lustre_fsnames
+ _lustre_pools)' -- "$cur"))
+ return 0
+ fi
+ ;;
+ pool_destroy)
+ if [[ "$cword" == "2" ]]; then
+ COMPREPLY+=($(compgen -W '$(_lustre_pools)' -- "$cur"))
+ return 0
+ fi
+ return 0
+ ;;
+ pool_add|pool_remove)
+ if [[ "$cword" == "2" ]]; then
+ COMPREPLY+=($(compgen -W '$(_lustre_pools)' -- "$cur"))
+ return 0
+ elif [[ "$cword" == "3" ]]; then
+ COMPREPLY+=($(compgen -W '$(_lustre_osts)' -- "$cur"))
+ return 0
+ fi
+ return 0
+ ;;
+ esac
+} &&
+complete -F _lctl lctl