#!/bin/bash
#
# original script by Jim Pick <jim@jimpick.com>, GPL'd of course
# many changes and enhancement by Craig Sander <cas@taz.net.au>
#
# hacked by cas to use case instead of if/elif/fi
# hacked by cas to add '-ls' option.  also added error checking for
# -L and -ls options.
# hacked by cas to add '-conf' and '-lsconf' options.
# hacked by cas to add '-md5sum' and '-md5check' options.
# hacked by cas to add '-man' option.
# hacked by cas to add '-s' option (requires grep-dctrl).
# hacked by cas to support multiple string/package arguments
# hacked by cas to add simplistic emulation of 'dpkg -l'

DLOCATEDB=/var/lib/dlocate/dlocatedb
DPKGLIST=/var/lib/dlocate/dpkg-list
DPKG_INFO=/var/lib/dpkg/info

COMPRESS_DLOCATEDB='0'
GREP='grep'
[ -e /etc/default/dlocate ] && . /etc/default/dlocate
[ "$COMPRESS_DLOCATE" = "1" ] && GREP='zgrep'

function dlocate_help {
  cat <<__EOF__
Usage: dlocate [option...] [command] [PATTERN...]

Commands:
  DEFAULT/none PATTERN   list records that match either package or files names
  -S           PATTERN   list records that match filenames

  -L           package   list all files in package
  -l           package   regexp-enhanced emulation of 'dpkg -l'
  -s           package   print package's status
  -ls          package   'ls -ldF' of all files in package
  -du          package   'du -sck' of all files in package
  -conf        package   list conffiles in package
  -lsconf      package   'ls -ldF' of conffiles in package
  -md5sum      package   list package's md5sums (if any)
  -md5check    package   check package's md5sums (if any)
  -man         package   list package's man pages (if any)
  -lsman       package   list full path/filenames of man pages
  -lsbin       package   list full path/filenames of executable files
  -K                     list installed kernel & related packages
  -k                     detailed list of installed kernel & related packages

  --                    stop processing commands and options.  remainder of
                        command-line is filename(s)


The -l, and -S commands are approximately the same as the equivalent dpkg
options except that the search is performed using regular expressions
rather than fixed strings.

Options

  --filename-only        only output file names when searching for files
  --package-only         only output package names when searching for files

Regular Expression Options (see grep(1) for details):

  -E, --extended-regexp
  -F, --fixed-strings
  -G, --basic-regexp
  -P, --perl-regexp

  -w, --word-regexp     restrict matches to whole words
  -i, --ignore-case     case-insensitive match

Miscellaneous Options:

  -h, -H, --help          display this help message and exit.
  -V, --version           display dlocate's version number and exit.
  -v, --verbose, --debug  verbose/debug output
__EOF__
  exit 1
}

function dlocate_version {
  VERSION_BANNER="1.02"
  echo "dlocate version $VERSION_BANNER"
  exit 0
}

function dlocate_option_error () {
  echo "dlocate: unknown option '$1'"
  echo
  echo "Use: 'dlocate -- $1' if you want to search for '$1'"
  exit 1
}

OPTION="DEFAULT"

LAST_OPT=0

# default to extended regexp
RE_TYPE="-E"
DCTRL_RE_TYPE="-e"
RE_SEPARATOR="|"

# default to case-sensitive
IGNORE_CASE=""
# default to non- word-based searches
WORD_RE=""

# output filters for -S and DEFAULT option
OUTPUT_FILTER="none"

function output_filter() {
  [ "$VERBOSE" = "1" ] && echo "OUTPUT FILTER: $OUTPUT_FILTER"

  case "$OUTPUT_FILTER" in

      ("Filenames only")    
            cut -d\  -f 2 | sort -u
            ;;

      ("Packages only")
            cut -d:  -f 1 | sort -u
            ;;

      (*)
            cat
            ;;
  esac
}

VERBOSE=0

SEPARATOR='::::::::'
PKGS=""
for p in "$@"; do
    if [ "$LAST_OPT" -eq 1 ] ; then
        PKGS="$PKGS$SEPARATOR$p"
    else case "$p" in

        ('--')
                LAST_OPT=1
            ;;

        (''|'-h'|'-H'|'--help')
                dlocate_help
            ;;

        ('-V'|'--version')
                dlocate_version
            ;;

        ('-v'|'--verbose'|'--debug')
                VERBOSE=1
            ;;

        ('-K'|'-k')
                OPTION="$p"
                PKGS="dummy"
            ;;

        ('-S'|'-L'|'-l'|'-s'|'-ls'|'-du'|'-conf'|'-lsconf'|'-md5sum'|'-md5check'|'-man'|'-lsman'|'-lsbin')
                OPTION="$p"
            ;;

        ('-i'|'--ignore-case')
                IGNORE_CASE='-i'
            ;;

        ('-E'|'--extended-regexp')
                RE_TYPE='-E'
                DCTRL_RE_TYPE='-e'
                RE_SEPARATOR='|'
            ;;

        ('-F'|'--fixed-strings')
                RE_TYPE='-F'
                RE_SEPARATOR="\n"
            ;;
            
        ('-G'|'--basic-regexp')
                RE_TYPE='-G'
                DCTRL_RE_TYPE='-r'
                RE_SEPARATOR='\\|'
            ;;

        ('-P'|'--perl-regexp')
                RE_TYPE='-P'
                RE_SEPARATOR='|'
            ;;

        ('-w'|'--word-regexp')
                WORD_RE="-w"
            ;;

        ('--filename-only')
                OUTPUT_FILTER='Filenames only'
            ;;

        ('--package-only')
                OUTPUT_FILTER='Packages only'
            ;;

        (-*)
                dlocate_option_error $p
            ;;

        (*)
                PKGS="$PKGS$SEPARATOR$p"
            ;;
      esac
    fi
