#!/bin/bash
# mkinitcpio - modular tool for building an init ramfs cpio image
#
# IMPORTANT: We need to keep a common base syntax here because
# some of these hooks/scripts need to run under busybox's ash -
# therefore, the following constraints should be enforced:
#   variables should be quoted and bracketed "${SOMEVAR}"
#   inline execution should be done with $() instead of backticks
#   use -z "${var}" to test for nulls/empty strings
#   in case of embedded spaces, quote all path names and string comparisons
#


# Settings
TMPDIR="$(mktemp -d /tmp/mkinitcpio.XXXXXX)"
BASEDIR=""
FILELIST="${TMPDIR}/filelist"
MESSAGEFILE="${TMPDIR}/message"
KERNELVERSION="$(uname -r)"
FUNCTIONS="functions"
CONFIG="mkinitcpio.conf"
HOOKDIR="hooks"
INSTDIR="install"
MODULE_FILE=""
SAVELIST=""
GENIMG=""
APPEND=""
PRESET=""
MESSAGE=""
SKIPHOOKS=()
PRESETDIR="mkinitcpio.d"
QUIET="y"
SHOW_AUTOMODS="n"
COMPRESSION="gzip"
COMPRESSION_OPTIONS=""

if [ ! $UID ]; then
    UID=$(id -u)
fi

# Add /{,usr}/sbin to path
# works around undetected problems like in #8448
PATH="${PATH}:/sbin:/usr/sbin"

APPNAME=$(basename "${0}")

usage ()
{
    echo "${APPNAME}: usage"
    echo "  -c CONFIG        Use CONFIG file. default: /etc/mkinitcpio.conf"
    echo "  -k KERNELVERSION Use KERNELVERSION. default: $(uname -r)"
    echo "  -s NAME          Save filelist. default: no"
    echo "  -b BASEDIR       Use BASEDIR. default: /"
    echo "  -g IMAGE         Generate a cpio image as IMAGE. default: no"
    echo "  -a NAME          Append to an existing filelist. default: no"
    echo "  -p PRESET        Build specified preset."
    echo "  -m MESSAGE       Print MESSAGE before passing control to init."
    echo "  -S SKIPHOOKS     Skip SKIPHOOKS (comma-separated) when building the image."
    echo "  -v               Verbose output. Default: no"
    echo "  -M               Display modules found via autodetection." 
    echo "  -L               List all available hooks." 
    echo "  -H HOOKNAME      Output help for hook 'HOOKNAME'."
    echo "  -h               This message."
    cleanup
    exit 1
}

cleanup ()
{
    [ -n "${TMPDIR}" -a -d "${TMPDIR}" ] && rm -rf ${TMPDIR}
}

sighandler() {
    cleanup
    exit 1
}

trap sighandler TERM INT

