#!/bin/sh -e

# update-kernel
#
# Kernel and firmware update script for Alpine installations set up
# with setup-bootable
#
# Copyright (c) 2014 Timo Teräs
# Copyright (c) 2014-2015 Kaarle Ritvanen


SCRIPT=update-kernel
VIRTUAL=.tmp-$SCRIPT

SUPERUSER=
[ $(id -u) -eq 0 ] && SUPERUSER=Y

ARCH=
BUILDDIR=
FLAVOR=
MNTDIR=
PACKAGES=
MKINITFS_ARGS=
REPOSITORIES_FILE=/etc/apk/repositories
SIGNALS="HUP INT TERM"
TMPDIR=
features=

error() {
    echo "$SCRIPT: $1" >&2
}

usage() {
    [ "$2" ] && error "$2"
    local opts="[-F <feature>]... [-p <package>]..."
    local dest_args="[-a <arch>] <dest_dir>"
    cat >&2 <<EOF

Syntax: $SCRIPT $opts [$dest_args]
        $SCRIPT -f <flavor> $opts $dest_args
        $SCRIPT -b <build_dir> $opts [$dest_args]

Options: -a|--arch <arch>        Install kernel for specified architecture
         -b|--build <build_dir>  Install custom-built kernel
         -f|--flavor <flavor>    Install kernel of specified flavor
         -F|--feature <feature>  Enable initfs feature
         -p|--package <package>  Additional module or firmware package
         -v|--verbose            Verbose output
         -K|--hostkeys           Include host keys in initramfs
         --repositories-file <f> apk repositories file

EOF
    exit $1
}

QUIET_OPT="--quiet"
OPTS=$(getopt -l arch:,build-dir:,flavor:,feature:,help,package:,verbose,hostkeys,repositories-file: \
	      -n $SCRIPT -o a:b:f:F:hp:vK -- "$@") || usage 1
eval set -- "$OPTS"
while :; do
    case "$1" in
	-a|--arch)
	    shift
	    ARCH=$1
	    ;;
	-b|--build-dir)
	    shift
	    BUILDDIR=$1
	    ;;
	-f|--flavor)
	    shift
	    FLAVOR=$1
	    ;;
	-F|--feature)
	    shift
	    features="$features $1"
	    ;;
	-h|--help)
	    echo "$SCRIPT 3.4.1-r5" >&2
	    usage 0
	    ;;
	-p|--package)
	    shift
	    PACKAGES="$PACKAGES $1"
	    ;;
	-v|--verbose)
	    QUIET_OPT=
	    ;;
	-K|--hostkeys)
	    MKINITFS_ARGS="$MKINITFS_ARGS -K"
	    ;;
        --repositories-file)
	    shift
	    REPOSITORIES_FILE=$1
	    ;;
	--)
	    break
	    ;;
    esac
    shift
done

DESTDIR=$2


[ "$BUILDDIR" -a "$FLAVOR" ] && \
    usage 1 "Cannot specify both build directory and flavor"

if [ -z "$DESTDIR" ]; then
    [ "$ARCH" ] && \
	usage 1 "Cannot specify architecture when updating the current kernel"

    [ "$FLAVOR" ] && \
	usage 1 "Cannot specify flavor when updating the current kernel"

    [ "$SUPERUSER" ] || \
	usage 1 "Specify destination directory or run as superuser"

    while read MOUNT; do
	set -- $MOUNT
	[ $2 = /.modloop ] || continue
	DESTDIR=$(dirname $(losetup $1 | cut -d " " -f 3))
	MNTDIR=$(dirname "$DESTDIR")
	break
    done < /proc/mounts

    if [ -z "$MNTDIR" ]; then
	error "Module loopback device not mounted"
	exit 1
    fi
fi

remount() {
    mount $1 -o remount "$MNTDIR"
}


ignore_sigs() {
    trap "" $SIGNALS
}

clean_up() {
    set +e
    ignore_sigs

    [ "$SUPERUSER" ] && apk del $QUIET_OPT $VIRTUAL
    rm -fr $TMPDIR
}

trap clean_up EXIT $SIGNALS


if [ "$SUPERUSER" ]; then
    apk add $QUIET_OPT --update-cache -t $VIRTUAL mkinitfs squashfs-tools kmod
fi

if [ -z "$features" ]; then
    . /etc/mkinitfs/mkinitfs.conf
fi

