#!/bin/bash
#
# lsinitcpio - dump the contents of an initramfs image
#

shopt -s extglob

declare verbose=
declare list='--list'
declare -i color=1
declare NC= BOLD= BLUE= GREEN= RED= YELLOW=
declare FUNCTIONS=functions

usage() {
    cat<<USAGE
lsinitcpio %VERSION%
usage: ${0##*/} [options] <initramfs>

  Options:
   -a, --analyze        analyze contents
   -h, --help           display this help
   -n, --nocolor        disable colorized output
   -v, --verbose        more verbose output
   -x, --extract        extract image to disk

USAGE
}

decomp() {
    ${compress:-cat} ${compress:+-cd} "$@"
}

. "$FUNCTIONS"

# override the die method from functions
die() {
    error "$@"
    exit 1
}

size_to_human() {
    awk -v size="$1" '
    BEGIN {
        suffix[1] = "B"
        suffix[2] = "KiB"
        suffix[3] = "MiB"
        suffix[4] = "GiB"
        suffix[5] = "TiB"
        count = 1

        while (size > 1024) {
            size /= 1024
            count++
        }

        sizestr = sprintf("%.2f", size)
        sub(/\.?0+$/, "", sizestr)
        printf("%s %s", sizestr, suffix[count])
    }'
}

OPT_SHORT='ahnvx'
OPT_LONG=('analyze' 'help' 'nocolor' 'verbose' 'extract')

if ! parseopts "$OPT_SHORT" "${OPT_LONG[@]}" -- "$@"; then
    exit 1
fi
set -- "${OPTRET[@]}"
unset OPT_SHORT OPT_LONG OPTRET

while :; do
    case $1 in
        -a|--analyze)
            analyze=1 ;;
        -h|--help)
            usage
            exit 0 ;;
        -n|--nocolor)
            color=0 ;;
        -v|--verbose)
            verbose='--verbose' ;;
        -x|--extract)
            unset list ;;
        --)
            shift
            break 2 ;;
    esac
    shift
done

declare image=$1

if [[ -t 1 ]] && (( color )); then
    # prefer terminal safe colored and bold text when tput is supported
    if tput setaf 0 &>/dev/null; then
        NC="$(tput sgr0)"
        BOLD="$(tput bold)"
        BLUE="$BOLD$(tput setaf 4)"
        GREEN="$BOLD$(tput setaf 2)"
        RED="$BOLD$(tput setaf 1)"
        YELLOW="$BOLD$(tput setaf 3)"
    else
        NC="\e[1;0m"
        BOLD="\e[1;1m"
        BLUE="$BOLD\e[1;34m"
        GREEN="$BOLD\e[1;32m"
        RED="$BOLD\e[1;31m"
        YELLOW="$BOLD\e[1;33m"
    fi
fi
readonly NC BOLD BLUE GREEN RED YELLOW

[[ $image ]] || die "No image specified (use -h for help)"
[[ -f $image ]] || die "No such file: $image"

# read compression type
case "$(file -Lb "$image")" in
    @(data|LZMA)*) compress=lzma ;;
    gzip*) compress=gzip ;;
    bzip2*) compress=bzip2 ;;
    lzop*) compress=lzop ;;
    XZ*) compress=xz ;;
esac

if (( analyze )); then
    declare -a binaries explicitmod modules foundhooks hooks
    declare kernver ratio columns=$(tput cols)

    workdir=$(mktemp -d --tmpdir="$TMPDIR" lsinitcpio.XXXXXX)
    trap 'rm -rf "$workdir"' EXIT

    # fallback in case tput failed us
    columns=${columns:-80}

    zsize=$(stat -c %s "$image")

    # calculate compression ratio
    TIMEFORMAT=%R decomptime=$({ time decomp "$image" >/dev/null; } 2>&1 )
    if [[ $compress ]]; then
        fullsize=$(decomp "$image" | bsdtar xOf - | wc -c)
        ratio=.$(( zsize * 1000 / fullsize % 1000 ))
    fi

    # decompress the image since we need to read from it multiple times
    decomp "$image" | bsdtar -C "$workdir" -xf -

    # collect stats
    kernver=("$workdir"/usr/lib/modules/*/)
    kernver=${kernver%/}
    kernver=${kernver##*/}

    modules=("$workdir/usr/lib/modules/$kernver"/kernel/*.ko*)
    if [[ -f ${modules[0]} ]]; then
        modules=("${modules[@]##*/}")
        modules=("${modules[@]%.ko*}")
    else
        unset modules
    fi

    foundhooks=("$workdir"/hooks/*)
    [[ -f ${foundhooks[0]} ]] && foundhooks=("${foundhooks[@]##*/}") || unset foundhooks

    binaries=("$workdir"/usr/bin/*)
    binaries=("${binaries[@]##*/}")

    read -r version < "$workdir/VERSION"

    # source and read config
    . "$workdir/config"

    explicitmod=($MODULES)

    # print results
    imagename=$image
    [[ -L $image ]] && imagename+=" -> $(readlink -e "$image")"
    msg 'Image: %s %s' "$imagename"
    [[ $version ]] && msg 'Created with mkinitcpio %s' "$version"
    msg 'Kernel: %s' "${kernver:-unknown}"
    msg 'Size: %s' "$(size_to_human "$zsize")"

    if [[ $compress ]]; then
        msg 'Compressed with: %s' "$compress"
        msg2 'Uncompressed size: %s (%s ratio)' "$(size_to_human "$fullsize")" "$ratio"
    fi
    msg2 'Estimated extraction time: %ss' "$decomptime"
    printf '\n'

    if (( ${#modules[*]} )); then
        msg 'Included modules:'
        for mod in "${modules[@]}"; do
            printf '  %s' "$mod"
            in_array "${mod//_/-}" "${explicitmod[@]//_/-}" && printf ' [explicit]'
            printf '\n'
        done | sort | column -c$columns
        printf '\n'
    fi

    msg 'Included binaries:'
    printf '  %s\n' "${binaries[@]}" | sort | column -c$columns
    printf '\n'

    if [[ $EARLYHOOKS ]]; then
        msg 'Early hook run order:'
        printf '  %s\n' $EARLYHOOKS
        printf '\n'
    fi

    if [[ $HOOKS ]]; then
        msg 'Hook run order:'
        printf '  %s\n' $HOOKS
        printf '\n'
    fi

    if [[ $LATEHOOKS ]]; then
        msg 'Late hook run order:'
        printf '  %s\n' $LATEHOOKS
        printf '\n'
    fi

    if [[ $CLEANUPHOOKS ]]; then
        msg 'Cleanup hook run order:'
        printf '  %s\n' $CLEANUPHOOKS
        printf '\n'
    fi
else
    decomp "$image" | bsdcpio -i --quiet $verbose $list
fi

# vim: set ft=sh ts=4 sw=4 et:
