| #!/bin/sh | |
| # | |
| # Generate a network bootable directory image | |
| set -e | |
| set -u | |
| # Print usage message | |
| # | |
| help() { | |
| echo "usage: ${0} [OPTIONS] foo.[k]pxe|foo.efi [bar.[k]pxe|bar.efi,...]" | |
| echo | |
| echo "where OPTIONS are:" | |
| echo " -h show this help" | |
| echo " -a ARCH select default CPU architecture [x86_64]" | |
| echo " -d DIR install images to directory" | |
| echo " -e SHIM specify an EFI shim helper" | |
| echo " -o FILE save image archive to file" | |
| } | |
| # 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 hex dword from binary file | |
| # | |
| get_dword() { | |
| local FILENAME | |
| local OFFSET | |
| local LSW | |
| local MSW | |
| FILENAME="${1}" | |
| OFFSET="${2}" | |
| LSW=$(get_word "${FILENAME}" $(( ${OFFSET} + 0 )) ) | |
| MSW=$(get_word "${FILENAME}" $(( ${OFFSET} + 2 )) ) | |
| echo "${MSW}${LSW}" | |
| } | |
| # Get appropriate subdirectory name for CPU architecture from EFI binary | |
| # | |
| efi_subdir_name() { | |
| local FILENAME | |
| local MZSIG | |
| local PEOFF | |
| local PESIG | |
| local ARCH | |
| local OPTSIG | |
| local SECSIZE | |
| local SBSUFFIX | |
| 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 )) ) | |
| OPTSIG=$(get_word "${FILENAME}" $(( 0x${PEOFF} + 24 )) ) | |
| case "${OPTSIG}" in | |
| "010b" ) | |
| SECSIZE=$(get_dword "${FILENAME}" $(( 0x${PEOFF} + 156 )) ) | |
| ;; | |
| "020b" ) | |
| SECSIZE=$(get_dword "${FILENAME}" $(( 0x${PEOFF} + 172 )) ) | |
| ;; | |
| * ) | |
| echo "${FILENAME}: unrecognised optional header ${OPTSIG}" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| if [ "${SECSIZE}" != "00000000" ] ; then | |
| SBSUFFIX="-sb" | |
| else | |
| SBSUFFIX="" | |
| fi | |
| case "${ARCH}" in | |
| "014c" ) | |
| echo "i386${SBSUFFIX}" | |
| ;; | |
| "8664" ) | |
| echo "x86_64${SBSUFFIX}" | |
| ;; | |
| "01c2" ) | |
| echo "arm32${SBSUFFIX}" | |
| ;; | |
| "6264" ) | |
| echo "loong64${SBSUFFIX}" | |
| ;; | |
| "aa64" ) | |
| echo "arm64${SBSUFFIX}" | |
| ;; | |
| "5064" ) | |
| echo "riscv64${SBSUFFIX}" | |
| ;; | |
| "5032" ) | |
| echo "riscv32${SBSUFFIX}" | |
| ;; | |
| * ) | |
| echo "${FILENAME}: unrecognised EFI architecture ${ARCH}" >&2 | |
| exit 1 | |
| esac | |
| } | |
| # Get appropriate subdirectory name for CPU architecture from iPXE NBP | |
| # | |
| nbp_subdir_name() { | |
| local FILENAME | |
| local LJMP | |
| local SEGMENT | |
| local MAGIC | |
| local ARCH | |
| FILENAME="${1}" | |
| LJMP=$(get_byte "${FILENAME}" 0) | |
| if [ "${LJMP}" != "ea" ] ; then | |
| echo "${FILENAME}: invalid LJMP instruction" >&2 | |
| exit 1 | |
| fi | |
| SEGMENT=$(get_word "${FILENAME}" 3) | |
| if [ "${SEGMENT}" != "07c0" ] ; then | |
| echo "${FILENAME}: invalid LJMP segment" >&2 | |
| exit 1 | |
| fi | |
| MAGIC=$(get_word "${FILENAME}" 6) | |
| if [ "${MAGIC}" != "18ae" ] ; then | |
| echo "${FILENAME}: invalid iPXE magic" >&2 | |
| exit 1 | |
| fi | |
| ARCH=$(get_byte "${FILENAME}" 5) | |
| case "${ARCH}" in | |
| "32" ) | |
| echo "i386" | |
| ;; | |
| "64" ) | |
| echo "x86_64" | |
| ;; | |
| * ) | |
| echo "${FILENAME}: unrecognised NBP architecture ${ARCH}" >&2 | |
| exit 1 | |
| esac | |
| } | |
| # Get appropriate subdirectory name for CPU architecture | |
| # | |
| subdir_name() { | |
| local FILENAME | |
| local BYTE | |
| FILENAME="${1}" | |
| BYTE=$(get_byte "${FILENAME}" 0) | |
| case "${BYTE}" in | |
| "4d" ) | |
| efi_subdir_name "${FILENAME}" | |
| ;; | |
| "ea" ) | |
| nbp_subdir_name "${FILENAME}" | |
| ;; | |
| * ) | |
| echo "${FILENAME}: unrecognised format" >&2 | |
| exit 1 | |
| esac | |
| } | |
| # Parse command-line options | |
| # | |
| DEFARCH=x86_64 | |
| OUTDIR= | |
| OUTFILE= | |
| SHIMAA64= | |
| SHIMX64= | |
| while getopts "ha:d:e:o:" OPTION ; do | |
| case "${OPTION}" in | |
| h) | |
| help | |
| exit 0 | |
| ;; | |
| a) | |
| DEFARCH="${OPTARG}" | |
| ;; | |
| d) | |
| OUTDIR="${OPTARG}" | |
| ;; | |
| e) | |
| SHIM="${OPTARG}" | |
| SHIMARCH=$(subdir_name "${SHIM}") | |
| case "${SHIMARCH}" in | |
| arm64* ) | |
| SHIMAA64="${SHIM}" | |
| ;; | |
| x86_64* ) | |
| SHIMX64="${SHIM}" | |
| ;; | |
| * ) | |
| echo "${SHIM}: unsupported shim architecture" >&2 | |
| exit 1 | |
| esac | |
| ;; | |
| o) | |
| OUTFILE="${OPTARG}" | |
| ;; | |
| *) | |
| help | |
| exit 1 | |
| ;; | |
| esac | |
| done | |
| if [ -z "${OUTDIR}" -a -z "${OUTFILE}" ] ; then | |
| echo "${0}: no output directory or 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, if applicable | |
| # | |
| WORKDIR= | |
| if [ -z "${OUTDIR}" ] ; then | |
| WORKDIR=$(mktemp -d "${OUTFILE}.XXXXXX") | |
| OUTDIR="${WORKDIR}/ipxeboot" | |
| fi | |
| mkdir -p "${OUTDIR}" | |
| # Copy files to output directory | |
| # | |
| for FILENAME ; do | |
| SUBDIR=$(subdir_name "${FILENAME}") | |
| ARCH="${SUBDIR%-sb}" | |
| DESTDIR="${OUTDIR}/${SUBDIR}" | |
| BASENAME=$(basename "${FILENAME}") | |
| SHIMLINK="${BASENAME%.efi}-shim.efi" | |
| mkdir -p "${DESTDIR}" | |
| install -m 644 "${FILENAME}" "${DESTDIR}/${BASENAME}" | |
| case "${SUBDIR}" in | |
| arm64-sb ) | |
| if [ -n "${SHIMAA64}" ] ; then | |
| install -m 644 "${SHIMAA64}" "${DESTDIR}/shimaa64.efi" | |
| ln -sfn "shimaa64.efi" "${DESTDIR}/${SHIMLINK}" | |
| fi | |
| ;; | |
| x86_64-sb ) | |
| if [ -n "${SHIMX64}" ] ; then | |
| install -m 644 "${SHIMX64}" "${DESTDIR}/shimx64.efi" | |
| ln -sfn "shimx64.efi" "${DESTDIR}/${SHIMLINK}" | |
| fi | |
| ;; | |
| esac | |
| if [ "${ARCH}" = "${DEFARCH}" ] ; then | |
| if [ "${ARCH}" = "${SUBDIR}" ] ; then | |
| ln -sfn "${SUBDIR}/${BASENAME}" "${OUTDIR}/${BASENAME}" | |
| else | |
| ln -sfn "${SUBDIR}" "${OUTDIR}/sb" | |
| fi | |
| fi | |
| done | |
| # Create output archive file, if applicable | |
| # | |
| if [ -n "${OUTFILE}" ] ; then | |
| TOPDIR=$(dirname "${OUTDIR}") | |
| BASENAME=$(basename "${OUTDIR}") | |
| case "${OUTFILE}" in | |
| *.tar ) | |
| tar cf "${OUTFILE}" -C "${TOPDIR}" "${BASENAME}" | |
| ;; | |
| *.tar.gz | *.tgz ) | |
| tar czf "${OUTFILE}" -C "${TOPDIR}" "${BASENAME}" | |
| ;; | |
| *.tar.bz2 ) | |
| tar cjf "${OUTFILE}" -C "${TOPDIR}" "${BASENAME}" | |
| ;; | |
| *.tar.xz ) | |
| tar cJf "${OUTFILE}" -C "${TOPDIR}" "${BASENAME}" | |
| ;; | |
| * ) | |
| echo "${OUTFILE}: unrecognised archive format" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| fi | |
| # Clean up temporary working directory | |
| # | |
| if [ -n "${WORKDIR}" ] ; then | |
| rm -rf "${WORKDIR}" | |
| fi |