#!/bin/bash
#
# This file is subject to the terms and conditions of the GNU General Public
# License.  See the file "COPYING" in the main directory of this archive
# for more details.
#
# Copyright (C) 2017 by Changbin Du <changbin.du@intel.com>
#
# Adapted from code in arch/x86/boot/Makefile by H. Peter Anvin and others
#
# "make fdimage/fdimage144/fdimage288/hdimage/isoimage"
# script for x86 architecture
#
# Arguments:
#   $1  - fdimage format
#   $2  - target image file
#   $3  - kernel bzImage file
#   $4  - mtools configuration file
#   $5  - kernel cmdline
#   $6+ - initrd image file(s)
#
# This script requires:
#   bash
#   syslinux
#   mtools (for fdimage* and hdimage)
#   edk2/OVMF (for hdimage)
#
# Otherwise try to stick to POSIX shell commands...
#

# Use "make V=1" to debug this script
case "${KBUILD_VERBOSE}" in
*1*)
        set -x
        ;;
esac

# Exit the top-level shell with an error
topshell=$$
trap 'exit 1' USR1
die() {
	echo ""        1>&2
	echo " *** $*" 1>&2
	echo ""        1>&2
	kill -USR1 $topshell
}

# Verify the existence and readability of a file
verify() {
	if [ ! -f "$1" -o ! -r "$1" ]; then
		die "Missing file: $1"
	fi
}

diskfmt="$1"
FIMAGE="$2"
FBZIMAGE="$3"
MTOOLSRC="$4"
KCMDLINE="$5"
shift 5				# Remaining arguments = initrd files

export MTOOLSRC

# common options for dd
dd='dd iflag=fullblock'

# Make sure the files actually exist
verify "$FBZIMAGE"

declare -a FDINITRDS
irdpfx=' initrd='
initrdopts_syslinux=''
initrdopts_efi=''
for f in "$@"; do
	if [ -f "$f" -a -r "$f" ]; then
	    FDINITRDS=("${FDINITRDS[@]}" "$f")
	    fname="$(basename "$f")"
	    initrdopts_syslinux="${initrdopts_syslinux}${irdpfx}${fname}"
	    irdpfx=,
	    initrdopts_efi="${initrdopts_efi} initrd=${fname}"
	fi
done

# Read a $3-byte littleendian unsigned value at offset $2 from file $1
le() {
	local n=0
	local m=1
	for b in $(od -A n -v -j $2 -N $3 -t u1 "$1"); do
		n=$((n + b*m))
		m=$((m * 256))
	done
	echo $n
}

# Get the EFI architecture name such that boot{name}.efi is the default
# boot file name. Returns false with no output if the file is not an
# EFI image or otherwise unknown.
efiarch() {
	[ -f "$1" ] || return
	[ $(le "$1" 0 2) -eq 23117 ] || return		# MZ magic
	peoffs=$(le "$1" 60 4)				# PE header offset
	[ $peoffs -ge 64 ] || return
	[ $(le "$1" $peoffs 4) -eq 17744 ] || return	# PE magic
	case $(le "$1" $((peoffs+4+20)) 2) in		# PE type
		267)	;;				# PE32
		523)	;;				# PE32+
		*) return 1 ;;				# Invalid
	esac
	[ $(le "$1" $((peoffs+4+20+68)) 2) -eq 10 ] || return # EFI app
	case $(le "$1" $((peoffs+4)) 2) in		# Machine type
		 332)	echo i386	;;
		 450)	echo arm	;;
		 512)	echo ia64	;;
		20530)	echo riscv32	;;
		20580)	echo riscv64	;;
		20776)	echo riscv128	;;
		34404)	echo x64	;;
		43620)	echo aa64	;;
	esac
}

# Get the combined sizes in bytes of the files given, counting sparse
# files as full length, and padding each file to a 4K block size
filesizes() {
	local t=0
	local s
	for s in $(ls -lnL "$@" 2>/dev/null | awk '/^-/{ print $5; }'); do
		t=$((t + ((s+4095)/4096)*4096))
	done
	echo $t
}

# Expand directory names which should be in /usr/share into a list
# of possible alternatives
sharedirs() {
	local dir file
	for dir in /usr/share /usr/lib64 /usr/lib; do
		for file; do
			echo "$dir/$file"
			echo "$dir/${file^^}"
		done
	done
}
efidirs() {
	local dir file
	for dir in /usr/share /boot /usr/lib64 /usr/lib; do
		for file; do
			echo "$dir/$file"
			echo "$dir/${file^^}"
		done
	done
}