done

PKGS=$(echo "$PKGS" | sed -e "s/^$SEPARATOR//")
[ -z "$PKGS" ] && dlocate_help

PKGS_REGEXP=$(echo "$PKGS" | sed -e "s/$SEPARATOR/$RE_SEPARATOR/g")

FILES_REGEXP="($PKGS_REGEXP)"
[ "$WORD_RE" = "-w" ] && FILES_REGEXP="\b$FILES_REGEXP\b"

PKGS=$(echo "$PKGS" | sed -e "s/$SEPARATOR/ /g")

if [ "$VERBOSE" = "1" ] ; then
    echo "PKGS:" $PKGS
    echo "RE_SEPARATOR:" $RE_SEPARATOR
    echo "PKGS_REGEXP:" $PKGS_REGEXP
fi

[ -z "$PKGS_REGEXP" ] && PKGS_REGEXP='^$'


if [ "$OPTION" = '-l' ] ; then

    [ -z "$COLUMNS" ] &&
      COLUMNS=$(stty -a 2>&- |
      sed -ne '/columns/s/.*columns \([0-9]*\)[^0-9].*/\1/p');

    [ 0"$COLUMNS" -lt 80 ] && COLUMNS=80;

    ((fieldw=(COLUMNS-24)/4));

    # dpkg uses ((fieldd=fieldw*2+16));
    #
    # limiting the output to COLUMNS-2 characters and losing up to
    # additional 3 characters due to the rounding error.

    ((fieldd=COLUMNS-fieldw*2-6));

    fmt_eq=$(echo ==================================================== |
      cut -c-$fieldw)

    HEADER=$( printf \
"Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Installed/Config-files/Unpacked/Failed-config/Half-installed
|/ Err?=(none)/Hold/Reinst-required/X=both-problems (Status,Err: uppercase=bad)
||/ %-${fieldw}s %-${fieldw}s %s
+++-${fmt_eq}-${fmt_eq}-${fmt_eq}${fmt_eq}==================\\n" \
              Name Version Description)

    BODY_FMT_STRING="%-2s  %-${fieldw}.${fieldw}s %-${fieldw}.${fieldw}s %-${fieldd}.${fieldd}s\\n"

    [ "$VERBOSE" = "1" ] && echo RUNNING: $GREP $RE_TYPE $WORD_RE $IGNORE_CASE -- "$PKGS_REGEXP" "$DPKGLIST" 

    BODY=$($GREP $RE_TYPE $WORD_RE $IGNORE_CASE -- "$PKGS_REGEXP" "$DPKGLIST" | \
            awk -F'\t' "{ printf \"$BODY_FMT_STRING\", \$1, \$2, \$3, \$4 }" |
            sed -e 's/ *$//g'
          )

    [ -n "$BODY" ] && echo -e "$HEADER\n$BODY"

elif [ "$OPTION" = '-S' ] ; then

    if [ "$RE_TYPE" = '-F' ] ; then
        echo "Error: -F Fixed String searches are incompatible with -S"
        echo
        exit 1
    fi
    PREFIX="^([-a-zA-Z0-9_.+]+:|diversion by )"
    [ "$RE_TYPE" = "-G" ] && PREFIX="^([-a-zA-Z0-9_.+]+:\|diversion by )"

    [ "$VERBOSE" = "1" ] && echo RUNNING: $GREP $RE_TYPE $IGNORE_CASE -- "$PREFIX.*$FILES_REGEXP" "$DLOCATEDB"
    $GREP $RE_TYPE $IGNORE_CASE -- "$PREFIX.*$FILES_REGEXP" "$DLOCATEDB" | output_filter
    result=${PIPESTATUS[0]}

elif [ "$OPTION" = 'DEFAULT' ] ; then

    [ "$VERBOSE" = "1" ] && echo RUNNING: $GREP $RE_TYPE $IGNORE_CASE -- $WORD_RE "$PKGS_REGEXP" "$DLOCATEDB"
    $GREP $RE_TYPE $IGNORE_CASE -- $WORD_RE "$PKGS_REGEXP" "$DLOCATEDB" | output_filter
    result=${PIPESTATUS[0]}

elif [ "$OPTION" = '-s' ] ; then
    DCTRL_REGEXP="^($PKGS_REGEXP)$"

    [ "$VERBOSE" = "1" ] && echo RUNNING: grep-dctrl -X -P $DCTRL_RE_TYPE $DCTRL_REGEXP /var/lib/dpkg/status 
    grep-dctrl -P $DCTRL_RE_TYPE $DCTRL_REGEXP /var/lib/dpkg/status 
    result=$?

elif [ "$OPTION" = '-k' ] ; then

    grep-status -P -e "(^linux-(image|source|headers|doc|debug)|nvidia-kernel-|.*-modules-)" | \
        grep-dctrl -n -s Package -F status " installed" - | \
        egrep -v -- '^(lib|openser|pam|scim|tryton|wims)|-perl'

    result=$?

elif [ "$OPTION" = '-K' ] ; then
    KERN_RE=$(dlocate -k | xargs | sed -e 's/ /|/g')

    [ "$VERBOSE" = "1" ] && echo echo RUNNING: dlocate -P -l "\s($KERN_RE)\s"
    dlocate -P -l "\s($KERN_RE)\s"
    result=$?

else
    for PKG in $PKGS; do
        case "$OPTION" in

            '-L')
                if [ -s $DPKG_INFO/$PKG.list ] ; then
                    cat $DPKG_INFO/$PKG.list
                    result=$?
                else
                    echo Package $PKG not installed or $PKG.list is empty. >&2
                    result=1
                fi
                ;;

            '-ls')
                if [ -s $DPKG_INFO/$PKG.list ] ; then
                    xargs -r ls -ldF < $DPKG_INFO/$PKG.list
                    result=$?
                else
                    echo Package $PKG not installed or $PKG.list is empty. >&2
                    result=1
                fi
                ;;

            '-du')
                if [ -s $DPKG_INFO/$PKG.list ] ; then
                    du -sck $(cat $DPKG_INFO/$PKG.list | xargs -r ls -1dF | grep -v "@$\|/$" )
                    result=$?
                else
                    echo Package $PKG not installed or $PKG.list is empty. >&2
                    result=1
                fi
                ;;

            '-conf')
                if [ -s $DPKG_INFO/$PKG.conffiles ] ; then
                    cat $DPKG_INFO/$PKG.conffiles
                    result=$?
                else
                    echo Package $PKG not installed or has no conffiles. >&2
                    result=1
                fi
                ;;

            '-lsconf')
                if [ -s $DPKG_INFO/$PKG.conffiles ] ; then
                    ls -ldF $(cat $DPKG_INFO/$PKG.conffiles)
                    result=$?
                else
                    echo Package $PKG not installed or has no conffiles. >&2
                    result=1
                fi
                ;;

            '-md5sum')
                if [ -s $DPKG_INFO/$PKG.md5sums ] ; then
                    cat $DPKG_INFO/$PKG.md5sums
                    result=$?
                else
                    echo Package $PKG not installed or has no md5sums. >&2
                    result=1
                fi
                ;;

            '-lsman')
                if [ -s $DPKG_INFO/$PKG.list ] ; then
                    dlocate -L $PKG | \
                    grep -E '/man[0-9]+/'
                    result=$?
                else
                    echo Package $PKG not installed or $PKG.list is empty. >&2
                    result=1
                fi
                ;;

            '-man')
                if [ -s $DPKG_INFO/$PKG.list ] ; then
                    dlocate -L $PKG | \
                        grep -E '/man[0-9]+/' | \
                        sed -e 's/\.gz$//' \
                            -e 's:.*/::' \
                            -e 's/\(^.*\)\.\(.*\)/\2 \1/' | \
                        sort -u
                    result=$?
                else
                    echo Package $PKG not installed or $PKG.list is empty. >&2
                    result=1
                fi
                ;;

            '-lsbin')
                if [ -s $DPKG_INFO/$PKG.list ] ; then
                    dlocate -L $PKG | \
                    xargs -r ls -lLd 2> /dev/null | \
                    grep -v "^[^-]" | \
                    grep -E '^-.{2,8}[xs]' | \
                    sed -e 's/.* \//\//' | \
                    xargs -r ls -1
                    result=$?
                else
                    echo Package $PKG not installed or $PKG.list is empty. >&2
                    result=1
                fi
                ;;

            '-md5check')
                if [ -s $DPKG_INFO/$PKG.md5sums ] ; then
                    pushd / >/dev/null 2>&1
                    cat $DPKG_INFO/$PKG.md5sums | \
                        md5sum -c -
                    popd >/dev/null 2>&1
                    result=$?
                else
                    echo Package $PKG not installed or has no md5sums. >&2
                    result=1
                fi
                ;;

        esac
    done
fi


test -n "$result" && exit $result

