#!/bin/sh -efu
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2020-2023  Alexey Gladkov <gladkov.alexey@gmail.com>

# shellcheck source=tools/mki-chroot-sh-functions
. /.host/mki-chroot-sh-functions

PROG='mki-copy-efiboot-chrooted'

# External globals
verbose="${verbose-}"
EFI_CERT="${EFI_CERT-}"
EFI_FILES_COPY="${EFI_FILES_COPY-}"
EFI_FILES_REPLACE="${EFI_FILES_REPLACE-}"

cd /.image

ia32efi_flag="present"
img='.efiboot.img'
boot="EFI/BOOT"
boot_grub="boot/grub"
cert="EFI/enroll"
efi_bindir='/usr/lib64/efi'
grub_modules='/usr/lib64/grub'

mkdir $verbose -p -- "$boot"

shim_path=
if [ -n "$EFI_CERT" ]; then
	shim_path="$boot/bootx64.efi"
fi

# find out if shim version is not less than 15
# looking at directory structure
# ia32 binaries should be present since that version
check_shim_teen_path()
{
	local shim_max_version shim_dir_prefix

	shim_dir_prefix="/usr/share/shim"

	[ -d "$shim_dir_prefix" ] ||
		return 0

	shim_max_version="$(find  "$shim_dir_prefix" -maxdepth 1 -type d | sed 's|\./||' | sort -n -r | head -1)"

	if [ ! "$shim_max_version" = "$shim_dir_prefix" ]; then
		shimia32_dir="$shim_max_version/ia32"
	else
		ia32efi_flag="absent"
	fi
}

# check if shim and grub-efi include ia32 EFI binaries
# if signed shim is absent try to fallback to unsigned one
check_ia32efi_stack_full()
{
	[ -f "$efi_bindir/shimia32.efi" ] ||
		check_shim_teen_path

	[ -f "$efi_bindir/grubia32.efi" ] ||
		ia32efi_flag="absent"

	[ "$ia32efi_flag" != "absent" ] ||
		message "WARNING:" \
			"Some of required bootloader packages in current branch" \
			"miss ia32 binaries, ia32 EFI support is therefore disabled."
}

copy_cert()
{
	[ -n "$EFI_CERT" ] ||
		return 0

	local keyfile="/etc/pki/uefi/$EFI_CERT.cer"

	[ -s "$keyfile" ] ||
		fatal "invalid $keyfile"

	mkdir $verbose -p "$cert"
	cp $verbose -pLft "$cert" -- "$keyfile"
}

copy_shim()
{
	[ -n "${shim_path:-}" ] ||
		return 0
	copy_cert
	if [ -s "$efi_bindir/shimx64.efi" ]; then
		cp $verbose -pLf "$efi_bindir/shimx64.efi" "$shim_path"
		cp $verbose -pLf "$efi_bindir/mmx64.efi" "$boot"
	else
		# compatibility with shim-signed-0.4
		cp $verbose -pLf "$efi_bindir/shim.efi" "$shim_path"
		cp $verbose -pLf "$efi_bindir/MokManager.efi" "$boot"
	fi

	# injection of ia32 binaries
	[ "$ia32efi_flag" = "present" ] ||
		return 0

	if [ -s "$efi_bindir/shimia32.efi" ]; then
		# shimia32.efi binary is a first stage loader therefore renamed
		cp $verbose -pLf "$efi_bindir/shimia32.efi" "$boot/bootia32.efi"
		cp $verbose -pLf "$efi_bindir/mmia32.efi" "$boot"
	else
		# use unsigned shim if signed is not available
		cp $verbose -pLf "$shimia32_dir/shimia32.efi" "$boot/bootia32.efi"
		cp $verbose -pLf "$shimia32_dir/mmia32.efi" "$boot"
		cp $verbose -pLf "$shimia32_dir/shimia32.hash" "$boot/bootia32.hash"
	fi
}

kimage=
copy_kernel()
{
	if [ -s "$boot/vmlinuz" ] || [ -s "$boot/full.cz" ]; then
		return 0
	fi

	if [ -f syslinux/alt0/vmlinuz ]; then
		cp $verbose -lpLft "$boot" -- \
			syslinux/alt0/vmlinuz \
			syslinux/alt0/full.cz
		return 0
	fi

	if [ -n "$kimage" ]; then
		cp $verbose -af "$kimage" "$boot"/vmlinuz
		[ ! -f /boot/full.cz ] ||
			cp $verbose -af /boot/full.cz "$boot"/full.cz
	fi
}

copy_grub_primary() {
	boot_prefix=grub
	[ -n "$EFI_CERT" ] || boot_prefix=boot
	if [ -f "$efi_bindir/grubx64.efi" ] && [ -d "$grub_modules/x86_64-efi" ]; then
		cp $verbose -pLf "$efi_bindir/grubx64.efi" "$boot/${boot_prefix}x64.efi"
	fi
	if [ -f "$efi_bindir/grubia32.efi" ]; then
		cp $verbose -pLf "$efi_bindir/grubia32.efi" "$boot/${boot_prefix}ia32.efi"
	fi
	if [ -f "$efi_bindir/grubaa64.efi" ]; then
		cp $verbose -pLf "$efi_bindir/grubaa64.efi" "$boot/bootaa64.efi"
	elif [ -f "$efi_bindir/grubx64.efi" ] && [ -d "$grub_modules/arm64-efi" ]; then
		cp $verbose -pLf "$efi_bindir/grubx64.efi" "$boot/bootaa64.efi"
	fi
	if [ -f "$efi_bindir/grubriscv64.efi" ] && [ -d "$grub_modules/riscv64-efi" ]; then
		cp $verbose -pLf "$efi_bindir/grubriscv64.efi" "$boot/bootriscv64.efi"
	fi
	if [ -f "$efi_bindir/grubloongarch64.efi" ] && [ -d "$grub_modules/loongarch64-efi" ]; then
		cp $verbose -pLf "$efi_bindir/grubloongarch64.efi" "$boot/bootloongarch64.efi"
	fi
}