if [ -z "$FLAVOR" ]; then
    FLAVOR=$(uname -r | cut -d - -f 3-)
    [ "$FLAVOR" ] || FLAVOR=vanilla
fi

[ "$ARCH" ] || ARCH=$(apk --print-arch)

TMPDIR=$(mktemp -d /tmp/$SCRIPT.XXXXXX)
ROOT=$TMPDIR/root
BOOT=$ROOT/boot
WRAPPER=

_exec() {
    $WRAPPER "$@"
}

_apk() {
    local cmd=$1
    shift

    local wrapper=
    if [ -z "$SUPERUSER" ]; then
	local opt=
	local fake_env=$TMPDIR/fake-env
	if [ -f $fake_env ]; then
	    opt="-i $fake_env"
	    WRAPPER="fakeroot $opt --"
	fi
	wrapper="fakeroot $opt -s $fake_env --"
    fi

    $wrapper apk $cmd $QUIET_OPT -p $ROOT --arch "$ARCH" \
	--keys-dir /etc/apk/keys \
	--repositories-file "$REPOSITORIES_FILE" $*
}

extra_pkgs() {
    local res=$(_apk search -x $1)
    if [ "$res" ]; then
	echo $*
    fi
}

# set up the root and get the APKINDEX for search
_apk add --initdb --update-cache

if [ "$BUILDDIR" ]; then
    case "$ARCH" in
    arm*|aarch64*)	_install="zinstall dtbs_install" ;;
    *)			_install="install" ;;
    esac

    mkdir -p $BOOT
    make -C "$BUILDDIR" $_install firmware_install modules_install \
	INSTALL_MOD_PATH=$ROOT \
	INSTALL_PATH=$BOOT \
	INSTALL_DTBS_PATH='$ROOT/usr/lib/linux-$(KERNELRELEASE)'
else
    if [ -z "$PACKAGES" ]; then
	PACKAGES="$(extra_pkgs "dahdi-linux-$FLAVOR" dahdi-linux)
		$(extra_pkgs "xtables-addons-$FLAVOR")"
    fi
    PACKAGES="$PACKAGES linux-$FLAVOR linux-firmware"
fi
_apk add --no-scripts alpine-base $PACKAGES


KVER_FLAVOR=
[ "$FLAVOR" = vanilla ] || KVER_FLAVOR=-$FLAVOR
KVER=$(basename $(ls -d $ROOT/lib/modules/*"$KVER_FLAVOR"))
DTBDIR=$ROOT/usr/lib/linux-$KVER
depmod -b $ROOT "$KVER"


STAGING=$TMPDIR/boot
MODLOOP=$TMPDIR/modloop
MODIMG=modloop-$FLAVOR
DTB_STAGING=$TMPDIR/dtbs

mkdir $DTB_STAGING $MODLOOP $STAGING
cp -a $ROOT/lib/modules $MODLOOP
mkdir -p $MODLOOP/modules/firmware
find $ROOT/lib/modules -type f -name "*.ko" | xargs modinfo -F firmware | sort -u | while read FW; do
	if [ -e "$ROOT/lib/firmware/$FW" ]; then
		install -pD $ROOT/lib/firmware/$FW $MODLOOP/modules/firmware/$FW
	fi
done
_exec mksquashfs $MODLOOP "$STAGING/$MODIMG" -comp xz

_exec mkinitfs $MKINITFS_ARGS -q -b $ROOT -F "$features base squashfs" \
    -o "$STAGING/initramfs-$FLAVOR" "$KVER"

for file in System.map config vmlinuz; do
    cp "$BOOT/$file$KVER_FLAVOR" $STAGING
done

if [ "$MNTDIR" ]; then
    ignore_sigs
    umount /.modloop
    remount -w
fi

mv $STAGING/* "$DESTDIR"

if [ -d "$DTBDIR" ]; then
    _opwd=$PWD
    case "$FLAVOR" in
    rpi*) _dtb="$DESTDIR" ;;
    *) _dtb="$DESTDIR/dtbs" ;;
    esac
    mkdir -p "$_dtb"
    _dtb=$(realpath "$_dtb")
    cd "$DTBDIR"
    find -type f \( -name "*.dtb" -o -name "*.dtbo" \) | cpio -pudm "$_dtb" 2> /dev/null
    cd "$_opwd"
fi

if [ "$MNTDIR" ]; then
    set +e
    sync
    remount -r
    mount -o loop "$DESTDIR/$MODIMG" /.modloop
fi

exit 0