findsyslinux() {
	local f="$(find -L $(sharedirs syslinux isolinux) \
		    -name "$1" -readable -type f -print -quit 2>/dev/null)"
	if [ ! -f "$f" ]; then
		die "Need a $1 file, please install syslinux/isolinux."
	fi
	echo "$f"
	return 0
}

findovmf() {
	local arch="$1"
	shift
	local -a names=(-false)
	local name f
	for name; do
		names=("${names[@]}" -or -iname "$name")
	done
	for f in $(find -L $(efidirs edk2 ovmf) \
			\( "${names[@]}" \) -readable -type f \
			-print 2>/dev/null); do
		if [ "$(efiarch "$f")" = "$arch" ]; then
			echo "$f"
			return 0
		fi
	done
	die "Need a $1 file for $arch, please install EDK2/OVMF."
}

do_mcopy() {
	if [ ${#FDINITRDS[@]} -gt 0 ]; then
		mcopy "${FDINITRDS[@]}" "$1"
	fi
	if [ -n "$efishell" ]; then
		mmd "$1"EFI "$1"EFI/Boot
		mcopy "$efishell" "$1"EFI/Boot/boot${kefiarch}.efi
	fi
	if [ -n "$kefiarch" ]; then
		echo linux "$KCMDLINE$initrdopts_efi" | \
			mcopy - "$1"startup.nsh
	fi
	echo default linux "$KCMDLINE$initrdopts_syslinux" | \
		mcopy - "$1"syslinux.cfg
	mcopy "$FBZIMAGE" "$1"linux
}

genbzdisk() {
	verify "$MTOOLSRC"
	mformat -v 'LINUX_BOOT' a:
	syslinux "$FIMAGE"
	do_mcopy a:
}

genfdimage144() {
	verify "$MTOOLSRC"
	$dd if=/dev/zero of="$FIMAGE" bs=1024 count=1440 2>/dev/null
	mformat -v 'LINUX_BOOT' v:
	syslinux "$FIMAGE"
	do_mcopy v:
}

genfdimage288() {
	verify "$MTOOLSRC"
	$dd if=/dev/zero of="$FIMAGE" bs=1024 count=2880 2>/dev/null
	mformat -v 'LINUX_BOOT' w:
	syslinux "$FIMAGE"
	do_mcopy w:
}

genhdimage() {
	verify "$MTOOLSRC"
	mbr="$(findsyslinux mbr.bin)"
	kefiarch="$(efiarch "$FBZIMAGE")"
	if [ -n "$kefiarch" ]; then
		# The efishell provides command line handling
		efishell="$(findovmf $kefiarch shell.efi shell${kefiarch}.efi)"
		ptype='-T 0xef'	# EFI system partition, no GPT
	fi
	sizes=$(filesizes "$FBZIMAGE" "${FDINITRDS[@]}" "$efishell")
	# Allow 1% + 1 MiB for filesystem and partition table overhead,
	# syslinux, and config files
	megs=$(((sizes + sizes/100 + 2*1024*1024 - 1)/(1024*1024)))
	$dd if=/dev/zero of="$FIMAGE" bs=$((1024*1024)) count=$megs 2>/dev/null
	mpartition -I -c -s 32 -h 64 -t $megs $ptype -b 512 -a h:
	$dd if="$mbr" of="$FIMAGE" bs=440 count=1 conv=notrunc 2>/dev/null
	mformat -v 'LINUX_BOOT' -s 32 -h 64 -t $megs h:
	syslinux --offset $((512*512)) "$FIMAGE"
	do_mcopy h:
}

geniso() {
	tmp_dir="$(dirname "$FIMAGE")/isoimage"
	rm -rf "$tmp_dir"
	mkdir "$tmp_dir"
	isolinux=$(findsyslinux isolinux.bin)
	ldlinux=$(findsyslinux  ldlinux.c32)
	cp "$isolinux" "$ldlinux" "$tmp_dir"
	cp "$FBZIMAGE" "$tmp_dir"/linux
	echo default linux "$KCMDLINE" > "$tmp_dir"/isolinux.cfg
	cp "${FDINITRDS[@]}" "$tmp_dir"/
	genisoimage -J -r -appid 'LINUX_BOOT' -input-charset=utf-8 \
		    -quiet -o "$FIMAGE" -b isolinux.bin \
		    -c boot.cat -no-emul-boot -boot-load-size 4 \
		    -boot-info-table "$tmp_dir"
	isohybrid "$FIMAGE" 2>/dev/null || true
	rm -rf "$tmp_dir"
}

rm -f "$FIMAGE"

case "$diskfmt" in
	bzdisk)     genbzdisk;;
	fdimage144) genfdimage144;;
	fdimage288) genfdimage288;;
	hdimage)    genhdimage;;
	isoimage)   geniso;;
	*)          die "Unknown image format: $diskfmt";;
esac
