#!/bin/bash

plain() {
    local mesg=$1; shift
    printf "$BOLD    $mesg$NC\n" "$@" >&1
}

msg() {
    local mesg=$1; shift
    printf "$GREEN==>$NC$BOLD $mesg$NC\n" "$@" >&1
}

msg2() {
    local mesg=$1; shift
    printf "$BLUE  ->$NC$BOLD $mesg$NC\n" "$@" >&1
}

warning() {
    local mesg=$1; shift
    printf "$YELLOW==> WARNING:$NC$BOLD $mesg$NC\n" "$@" >&2
}

error() {
    local mesg=$1; shift
    printf "$RED==> ERROR:$NC$BOLD $mesg$NC\n" "$@" >&2
}

die() {
  error "$@"
  cleanup
  exit 1
}

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

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

in_array() {
    # Search for an element in an array.
    #   $1: needle
    #   ${@:2}: haystack

    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
}

kmodinfo() {
    # A wrapper around modinfo, which is aware of our $BASEDIR and
    # $KERNELVERSION
    #   $@: args to modinfo

    modinfo -b "$BASEDIR" -k "$KERNELVERSION" "$@" 2>/dev/null
}

auto_modules() {
    # Perform auto detection of modules via sysfs.

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

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

all_modules() {
    # Add modules to the initcpio, filtered by grep.
    #   $@: filter arguments to grep

    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() {
    # Add modules to the initcpio, filtered by the list of autodetected
    # modules.
    #   $@: arguments to all_modules

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

add_full_dir() {
    # Add a directory and all its contents, recursively, to the initcpio image.
    # No parsing is performed and the contents of the directory is added as is.
    #   $1: path to directory

    if [[ -n $1 && -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() {
    # Add a directory to the initcpio image.
    #   $1: absolute path of directory on image

    (( ! $# )) && return 1

    local path=$1 mode=${2:-755}

    _add_dir "$path" "$mode"
}

add_symlink() {
    # Add a symlink to the initcpio image.
    #   $1: pathname of symlink on image
    #   $2: absolute path to target of symlink

    (( $# == 2 )) || return 1

    _add_dir "$(get_dirname "$1")"
    _add_symlink "$2" "$1"
}

add_file() {
    # Add a plain file to the initcpio image. No parsing is performed and only
    # the singular file is added.
    #   $1: path to file
    #   $2: destination on initcpio (optional, defaults to same as source)

    (( $# )) || return 1

    # determine source and destination
    local src= dest=${2:-$1} mode=

    src=$BASEDIR$1

    [[ -f "$src" ]] || { error "$src: No such file"; return 1; }

    mode=$(stat -c %a "$src")
    if [[ -z "$mode" ]]; then
        error "failed to stat file: \`$src'"
        return 1
    fi

    _add_file "${dest#$BASEDIR}" "$src" "$mode"
}

add_module() {
    # Add a kernel module to the initcpio image. Dependencies will be
    # discovered and added.
    #   $1: module name

    local module path fw dep deps
    module=${1%.ko*}

    # skip expensive stuff if this module has already been added
    in_array "${module//-/_}" "${ADDED_MODULES[@]}" && return

    path=$(kmodinfo -0F filename "$module")
    if [[ $path ]]; then
        # get module firmware
        while read -r -d '' fw; do
            if [[ -e "$BASEDIR/lib/firmware/$fw" ]]; then
                add_file "/lib/firmware/$fw"
            fi
        done < <(kmodinfo -0F firmware "$module")

        # get module depends
        IFS=',' read -r -d '' -a deps < <(kmodinfo -0F depends "$module")
        for dep in "${deps[@]}"; do
            add_module "$dep"
        done

        ADDED_MODULES+=("${module//-/_}")
        add_file "${path#$BASEDIR}" || return
    else
        error "module '$module' not found"
        return 1
    fi

    # explicit module depends
    case "$module" in
        fat) add_module "nls_cp437" ;;
        ocfs2) add_module "configfs" ;;
        libcrc32c) add_module "crc32c"; add_module "crc32c_intel" ;;
    esac
}

_ldd() {
    LD_TRACE_LOADED_OBJECTS=1 "$LD_SO" "$@"
}

_add_file() {
  # add a file to $BUILDROOT
  #   $1: pathname on initcpio
  #   $2: source on disk
  #   $3: mode

  (( $# == 3 )) || return $EINVAL
  [[ -e "$BUILDROOT$1" ]] && return $EEXIST

  (( QUIET )) || plain "adding file: %s" "$1"
  command install -Dm$3 "$2" "$BUILDROOT$1"
}

_add_dir() {
  # add a directory (with parents) to $BUILDROOT
  #   $1: pathname on initcpio
  #   $2: mode

    (( $# == 2 )) || [[ "$1" == /?* ]] || return 1 # bad args
    [[ -e "$BUILDROOT$1" ]] && return 0 # file exists

    (( QUIET )) || plain "adding dir: %s" "$1"
    command install -dm$2 "$BUILDROOT$1"
}

_add_symlink() {
  # add a symlink to $buildroot
  #   $1: name on initcpio
  #   $2: target of $1

  (( $# == 2 )) || return $EINVAL
  [[ -L "$BUILDROOT$1" ]] && return $EEXIST

  (( QUIET )) || plain "adding symlink: %s -> %s" "$2" "$1"
  ln -s "$2" "$BUILDROOT$1"
}

add_binary() {
    # add a binary file to the initcpio image. library dependencies will
    # be discovered and added.
    #   $1: path to binary
    #   $2: destination on initcpio (optional, defaults to same as source)

    local -a sodeps
    local regex binary dest mode sodep resolved dirname

    binary=$BASEDIR$1

    [[ -f "$binary" ]] || { error "$binary not found"; return 1; }

    dest=${2:-$binary}
    mode=$(stat -c %a "$binary")

    # always add the binary itself
    _add_file "${dest#$BASEDIR}" "$binary" "$mode"

    $LD_SO --verify "$binary" &>/dev/null || return # not a binary!

    # resolve sodeps
    regex='^[[:space:]]*[^/].+ => (.*) \(.*\)'
    while read line; do
        [[ "$line" =~ $regex ]] && sodep=${BASH_REMATCH[1]} || continue

        if [[ -f "$sodep" ]]; then # -f follows symlinks, don't believe it!
            if [[ ! -L $sodep ]]; then
                _add_file "$sodep" "$BASEDIR$sodep" "$(stat -c %a "$sodep")"
            else
                resolved=$(readlink -e "$BASEDIR$sodep")
                dirname=${resolved%/*}
                _add_dir "${dirname#$BASEDIR}" 755
                _add_symlink "$sodep" "${resolved#$BASEDIR}"
                _add_file "${resolved#$BASEDIR}" "$resolved" 755
            fi
        fi
    done < <(_ldd "$binary")
}

parse_hook() {
    # parse key global variables set by install hooks. This function is called
    # prior to the start of hook processing, and after each hook's build
    # function is run.

    local item

    for item in $MODULES; do
        add_module "$item"
    done

    for item in $BINARIES; do
        add_binary "$item"
    done

    for item in $FILES; do
        add_file "$item"
    done

    [[ $SCRIPT ]] && add_file "$HOOKDIR/$SCRIPT" "/hooks/$SCRIPT"
}

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