#!/bin/sh -efu

PROG='mki-copy-efiboot-chrooted'

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

cd /.image

message()
{
	printf '%s: %s\n' "$PROG" "$*" >&2
}

fatal()
{
	message "$*"
	exit 1
}

n_stages=0
stage2=
for i in altinst live rescue; do
	if [ -f "$i" ]; then
		stage2="${stage2:+$stage2 }$i"
		n_stages=$(($n_stages + 1))
	fi
done

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

kernel_bootparams="fastboot live lowmem showopts automatic=method:cdrom $EFI_BOOTARGS"

mkdir $verbose -p -- "$boot"

# nexthop name is hardwired into shim-0.2
if [ -n "$EFI_CERT" ]; then
	shim_path="$boot/bootx64.efi"
	bin_path="$boot/grubx64.efi"
else
	shim_path=
	bin_path="$boot/bootx64.efi"
fi

bootparams()
{
	[ ! -s "$1" ] ||
		echo "ramdisk_size=$(( $(stat -c%s $1) / 1024 + 1 )) stagename=$1"
}

# 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, refind 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/refind_ia32.efi" ] || [ -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_shell()
{
	local dest src

	dest="$efi/$shell"
	src="$efi_bindir/shell.efi"

	[ ! -f "$src" ] ||
		cp $verbose -pLf "$src" "$dest"
}

copy_mt86()
{
	local dest src

	dest="$tools"
	src="$efi_bindir/memtest86.efi"

	mkdir $verbose -p "$dest"

	[ -f "$src" ] ||
		return 0

	cp $verbose -pLf "$src" "$dest/memtest86.efi" &&
	find /usr/share/efi-memtest86 -type f |
		xargs cp $verbose -pLft "$dest"
}

purge_mt86()
{
	(
		cd "$tools" ||
			exit 0

		if [ -s memtest86.efi ]; then
			rm $verbose -f memtest86.efi
			find /usr/share/efi-memtest86 -type f -printf '%f\n' |
				xargs rm $verbose -f
		fi
	)
}

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
}

copy_kernel()
{
	[ ! -s "$boot/vmlinuz" ] && [ ! -s "$boot/full.cz" ] ||
		return 0

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

	if [ -L /boot/vmlinuz ]; then
		kimage="$(readlink -v /boot/vmlinuz)"
	else
		kimage="$(find /boot -type f -name 'vmlinuz-*' -print -quit)"
	fi

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


copy_elilo()
{
	cp $verbose -pLf "$efi_bindir/elilo.efi" "${1:-$bin_path}"
	cat > "$boot/elilo.conf" <<- ELILO_EOF
	append="$kernel_bootparams"
	read-only
	image="vmlinuz"
	  label="linux"
	  initrd=full.cz
	ELILO_EOF
	[ "$EFI_BOOTLOADER" != elilo ] ||
		echo "  append=\"$kernel_bootparams stagename=${stage2% *}\"" \
			>> "$boot/elilo.conf"
}

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
}

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

	if [ -d "$grub_modules/x86_64-efi" ]; then
		cp $verbose -arf "$grub_modules/x86_64-efi" "$boot_grub"
		find "$boot_grub/x86_64-efi" -type f -name '*.module' -delete ||:
	fi
	if [ -d "$grub_modules/i386-efi" ]; then
		cp $verbose -arf "$grub_modules/i386-efi" "$boot_grub"
		find "$boot_grub/i386-efi" -type f -name '*.module' -delete ||:
	fi
	if [ -d "$grub_modules/arm64-efi" ]; then
		cp $verbose -arf "$grub_modules/arm64-efi" "$boot_grub"
		find "$boot_grub/arm64-efi" -type f -name '*.module' -delete ||:
	fi
	if [ -d "$grub_modules/riscv64-efi" ]; then
		cp $verbose -arf "$grub_modules/riscv64-efi" "$boot_grub"
		find "$boot_grub/riscv64-efi" -type f -name '*.module' -delete ||:
	fi

	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"
		for i in $(find /usr/share/locale/ -name grub.mo); do
			lct="$(echo $i | cut -d/ -f5)"
			cp $verbose -arf "$i" "$boot_grub/locale/$lct.mo"
		done
	fi
}

refind_add_stage_submenu()
{
	if [ "$1" = rescue ]; then
		[ -f rescue ] ||
			return 0

		local forensic_args

		forensic_args="$(sed -rn \
			's,^.* stagename=rescue (.*forensic hash=[0-9a-f]+)$,\1,p' \
			syslinux/isolinux.cfg)"

		[ -n "$forensic_args" ] ||
			return 0

		cat <<- REFIND_EOF
		  submenuentry "Forensic mode (leave disks alone)" {
		    add_options "$forensic_args"
		  }
		REFIND_EOF
		return 0
	fi

	local pair

	for pair in English:en_US Kazakh:kk_KZ Russian:ru_RU Ukrainian:uk_UA; do
		cat <<- REFIND_EOF
		  submenuentry "${pair%%:*}" {
		    add_options "lang=${pair##*:}"
		  }
		REFIND_EOF
	done
}

refind_add_stage2()
{
	[ -n "$stage2" ] ||
		fatal "no stage2 image found."
	local root icon splash loader options

	for root in $stage2; do
		case "$root" in
			altinst)
				splash="splash "
				label="Installation"
				;;
			live)
				splash="splash "
				label="Live"
				;;
			rescue)
				splash=
				label="Rescue"
				;;
			*)
				label="$root"
				;;
		esac

		# provide differentiating boot target icons if needed
		icon="$refind_icons/altlinux/$root.$icon_format"

		[ "$n_stages" -ne 1 ] && [ -s "$icon" ] ||
			icon="$refind_icons/os_altlinux.$icon_format"

		case "$1" in
			x64)
				# ELILO used as SB trampoline for unsigned kernels
				loader="$boot/elilo.efi"
				# NB: -v seems critical, otherwise we hit this:
				#     gzip_x86_64: invalid exec_header
				options="-v -i full.cz vmlinuz `bootparams $root` $splash"
				;;
			ia32)
				# GRUB-EFI is used as SB trampoline for unsigned kernels
				# for ia32 EFI on x86_64 platforms
				loader="$boot/grubia32_ldr.efi"
				options=""
				;;
			*)
				fatal "Unknown architecture argument '$1' in refind_add_stage2()!"
				;;
		esac

		cat <<- REFIND_EOF

		menuentry "ALT Linux $label" {
		  icon /$icon
		  loader /$loader
		  options "$options"
		  `refind_add_stage_submenu "$root"`
		}
		REFIND_EOF
	done
}

refind_add_banner() {
	local bgfile="$refind_icons/bg.png"

	if [ ! -s "$bgfile" ]; then
		local bootlogo=''

		bootlogo="$(find /usr/share/gfxboot -name bootlogo -print -quit 2>/dev/null)" ||
			return 0

		[ -n "$bootlogo" ] && [ -s "$bootlogo" ] ||
			return 0

		convert -version >/dev/null 2>&1 ||
			return 0

		cpio -i --quiet --to-stdout back.jpg < "$bootlogo" | convert - "$bgfile" ||
			return 0
	fi

	echo "banner /$bgfile"
}

# grub.cfg for .efiboot.img
add_grub_cfg() {
	{
		cat <<- GRUB_EOF
			search --file --set=root /.disk/info
			set prefix=(\$root)/$boot_grub
			source \$prefix/grub.cfg
		GRUB_EOF
	} > "$boot/grub.cfg"
}

add_grub_cfg_refind() {
	{
		cat <<- GRUB_EOF
		set default=1
		set timeout=1
		menuentry 'ALT Linux $label' {
			linuxefi /EFI/BOOT/vmlinuz $kernel_bootparams stagename=${stage2% *}
			initrdefi /EFI/BOOT/full.cz
		}
		GRUB_EOF
	} > "$boot/grub.cfg"
}

refind_cfg_add_autodetect() {
	# http://www.rodsbooks.com/refind/configfile.html
	local blacklist="shim.efi shimx64.efi shimia32.efi shim-fedora.efi PreLoader.efi TextMode.efi ebounce.efi GraphicsConsole.efi MokManager.efi mmx64.efi mmia32.efi HashTool.efi HashTool-signed.efi elilo.efi"

	echo "dont_scan_files $blacklist"
	echo 'dont_scan_volumes "Recovery HD", LRS_ESP, "El Torito"'
	echo "textonly 1"
	echo "textmode 0"	# 80x25
	echo "timeout 0"
}

copy_refind()
{
	local label refind_aux refind_boot refind_bootia32 refind_icons icon_format scan_icon

	refind_aux="$efi/refind"
	refind_boot="$refind_aux/refind_x64.efi"
	refind_bootia32="$refind_aux/refind_ia32.efi"
	refind_icons="$refind_aux/icons"
	icon_format=icns

	[ ! -f /usr/share/refind/icons/os_unknown.png ] ||
		icon_format=png

	scan_icon="os_unknown.$icon_format"

	copy_elilo "$boot/elilo.efi"

	mkdir $verbose -p "$boot" "$refind_aux"

	cp $verbose -pLf "$efi_bindir/refind_x64.efi" "$bin_path"
	cp $verbose -lpf "$bin_path" "$refind_boot"
	cp $verbose -aLf "$efi_bindir/drivers_x64/" "$refind_aux"

	cp $verbose -aLf /usr/share/refind/icons/ "$refind_aux"

	mkdir $verbose -p "$boot/icons"
	find "$refind_icons/" \
		-name 'func_*' -o -name 'tool_*' -o -name 'vol_*' -o -name "$scan_icon" |
		xargs -r cp $verbose -lpft "$boot/icons"

	[ ! -d "$refind_aux/altlinux" ] ||
		mv "$refind_aux/altlinux" "$boot/icons"

	# empty aux refind configuration makes it scan for loaders
	refind_cfg_add_autodetect > "$refind_aux/refind.conf"

	# overwrite the main configuration file (for x64 EFI version)
	{
		cat <<- REFIND_EOF
		timeout 20
		scanfor manual
		scan_driver_dirs /$refind_aux/drivers_x64
		REFIND_EOF

		refind_add_banner
		refind_add_stage2 x64

		cat <<- REFIND_EOF

		menuentry "others" {
		  icon /$boot/icons/$scan_icon
		  loader /$refind_boot
		}

		REFIND_EOF
	} > "$boot/refind.conf"

	[ "$ia32efi_flag" = "present" ] ||
		return 0

	#refind binary mimics the grub2 binary
	cp $verbose -pLf "$efi_bindir/refind_ia32.efi" "$boot/grubia32.efi"

	#have to rename real grubia32 to prevent interference
	cp $verbose -pLf "$efi_bindir/grubia32.efi" "$boot/grubia32_ldr.efi"
	cp $verbose -aLf "$efi_bindir/refind_ia32.efi" "$refind_bootia32"

	cp $verbose -aLf "$efi_bindir/drivers_ia32/" "$refind_aux"

	#append ia32 EFI boot options only if all ia32 bootloader stack parts are available in current branch
	refind_cfg_add_autodetect > "$refind_aux/refind_ia32.conf"

	# overwrite the main configuration file (for ia32 EFI version)
	{
		cat <<- REFIND_EOF
		timeout 20
		scanfor manual
		scan_driver_dirs /$refind_aux/drivers_ia32
		REFIND_EOF

		refind_add_banner
		refind_add_stage2 ia32

		cat <<- REFIND_EOF
		menuentry "others" {
		  icon /$boot/icons/$scan_icon
		  loader /$refind_bootia32
		}

		REFIND_EOF
	} > "$boot/refind_ia32.conf"

	add_grub_cfg_refind
}

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

	if [ "$EFI_BOOTLOADER" = grub-efi ]; then
		[ ! -f "$boot/grub.cfg" ] || [ -f "$boot_grub/grub.cfg" ] ||
			mv "$boot/grub.cfg" "$boot_grub"
		add_grub_cfg
	fi

	# put EFI shell into the FAT image's root (for firmware)...
	if [ -f "$efi/$shell" ]; then
		mv $verbose "$efi/$shell" .
		mcopy $verbose "$shell" -i "$img" ::
	fi

	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

case "$EFI_BOOTLOADER" in
	elilo)
		copy_elilo
		;;
	refind)
		copy_refind
		;;
	grub-efi)
		copy_grub_primary
		;;
	*)
		fatal "Unable to handle '$EFI_BOOTLOADER'."
		;;
esac

if [ "$EFI_BOOTLOADER" = grub-efi ]; then
	[ -d "$boot_grub" ] || mkdir -p "$boot_grub"
	[ -f "$efi/$img" ] || write_efiboot_img
	copy_kernel
	copy_grub_secondary
else
	copy_kernel
	copy_shell
	copy_mt86
	[ -f "$efi/$img" ] || write_efiboot_img
fi

# use ISO9660 hardlinks support if possible
hardlink $verbose -c "$efi"

# ...and finally into its ISO9660 location for refind
if [ -f "$shell" ]; then
	mkdir $verbose -p "$tools"
	mv $verbose "$shell" "$tools"
fi

# eltorito copy is enough for flash as well, at least with refind-0.6.12.1
[ "$EFI_BOOTLOADER" = grub-efi ] || purge_mt86
