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

shopt -s extglob

_list='--list'
_optcolor=1 _optverbose=
_f_functions=functions

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

  Actions:
   -a, --analyze        analyze contents of image
   -c, --config         show configuration file image was built with
   -l, --list           list contents of the image (default)
   -x, --extract        extract image to disk

  Options:
   -h, --help           display this help
   -n, --nocolor        disable colorized output
   -V, --version        display version information
   -v, --verbose        more verbose output

USAGE
}

version() {
    cat<<EOF
lsinitcpio %VERSION%
EOF
}

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

. "$_f_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])
    }'
}

analyze_image() {
    local -a binaries explicitmod modules foundhooks hooks
    local kernver ratio columns=$(tput cols) image=$1

    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. we
    # have to pass this through decomp() since the image might be lzop which
    # bsdtar can't read.
    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

    mapfile -t binaries < <(find "$workdir/usr/bin" -type f -printf %f\\n)

    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
}

_opt_short='achlnVvx'
_opt_long=('analyze' 'help' 'list' 'nocolor' 'showconfig' 'version' '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)
            _optanalyze=1 ;;
        -c|--config)
            _optshowconfig=1 ;;
        -h|--help)
            usage
            exit 0 ;;
        -l|--list)
            _optlistcontents=1 ;;
        -n|--nocolor)
            _optcolor=0 ;;
        -V|--version)
            version
            exit 0 ;;
        -v|--verbose)
            _optverbose='--verbose' ;;
        -x|--extract)
            unset _list ;;
        --)
            shift
            break 2 ;;
    esac
    shift
done

_image=$1

if [[ -t 1 ]] && (( _optcolor )); then
    # prefer terminal safe colored and bold text when tput is supported
    if tput setaf 0 &>/dev/null; then
        _color_none="$(tput sgr0)"
        _color_bold="$(tput bold)"
        _color_blue="$_color_bold$(tput setaf 4)"
        _color_green="$_color_bold$(tput setaf 2)"
        _color_red="$_color_bold$(tput setaf 1)"
        _color_yellow="$_color_bold$(tput setaf 3)"
    else
        _color_none="\e[1;0m"
        _color_bold="\e[1;1m"
        _color_blue="$_color_bold\e[1;34m"
        _color_green="$_color_bold\e[1;32m"
        _color_red="$_color_bold\e[1;31m"
        _color_yellow="$_color_bold\e[1;33m"
    fi
fi

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

case $(( _optanalyze + _optlistcontents + _optshowconfig )) in
    0)
        # default action when none specified
        _optlistcontents=1 ;;
    [!1])
        die "Only one action may be specified at a time" ;;
esac

# 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 (( _optanalyze )); then
    analyze_image "$_image"
elif (( _optshowconfig )); then
    decomp "$_image" | bsdtar xOf - buildconfig 2>/dev/null ||
        die 'Failed to extract config from image (mkinitcpio too old?)'
else
    decomp "$_image" | bsdcpio -i --quiet $_optverbose $_list
fi

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