#!/live/bin/sh

# See Documentation/x86/boot.txt in Linux source

ME=${0##*/}

usage() {
    local ret=${1:-0}

    cat <<Usage
Usage:  $ME [options] file1 file2 ...

List all of the vmlinuz image files from the input list along with their Linux
version strings. Silently ignore files that are not vmlinuz files.  Ignore
legit vmlinuz files that don't have a /lib/modules/\$VERSION directory.

Options:
  -c, --current         Only display filename of running kernel
  -d, --delimit=<s>     Use string <s> as the output delimiter
  -h, --help            Show this usage
  -m, --mod-dir=<dir>   Check for /lib/modules/\$VERSION directory under <dir>
  -n, --noheadings      Don't print headings
  -N, --nocheck         Don't check for /lib/modules/\$VERSION directory
  -r, --reverse         Print the version strings first
  -R, --raw             Don't send version strings through "cat -v"
  -t, --tabs            Use tab as the output delimiter
  -v, --verbose         Explain processing on stderr

If an output delimiter is give then the headings are automatically turned off.
Usage

    exit $ret
}

eval_argument() {
    local arg=$1 val=$2
        case $arg in
           -current|c) CURRENT=$(uname -r)             ;;
           -delimit|d) DELIMIT=$val                    ;;
       -delimit=*|d=*) DELIMIT=$val                    ;;
           -mod-dir|m) MOD_DIR=$val                    ;;
       -mod-dir=*|m=*) MOD_DIR=$val                    ;;
              -help|h) usage                           ;;
        -noheadings|n) SHOW_HEADER=                    ;;
           -nocheck|N) NO_CHECK=true                   ;;
               -raw|R) CAT_OPT=""                      ;;
           -reverse|r) REVERSE=true                    ;;
              -tabs|t) DELIMIT="\t"                    ;;
           -verbose|v) VERBOSE=true                    ;;
                    *) fatal "Unknown parameter -$arg" ;;
    esac
}

takes_param() {
    case $1 in
        -delimit|-mod-dir|[dm]) return 0 ;;
    esac
    return 1
}

main() {
    local SHIFT SHORT_STACK="cdfhnNrRtv"
    local CAT_OPT="-v" CURRENT DELIMIT REVERSE VERBOSE NO_CHECK
    local MOD_DIR=/ SHOW_HEADER=true

    [ $# -eq 0 ] && usage

    # This loop allows complete intermingling of filenames and options
    local fcnt=0
    while [ $# -gt 0 ]; do
        read_params "$@"
        shift $SHIFT

        while [ $# -gt 0 -a ${#1} -gt 0 -a -n "${1##-*}" ]; do
            files="$files$1\n"
            shift
            fcnt=$((fcnt + 1))
        done
    done

    [ $fcnt -lt 1 ] && fatal "Expected at least one filename"

    #[ $# -gt 0 ] && fatal "Too much intermingling of files and options"

    local file magic vm_version list f_width=4 v_width=7

    #SEP="\t"

    while read file; do
        test -e $file    || vsay "$file" "Could not find file"   || continue
        test -r $file    || vsay "$file" "Could not read file"   || continue
        test_magic $file || vsay "$file" "Not a vmlinuz file"    || continue

        vm_version=$(vm_version $file)

        if [ "$CURRENT" ]; then
            if [ "$vm_version" = "$CURRENT" ]; then
                echo $file
                exit 0
            fi
            continue
         fi

         if [ -z "$NO_CHECK" -a ! -d "$MOD_DIR/lib/modules/$vm_version" ]; then
            vsay "$file" "Could not find module directory for %s" "$vm_version"
            continue
         fi

         list="$list$vm_version $file\n"

         [ ${#file}       -gt $f_width ] && f_width=${#file}
         [ ${#vm_version} -gt $v_width ] && v_width=${#vm_version}
    done<<Files
$(echo -e "$files")
Files

    [ "$CURRENT" ] && exit 1

    local format
    if [ ${#DELIMIT} -gt 0 ]; then
        format="%s$DELIMIT%s"
    elif [ "$REVERSE" ]; then
        format="%-${v_width}s  %s"
        [ "$SHOW_HEADER" ] && printf "$format\n" Version File
    else
        format="%-${f_width}s  %s"
        [ "$SHOW_HEADER" ] && printf "$format\n" File Version
    fi

    while read vm_version file; do
        if [ "$REVERSE" ]; then
            printf "$format\n" "$vm_version" "$file"
        else
            printf "$format\n" "$file" "$vm_version"
        fi
    done <<Output
$(echo -e "$list")
Output
    exit 0
}

test_magic() {
    local file=$1
    magic=$(dd if=$file count=4 skip=514 bs=1 2>/dev/null)
    [ x"$magic" = x"HdrS" ]
    return $?
}

vm_version() {
    local file=$1
    hex_offset=$(dd if=$file count=2 skip=526 bs=1 2>/dev/null | hexdump \
        | head -n1 | cut -d" " -f2)
    #echo $hex_offset
    dec_offset=$(awk "BEGIN{ print 0x$hex_offset + 0x200 }")
    #echo $dec_offset
    #vsay "$file" "hex offset: %5s  decimal offset: %5s" $hex_offset $dec_offset
    dd if=$file count=40 skip=$dec_offset bs=1 2>/dev/null | cut -d" " -f1 | cat $CAT_OPT
}

vsay() {
    [ "$VERBOSE" ] || return 1
    local file=$1 format=$2
    shift 2
    printf "$file: $format\n" "$@" >&2
    return 1
}
#-------------------------------------------------------------------------------
# Send "$@".  Expects
#
#   SHORT_STACK               variable, list of single chars that stack
#   fatal(msg)                routine,  fatal("error message")
#   takes_param(arg)          routine,  true if arg takes a value
#   eval_argument(arg, [val]) routine,  do whatever you want with $arg and $val
#
# Sets "global" variable SHIFT to the number of arguments that have been read.
#-------------------------------------------------------------------------------
read_params() {
    # Most of this code is boiler-plate for parsing cmdline args
    SHIFT=0
    # These are the single-char options that can stack

    local arg val

    # Loop through the cmdline args
    while [ $# -gt 0 -a ${#1} -gt 0 -a -z "${1##-*}" ]; do
        arg=${1#-}
        shift
        SHIFT=$((SHIFT + 1))

        # Expand stacked single-char arguments
        case $arg in
            [$SHORT_STACK][$SHORT_STACK]*)
                if echo "$arg" | grep -q "^[$SHORT_STACK]\+$"; then
                    local old_cnt=$#
                    set -- $(echo $arg | sed -r 's/([a-zA-Z])/ -\1 /g') "$@"
                    SHIFT=$((SHIFT - $# + old_cnt))
                    continue
                fi;;
        esac

        # Deal with all options that take a parameter
        if takes_param "$arg"; then
            [ $# -lt 1 ] && fatal "Expected a parameter after: -$arg"
            val=$1
            [ -n "$val" -a -z "${val##-*}" ] \
                && fatal "Suspicious argument after -$arg: $val"
            SHIFT=$((SHIFT + 1))
            shift
        else
            case $arg in
                *=*)  val=${arg#*=} ;;
                  *)  val="???"     ;;
            esac
        fi

        eval_argument "$arg" "$val"
    done
}

fatal() {
    echo "$ME fatal error: $*"
    exit 2
}

main "$@"

