#!/bin/sh # Copyright 2023 Johannes Schauer Marin Rodrigues # Copyright 2023 Helmut Grohne # SPDX-License-Identifier: MIT # # This script is mostly compatible with autopkgtest-build-qemu as shipped in # autopkgtest. Main differences: # * It does not support any value for --boot but efi. # * It uses different tools, most importantly swaps out vmdb2. # * It can be run as non-root via user namespaces. # We generally use single quotes to avoid variable expansion: # shellcheck disable=SC2016 set -eu die() { echo "$*" 1>&2 exit 1 } usage() { die "usage: $0 [--boot=|--architecture=|--apt-proxy=|--keyring=|--mirror=|--script=|--size=] [MIRROR] [ARCHITECTURE] [SCRIPT] [SIZE]" } usage_error() { echo "error: $*" 1>&2 usage } BOOT=auto ARCHITECTURE=$(dpkg --print-architecture) IMAGE= MIRROR= KEYRING= RELEASE= SIZE=25G SCRIPT= # consumed by setup-testbed export AUTOPKGTEST_BUILD_QEMU=1 opt_boot() { BOOT="$1" } opt_architecture() { ARCHITECTURE="$1" } opt_apt_proxy() { # consumed by setup-testbed export AUTOPKGTEST_APT_PROXY="$1" # consumed by mmdebstrap if test "$1" = DIRECT; then unset http_proxy else export http_proxy="$1" fi } opt_keyring() { KEYRING="$1" } opt_mirror() { # consumed by setup-testbed export MIRROR="$1" } opt_script() { SCRIPT="$1" } opt_size() { SIZE="$1" } positional=1 positional_1() { # consumed by setup-testbed export RELEASE="$1" } positional_2() { IMAGE="$1" } positional_3() { opt_mirror "$@"; } positional_4() { opt_architecture "$@"; } positional_5() { opt_script "$@"; } positional_6() { opt_size "$@"; } positional_7() { die "too many positional options" } while test "$#" -gt 0; do case "$1" in --architecture=*|--boot=*|--keyring=*|--mirror=*|--script=*|--size=*) optname="${1%%=*}" "opt_${optname#--}" "${1#*=}" ;; --apt-proxy=*) opt_apt_proxy "${1#*=}" ;; --architecture|--boot|--keyring|--mirror|--script|--size) test "$#" -ge 2 || usage_error "missing argument for $1" "opt_${1#--}" "$2" shift ;; --apt-proxy) test "$#" -ge 2 || usage_error "missing argument for $1" opt_apt_proxy "$2" shift ;; --efi) opt_boot efi ;; --*) usage_error "unrecognized argument $1" ;; *) "positional_$positional" "$1" positional=$((positional + 1)) ;; esac shift done test -z "$RELEASE" -o -z "$IMAGE" && usage_error "missing positional arguments" test "$BOOT" = efi || die "this tool does not support boot modes other than efi" case "$ARCHITECTURE" in amd64) EFIIMG=bootx64.efi ;; arm64) EFIIMG=bootaa64.efi ;; armhf) EFIIMG=bootarm.efi ;; i386) EFIIMG=bootia32.efi ;; riscv64) EFIIMG=bootriscv64.efi ;; *) die "unsupported architecture" ;; esac GNU_ARCHITECTURE="$(dpkg-architecture "-a$ARCHITECTURE" -qDEB_HOST_GNU_TYPE)" for pkg in autopkgtest "binutils-$(echo "$GNU_ARCHITECTURE" | tr _ -)" dosfstools e2fsprogs fdisk mount mtools passwd "systemd-boot-efi:$ARCHITECTURE" uidmap; do test "$(dpkg-query -f '${db:Status-Status}' -W "$pkg")" = installed || die "please install $pkg" done BOOTSTUB="/usr/lib/systemd/boot/efi/linux${EFIIMG#boot}.stub" WORKDIR= cleanup() { test -n "$WORKDIR" && rm -Rf "$WORKDIR" } trap cleanup EXIT INT TERM QUIT WORKDIR=$(mktemp -d) FAT_OFFSET_SECTORS=$((1024*2)) FAT_SIZE_SECTORS=$((1024*254)) # Make the image writeable to the first subgid. mmdebstrap will map this gid to # the root group. unshare instead will map the current gid to 0 and the first # subgid to 1. Therefore mmdebstrap will be able to write to the image. rm -f "$IMAGE" : >"$IMAGE" unshare -U -r --map-groups=auto chown 0:1 "$IMAGE" chmod 0660 "$IMAGE" set -- \ --mode=unshare \ --variant=important \ --architecture="$ARCHITECTURE" test "$RELEASE" = jessie && set -- "$@" --hook-dir=/usr/share/mmdebstrap/hooks/jessie-or-older set -- "$@" \ "--include=init,linux-image-$ARCHITECTURE,python3" \ '--customize-hook=echo autopkgtestvm >"$1/etc/hostname"' \ '--customize-hook=echo 127.0.0.1 localhost autopkgtestvm >"$1/etc/hosts"' \ '--customize-hook=passwd --root "$1" --delete root' \ '--customize-hook=useradd --root "$1" --home-dir /home/user --create-home user' \ '--customize-hook=passwd --root "$1" --delete user' \ '--customize-hook=/usr/share/autopkgtest/setup-commands/setup-testbed' test -n "$SCRIPT" && set -- "$@" "--customize-hook=$SCRIPT" EXT4_OFFSET_BYTES=$(( (FAT_OFFSET_SECTORS + FAT_SIZE_SECTORS) * 512)) EXT4_OPTIONS="offset=$EXT4_OFFSET_BYTES,assume_storage_prezeroed=1" set -- "$@" \ "--customize-hook=download vmlinuz '$WORKDIR/kernel'" \ "--customize-hook=download initrd.img '$WORKDIR/initrd'" \ '--customize-hook=mount --bind "$1" "$1/mnt"' \ '--customize-hook=mount --bind "$1/mnt/mnt" "$1/mnt/dev"' \ '--customize-hook=/sbin/mkfs.ext4 -d "$1/mnt" -L autopkgtestvm -E '"'$EXT4_OPTIONS' '$IMAGE' '$SIZE'" \ '--customize-hook=umount --lazy "$1/mnt"' \ "$RELEASE" \ /dev/null test -n "$MIRROR" && set -- "$@" "$MIRROR" test -n "$KEYRING" && set -- "$@" "--keyring=$KEYRING" echo "mmdebstrap $*" mmdebstrap "$@" || die "mmdebstrap failed" unshare -U -r --map-groups=auto chown 0:0 "$IMAGE" chmod "$(printf %o "$(( 0666 - 0$(umask) ))")" "$IMAGE" echo "root=LABEL=autopkgtestvm rw console=ttyS0" > "$WORKDIR/cmdline" align_size() { echo "$(( ($1) + ($2) - 1 - (($1) + ($2) - 1) % ($2) ))" } alignment=$("$GNU_ARCHITECTURE-objdump" -p "$BOOTSTUB" | sed 's/^SectionAlignment\s\+\([0-9]\)/0x/;t;d') test -z "$alignment" && die "failed to discover the alignment of the efi stub" echo "determined efi vma alignment as $alignment" test "$RELEASE" = jessie -a "$((alignment))" -lt "$((1024*1024))" && { echo "increasing efi vma alignment for jessie" alignment=$((1024*1024)) } lastoffset=0 # shellcheck disable=SC2034 # unused variables serve documentation lastoffset="$("$GNU_ARCHITECTURE-objdump" -h "$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") echo "determined minimum efi vma offset as $lastoffset" cmdline_size="$(stat -Lc%s "$WORKDIR/cmdline")" cmdline_size="$(align_size "$cmdline_size" "$alignment")" linux_size="$(stat -Lc%s "$WORKDIR/kernel")" linux_size="$(align_size "$linux_size" "$alignment")" cmdline_offset="$lastoffset" linux_offset=$((cmdline_offset + cmdline_size)) initrd_offset=$((linux_offset + linux_size)) SOURCE_DATE_EPOCH=0 \ "$GNU_ARCHITECTURE-objcopy" \ --enable-deterministic-archives \ --add-section .cmdline="$WORKDIR/cmdline" \ --change-section-vma .cmdline="$(printf 0x%x "$cmdline_offset")" \ --add-section .linux="$WORKDIR/kernel" \ --change-section-vma .linux="$(printf 0x%x "$linux_offset")" \ --add-section .initrd="$WORKDIR/initrd" \ --change-section-vma .initrd="$(printf 0x%x "$initrd_offset")" \ "$BOOTSTUB" "$WORKDIR/efiimg" rm -f "$WORKDIR/kernel" "$WORKDIR/initrd" truncate -s "$((FAT_SIZE_SECTORS * 512))" "$WORKDIR/fat" /sbin/mkfs.fat -F 32 --invariant "$WORKDIR/fat" mmd -i "$WORKDIR/fat" EFI EFI/BOOT mcopy -i "$WORKDIR/fat" "$WORKDIR/efiimg" "::EFI/BOOT/$EFIIMG" rm -f "$WORKDIR/efiimg" truncate --size="+$((34*512))" "$IMAGE" /sbin/sfdisk "$IMAGE" <