blob: 611d3350af7de8213f96c2f0d7d917fd7120b81b [file]
#!/bin/sh
#
# Generate a filesystem-based image
set -e
set -u
# Print usage message
#
help() {
echo "usage: ${0} [OPTIONS] foo.lkrn|foo.efi [bar.lkrn|bar.efi,...]"
echo
echo "where OPTIONS are:"
echo " -h show this help"
echo " -e SHIM specify an EFI shim helper"
echo " -o FILE save image to file"
echo " -p PAD pad filesystem (in kB)"
echo " -s SCRIPT use executable script"
}
# Get hex byte from binary file
#
get_byte() {
local FILENAME
local OFFSET
FILENAME="${1}"
OFFSET="${2}"
od -j "${OFFSET}" -N 1 -A n -t x1 -- "${FILENAME}" | tr -d " "
}
# Get hex word from binary file
#
get_word() {
local FILENAME
local OFFSET
local LSB
local MSB
FILENAME="${1}"
OFFSET="${2}"
LSB=$(get_byte "${FILENAME}" $(( ${OFFSET} + 0 )) )
MSB=$(get_byte "${FILENAME}" $(( ${OFFSET} + 1 )) )
echo "${MSB}${LSB}"
}
# Get appropriate EFI boot filename portion for CPU architecture
#
efi_boot_arch() {
local FILENAME
local MZSIG
local PEOFF
local PESIG
local ARCH
FILENAME="${1}"
MZSIG=$(get_word "${FILENAME}" 0)
if [ "${MZSIG}" != "5a4d" ] ; then
echo "${FILENAME}: invalid MZ header" >&2
exit 1
fi
PEOFF=$(get_byte "${FILENAME}" 0x3c)
PESIG=$(get_word "${FILENAME}" 0x${PEOFF})
if [ "${PESIG}" != "4550" ] ; then
echo "${FILENAME}: invalid PE header" >&2
exit 1
fi
ARCH=$(get_word "${FILENAME}" $(( 0x${PEOFF} + 4 )) )
case "${ARCH}" in
"014c" )
echo "IA32"
;;
"8664" )
echo "X64"
;;
"01c2" )
echo "ARM"
;;
"6264" )
echo "LOONGARCH64"
;;
"aa64" )
echo "AA64"
;;
"5064" )
echo "RISCV64"
;;
"5032" )
echo "RISCV32"
;;
* )
echo "${FILENAME}: unrecognised EFI architecture ${ARCH}" >&2
exit 1
esac
}
# Check if binary wants a log partition
#
wants_disklog() {
local FILENAME
local OEMID
local OEMINFO
local FLAG
FILENAME="${1}"
OEMID=$(get_word "${FILENAME}" 0x24)
OEMINFO=$(get_word "${FILENAME}" 0x26)
FLAG=$(( OEMINFO & 0x0001 ))
[ "${OEMID}" = "18ae" -a "${FLAG}" -ne "0" ]
}
# Find syslinux file
#
find_syslinux_file() {
local FILENAME
local SRCDIR
FILENAME="${1}"
for SRCDIR in \
/usr/lib/syslinux \
/usr/lib/syslinux/bios \
/usr/lib/syslinux/mbr \
/usr/lib/syslinux/modules/bios \
/usr/share/syslinux \
/usr/share/syslinux/bios \
/usr/share/syslinux/mbr \
/usr/share/syslinux/modules/bios \
/usr/local/share/syslinux \
/usr/local/share/syslinux/bios \
/usr/local/share/syslinux/bios/core \
/usr/local/share/syslinux/bios/com32/elflink/ldlinux \
/usr/local/share/syslinux/mbr \
/usr/local/share/syslinux/modules/bios \
/usr/lib/ISOLINUX \
; do
if [ -e "${SRCDIR}/${FILENAME}" ] ; then
echo "${SRCDIR}/${FILENAME}"
return 0
fi
done
echo "${0}: could not find ${FILENAME}" >&2
return 1
}
# Copy syslinux file
#
copy_syslinux_file() {
local FILENAME
local DESTDIR
local SRCFILE
FILENAME="${1}"
DESTDIR="${2}"
SRCFILE=$(find_syslinux_file "${FILENAME}")
install -m 644 "${SRCFILE}" "${DESTDIR}/"
}
# Parse command-line options
#
OUTFILE=
PAD=0
SCRIPT=
SHIMAA64=
SHIMX64=
while getopts "he:o:p:s:" OPTION ; do
case "${OPTION}" in
h)
help
exit 0
;;
e)
SHIM="${OPTARG}"
SHIMARCH=$(efi_boot_arch "${SHIM}")
case "${SHIMARCH}" in
"AA64" )
SHIMAA64="${SHIM}"
;;
"X64" )
SHIMX64="${SHIM}"
;;
* )
echo "${SHIM}: unsupported shim architecture" >&2
exit 1
esac
;;
o)
OUTFILE="${OPTARG}"
;;
p)
PAD="${OPTARG}"
;;
s)
SCRIPT="${OPTARG}"
;;
*)
help
exit 1
;;
esac
done
if [ -z "${OUTFILE}" ]; then
echo "${0}: no output file given" >&2
help
exit 1
fi
shift $(( OPTIND - 1 ))
if [ $# -eq 0 ] ; then
echo "${0}: no input files given" >&2
help
exit 1
fi
# Create temporary working directory
#
WORKDIR=$(mktemp -d "${OUTFILE}.XXXXXX")
ISODIR="${WORKDIR}/iso"
FATDIR="${WORKDIR}/fat"
MTOOLSRC="${WORKDIR}/mtoolsrc"
mkdir -p "${ISODIR}" "${FATDIR}"
# Configure output
#
case "${OUTFILE}" in
*.iso)
ISOIMG="${OUTFILE}"
FATIMG="${ISODIR}/esp.img"
BIOSDIR="${ISODIR}"
SYSLINUXCFG="${ISODIR}/isolinux.cfg"
FATPART=
LOGPART=
;;
*.sdsk)
ISOIMG=
FATIMG="${OUTFILE}"
BIOSDIR="${FATDIR}"
SYSLINUXCFG="${FATDIR}/syslinux.cfg"
FATPART=
LOGPART=
;;
*)
ISOIMG=
FATIMG="${OUTFILE}"
BIOSDIR="${FATDIR}"
SYSLINUXCFG="${FATDIR}/syslinux.cfg"
FATPART="4"
LOGPART="3"
;;
esac
# Configure mtools
#
cat >"${MTOOLSRC}" <<EOF
drive F:
file="${FATIMG}"
${FATPART:+partition=}${FATPART}
drive L:
file="${FATIMG}"
${LOGPART:+partition=}${LOGPART}
EOF
export MTOOLSRC
# Copy files to temporary working directory
#
LKRN=
EFI=
DISKLOG=
for FILENAME ; do
case "${FILENAME}" in
*.lkrn)
DESTDIR="${BIOSDIR}"
DESTFILE=$(basename "${FILENAME}")
if [ -z "${LKRN}" ] ; then
echo "SAY iPXE boot image" > "${SYSLINUXCFG}"
echo "TIMEOUT 30" >> "${SYSLINUXCFG}"
echo "DEFAULT ${DESTFILE}" >> "${SYSLINUXCFG}"
if [ -n "${SCRIPT}" ] ; then
cp "${SCRIPT}" "${BIOSDIR}/autoexec.ipxe"
fi
fi
echo "LABEL ${DESTFILE}" >> "${SYSLINUXCFG}"
echo " KERNEL ${DESTFILE}" >> "${SYSLINUXCFG}"
if [ -n "${SCRIPT}" ] ; then
echo " APPEND initrd=autoexec.ipxe" >> "${SYSLINUXCFG}"
fi
LKRN=1
;;
*.efi)
DESTDIR="${FATDIR}/EFI/BOOT"
DESTARCH=$(efi_boot_arch "${FILENAME}")
case "${DESTARCH}" in
"AA64" )
DESTSHIM="${SHIMAA64}"
;;
"X64" )
DESTSHIM="${SHIMX64}"
;;
* )
DESTSHIM=
;;
esac
if [ -n "${DESTSHIM}" ] ; then
DESTFILE="IPXE.EFI"
else
DESTFILE="BOOT${DESTARCH}.EFI"
fi
if [ -z "${EFI}" ] ; then
mkdir -p "${DESTDIR}"
if [ -n "${SCRIPT}" ] ; then
cp "${SCRIPT}" "${FATDIR}/autoexec.ipxe"
fi
if [ -n "${SHIMAA64}" ] ; then
cp "${SHIMAA64}" "${DESTDIR}/BOOTAA64.EFI"
fi
if [ -n "${SHIMX64}" ] ; then
cp "${SHIMX64}" "${DESTDIR}/BOOTX64.EFI"
fi
fi
EFI=1
;;
*)
echo "${0}: unrecognised input filename ${FILENAME}" >&2
help
exit 1
;;
esac
if [ -e "${DESTDIR}/${DESTFILE}" ] ; then
echo "${0}: duplicate ${DESTFILE} from ${FILENAME}" >&2
exit 1
fi
cp "${FILENAME}" "${DESTDIR}/${DESTFILE}"
if wants_disklog "${FILENAME}" ; then
DISKLOG=1
fi
done
# Configure ISO image, if applicable
#
# Note that the BIOS boot files are required even for an EFI-only ISO,
# since isohybrid will refuse to work without them.
#
if [ -n "${ISOIMG}" ] ; then
ISOARGS="-J -R -l"
copy_syslinux_file "isolinux.bin" "${ISODIR}"
copy_syslinux_file "ldlinux.c32" "${ISODIR}" 2>/dev/null || true
ISOARGS="${ISOARGS} -no-emul-boot -eltorito-boot isolinux.bin"
ISOARGS="${ISOARGS} -boot-load-size 4 -boot-info-table"
if [ -n "${EFI}" ] ; then
ISOARGS="${ISOARGS} -eltorito-alt-boot -no-emul-boot -e esp.img"
else
FATIMG=
fi
if [ -n "${SOURCE_DATE_EPOCH:-}" ] ; then
DATE_FMT="+%Y%m%d%H%M%S00"
BUILD_DATE=$(date -u -d "@${SOURCE_DATE_EPOCH}" "${DATE_FMT}" \
2>/dev/null || \
date -u -r "${SOURCE_DATE_EPOCH}" "${DATE_FMT}" \
2>/dev/null || \
date -u "${DATE_FMT}")
ISOARGS="${ISOARGS} --set_all_file_dates ${BUILD_DATE}"
ISOARGS="${ISOARGS} --modification-date=${BUILD_DATE}"
fi
fi
# Create FAT filesystem image, if applicable
#
if [ -n "${FATIMG}" ] ; then
FATUSED=$(du -s -k "${FATDIR}" | cut -f1)
FATSIZE=$(( ( FATUSED + PAD + 256 ) * 2 ))
if [ -n "${FATPART}" -o "${FATSIZE}" -gt "2880" ] ; then
FATHEADS=64
FATSECTS=32
FATALIGN=$(( FATHEADS * FATSECTS ))
FATCYLS=$(( ( FATSIZE + FATALIGN - 1 ) / FATALIGN ))
FATSIZE=$(( FATCYLS * FATALIGN ))
FATCLUST=8
if [ "${FATSIZE}" -eq $(( FATCLUST * 4096 )) -o \
"${FATSIZE}" -eq $(( FATCLUST * 65536 )) ] ; then
# Avoid cluster counts close to the FAT12/FAT16 limits to
# work around syslinux bugs
FATCLUST=$(( FATCLUST * 2 ))
fi
FATARGS="-t ${FATCYLS} -h ${FATHEADS} -s ${FATSECTS} -c ${FATCLUST}"
else
FATSIZE=2880
FATARGS="-f 1440"
fi
if [ -n "${FATPART}" ] ; then
FATOFFS="${FATSECTS}"
FATMBR=$(find_syslinux_file "mbr.bin")
else
FATOFFS=0
fi
if [ -n "${SOURCE_DATE_EPOCH:-}" ] ; then
FATSERIAL=$(( SOURCE_DATE_EPOCH % 100000000 ))
FATARGS="${FATARGS} -N ${FATSERIAL}"
fi
if [ -n "${DISKLOG}" -a -n "${LOGPART}" ] ; then
LOGTYPE=0xe0
LOGCYLS=1
LOGSIZE=$(( LOGCYLS * FATALIGN ))
FATSIZE=$(( LOGSIZE + FATSIZE ))
LOGOFFS="${FATOFFS}"
FATOFFS="${LOGSIZE}"
fi
touch "${FATIMG}"
truncate -s 0 "${FATIMG}"
truncate -s $(( FATSIZE * 512 )) "${FATIMG}"
if [ -n "${FATPART}" ] ; then
dd if="${FATMBR}" of="${FATIMG}" conv=notrunc status=none
mpartition -c -I -t "${FATCYLS}" -h "${FATHEADS}" -s "${FATSECTS}" \
-b "${FATOFFS}" F:
mpartition -a F:
fi
if [ -n "${DISKLOG}" -a -n "${LOGPART}" ] ; then
mpartition -c -t "${LOGCYLS}" -h "${FATHEADS}" -s "${FATSECTS}" \
-b "${LOGOFFS}" -T "${LOGTYPE}" L:
printf "iPXE LOG\n\n" |
dd of="${FATIMG}" seek="${LOGOFFS}" conv=notrunc status=none
fi
mformat -v iPXE ${FATARGS} F:
mcopy -s "${FATDIR}"/* F:
if [ "${BIOSDIR}" = "${FATDIR}" ] ; then
syslinux --offset "$(( FATOFFS * 512 ))" "${FATIMG}"
fi
fi
# Create ISO filesystem image, if applicable
#
if [ -n "${ISOIMG}" ] ; then
MKISOFS=
MKISOFS_MISSING=
MKISOFS_NOTSUP=
NOISOHYBRID=
for CMD in genisoimage mkisofs xorrisofs ; do
if ! "${CMD}" --version >/dev/null 2>&1 ; then
MKISOFS_MISSING="${MKISOFS_MISSING} ${CMD}"
continue
fi
if ! "${CMD}" ${ISOARGS} --version "${ISODIR}" >/dev/null 2>&1 ; then
MKISOFS_NOTSUP="${MKISOFS_NOTSUP} ${CMD}"
continue
fi
MKISOFS="${CMD}"
break
done
if [ -z "${MKISOFS}" ] ; then
if [ -n "${MKISOFS_MISSING}" ] ; then
echo "${0}:${MKISOFS_MISSING}: not installed" >&2
fi
if [ -n "${MKISOFS_NOTSUP}" ] ; then
echo "${0}:${MKISOFS_NOTSUP}: cannot handle ${ISOARGS}" >&2
fi
echo "${0}: cannot find a suitable mkisofs or equivalent" >&2
exit 1
fi
if [ "${MKISOFS}" = "xorrisofs" ] ; then
ISOARGS="${ISOARGS} -isohybrid-gpt-basdat"
NOISOHYBRID=1
fi
"${MKISOFS}" -quiet -volid "iPXE" -preparer "iPXE build system" \
-appid "iPXE - Open Source Network Boot Firmware" \
-publisher "ipxe.org" -sysid "iPXE" -o "${ISOIMG}" \
${ISOARGS} "${ISODIR}"
if [ -z "${NOISOHYBRID}" ] && isohybrid --version >/dev/null 2>&1 ; then
ISOHYBRIDARGS=
if [ -n "${EFI}" ] ; then
ISOHYBRIDARGS="${ISOHYBRIDARGS} --uefi"
fi
if [ -n "${SOURCE_DATE_EPOCH:-}" ] ; then
ISOHYBRIDARGS="${ISOHYBRIDARGS} --id ${SOURCE_DATE_EPOCH}"
fi
isohybrid ${ISOHYBRIDARGS} "${ISOIMG}"
fi
fi
# Clean up temporary working directory
#
rm -rf "${WORKDIR}"