#!/bin/sh
# SPDX-License-Identifier: MIT

# minimal init script:
# - mount sys, proc, dev (does cryptsetup need it all?)
# - mount rootfs
# - pivot_root into it

error() {
	printf "initrd_error: %s\n" "$@" > /dev/kmsg
	printf "ERROR: %s\n" "$@"

	if [ -e "/noshell" ]; then
		echo "No shell allowed, press enter to reboot"
		read -r _
		exec /bin/busybox reboot -f
	else
		echo "Starting initramfs emergency recovery shell"
		/bin/sh
	fi
}

echo "Starting init"

/bin/busybox mkdir -p /usr/bin /usr/sbin /proc /sys /dev /sysroot \
	/tmp /run/cryptsetup

# Spread out busybox symlinks and make them available without full path
# for interactive shells
/bin/busybox --install -s
export PATH=/usr/bin:/bin:/usr/sbin:/sbin

mount -t proc proc /proc \
	|| error "Could not mount /proc"
mount -t devtmpfs devtmpfs /dev \
	|| error "Could not mount /dev"
mount -t sysfs sysfs /sys \
	|| error "Could not mount /sys"

# supported options
root=""
init="/sbin/init"

shell_allowed=1
[ -e "/noshell" ] && shell_allowed=""

# shellcheck disable=SC2013 # we want word splitting
for arg in $(cat /proc/cmdline); do
	case "$arg" in
	root=*) root="${arg#root=}";;
	init=*) init="${arg#init=}";;
	shell) error "Running shell, exit to continue boot";;
	esac
done

while [ -z "$root" ]; do
	error "Could not find root= setting in cmdline." \
		${shell_allowed:+"Write valid root in /rootdev file and exit shell."}
	root="$(cat /rootdev 2>/dev/null)"
done

# sd card can take some time to show up, wait a bit...
delay=100
echo "$root" > /rootdev
while ! [ -e "$root" ]; do
	if [ "$delay" = 100 ]; then
		printf "%s" "'$root' missing, waiting up to 10 seconds..."
	fi
	if [ "$delay" -gt 0 ]; then
		delay=$((delay-1))
		sleep 0.1
		[ $((delay % 5)) = 0 ] && printf '.'
		continue
	fi
	echo
	error "'$root' does not exist" \
		${shell_allowed:+"Write valid root in /rootdev file and exit shell."}
	root="$(cat /rootdev)"
	delay=100
done
if [ "$delay" != 100 ]; then
	echo " found"
fi

if [ -e /root_pattern ]; then
	root_pattern=$(cat /root_pattern)
	# shellcheck disable=SC2254 # we want $root_pattern to be a pattern
	case "$root" in
	$root_pattern) ;;
	*) error "root=$root did not match $root_pattern, refusing to boot.";;
	esac
fi

case "$root" in
/dev/mmcblk?p[0-9])
	root_index="${root##*p}"
	partdev="${root%[0-9]}"
	rootdev="${partdev%p}"
	;;
*)
	error "This script only supports root=/dev/mmcblkXpY"
	;;
esac

if [ -e "/verity.crt" ]; then
	command -v veritysetup > /dev/null \
		|| error "root filesystem required verity verification but veritysetup is missing"
	command -v openssl > /dev/null \
		|| error "missing openssl to check verity root hash"

	# We could embed hash tree/root hash directly in SD card like we do
	# for encryption but it's easier to manipulate files.
	# We define files as follow:
	# - files stored in p127 partition, must be vfat
	# - for every partition, verity_pX.hashdev, verity_pX.hashroot
	# and verity_pX.hashroot.sig should exist and will be used to
	# unlock /dev/mapper/verity_pX
	#
	# In practice this means that this could be used for eMMC as
	# well, if some partition can be used read-only; but for eMMC
	# we should rather use integritysetup in a similar fashion...
	mount -t vfat "${partdev}127" /tmp \
		|| error "Could not mount ${partdev}127." \
			"Specify --verity when executing build-rootfs/build_image.sh."
	for file in /tmp/verity_p*.hashroot.sig; do
		index="${file#/tmp/verity_p}"
		index="${index%.hashroot.sig}"
		[ -e "$partdev$index" ] || error "$partdev$index partition not found"
		[ -e "/tmp/verity_p$index.hashdev" ] || error "missing verity_p$index.hashdev"
		[ -e "/tmp/verity_p$index.hashroot" ] || error "missing verity_p$index.hashroot"
		echo "Setting up verity for $partdev$index..."
		roothash=$(openssl cms -verify -inform DER \
				-in "/tmp/verity_p$index.hashroot.sig" \
				-content "/tmp/verity_p$index.hashroot" \
				-nosmimecap -binary -CAfile /verity.crt \
				-no_check_time) \
			|| error "verity_p$index.hashroot.sig signature check failed"
		veritysetup open "$partdev$index" "verity_p$index" \
				"/tmp/verity_p$index.hashdev" "$roothash" \
			|| error "veritysetup failed for $partdev$index"
	done

	root="/dev/mapper/verity_p$root_index"
	[ -e "$root" ] || error "root partition $root_index did not have verity image"
fi

if ! [ "$(head -c 4 "$root" 2>/dev/null)" = LUKS ]; then
	if [ -e /noplain ]; then
		error "not allowed to boot a plain filesystem, refusing to boot."
	fi
else
	command -v cryptsetup > /dev/null \
		|| error "root filesystem is LUKS encrypted but cryptsetup is not available"

	# keys are stored as follow
	# 0MB        <GPT header and partition table>
	# 9MB        key for part 1
	# 9MB+4k     key for part 2
	# 9MB+(n*4k) key for part n+1
	# 10MB       first partition
	index=$((root_index-1))
	offset=$(((9*1024 + index * 4)*1024))
	dd if="$rootdev" of=/key.mmc bs=4k count=1 status=none \
			iflag=skip_bytes skip="$offset" \
		|| error "Could not extract key to /key"

	# key is:
	# - 112 bytes of caam black key
	# - 16 bytes of iv followed by rest of key
	dd if=/key.mmc of=/key.bb bs=112 count=1 status=none \
		|| error "Could not extract black key"
	dd if=/key.mmc of=/key.enc bs=4k status=none \
			iflag=skip_bytes skip=112 \
		|| error "Could not extract encrypted key"
	caam-decrypt /key.bb AES-256-CBC /key.enc /key.luks \
		|| error "Could not decrypt luks key"
	cryptsetup luksOpen --key-file /key.luks --allow-discards \
			"$root" "rootfs_$index" \
		|| error "Could not decrypt $root to rootfs_$index"
	rm -f /key.*
	root="/dev/mapper/rootfs_$index"
fi


mount -o ro -t btrfs,ext4 "$root" /sysroot \
	|| error "Could not mount $root to /sysroot"

# system expects /dev already mounted, otherwise it
# fails trying to write to /dev/null...
mount -o move /dev /sysroot/dev
mount -o move /sys /sysroot/sys
mount -o move /proc /sysroot/proc

exec /bin/busybox switch_root /sysroot "$init"
