From 0c73892464349997dbc8d619fa4c01e30f0c87a2 Mon Sep 17 00:00:00 2001 From: Andres P Date: Wed, 20 Jan 2010 00:26:34 -0430 Subject: [PATCH] Undeclared local vars/filenames with spaces/other Fix: 3 Undeclared local vars with common enough names to warrant breakage Performance issues with _pacman trying to replicate /usr/bin/pacman with find and other slow tools. Performance issues with expanding an array (with sometimes hundreds of items) over three times. Expanding said array to remove already completed entries had the side effect of braking filenames with spaces and or \n. The full description of fixes are already posted at: http://bugs.archlinux.org/task/16630#comment55779 along with time diffs. Signed-off-by: Andres P --- contrib/bash_completion | 535 ++++++++++++++++++----------------------------- 1 files changed, 202 insertions(+), 333 deletions(-) diff --git a/contrib/bash_completion b/contrib/bash_completion index 62e5bc9..65051f5 100644 --- a/contrib/bash_completion +++ b/contrib/bash_completion @@ -1,365 +1,234 @@ -# vim: set ft=sh ts=2 sw=2 et: -# file: /etc/bash_completion.d/pacman - -# Bash completion for pacman -# Original: Manolis Tzanidakis +# pacman/makepkg completion by Andres Perera # -# Distributed under the terms of the GNU General Public License, v2 or later. +# Distributed under the terms of the GNU General Public License v3 or +# later. # - -## initial functions - -rem_selected () -{ - # (Adapted from bash_completion by Ian Macdonald ) - # This removes any options from the list of completions that have - # already been specified on the command line. - COMPREPLY=($(/bin/echo "${COMP_WORDS[@]}" | \ - (while read -d ' ' i; do - [ "${i}" == "" ] && continue - # flatten array with spaces on either side, - # otherwise we cannot grep on word boundaries of - # first and last word - COMPREPLY=" ${COMPREPLY[@]} " - # remove word from list of completions - COMPREPLY=(${COMPREPLY/ ${i%% *} / }) +# Local variables: common core cur glob list long m o prev query r +# remove s short sync upgrade w +# +# vim: ft=sh sts=2 sw=2 et: + +# Removes packages/files already present in the line +_arch_rem_selected() { + local w r + + for (( w=0; w<${#COMP_WORDS[@]}-1; w++)); do + # Don't touch operands + [[ ${COMP_WORDS[w]} == -* ]] && continue + for r in "${!COMPREPLY[@]}"; do + # Only deal with full matches + if [[ ${COMP_WORDS[w]} == ${COMPREPLY[r]} ]]; then + unset 'COMPREPLY[r]'; break + fi done - /bin/echo ${COMPREPLY[@]}))) - return 0 -} - -_available_repos () -{ - COMPREPLY=( $( compgen -W "$(/bin/grep '\[' /etc/pacman.conf | /bin/grep -v -e 'options' -e '^#' | tr -d '[]' )" -- $cur ) ) -} - -_installed_pkgs () -{ - local installed_pkgs - installed_pkgs=$( /bin/ls /var/lib/pacman/local/ ) - COMPREPLY=( $( compgen -W "$( for i in $installed_pkgs; do /bin/echo ${i%-*-*}; done )" -- $cur ) ) + done } -_available_pkgs () -{ - #find balks easilly on a find /foo/*/* type dir, especially one like - # /var/lib/pacman/*/* - # This little change-up removes the find *and* only uses enabled repos - local available_pkgs - local enabled_repos - enabled_repos=$( /bin/grep '\[' /etc/pacman.conf | /bin/grep -v -e 'options' -e '^#' | tr -d '[]' ) - available_pkgs=$( for r in $enabled_repos; do /bin/echo /var/lib/pacman/sync/$r/*; done ) - COMPREPLY=( $( compgen -W "$( for i in $available_pkgs; do j=${i##*/}; echo ${j%-*-*}; done )" -- $cur ) ) -} +# makepkg purge functions +_makepkg_deselect() { + local o -_installed_groups () -{ - local installed_groups - installed_groups=$( /bin/find /var/lib/pacman/local -name desc -exec /bin/sed -ne '/%GROUPS%/,/^$/{//d; p}' {} \; | /bin/sort -u ) - COMPREPLY=( $( compgen -W "$( for i in $installed_groups; do /bin/echo ${i%-*-*}; done )" -- $cur ) ) + if [[ $1 == l ]]; then + for o in "${!long[@]}"; do + if [[ ${COMP_WORDS[w]} == ${long[o]} ]]; then + unset 'long[o]'; break + fi + done + else + for o in "${!short[@]}"; do + if [[ ${COMP_WORDS[w]} == -*([^- ])${short[o]}* ]]; then + unset 'short[o]' + fi + done + fi } -_available_groups () -{ - #find balks easilly on a find /foo/*/* type dir, especially one like - # /var/lib/pacman/*/* - # This little change-up removes the find *and* only uses enabled repos - local available_groups - local enabled_repos - enabled_repos=$( /bin/grep '\[' /etc/pacman.conf | /bin/grep -v -e 'options' -e '^#' | tr -d '[]' ) - available_groups=$( for r in $enabled_repos; do /bin/sed '/%GROUPS%/,/^$/{//d; p}' /var/lib/pacman/sync/$r/*/desc | /bin/sort -u; done ) - COMPREPLY=( $( compgen -W "$( for i in $available_groups; do /bin/echo ${i%-*-*}; done )" -- $cur ) ) +_makepkg_count_words() { + local w + + for (( w=0; w<${#COMP_WORDS[@]}; w++)); do + [[ ${COMP_WORDS[w]} == @(-*([^- ])[hC]*|--@(help|cleancache)) ]] && return 0 + _makepkg_deselect "$1" + done } -## makepkg completion - -_makepkg () -{ - local cur prev +# makepkg main +_makepkg() { COMPREPLY=() - cur=${COMP_WORDS[COMP_CWORD]} - prev=${COMP_WORDS[COMP_CWORD-1]} - case "$prev" in - -p) - _filedir - return 0 - ;; - --help|--cleancache) - COMPREPLY='' - return 0 - ;; - esac - - if [[ "$cur" == -* ]]; then - COMPREPLY=( $( compgen -W '\ - -A --ignorearch \ - -b --builddeps \ - -c --clean \ - -C --cleancache \ - -d --nodeps \ - -e --noextract \ - -f --force \ - -g --geninteg \ - -h --help \ - -i --install \ - -L --log \ - -m --nocolor \ - -o --nobuild \ - -p \ - -r --rmdeps \ - -s --syncdeps \ - --asroot \ - --source \ - --noconfirm \ - --noprogressbar' -- $cur ) ) + local prev cur short long parse glob + prev=${COMP_WORDS[COMP_CWORD-1]} + cur=`_get_cword` + + short=( A C L R c d e f g h i m o p r s ) + + long=( --allsource --asroot --clean --cleancache --config --force + --geninteg --help --holdver --ignorearch --install --log + --nobuild --nocolor --noconfirm --nodeps --noextract + --noprogressbar --repackage --rmdeps --skipinteg --source + --syncdeps ) + + if (( COMP_CWORD == 1 )) && [[ $cur != -[^-]* ]]; then + # First word check + COMPREPLY=($(compgen -W "$(echo "${short[@]/#/-}" "${long[@]}")" -- "$cur")) + elif [[ $prev == @(--config|-*([^- ])p*) ]]; then + # Complete filenames for selected options + _arch_rem_selected + return 0 + else + # Generate the list depending on current word + case $cur in + -+($(IFS='|'; echo "${short[*]}"))) + _makepkg_count_words s + COMPREPLY=($(compgen -W "$(echo "${short[@]/#/$cur}")" -- "$cur")) + ;; + -*) + # If $cur isn't a combination option, fall back to displaying + # long options only. These are the prime target for completion + _makepkg_count_words l + COMPREPLY=($(compgen -W "$(echo "${long[@]}")" -- "$cur")) + ;; + esac fi - - rem_selected } -complete -o default -F _makepkg makepkg -## pacman completion - -_instring () -{ - str="${1}" - shift 1 - for c in "${@}"; do - if [ $(/bin/expr index "${str}" "${c}") -gt 0 ]; then - return 0 - fi - done - return 1 +# pacman packages +_pacman_pkg() { + COMPREPLY=($(compgen -W "$(if [[ $2 ]]; then + command pacman -"$1" |\ + command sed 's, .*,,' |\ + command sort -u + else + command pacman -"$1" + fi)" -- "$cur" )) } -_pacman () -{ - local a arg toparse op mod cur +# pacman main +_pacman() { COMPREPLY=() - # This argument parsing is done so we can check for flag existance later - # right now it's a tad crappy, but does the job - for (( i=1; i < ${#COMP_WORDS[@]}-1; i++ )); do - a=${COMP_WORDS[i]} - arg="${a:0:2}" - toparse="${a:2}" + local prev cur query remove sync upgrade core common list w m o s=() + prev=${COMP_WORDS[COMP_CWORD-1]} + cur=`_get_cword` - case "${arg}" in - -@(U|R|S|Q|h|V)) - op="${arg/-}" - mod="${mod}${a:2}" - ;; - --) - arg="${a:2}" - case "${arg}" in - remove) op="R" ;; - upgrade) op="U" ;; - query) op="Q" ;; - sync) op="S" ;; - help) op="h" ;; - version) op="V" ;; - verbose) mod="${mod}v" ;; - root) mod="${mod}r" ;; - dbpath) mod="${mod}b" ;; - nodeps) mod="${mod}d" ;; - force) mod="${mod}f" ;; - groups) mod="${mod}g" ;; - info) mod="${mod}i" ;; - list) mod="${mod}l" ;; - print-uris) mod="${mod}p" ;; - search) mod="${mod}s" ;; - sysupgrade) mod="${mod}u" ;; - upgrades) mod="${mod}u" ;; - downloadonly) mod="${mod}w" ;; - refresh) mod="${mod}y" ;; - changelog) mod="${mod}c" ;; - deps) mod="${mod}d" ;; - explicit) mod="${mod}e" ;; - unrequired) mod="${mod}t" ;; - foreign) mod="${mod}m" ;; - owns) mod="${mod}o" ;; - file) mod="${mod}p" ;; - search) mod="${mod}s" ;; - upgrades) mod="${mod}u" ;; - cascade) mod="${mod}c" ;; - check) mod="${mod}k" ;; - dbonly) mod="${mod}k" ;; - nosave) mod="${mod}n" ;; - recursive) mod="${mod}s" ;; - unneeded) mod="${mod}u" ;; - esac ;; - *) toparse="${a}" ;; - esac + # These are split in a subshell with IFS=' '. No point in listing + # them by \n since compgen -W can't handle spaces or new lines in + # operands + query=( '--changelog --check --deps --explicit --file --foreign --groups --info + --list --owns --quiet --search --unrequired --upgrades' 'c e g i k l m o p q s t u' ) - arglen=$(( ${#toparse}-1 )) - for c in $(/bin/seq 0 "${arglen}"); do - arg=${toparse:$c:1} - [ "${arg}" != "-" ] && mod="${mod}${arg}" - done - done + remove=( '--cascade --dbonly --nodeps --nosave --recursive --unneeded' 'c k n s u' ) - cur=${COMP_WORDS[COMP_CWORD]} + sync=( '--asdeps --asexplicit --clean --downloadonly --force --groups --ignore --ignoregroup + --info --list --needed --nodeps --print-uris --quiet --refresh --search --sysupgrade' + 'c f g i l p q s u w y' ) - if [ $COMP_CWORD -eq 1 ] && [[ "$cur" == -* ]]; then - COMPREPLY=( $( compgen -W '\ - -h --help \ - -Q --query \ - -R --remove \ - -S --sync \ - -U --upgrade \ - -V --version \ - ' -- $cur ) ) - rem_selected - return 0 - fi + upgrade=( '--asdeps --asexplicit --force --nodeps' f ) - if [[ "$cur" == -* ]]; then - case "${op}" in - U) - COMPREPLY=( $( compgen -W '\ - --asdeps \ - --asexplicit \ - -d --nodeps \ - -f --force \ - -h --help \ - --config \ - --logfile \ - --noconfirm \ - --noprogressbar \ - --noscriptlet \ - -v --verbose \ - -r --root \ - -b --dbpath \ - --cachedir \ - ' -- $cur ) ) - return 0 - ;; - R) - COMPREPLY=( $( compgen -W '\ - -c --cascade \ - -d --nodeps \ - -h --help \ - -k --dbonly \ - -n --nosave \ - -s --recursive \ - -u --unneeded \ - --config \ - --logfile \ - --noconfirm \ - --noprogressbar \ - --noscriptlet \ - -v --verbose \ - -r --root \ - -b --dbpath \ - --cachedir \ - ' -- $cur ) ) - return 0 - ;; - S) - COMPREPLY=( $( compgen -W '\ - --asdeps \ - --asexplicit \ - -c --clean \ - -d --nodeps \ - -f --force \ - -g --groups \ - -h --help \ - -i --info \ - -l --list \ - -p --print-uris \ - -s --search \ - -u --sysupgrade \ - -w --downloadonly \ - -y --refresh \ - --needed \ - --ignore \ - --ignoregroup \ - --config \ - --logfile \ - --noconfirm \ - --noprogressbar \ - --noscriptlet \ - -v --verbose \ - -r --root \ - -b --dbpath \ - --cachedir \ - ' -- $cur ) ) - return 0 - ;; - Q) - COMPREPLY=( $( compgen -W '\ - -c --changelog \ - -d --deps \ - -e --explicit \ - -g --groups \ - -h --help \ - -i --info \ - -k --check \ - -l --list \ - -m --foreign \ - -o --owns \ - -p --file \ - -s --search \ - -t --unrequired \ - -u --upgrades \ - --config \ - --logfile \ - --noconfirm \ - --noprogressbar \ - --noscriptlet \ - -v --verbose \ - -r --root \ - -b --dbpath \ - --cachedir \ - ' -- $cur ) ) - return 0 - ;; - esac - rem_selected + common=( '--cachedir --config --dbpath --debug --help --logfile --noconfirm + --noprogressbar --noscriptlet --root --verbose' 'b d h r v' ) + + core=( --query --remove --sync --upgrade --version -Q -R -S -U -V ) + + if (( COMP_CWORD == 1 )) && [[ $cur != -[^-]* ]]; then + COMPREPLY=($(compgen -W "$(echo "${core[@]}") '--help' '-h'" -- "$cur")) + return 0 + elif [[ $prev == @(-*([^- ])[br]*|--@(config|logfile|root|dbpath|cachedir)) ]]; then + _arch_rem_selected + return 0 else - case "${op}" in - U) - COMPREPLY=( $( compgen -d -- "$cur" ) \ - $( compgen -f -X '!*.pkg.tar.gz' -- "$cur" ) ) + # Main option check + for m in 'Q query' 'R remove' 'S sync' 'U upgrade' 'null'; do + if [[ $m == null ]]; then + # Go back to core if null + COMPREPLY=($(compgen -W "$(echo "${core[@]}")" -- "$cur")) return 0 - ;; - h|V) - COMPREPLY='' - return 0 - ;; - Q) - if _instring $mod g; then - _installed_groups - elif _instring $mod o; then - COMPREPLY=( $( compgen -d -- "$cur" ) \ - $( compgen -f -- "$cur" ) ) - elif _instring $mod p; then - COMPREPLY=( $( compgen -d -- "$cur" ) \ - $( compgen -f -X '!*.pkg.tar.gz' -- "$cur" ) ) - elif _instring $mod u; then - COMPREPLY='' + fi + # Break on match + [[ $COMP_LINE == @(* -*([^- ])${m% *}*|* --${m#* } *) ]] && break + done + fi + + # See if $cur is an operand before calling _pacman_pkg + case $cur in + + # Check if it's a combination option first + -"${m% *}"*) + # Generate the list from the option arrays. eval is being using on + # possible matches between query, remove, sync, upgrade and common + # arrays; completely sanitized input + eval "list=($(command sed 's,.*,\${&[1]},' <<<"${m#* }"; IFS=' '; echo ${common[1]}))" + # Parse previous arguments + for (( w=0; w<${#COMP_WORDS[@]}; w++)); do + if [[ ${COMP_WORDS[w]} == -*([^-])[hV]* ]]; then return 0 - else - _installed_pkgs fi - return 0 + for o in "${!list[@]}"; do + if [[ ${COMP_WORDS[w]} == -*([^-])*${list[o]}* ]]; then + unset 'list[o]' + fi + done + done + # Allow special double letters + case ${m% *} in + Q) [[ $cur == *([^i])i*([^i]) ]] && s+=(i) ;; + R) [[ $cur == *([^s])s*([^s]) ]] && s+=(s) ;; + S) [[ $cur == *([^c])c*([^c]) ]] && s+=(c) + [[ $cur == *([^u])u*([^u]) ]] && s+=(u) + [[ $cur == *([^y])y*([^y]) ]] && s+=(y) ;; + esac + list=("${list[@]/#/$cur}" "${s[@]/#/$cur}") + ;; + + # Use long array instead + -*) + eval "list=($(command sed 's,.*,\${&[0]},' <<<"${m#* }"; IFS=' '; echo ${common[0]}))" + for (( w=0; w<${#COMP_WORDS[@]}-1; w++)); do + if [[ ${COMP_WORDS[w]} == --@(help|version) ]]; then + return 0 + fi + for o in "${!list[@]}"; do + if [[ ${COMP_WORDS[w]} == ${list[o]} ]]; then + unset 'list[o]'; break + fi + done + done + ;; + + # The word isn't an operand; use pacman or _filedir + *) + case ${m% *} in + # The weak extglobs are due to a previous match already + # confirming validity + Q) + case $COMP_LINE in + @(* -*([^- ])g*|* --groups *)) _pacman_pkg Qgq sort ;; + @(* -*([^- ])o*|* --owns *)) ;; + @(* -*([^- ])p*|* --file *)) _filedir 'pkg.tar.gz' ;; + @(* -*([^- ])u*|* --upgrades *)) _pacman_pkg Quq ;; + *) _pacman_pkg Qq ;; + esac ;; - R) - _installed_pkgs - return 0 + R) + _pacman_pkg Qq ;; - S) - if _instring $mod l; then - _available_repos - else - _available_pkgs - fi - return 0 + S) + case $COMP_LINE in + @(* -*([^- ])l*|* --list *)) _pacman_pkg Sl sort ;; + @(* -*([^- ])g*|* --groups *)) _pacman_pkg Sg ;; + *) _pacman_pkg Slq ;; + esac ;; - esac - fi - - rem_selected + U) + _filedir 'pkg.tar.gz' + ;; + esac + _arch_rem_selected + return 0 + ;; + esac + COMPREPLY=($(compgen -W "$(echo "${list[@]}")" -- "$cur")) } -complete -o filenames -F _pacman pacman + +complete -F _makepkg -o default -o filenames -o plusdirs makepkg +complete -F _pacman -o default -o filenames -o plusdirs pacman -- 1.6.6