From 6f2a356ca10ab97ed257097996593d707eca9bd7 Mon Sep 17 00:00:00 2001
From: Helmut Grohne <helmut@subdivi.de>
Date: Sun, 6 Apr 2025 07:59:25 +0200
Subject: add a new family of wrappers for EFI based images

debefivm-create is based on mmdebstrap-autopkgtest-build-qemu, which is
is co-authored with Johannes Schauer Marin Rodrigues. Also thanks to
Jochen Sprickerhof for suggesting the --rootsize option for use in
Debusine.
---
 bin/debefivm-ukify | 329 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 329 insertions(+)
 create mode 100755 bin/debefivm-ukify

(limited to 'bin/debefivm-ukify')

diff --git a/bin/debefivm-ukify b/bin/debefivm-ukify
new file mode 100755
index 0000000..9267158
--- /dev/null
+++ b/bin/debefivm-ukify
@@ -0,0 +1,329 @@
+#!/bin/sh
+# SPDX-FileCopyrightText: 2023-2025 Helmut Grohne <helmut@subdivi.de>
+# SPDX-License-Identifier: MIT
+#
+
+: <<'POD2MAN'
+=head1 NAME
+
+debefivm-ukify - Create Unified Kernel Images for UEFI systems in a similar way to systemd's ukify
+
+=head1 SYNOPSIS
+
+B<debefivm-ukify> B<build> B<--linux>=F<vmlinuz> B<--output>=F<uki.efi> [options]
+
+=head1 DESCRIPTION
+
+B<debefivm-ukify> is a reimplementation of parts of B<systemd-ukify> and mainly meant for backports.
+Please prefer B<systemd>'s implementation.
+It only supports the B<build> subcommand, does not implement any functionality related to secure boot and lacks configuration file support.
+Other than that, it can be used to assemble a linux kernel image, an initrd and an EFI stub image from B<systemd> into an EFI-bootable Unified Kernel Image.
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<--deb-arch>=I<debian_architecture>
+
+Set the architecture of the supplied and produced artifacts using Debian's architecture nomenclature.
+See B<--efi-arch> for another supplying it using the EFI nomenclature instead.
+This option is not available on the B<systemd> implementation.
+
+=item B<--devicetree>=F<devicetree_blob>
+
+Add a device tree file as a dtb section to the generated UKI.
+There can be at most one device tree.
+
+=item B<--cmdline>=I<kernel_cmdline>
+
+Supply extra arguments to be appended to the Linux kernel command line during boot.
+Can be given given at most once.
+If the value starts with an B<@>, it is treated as a filename whose contents will be added.
+
+=item B<--efi-arch>=I<efi_architecture>
+
+Set the architecture of the supplied and produced artifacts using EFI's architecture nomenclature.
+See B<--deb-arch> for another supplying it using the Debian nomenclature instead.
+
+=item B<--initrd>=F<initrd>
+
+Supply an initrd image to be embedded.
+There can be at most one initrd.
+
+=item B<--linux>=F<vmlinuz>
+
+Supply a Linux kernel image to be embedded.
+This option must be given exactly once.
+
+=item B<--os-release>=I<release_information>
+
+Set the os-release description of in the UKI.
+Can be given given at most once.
+If the value starts with an B<@>, it is treated as a filename whose contents will be added.
+
+=item B<--output>=<uki.efi>
+
+Set the name of the resulting UKI image.
+This option must be given exactly once and the target file must not exist already.
+
+=item B<--section>=<name:value>
+
+Add an arbitrary ELF section to the resulting UKI.
+If the value starts with an B<@>, it is treated as a filename whose contents will be added.
+Most image components actually are such ELF sections.
+For instance B<--os-release>, adds a section named B<osrel>.
+
+=item B<--stub>=F<efi_stub>
+
+Supply the location of B<systemd>'s EFI stub image.
+Unlike the B<systemd> implementation, this option accepts a directory containing stubs named by architecture.
+By default, stubs will be looked up in B</usr/lib/systemd/boot/efi>.
+
+=item B<--verbose>
+
+Print diagnostic information during image conversion.
+This option is not available on the B<systemd> implementation.
+
+=back
+
+=head1 SEE ALSO
+
+    ukify(1)
+
+=cut
+POD2MAN
+
+set -eu
+
+die() {
+	echo "$*" >&2
+	exit 1
+}
+usage() {
+	die "usage: $0 [build] <--linux=|--initrd=|--stub=> [--cmdline=|--deb-arch=|--devicetree=|--efi-arch=|--os-release=|--output=|--section=|--verbose]"
+}
+usage_error() {
+	echo "error: $*" >&2
+	usage
+}
+
+DEBARCH=
+BOOTSTUB=/usr/lib/systemd/boot/efi
+OUTPUT=
+VERBOSE=
+
+TDIR=
+cleanup() {
+	if test -n "$TDIR"; then
+		rm -Rf "$TDIR"
+	fi
+}
+cleanup_abort() {
+	cleanup
+	echo aborted >&2
+	exit 2
+}
+trap cleanup EXIT
+trap cleanup_abort HUP INT TERM QUIT
+
+# The TDIR will contain one file per section. It can be a symbolic link or
+# regular file. The section name is the filename with a leading dot.
+TDIR=$(mktemp --directory --tmpdir mini-ukify.XXXXXXXXXX)
+
+add_section_content() {
+	test -e "$TDIR/$1" && die "section '$1' already defined"
+	printf "%s" "$2" > "$TDIR/$1"
+}
+add_section_file() {
+	test -e "$TDIR/$1" && die "section '$1' already defined"
+	test -f "$2" || die "file '$2' does not exist"
+	if test "${2#/}" = "$2"; then
+		ln --symbolic --relative "$2" "$TDIR/$1"
+	else
+		ln --symbolic "$2" "$TDIR/$1"
+	fi
+}
+add_section_either() {
+	if test "${2#@}" = "$2"; then
+		add_section_content "$1" "$2"
+	else
+		add_section_file "$1" "${2#@}"
+	fi
+}
+
+opt_cmdline() {
+	add_section_either cmdline "$1"
+}
+opt_deb_arch() {
+	case "$1" in
+		amd64)
+			EFIARCH=x64
+			GNU_TYPE=x86_64-linux-gnu
+		;;
+		arm64)
+			EFIARCH=aa64
+			GNU_TYPE=aarch64-linux-gnu
+		;;
+		armhf)
+			EFIARCH=arm
+			GNU_TYPE=arm-linux-gnueabihf
+		;;
+		i386)
+			EFIARCH=ia32
+			GNU_TYPE=i686-linux-gnu
+		;;
+		riscv64)
+			EFIARCH=riscv64
+			GNU_TYPE=riscv64-linux-gnu
+		;;
+		*)
+			die "unsupported Debian architecture: $1"
+		;;
+	esac
+	DEBARCH="$1"
+}
+opt_devicetree() {
+	add_section_file dtb "$1"
+}
+opt_efi_arch() {
+	case "$1" in
+		aa64) opt_deb_arch arm64 ;;
+		arm) opt_deb_arch armhf ;;
+		ia32) opt_deb_arch i386 ;;
+		x64) opt_deb_arch amd64 ;;
+		riscv64) opt_deb_arch "$1" ;;
+		*)
+			die "unsupported EFI architecture: $1"
+		;;
+	esac
+}
+opt_initrd() {
+	add_section_file initrd "$1"
+}
+opt_linux() {
+	add_section_file linux "$1"
+	test -z "$OUTPUT" && OUTPUT="$1.unsigned.efi"
+}
+opt_os_release() {
+	add_section_either osrel "$1"
+}
+opt_output() {
+	OUTPUT="$1"
+}
+opt_section() {
+	case "$1" in
+		*[!a-z]*:*)
+			die "invalid section name '${1%%:*}'"
+		;;
+		*:*)
+		;;
+		*)
+			die "missing section name separated by colon in '$1'"
+		;;
+	esac
+	add_section_either "${1%%:*}" "${1#*:}"
+}
+opt_stub() {
+	BOOTSTUB="$1"
+}
+opt_verbose() {
+	VERBOSE=yes
+}
+positional=1
+positional_1() {
+	test "$1" = "build" || usage_error "command not understood: '$1'"
+}
+positional_2() {
+	usage_error "too many positional arguments"
+}
+
+while test "$#" -gt 0; do
+	case "$1" in
+		--cmdline=*|--deb-arch=*|--devicetree=*|--efi-arch=*|--initrd=*|--linux=*|--os-release=*|--output=*|--section=*|--stub=*)
+			optname="${1%%=*}"
+			optname="${optname#--}"
+			test "${optname#*-}" = "$optname" ||
+				optname="${optname%%-*}_${optname#*-}"
+			"opt_$optname" "${1#*=}"
+		;;
+		--cmdline|--deb-arch|--devicetree|--efi-arch|--initrd|--linux|--os-release|--output|--section|--stub)
+			test "$#" -ge 2 || usage_error "missing argument for $1"
+			optname="${1#--}"
+			test "${optname#*-}" = "$optname" ||
+				optname="${optname%%-*}_${optname#*-}"
+			"opt_$optname" "$2"
+			shift
+		;;
+		--verbose)
+			"opt_${1#--}"
+		;;
+		--*)
+			usage_error "unrecognized option $1"
+		;;
+		*)
+			"positional_$positional" "$1"
+			positional=$((positional + 1))
+		;;
+	esac
+	shift
+done
+
+test "$positional" = 2 || usage_error "missing subcommand, only 'build' is supported"
+test -z "$DEBARCH" && opt_deb_arch "$(dpkg --print-architecture)"
+test -e "$TDIR/linux" || usage_error "missing --linux argument"
+test -e "$BOOTSTUB" || die "stub image or directory '$BOOTSTUB' does not exist"
+test -e "$OUTPUT" && die "output '$OUTPUT' already exists"
+
+test -d "$BOOTSTUB" && BOOTSTUB="$BOOTSTUB/linux$EFIARCH.efi.stub"
+test -f "$BOOTSTUB" || die "efi boot stub $BOOTSTUB not found"
+
+GNU_PREFIX="$GNU_TYPE-"
+test "$(dpkg-query --showformat='${db:Status-Status}' --show binutils-multiarch)" = installed &&
+	GNU_PREFIX=
+
+# Compute the next multiple of $2 greater than or equal to $1.
+align_size() {
+	echo "$((($1) + ($2) - 1 - (($1) + ($2) - 1) % ($2)))"
+}
+alignment=$("${GNU_PREFIX}objdump" --private-headers "$BOOTSTUB" | sed 's/^SectionAlignment\s\+\([0-9]\)/0x/;t;d')
+test -z "$alignment" &&
+	die "failed to discover the alignment of the efi stub"
+test -n "$VERBOSE" &&
+	echo "determined efi vma alignment as $alignment"
+
+# Discover the last section in terms of vma + size. We'll append sections
+# beyond.
+lastoffset=0
+# shellcheck disable=SC2034  # unused variables serve documentation
+lastoffset="$("${GNU_PREFIX}objdump" --section-headers "$BOOTSTUB" \
+	| while read -r idx name size vma lma fileoff algn behind; do
+		test -z "$behind" -a "${algn#"2**"}" != "$algn" || continue
+		offset=$((0x$vma + 0x$size))
+		test "$offset" -gt "$lastoffset" || continue
+		lastoffset="$offset"
+		echo "$lastoffset"
+	done | tail -n1)"
+lastoffset=$(align_size "$lastoffset" "$alignment")
+test -n "$VERBOSE" &&
+	echo "determined minimum efi vma offset as $lastoffset"
+
+# Compute the objdump invocation that constructs the UKI. Successively add
+# sections and compute non-overlapping VMAs.
+set -- \
+	"${GNU_PREFIX}objcopy" \
+	--enable-deterministic-archives
+for sectionfile in "$TDIR/"*; do
+	section="${sectionfile##*/}"
+	size=$(stat -Lc%s "$sectionfile")
+	size=$(align_size "$size" "$alignment")
+	set -- "$@" \
+		--add-section ".$section=$sectionfile" \
+		--change-section-vma ".$section=$lastoffset"
+	lastoffset=$((lastoffset + size))
+done
+set -- "$@" "$BOOTSTUB" "$OUTPUT"
+if test -n "$VERBOSE"; then
+	ls -lA "$TDIR"
+	printf "%s\n" "$*"
+fi
+"$@" || die "failed to construct UKI"
-- 
cgit v1.2.3