#!/bin/sh
#
# tiny init
#
# false positive
# shellcheck disable=2154

print()
{
    printf "%b %s\n" "${2:-"\033[1;37m>>\033[m"}" "$1"
}

panic()
{
    print "${1:-unexpected error occurred}" \
          "\033[1;31m!!\033[m"      >&2; sh
}

resolve_device()
{
    count=0; device="$1"

    case "${device%%=*}" in /dev/*) ;;
        UUID)     device="/dev/disk/by-uuid/${device#*=}"     ;;
        LABEL)    device="/dev/disk/by-label/${device#*=}"    ;;
        PARTUUID) device="/dev/disk/by-partuuid/${device#*=}" ;;
    esac

    # prevent race condition
    # XXX what the hell happens here?
    # why this loop sometimes trigger panic if i remove '|| :'
    while [ ! -b "$device" ]; do sleep 1
        [ "$((count += 1))" = 30 ] && {
            panic "failed to lookup partition"
            break
        }
    done || :
}

run_hook()
{
    type="$1"; hksdir=/usr/share/tinyramfs/hooks

    # run hooks if any
    # false positive
    # shellcheck disable=1090
    for hook in $hooks; do
        [ -f "${hksdir}/${hook}/${hook}.${type}" ] || continue
           . "${hksdir}/${hook}/${hook}.${type}"
    done
}

prepare_environment()
{
    # false positive
    # shellcheck disable=1091
    . /etc/tinyramfs/config

    export \
        PATH=/bin TERM=linux SHELL=/bin/sh \
        LANG=C LC_ALL=C PS1="# " HOME=/root

    mount -t proc     -o nosuid,noexec,nodev     proc /proc
    mount -t sysfs    -o nosuid,noexec,nodev     sys  /sys
    mount -t tmpfs    -o nosuid,nodev,mode=0755  run  /run
    mount -t devtmpfs -o nosuid,noexec,mode=0755 dev  /dev

    ln -s /proc/self/fd /dev/fd
    ln -s fd/0          /dev/stdin
    ln -s fd/1          /dev/stdout
    ln -s fd/2          /dev/stderr
}

parse_cmdline()
{
    read -r cmdline < /proc/cmdline

    for line in $cmdline; do case "$line" in
        rootfstype=*) root_type="${line#*=}" ;;
        rootflags=*)  root_opts="${line#*=}" ;;
        debug=1)      set -x ;;
        ro | rw)      rorw="-o $line" ;;
        --*)          init_args="${cmdline#*-- }"; break ;;
        *=*)          command export "$line"     ;;
        *)            command export "${line}=1" ;;
    esac 2> /dev/null || :; done
}

mount_root()
{
    [ "$break" = root ] && { print "break before mount_root()"; sh; }

    resolve_device "$root"

    set -- \
        "${rorw:--o ro}${root_opts:+,$root_opts}" \
        "${root_type:+-t $root_type}" "$device" "/mnt/root"

    # word splitting is safe by design
    # shellcheck disable=2068
    mount $@ || panic "failed to mount root"
}

boot_system()
{
    [ "$break" = boot ] && { print "break before boot_system()"; sh; }

    for dir in run dev sys proc; do
        mount -o move "$dir" "/mnt/root/${dir}"
    done

    set -- "/mnt/root" "${init:-/sbin/init}" "$init_args"

    # POSIX exec has no -c flag to execute command with empty environment
    # use 'env -i' to prevent leaking exported variables
    # word splitting is safe by design
    # shellcheck disable=2068
    exec env -i TERM=linux PATH=/bin:/sbin:/usr/bin:/usr/sbin \
        switch_root $@ || panic "failed to boot system"
}

# int main()
{
    # enable exit on error and disable globbing
    # trap EXIT signal
    set -ef; trap panic EXIT

    prepare_environment
    parse_cmdline
    run_hook init
    mount_root
    run_hook init.late
    boot_system
}