#!/bin/bash
#
# Generate a isolinux/bios and efi ISO boot image

set -e
PATH=/usr/bin:/bin:/usr/sbin:/sbin

OUT=$1
BIOS=$2
shift
shift
EFI=( "$@" )

dir=$(mktemp -d $(dirname $OUT)/iso.dir.XXXXXX)
cfg=${dir}/isolinux.cfg

b=$(basename ${BIOS})
g=${b%.lkrn}
g=${g//[^a-z0-9]}
g=${g:0:8}.krn
# generate the config reproducibly
cat > ${cfg} <<EOF
# These default options can be changed in the geniso script
SAY iPXE ISO boot image
TIMEOUT 0
DEFAULT ${b}
LABEL ${b}
 KERNEL ${g}
EOF
touch --date="@$SOURCE_DATE_EPOCH" ${cfg}

# copy BIOS boot file reproducibly
cp ${BIOS} ${dir}/${g}
touch --date="@$SOURCE_DATE_EPOCH" ${dir}/${g}

# copy isolinux bootloader reproducibly
cp -p /usr/lib/ISOLINUX/isolinux.bin ${dir}
cp -p /usr/lib/syslinux/modules/bios/ldlinux.c32 ${dir}

# generate EFI boot image reproducibly
blocks=$((((0 $(stat -c "+%s" "${EFI[@]}")) / 512 + 55 + 1) / 32 * 32 ))
# use the first 32 bit of SOURCE_DATE_EPOCH as volume_id
volume_id=$(printf "%x" $(($SOURCE_DATE_EPOCH & 0xffffffff)))
mkfs.msdos -i $volume_id -C ${dir}/efi.img $blocks >/dev/null
# mtools is also SOURCE_DATE_EPOCH aware, but we need to set the timezone to UTC
# to make it reproducible
LC_ALL=C TZ=UTC mmd -i ${dir}/efi.img ::/efi
LC_ALL=C TZ=UTC mmd -i ${dir}/efi.img ::/efi/boot
# sort the EFI files to make the image reproducible
readarray -d '' -t EFI < <(printf '%s\0' "${EFI[@]}" | LC_ALL=C sort -z)
for efi in "${EFI[@]}"; do
    arch=$(basename -- "$(dirname -- "$efi")")
	case $arch in
		bin-x86_64-efi) efi_boot="bootx64.efi" ;;
		bin-arm64-efi) efi_boot="bootaa64.efi" ;;
		bin-riscv64-efi) efi_boot="bootriscv64.efi" ;;
		bin-loong64-efi) efi_boot="bootloongarch64.efi" ;;
		*) echo "Unknown architecture $arch"; exit 1 ;;
	esac
	LC_ALL=C TZ=UTC mcopy -v -i ${dir}/efi.img "$efi" "::/efi/boot/${efi_boot}"
done

touch --date="@$SOURCE_DATE_EPOCH" ${dir}/efi.img

# make top directory reproducible
touch --date="@$SOURCE_DATE_EPOCH" ${dir}

# generate the iso image
xorriso -as mkisofs \
	-r -J -preparer "iPXE build system" \
	-appid "iPXE ${VERSION} - Open Source Network Boot Firmware" \
	-publisher "https://ipxe.org/" \
	-b isolinux.bin -c boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table \
	-isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin \
	-eltorito-alt-boot --efi-boot efi.img -no-emul-boot \
	-output ${OUT} ${dir}

# reproducibly create mbr_id from volume_id toggling its first bit
mbr_id=$(printf "%x" $((0x$volume_id ^ 1)))
echo "$mbr_id" | xxd -r -p | dd of=${OUT} bs=1 seek=440 conv=notrunc status=none

# clean up temporary dir
rm -fr ${dir}