copy_grub_secondary() {
	[ -d "$boot_grub/fonts" ] ||
		mkdir $verbose -p "$boot_grub/fonts"

	local grub_arch

	for grub_arch in x86_64-efi i386-efi arm64-efi riscv64-efi loongarch64-efi; do
		[ -d "$grub_modules/$grub_arch" ] ||
			continue

		mkdir -p -- "$boot_grub/$grub_arch"

		find "$grub_modules/$grub_arch" -type f \! -name '*.module' \
			-exec cp $verbose -arf -t "$boot_grub/$grub_arch" -- '{}' '+'
	done

	cp $verbose -Lf /boot/grub/fonts/unicode.pf2 "$boot_grub/fonts"
	cp $verbose -Lf /boot/grub/unifont.pf2 "$boot_grub"

	[ ! -d /boot/grub/themes ] || [ -d "$boot_grub/themes" ] ||
		cp $verbose -arf /boot/grub/themes "$boot_grub"

	if [ ! -e "$boot_grub/locale" ]; then
		mkdir "$boot_grub/locale"

		find /usr/share/locale/ -name grub.mo -print |
		while read -r i; do
			lct="$(printf '%s\n' "$i" | cut -d/ -f5)"
			cp $verbose -arf "$i" "$boot_grub/locale/$lct.mo"
		done
	fi
}

add_grub_cfg() {
	local main_grub_cfg="$boot_grub/grub.cfg"
	local interim_grub_cfg="$boot/grub.cfg"

	# If both the main and intermediate grub.cfg files already exist,
	# no action is required.
	if [ -f "$interim_grub_cfg" ] && [ -f "$main_grub_cfg" ]; then
		return 0
	fi

	# If the intermediate grub.cfg exists, but the main grub.cfg doesn't
	# use it as the main one.
	if [ -f "$interim_grub_cfg" ] && [ ! -f "$main_grub_cfg" ]; then
		mv -T "$interim_grub_cfg" "$main_grub_cfg"
	fi

	[ -f "$main_grub_cfg" ] ||
		fatal "GRUB config $main_grub_cfg doesn't exist!"

	# The intermediate grub.cfg is essential to minimize the size
	# of the efiboot.img.
	cat >"$interim_grub_cfg" <<-GRUB_EOF
		search --file --set=root /.disk/info
		set prefix=(\$root)/$boot_grub
		source \$prefix/grub.cfg
	GRUB_EOF
}

write_efiboot_img () {
	imgsize="$(( $(du -lsB32k EFI/ | cut -f1) + 10 ))"

	# additional files or directories for efiboot.img
	for efi_file in $EFI_FILES_COPY $EFI_FILES_REPLACE; do
		[ -n "${efi_file##*/*}" ] ||
			fatal "EFI_FILES_COPY or EFI_FILES_REPLACE contains '/': $efi_file"
		[ -e "$efi_file" ] || fatal "$efi_file does not exist"
		imgsize="$(( imgsize + $(du -lsB32k "$efi_file" | cut -f1) ))"
	done

	dd if=/dev/zero of="$img" bs=32k count="$imgsize"

	# dosfstools-4.0 has dropped those ancient symlinks, *sigh*
	mkfs=
	for bin in mkfs.fat mkfs.vfat; do
		if $bin --help >/dev/null 2>&1; then
			mkfs="$bin"
			break
		fi
	done

	[ -n "$mkfs" ] ||
		fatal "Not found: mkfs.fat or mkfs.vfat"

	"$mkfs" $verbose -n "El Torito" "$img"

	# mtools insists that total number of sectors
	# is a multiple of sectors per track (the infamous 63),
	# and dosfstools-4.0 doesn't follow that anymore
	echo "mtools_skip_check=1" >~/.mtoolsrc

	add_grub_cfg

	mcopy $verbose -i "$img" -s EFI \
		$EFI_FILES_COPY $EFI_FILES_REPLACE ::

	# cleanup additional files for efiboot.img only
	[ -z "$EFI_FILES_REPLACE" ] || rm -r $EFI_FILES_REPLACE

	mv "$img" EFI/
}

#find out if all bootloader stages have ia32 binaries in the package
check_ia32efi_stack_full

[ -z "$EFI_CERT" ] ||
	copy_shim

copy_grub_primary

# The find_vmlinuz() function defines the kimage variable, which is
# subsequently used by the copy_kernel() function.
find_vmlinuz kimage

[ -d "$boot_grub" ] || mkdir -p "$boot_grub"
[ -f "EFI/$img" ] || write_efiboot_img
copy_kernel
copy_grub_secondary

# use ISO9660 hardlinks support if possible
hardlink $verbose -c EFI/