while getopts ':c:k:s:b:g:a:p:m:vH:LMhS:' arg; do
    if [ "${OPTARG#-}" != "${OPTARG}" ]; then
        echo "error: optional argument to '-${arg}' begins with a '-'"
        echo "  you probably don't want this....aborting."
        usage
    fi
    case "${arg}" in
        c) CONFIG="${OPTARG}" ;;
        k) KERNELVERSION="${OPTARG}" ;;
        s) SAVELIST="y"; FILELIST="${OPTARG}" ;;
        b) BASEDIR="${OPTARG}" ;;
        g) GENIMG="${OPTARG}" ;;
        a) APPEND="y"; SAVELIST="y"; FILELIST="${OPTARG}" ;;
        p) PRESET="${OPTARG}" ;;
        m) MESSAGE="${OPTARG}" ;;
        v) QUIET="n" ;;
        S) OLDIFS=${IFS}
           IFS=,
           SKIPHOOKS=(${OPTARG})
           IFS=${OLDIFS}
           unset OLDIFS
           ;;
        H) . "${INSTDIR}/${OPTARG}";
           echo "Help for hook '${OPTARG}':"
           help
           cleanup
           exit 0 ;;
        L) echo "Available hooks: " 
           for h in ${INSTDIR}/*; do
               echo "   $(basename ${h})"
           done
           cleanup
           exit 0 ;;
        M) SHOW_AUTOMODS="y" ;;
        h|?) usage ;;
        :) echo "${OPTARG} requires a value..."; usage ;;
        *) echo "invalid argument '${arg}'"; usage ;;
    esac
done
shift $((${OPTIND} - 1))

# use preset $PRESET
if [ -n "${PRESET}" ]; then
    if [ -f "${PRESETDIR}/${PRESET}.preset" ]; then
        # Use -b, -m and -v options specified earlier
        PRESET_MKOPTS="${0}"
        [ -n "${BASEDIR}" ] && PRESET_MKOPTS="${PRESET_MKOPTS} -b ${BASEDIR}"
        [ -n "${MESSAGE}" ] && PRESET_MKOPTS="${PRESET_MKOPTS} -m \"${MESSAGE}\""
        [ "${QUIET}" = "n" ] && PRESET_MKOPTS="${PRESET_MKOPTS} -v"
        # Build all images
        . ${PRESETDIR}/${PRESET}.preset
        for p in ${PRESETS[@]}; do
            echo "==> Building image \"${p}\""
            PRESET_CMD="${PRESET_MKOPTS}"

            eval "PRESET_KVER=\"\${${p}_kver}\""
            [ -z "${PRESET_KVER}" ] && PRESET_KVER="${ALL_kver}"
            eval "PRESET_CONFIG=\"\${${p}_config}\""
            [ -z "${PRESET_CONFIG}" ] && PRESET_CONFIG="${ALL_config}"
            eval "PRESET_IMAGE=\"\${${p}_image}\""
            eval "PRESET_OPTIONS=\"\${${p}_options}\""

            if [ -n "${PRESET_KVER}" ]; then
                PRESET_CMD="${PRESET_CMD} -k ${PRESET_KVER}"
            else
                echo "==> No kernel version specified. Skipping image \"${p}\"."
                continue
            fi

            if [ -n "${PRESET_CONFIG}" ]; then
                PRESET_CMD="${PRESET_CMD} -c ${PRESET_CONFIG}"
            else
                echo "==> No configuration file specified. Skipping image \"${p}\"."
                continue
            fi

            if [ -n "${PRESET_IMAGE}" ]; then
                PRESET_CMD="${PRESET_CMD} -g ${PRESET_IMAGE}"
            else
                echo "==> No image file specified. Skipping image \"${p}\"."
                continue
            fi

            if [ -n "${PRESET_OPTIONS}" ]; then
                PRESET_CMD="${PRESET_CMD} ${PRESET_OPTIONS}"
            fi

            echo "==> Running command: ${PRESET_CMD}"
            if eval ${PRESET_CMD}; then
                echo "==> SUCCESS"
            else
                echo "==> FAIL"
            fi
        done
        cleanup
        exit 0
    else
        echo "Preset ${PRESET} does not exist. Exiting."
        cleanup
        exit 1
    fi
fi

# remove trailing / from BASEDIR
BASEDIR="${BASEDIR%/}"

MODULEDIR="${BASEDIR}/lib/modules/${KERNELVERSION}"

if [ -n "${BASEDIR}" ]; then
    if [ "${BASEDIR}" = "${BASEDIR#/}" ]; then
        BASEDIR="$(pwd)/${BASEDIR}"
    elif [ ! -d "${BASEDIR}" ]; then
        echo "base directory '${BASEDIR}' does not exist or is not a directory"
        cleanup
        exit 1
    fi
fi

if [ ! -f "${CONFIG}" ]; then
    echo "config file '${CONFIG}' cannot be found, aborting..."
    cleanup
    exit 1
fi
. "${CONFIG}"

if [ -f "${FILELIST}" -a -z "${APPEND}" ]; then
    if [ -z "${SAVELIST}" ]; then
        rm ${FILELIST}
        touch "${FILELIST}"
    else
        echo "destination file list '${FILELIST}' exists - remove before running"
        cleanup
        exit 1
    fi
else
    touch "${FILELIST}"
fi

BASEDIR=$(echo ${BASEDIR} | tr -s /)
MODULEDIR=$(echo ${MODULEDIR} | tr -s /)

. "${FUNCTIONS}"

if [ "${SHOW_AUTOMODS}" = "y" ]; then
    echo "Modules autodetected:"
    . "${INSTDIR}/autodetect"
    install
    cat "${MODULE_FILE}"
    cleanup
    exit 0
fi

if [ -z "${GENIMG}" ]; then
    echo ":: Begin dry run"
else
    echo ":: Begin build"
fi
#parse 'global' hook, as defined in ${CONFIG}
parse_hook

for hook in ${HOOKS}; do
    in_array ${hook} ${SKIPHOOKS[@]} && continue
    unset MODULES
    unset BINARIES
    unset FILES
    install () { msg "${hook}: no install function..."; }
    # Deprecation check
    # A hook is considered deprecated if it is a symlink
    # within $INSTDIR.
    if [ -h "${INSTDIR}/${hook}" ]; then
        newhook="$(readlink -ne "${INSTDIR}/${hook}")"
        if [ -n "${newhook}" -a "${INSTDIR}/$(get_basename ${newhook})" -ef "${newhook}" ]; then
            newhook="$(get_basename ${newhook})"
            echo "   -------------------------------------------------------------------"
            echo "   WARNING: Hook \"${hook}\" is deprecated."
            echo "            Replace it with \"${newhook}\" in your configuration file."
            echo "   -------------------------------------------------------------------"
            hook="${newhook}"
        fi
    fi
    if grep -q "install" "${INSTDIR}/${hook}"; then
        . "${INSTDIR}/${hook}"
        echo ":: Parsing hook [${hook}]"
        install
        parse_hook
    else
        die "Hook '${hook}' can not be found."
    fi
done

if [ "${HAS_MODULES}" = "y" ]; then
    echo ":: Generating module dependencies"
    # unfortuate name collision between a function and utility program
    unset install
    for mod in $(grep "file /lib/modules/${KERNELVERSION}" ${FILELIST} | cut -d' ' -f2); do
        install -m 644 -D "${BASEDIR}${mod}" "${TMPDIR}${mod}"
    done
    /sbin/depmod -b ${TMPDIR} ${KERNELVERSION}
    add_file "${TMPDIR}/lib/modules/${KERNELVERSION}/modules.dep"     "/lib/modules/${KERNELVERSION}/modules.dep"
    add_file "${TMPDIR}/lib/modules/${KERNELVERSION}/modules.alias"   "/lib/modules/${KERNELVERSION}/modules.alias"
    add_file "${TMPDIR}/lib/modules/${KERNELVERSION}/modules.symbols" "/lib/modules/${KERNELVERSION}/modules.symbols"
fi

status=0
if [ -n "${GENIMG}" ]; then
    echo ":: Generating image '${GENIMG}'"
    shopt -s -o pipefail
    [ ${COMPRESSION} = "xz" ] && COMPRESSION_OPTIONS="${COMPRESSION_OPTIONS} --check=crc32"
    if ! /sbin/gen_init_cpio ${FILELIST} | ${COMPRESSION} ${COMPRESSION_OPTIONS} > "${GENIMG}"; then
        echo ":: Image generation FAILED"
        status=1
    else
        echo ":: Image generation successful"
        status=0
    fi

    if [ -z "${SAVELIST}" ]; then
        rm ${FILELIST}
    fi
else
    echo ":: Dry run complete, use -g IMAGE to generate a real image"
fi

cleanup

exit ${status}
#vim:set ft=sh ts=4 sw=4 noet:
