msg () { [ "${QUIET}" = "n" ] && echo $@; }
err () { echo "ERROR: $@" >&2; }
die () { echo "FATAL: $@" >&2; cleanup; exit 1; }

get_dirname ()
{
    # strip any trailing slash first...
    local dir="${1%/}"
    # then get the directory portion
    echo "${dir%/*}"
}

get_basename ()
{
    echo "${1##*/}"
}

get_module_name ()
{
    #cleanup - remove .ko, replace - with _
    local modname="${1%.gz}"
    modname=$(get_basename "${modname%.ko}")
    modname="${modname//-/_}"
    echo "$modname"
}

##
#  usage : in_array( $needle, $haystack )
# return : 0 - found
#          1 - not found
##
in_array() {
    local needle=$1; shift
    [[ -z $1 ]] && return 1 # Not Found
    local item
    for item in "$@"; do
        [[ $item = $needle ]] && return 0 # Found
    done
    return 1 # Not Found
}

auto_modules ()
{
    IFS=$'\n' read -rd '' -a mods < \
        <(find /sys/devices -name modalias -exec sort -zu {} + |
        xargs -0 modprobe -aRS "$KERNELVERSION" |
        sort -u)

    printf "%s\n" "${mods[@]//-/_}"
    (( ${#mods[*]} ))
}

all_modules ()
{
    local -i count=0
    while read -r -d '' mod; do
        (( ++count ))
        mod=${mod##*/}
        mod="${mod%.ko.*}"
        printf '%s\n' "${mod//-/_}"
    done < <(find "$MODULEDIR" -name '*.ko*' -print0 2>/dev/null | grep -Zz "$@")

    (( count ))
}

checked_modules ()
{
    if [[ -s "$MODULE_FILE" ]]; then
        grep -xFf "$MODULE_FILE" <(all_modules "$@")
        return 1
    else
        all_modules "$@"
    fi
}

add_full_dir ()
{
    if [ -n "${1}" -a -d "${1}" ]; then
        for f in ${1}/*; do
            if [ -d "${f}" ]; then
                add_full_dir "${f}"
            else
                add_file "${f}"
            fi
        done
    fi
}

add_dir ()
{
    #skip root directory and "." for relative directories... i.e. /foo/bar/./blah
    if [ -n "${1}" -a "${1}" != "/" -a "${1}" != "." ]; then
        if ! grep -q "dir ${1} " "${FILELIST}"; then
            add_dir $(get_dirname "${1}")
            msg "  adding  dir ${1}"
            echo "dir ${1} 755 0 0" >> "${FILELIST}"
        fi
    fi
}

# add_device /dev/foo type major minor [permissions]
add_device ()
{
    if [ $# -ge 4 ]; then
        local perms
        perms="${5:-644}"
        if ! grep -q "nod ${1}" "${FILELIST}"; then
            add_dir $(get_dirname "${1}")
            msg "  adding node ${1}"
            echo "nod ${1} ${perms} 0 0 ${2} ${3} ${4}" >> "${FILELIST}"
        fi
    else
        err "invalid device node format: $@"
        return 1
    fi
}

# what the hell does this do?
add_symlink ()
{
    local fil dest dir
    if [ -h ${1} ]; then
        fil="${1##$BASEDIR}"
        dest="${2##$BASEDIR}"
        add_dir $(get_dirname "${dest}")
        add_dir $(get_dirname "${fil}")
        if ! grep -q "slink ${fil} " "${FILELIST}"; then
            msg "  adding link ${fil} -> ${dest}"
            echo "slink ${fil} ${dest} $(stat -c '%a' ${1}) 0 0" >> "${FILELIST}"
        fi
    fi
    #fail quietly
}

add_symlink2 ()
{
    add_dir $(get_dirname ${1})
    add_dir $(get_dirname ${2})
    if ! grep -q "slink ${1} " "${FILELIST}"; then
         msg "  adding link ${1} -> ${2}"
         echo "slink ${1} ${2} 777 0 0" >> "${FILELIST}"
    fi
}

add_file ()
{
    local fil lnk dir dest
    if [ -f "${1}" ]; then
        fil="${1}"
        lnk=$(readlink -f "${fil}")
        if [ -n "${lnk}" ]; then
            add_symlink "${fil}" "${lnk}"
            fil="${lnk}"
        fi
        if [ $# -eq 2 ]; then
            dest="${2}"
        else
            dest="${fil##$BASEDIR}"
            if [ "${dest}" = "${dest#/}" ]; then
                dest="/${dest}"
            fi
        fi

        add_dir $(get_dirname "${dest}")

        if ! grep -q "file ${dest} " "${FILELIST}"; then
            msg "  adding file ${dest}"
            echo "file ${dest} ${fil} $(stat -c '%a' ${fil}) 0 0" >> "${FILELIST}"
        fi
    else
        err "file '${1}' does not exist"
        return 1
    fi
}

HAS_MODULES="n"
declare -a ADDED_MODULES
#modules are handled specially in order to enable autodetection
add_module ()
{
    local m fil path fw mod deps
    m=$(get_module_name "${1}")
    #find pattern - replace _ with [-_] to match either
    fil="${m//_/[-_]}"

    #skip expensive stuff if this module has already been added
    if in_array $m ${ADDED_MODULES[@]}; then
        msg "module $m was already added"
        return
    fi

    found=0
    for path in $(find "${MODULEDIR}" -type f -name "${fil}.ko" -or -name "${fil}.ko.gz"); do
        #get needed firmware files
        for fw in $(/sbin/modinfo -F firmware "${path}"); do
            [ -f "/lib/firmware/$fw" ] && add_file "/lib/firmware/$fw"
        done
        #get module depends
        deps="$(/sbin/modinfo -F depends "${path}")"
        for mod in ${deps//,/ }; do
            if [ -n "${mod}" ]; then
                add_module "${mod}"
            fi
        done
        HAS_MODULES="y"
        ADDED_MODULES[${#ADDED_MODULES[*]}]="$m"
        msg "  adding module ${fil}"
        add_file "${path}" && found=1
    done
    if [ ${found} -eq 1 ]; then
        #explicit module depends
        case "$m" in 
            fat) add_module "nls_cp437" ;;
            ocfs2) add_module "configfs" ;;
            libcrc32c) add_module "crc32c"; add_module "crc32c_intel" ;;
        esac
    else
        err "module '$m' not found"
    fi
}

add_binary ()
{
    local bin dest type lib
    bin=$(which "${1}")
    if [ $? -ne 0 ]; then
        bin="${1}"
    fi

    dest=""
    if [ $# -eq 2 ]; then
        dest=${2}
    fi

    if [ ! -f "${bin}" ]; then
        err "'${bin}' is not a file"
        return 1
    fi

    if [ $? -eq 0 ]; then
        type=$(file -b "${bin}")
        case "${type}" in
            *script*)
            msg "   adding '${type}' script, ensure proper interp exists..."
            add_file "${bin}" ${dest}
            ;;
            *executable*|*shared\ object*|*symbolic\ link*)
            add_file "${bin}" ${dest}
            #note, this will also handle 'not a dynamic executable' spit out by
            # static binaries... the deps will produce nothing
            for lib in $(ldd ${bin} 2>/dev/null | sed "s|.*=>\(.*\)|\1|"); do
                if [ -n "${lib}" ]; then
                    #remove TLS libraries
                    notls=$(echo ${lib} | sed 's|/lib/tls.*/\(lib.*\)|/lib/\1|')
                    [ -e "${notls}" ] && lib="${notls}"
                    [ -f "${lib}" ] && add_file "${lib}"
                fi
            done
            ;;
            *)
            err "unknown type '${type}' for binary '${bin}'"
            return 1
            ;;
        esac
    fi
}

parse_hook ()
{
    local mod bin fil
    for mod in ${MODULES}; do
        if [ -n "${mod}" ]; then
            add_module "${mod}"
        fi
    done

    for bin in ${BINARIES}; do
        if [ -n "${bin}" ]; then
            add_binary "${bin}"
        fi
    done

    for fil in ${FILES}; do
        if [ -n "${fil}" ]; then
            add_file "${fil}"
        fi
    done

    if [ -n "${SCRIPT}" ]; then
        add_file "${HOOKDIR}/${SCRIPT}" "/hooks/${SCRIPT}"
    fi
}
# vim: set ft=sh ts=4 sw=4 et:
