#!/bin/ash -efu
#
# mki-copy-efiboot
#
# This file is part of mkimage
# Copyright (C) 2007-2009  Alexey Gladkov <gladkov.alexey@gmail.com>
# Copyright (C) 2012       Michael Shigorin <mike@altlinux.org>
#
# This file is covered by the GNU General Public License,
# which should be included with mkimage as the file COPYING.
#

. "${0%/*}"/mki-sh-functions

verbose "has started executing."

pkgs="${PACKAGES_REQUIRED_COPY_EFIBOOT:-}"

[ -d "$chroot" ] ||
	fatal "$dir: does not look like a hasher work directory."

# should be a package name
[ -n "${EFI_BOOTLOADER:-}" ] ||
	fatal "EFI_BOOTLOADER is empty."

pkgs="$pkgs $EFI_BOOTLOADER"

# should be a package name
[ -z "${EFI_SHELL:-}" ] ||
	pkgs="$pkgs $EFI_SHELL"

[ -z "${EFI_CERT:-}" ] ||
	pkgs="$pkgs alt-uefi-certs shim-signed shim-unsigned"

case "${EFI_BOOTLOADER:-}" in
refind)	# won't boot unsigned kernels in SB mode
	# elilo for x86_64 boot stack compatibility
	# grub-efi for ia32 EFI on x86_64
	pkgs="$pkgs elilo grub-efi"
	;;
grub-efi)
	pkgs="$pkgs grub-efi"
	;;
esac

[ -z "${EFI_MEMTEST86:-}" ] ||
	pkgs="$pkgs $EFI_MEMTEST86"

mki-install $pkgs ||
	fatal "failed to install packages: $pkgs."

make_exec "$chroot/.host/efiboot.sh" <<EOF
#!/bin/sh -efu${verbose:+x}

cd /.image

fatal() { echo $*; exit 1; }

stage2=
for i in altinst live rescue; do
	if [ -f "\$i" ]; then
		stage2="\${stage2:+\$stage2 }\$i"
	fi
done
[ -n "\${stage2:-}" ] ||
	fatal "no stage2 image found."

ia32efi_flag="present"
efi=EFI
boot=\$efi/BOOT
cert=\$efi/enroll
tools=\$efi/tools
shell=shellx64.efi
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" ] || return 0
	stage2_size="\$[ \$(stat -c%s \$1) / 1024 + 1 ]"
	echo "ramdisk_size=\$stage2_size stagename=\$1"
}

shim_teen_path() {
	# find out if shim version is not less than 15
	# looking at directory structure
	# ia32 binaries should be present since that version
	shim_dir_prefix="/usr/share/shim"
	if [ -d "\$shim_dir_prefix" ]; then
		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
	fi
}

ia32efi_stack_full() {
	# check if shim, refind and grub-efi include ia32 EFI binaries
	# if signed shim is absent try to fallback to unsigned one
	[ -f \$efi_bindir/shimia32.efi ] || shim_teen_path
	[ -f \$efi_bindir/refind_ia32.efi ] || ia32efi_flag="absent"
	[ -f \$efi_bindir/grubia32.efi ] || ia32efi_flag="absent"

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

}

efi_bindir=/usr/lib64/efi

copy_shell() {
	dest="\$efi/\$shell"
	src="\$efi_bindir/shell.efi"
	[ ! -f "\$src" ] ||
		cp $verbose -pLf "\$src" "\$dest"
}

copy_mt86() {
	dest="\$tools"
	mkdir $verbose -p "\$dest"
	src="\$efi_bindir/memtest86.efi"
	if [ -f "\$src" ]; then
		cp $verbose -pLf "\$src" "\$dest/memtest86.efi" &&
		find /usr/share/efi-memtest86 -type f |
			xargs cp $verbose -pLft "\$dest"
	fi
}

purge_mt86() {
	(
	cd "\$tools" ||
		return 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
	if [ "\$ia32efi_flag" = "present" ]; then
		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

	fi
}

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

	cp $verbose -lpLft \$boot -- syslinux/alt0/{vmlinuz,full.cz}
}

kargs="fastboot live lowmem showopts automatic=method:cdrom ${EFI_BOOTARGS:-}"

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

copy_grub() {

	cp $verbose -pLf \$efi_bindir/grubx64.efi \$boot/grubx64.efi
	cp $verbose -pLf \$efi_bindir/grubia32.efi \$boot/grubia32.efi
	[ -d \$boot/fonts ] || mkdir $verbose -p \$boot/fonts
	cp $verbose -arf /usr/lib64/grub/x86_64-efi \$boot/
	cp $verbose -arf /usr/lib64/grub/i386-efi \$boot/
	find \$boot/x86_64-efi -type f -name '*.module' -delete ||:
	find \$boot/i386-efi -type f -name '*.module' -delete ||:
	cp $verbose -Lf /boot/grub/fonts/unicode.pf2 \$boot/fonts/
	cp $verbose -Lf /boot/grub/unifont.pf2 \$boot/

	# add_grub_cfg
}


add_langs() {
	cat <<- REFIND_EOF
	  submenuentry "English" {
	    add_options "lang=en_US"
	  }
	  submenuentry "Kazakh" {
	    add_options "lang=kk_KZ"
	  }
	  submenuentry "Russian" {
	    add_options "lang=ru_RU"
	  }
	  submenuentry "Ukrainian" {
	    add_options "lang=uk_UA"
	  }
	REFIND_EOF
}

add_forensic_mode() {
	[ -f rescue ] || return 0
	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
}

add_stage2() {
	count="\`echo \$stage2 | wc -w\`"
	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"
		[ "\$count" -ne 1 -a -s "\$icon" ] ||
			icon="\$refind_icons/os_altlinux.\$icon_format"


		case "\$1" in
		x64)
			cat <<- REFIND_EOF

			# ELILO used as SB trampoline for unsigned kernels
			menuentry "ALT Linux \$label" {
			  icon /\$icon
			  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"
			REFIND_EOF
			[ "\$root" = rescue ] && add_forensic_mode || add_langs
			echo "}";;
		ia32)
			cat <<- REFIND_EOF

			# GRUB-EFI is used as SB trampoline for unsigned kernels
			# for ia32 EFI on x86_64 platforms
			  menuentry "ALT Linux \$label" {
			  icon /\$icon
			  loader /\$boot/grubia32_ldr.efi
			  options ""
			REFIND_EOF
			[ "\$root" = rescue ] && add_forensic_mode || add_langs
			echo "}";;
		*)
			fatal "Unknown architecture argument '\$1' in add_stage2()!";;
		esac
	done
}

gfxprefix=/usr/share/gfxboot

add_banner() {
	local bgfile="\$refind_icons/bg.png"
	[ -d \$gfxprefix ] || return 0
	type -t convert >&/dev/null || return 0
	bootlogo="\`find \$gfxprefix -name bootlogo | tail -1\`"
	[ -s "\$bootlogo" ] || return 0
	if [ -s "\$bgfile" ] || cpio -i --quiet --to-stdout back.jpg \
		< "\$bootlogo" | convert - \$bgfile; then
		echo "banner /\$bgfile"
	fi
}

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

refind_autodetect() {
	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"
}

refind_aux=\$efi/refind
refind_boot=\$refind_aux/refind_x64.efi
refind_bootia32=\$refind_aux/refind_ia32.efi
refind_icons=\$refind_aux/icons
if [ -f /usr/share/refind/icons/os_unknown.png ]; then
   icon_format=png
else
   icon_format=icns
fi
scan_icon=os_unknown.\$icon_format

# http://www.rodsbooks.com/refind/configfile.html
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"

copy_refind() {
	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

	if [ "\$ia32efi_flag" = "present" ]; then
		#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
	fi

	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_autodetect > \$refind_aux/refind.conf

	#if ia32 EFI bootloader stack is full
	if [ "\$ia32efi_flag" = "present" ]; then
		refind_autodetect > \$refind_aux/refind_ia32.conf
	fi

	# 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

		add_banner
		add_stage2 x64

		cat <<- REFIND_EOF

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

		REFIND_EOF

	} > \$boot/refind.conf


	#append ia32 EFI boot options only if all ia32 bootloader stack parts are available in current branch
	if [ "\$ia32efi_flag" = "present" ]; then
		# 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

			add_banner
			add_stage2 ia32

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

			REFIND_EOF
		} > \$boot/refind_ia32.conf
	fi

	#if ia32 EFI bootloader stack is full
	[ "\$ia32efi_flag" = "present" ] && add_grub_cfg || :
}

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

[ -z "${EFI_CERT:-}" ] ||
	copy_shim

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

copy_kernel	# seems to be unavoidable
copy_shell
copy_mt86

imgsize="\$[ \$(du -lsB32k \$efi | cut -f1) + 10 ]"
img=.efiboot.img

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

# dosfstools-4.0 has dropped those ancient symlinks, *sigh*
if type -t mkfs.fat >/dev/null; then
	mkfs=mkfs.fat
else
	mkfs=mkfs.vfat
fi
\$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

# 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 ::

# 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

mv \$img \$efi/

# eltorito copy is enough for flash as well, at least with refind-0.6.12.1
purge_mt86
EOF

rc=0
mki-run "/.host/efiboot.sh" || rc=$?
rm -f -- "$chroot/.host/efiboot.sh"
find "$chroot/.in" -mindepth 1 -maxdepth 1 -exec rm -rf -- '{}' '+'
exit $rc
