1#!/bin/sh 2# 3# boot-test.sh - Automated boot loader regression tests 4# 5# Builds a minimal bootable tree, assembles disk images for all supported boot 6# configurations, then runs each in QEMU with a timeout looking for "SUCCESS". 7# 8# All tests run as an unprivileged user. No root required, with one exception: 9# netboot-bios/netboot-efi need a real tap(4) device + dnsmasq (so DHCP can 10# carry a root-path) instead of QEMU's slirp networking, and both creating a tap 11# and giving it an address require root. That setup runs once (via sudo, 12# prompting interactively) and is left running -- later runs detect the existing 13# setup and skip sudo entirely. Assumes buildworld and buildkernel have already 14# been done for the target architecture. 15# 16# Usage: 17# cd /usr/src/stand && sh ../tools/boot/boot-test.sh [options] 18# 19# Options: 20# -a ARCH Architecture to test; repeat -a to test several 21# (default: host arch) 22# Supported: amd64, aarch64, armv7, riscv64, powerpc, powerpc64, 23# powerpc64le 24# -A Test every supported architecture 25# -b Skip build/install phase (reuse existing tree) 26# -B Skip build/install and image creation (reuse existing images) 27# -l Tell us the log directory and exit 28# -j JOBS Max parallel QEMU instances (default: unlimited) 29# -o DIR Output directory for images and logs 30# -t REGEX Only run tests matching REGEX 31# -T SECONDS QEMU timeout (default: per-arch, 60; 180 for powerpc64) 32# --netboot-teardown 33# Destroy the tap(4)/dnsmasq netboot setup (run with sudo) 34 35set -e 36 37die() { 38 echo "FATAL: $*" >&2 39 exit 1 40} 41 42# FreeBSD port package (origin) that provides a command or file; "" = base 43# system. 44pkg_for() { 45 case "$1" in 46 jq) echo textproc/jq ;; 47 expect) echo lang/expect ;; 48 qemu-system-*) echo emulators/qemu ;; 49 *ipxe*) echo sysutils/ipxe ;; 50 *syslinux*|*memdisk*) echo sysutils/syslinux ;; 51 *edk2*) echo emulators/qemu ;; # edk2-*.fd ship with qemu 52 *) echo "" ;; # makefs/mkimg etc. = base 53 esac 54} 55 56# Hard requirement: a command that must be in PATH, else abort naming the pkg. 57need_cmd() { 58 which "$1" >/dev/null 2>&1 && return 0 59 p=$(pkg_for "$1") 60 [ -n "${p}" ] && die "$1 not found; install with: pkg install ${p##*/}" 61 die "$1 not found (expected in the base system)" 62} 63 64# Optional requirement: a file for a feature; warn + skip (return 1) if absent. 65have_file() { 66 [ -e "$1" ] && return 0 67 p=$(pkg_for "$1") 68 echo " WARNING: $1 missing${p:+; pkg install ${p##*/}} -- skipping" >&2 69 return 1 70} 71 72# -------------------------------------------------------------------------- 73# Architecture configuration 74# 75# Per-arch parameters live in boot-test.json, as an arch object merged over a 76# "defaults" object. Arrays are expanded to a space separated list. Missing 77# values default to an empty string. ARCH_CAPS is a cached copy of the 78# capabilities array for the architecture. TARGET and TARGET_ARCH are cached due 79# to heavy use. Other parameters are fetched as needed as their use is 80# infrequent. 81# -------------------------------------------------------------------------- 82 83# Test whether the current arch declares a capability, e.g. "has efi". 84has() { 85 case " ${ARCH_CAPS} " in 86 *" $1 "*) return 0 ;; 87 esac 88 return 1 89} 90 91# Fetch the named parameter for this $ARCH. 92param() { 93 jq -r --arg a "${ARCH}" --arg k "$1" ' 94 (.defaults + .arch[$a])[$k] as $v 95 | if ($v | type) == "array" then $v | join(" ") 96 elif $v == null then "" 97 else $v end 98 ' "${CONF}" 99} 100 101# Validate ${ARCH} and cache the parameters that are heavily used in global 102# variables. 103load_arch_config() { 104 jq -e --arg a "${ARCH}" '.arch | has($a)' "${CONF}" >/dev/null 2>&1 \ 105 || die "Unknown architecture: ${ARCH}" 106 ARCH_CAPS=$(param caps) 107 TARGET=$(param target) 108 TARGET_ARCH=$(param target_arch) 109} 110 111# Derive every per-arch value for ${1} into the ARCH_*/TARGET/OUTDIR/... globals. 112# Callers isolate architectures via subshells (build) or job backgrounding (run) 113# -- both snapshot these globals -- so they never collide across arches, and we 114# never have to pass the whole bundle around. 115setup_arch_env() { 116 ARCH=$1 117 load_arch_config 118 TIMEOUT=${TIMEOUT_OVERRIDE:-$(param timeout)} 119 MK="make TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH}" 120 ARCH_OBJDIR=$(${MK} -v .OBJDIR) 121 [ -n "${ARCH_OBJDIR}" ] || die "Cannot determine OBJDIR for ${ARCH}" 122 OUTDIR=${OUTDIR_OVERRIDE:-${ARCH_OBJDIR}/boot-test} 123 IMGDIR=${OUTDIR}/images 124 LOGDIR=${OUTDIR}/logs 125 DESTDIR=${OUTDIR}/tree 126 TESTLIST=${OUTDIR}/test-list.txt 127 mkdir -p ${IMGDIR} ${LOGDIR} 128} 129 130# --- Configuration --- 131 132SKIP_BUILD=false 133SKIP_IMAGES=false 134TEST_FILTER="" 135MAX_JOBS=0 136OUTDIR_OVERRIDE="" # from -o; only valid with a single arch 137TIMEOUT_OVERRIDE="" # from -T; else each arch's param timeout 138ARCHES="" # from -a (repeatable) / -A; defaults to $(uname -p) 139ALL=false 140 141# State shared by tap(4)/dnsmasq netboot networking (see netboot_network_setup / 142# netboot_helper below). Key off user's ID to allow multiple people to 143# run the script at the same time. 144NETBOOT_STATE_DIR=${TMPDIR:-/tmp}/boot-test-net.${SUDO_UID:-$(id -u)} 145 146# --netboot-helper/--netboot-teardown are internal entry points used to run 147# privileged setup/teardown via sudo (see netboot_network_setup); they bypass 148# normal option parsing and are dispatched once every function is defined, in 149# the Main section at the bottom of this script. 150NETBOOT_MODE="" 151case "$1" in 152 --netboot-helper) NETBOOT_MODE=helper; NETBOOT_HELPER_PLAN=$2 ;; 153 --netboot-teardown) NETBOOT_MODE=teardown ;; 154esac 155 156do_report_dirs=false 157if [ -z "${NETBOOT_MODE}" ]; then 158 while getopts "a:AbBlj:o:t:T:" opt; do 159 case "$opt" in 160 a) ARCHES="${ARCHES} $OPTARG" ;; 161 A) ALL=true ;; 162 b) SKIP_BUILD=true ;; 163 B) SKIP_BUILD=true; SKIP_IMAGES=true ;; 164 j) MAX_JOBS="$OPTARG" ;; 165 l) do_report_dirs=true ;; 166 o) OUTDIR_OVERRIDE="$OPTARG" ;; 167 t) TEST_FILTER="$OPTARG" ;; 168 T) TIMEOUT_OVERRIDE="$OPTARG" ;; 169 ?) echo "Usage: $0 [-a arch]... | -A] [-b] [-B] [-t regex] [-T secs] [-j jobs] [-o dir]" >&2 170 exit 1 ;; 171 esac 172 done 173 174 # Resolve the config file next to this script before we cd elsewhere. 175 CONF="$(cd "$(dirname "$0")" && pwd)/boot-test.json" 176 [ -f "${CONF}" ] || die "Config file not found: ${CONF}" 177 178 SRCTOP=$(make -v SRCTOP) || die "Run from stand/ directory in a FreeBSD source tree" 179 cd ${SRCTOP}/stand 180 181 # Build the architecture list from json. Partially supported architectures 182 # are omitted from -A, but accessible with a direct -a. 183 if ${ALL}; then 184 for a in $(jq -r '.arch | keys_unsorted[]' "${CONF}"); do 185 if [ $(jq ".arch.${a}.disabled" "${CONF}") != "true" ]; then 186 ARCHES="$ARCHES $a" 187 fi 188 done 189 fi 190 [ -n "${ARCHES}" ] || ARCHES=$(uname -p) 191 192 # -o names one output directory, so it only makes sense for a single arch. 193 if [ -n "${OUTDIR_OVERRIDE}" ] && [ $(echo ${ARCHES} | wc -w) -gt 1 ]; then 194 die "-o cannot be combined with multiple architectures" 195 fi 196fi 197 198# The smallest FAT32 filesystem is 33292 KB 199espsize=33292 200 201# Linux kernel version for linuxboot tests 202LINUX_VERSION=6.18.2 203 204# -------------------------------------------------------------------------- 205# QEMU command builders 206# 207# qemu_base wraps the constant bits -- binary, memory, machine, per-arch extra 208# flags, and the -nographic/serial tail -- around the device arguments each 209# specific builder passes in (disks, CDs, firmware, bios). 210# -------------------------------------------------------------------------- 211 212qemu_base() { 213 echo "$(param qemu_bin) -m 1g $(param qemu_machine) $(param qemu_extra) $* -nographic -monitor none -serial stdio" 214} 215 216# -drive for the EFI firmware pflash; empty when the arch has no separate 217# firmware (e.g. riscv64's u-boot payload). 218qemu_efi_firmware() { 219 [ -z "$(param efi_firmware)" ] && return 0 220 echo "-drive file=$(param efi_firmware),format=raw,if=pflash,readonly=on" 221} 222 223# Custom OpenBIOS cached beside the ISOs, if present (QEMU's bundled one is 224# missing fixes we need for now); empty otherwise. 225qemu_ofw_bios() { 226 [ -f "${ISODIR}/openbios-ppc" ] || return 0 227 echo "-bios ${ISODIR}/openbios-ppc" 228} 229 230qemu_bios() { qemu_base "-drive file=$1,format=raw"; } 231qemu_efi() { qemu_base "$(qemu_efi_firmware) -drive file=$1,format=raw"; } 232qemu_bios_cdrom() { qemu_base "-cdrom $1"; } 233qemu_efi_cdrom() { qemu_base "$(qemu_efi_firmware) -cdrom $1"; } 234 235# OFW disk on the default (macio IDE) bus: OpenBIOS aliases it "hd" and 236# auto-probes hd:,\\:tbxi for the Apple_Bootstrap (boot1.hfs) partition. 237# virtio disks are not reachable as "hd", so OF finds nothing and drops to "0 >". 238qemu_ofw() { qemu_base "$(qemu_ofw_bios) -drive file=$1,format=raw"; } 239# -boot d boots the (macio IDE) CD-ROM, which OpenBIOS probes cd:,\\:tbxi. 240qemu_ofw_cdrom() { qemu_base "$(qemu_ofw_bios) -boot d -cdrom $1"; } 241 242# pseries PReP disk boot: virtio-blk (vtbd0), matching freebsd-ci; SLOF finds 243# the PReP boot partition on it and runs boot1.elf. 244qemu_prep() { qemu_base "-drive if=none,file=$1,format=raw,id=hd0 -device virtio-blk,drive=hd0"; } 245# pseries CD boot: SLOF boots the El Torito CHRP image; -boot d selects the CD. 246qemu_prep_cdrom() { qemu_base "-cdrom $1 -boot d"; } 247 248# Direct linuxboot: hand the Linux kernel and initrd to QEMU on the command 249# line (-kernel/-initrd) rather than off an ESP. Used by platforms with no 250# EFI/ESP (e.g. powerpc64le/pseries); the FreeBSD root disk is attached the 251# same way as every other test. 252qemu_linuxboot() { 253 extra="-kernel $2 -initrd $3" 254 [ -n "$(param linux_console)" ] && extra="${extra} -append $(param linux_console)" 255 qemu_base "${extra} -drive file=$1,format=raw" 256} 257 258# Netboot over $1 -- vmnet(4): dnsmasq owns DHCP/TFTP/root-path, which we use so 259# we use tftp, not NFS, for all the files. QEMU's netdev type is still "tap" (it 260# treats vmnet(4) and tap(4) identically). $2 = bios|efi (efi adds the pflash 261# firmware; bios relies on the NIC's PXE option ROM). 262qemu_netboot() { 263 netif=$1 264 fw="" 265 [ "$2" = efi ] && fw="$(qemu_efi_firmware)" 266 qemu_base "${fw} -netdev tap,id=net0,ifname=${netif},script=no,downscript=no -device $(param netboot_nic),netdev=net0 -boot n" 267} 268 269# RAM-disk netboot (x86 EFI). Boots iPXE from -hda (edk2's own PXE is disabled 270# via fw_cfg); iPXE DHCPs, fetches the bootfile (an iPXE script) over TFTP, and 271# chains loader.efi with memdisk=<url>. The loader downloads that image and 272# boots it entirely from RAM -- no NFS and no DHCP root-path needed. Mirrors 273# ~/memdisk/do-memddisk-efi. ${OUTDIR}/netboot-vars.fd is a writable edk2 vars 274# copy made by assemble_netboot. 275# 276# We'll need to to http boots in the future, and that will likely require 277# we don't use the ipxe USB path we use here. We use that because Tianocore 278# expects http/https booting when the obvious '-boot n' sort of things 279# are used. 280qemu_netboot_ramdisk() { 281 netif=$1 282 echo "$(param qemu_bin) -M q35 -cpu max -m 2g \ 283 -drive if=pflash,format=raw,readonly=on,file=$(param efi_firmware) \ 284 -drive if=pflash,format=raw,file=${OUTDIR}/netboot-vars.fd \ 285 -hda ${OUTDIR}/netboot-ipxe.img \ 286 -device virtio-net,netdev=net0 \ 287 -netdev tap,id=net0,ifname=${netif},script=no,downscript=no \ 288 -fw_cfg name=opt/org.tianocore/IPv4PXESupport,string=no \ 289 -fw_cfg name=opt/org.tianocore/IPv6PXESupport,string=no \ 290 -nographic -monitor none -serial stdio" 291} 292 293# -------------------------------------------------------------------------- 294# Phase 0: Extract minimal userland from release ISO 295# -------------------------------------------------------------------------- 296 297# Binaries needed for a minimal bootable userland. 298# Libraries are inferred from these via ldd on the host equivalents. 299USERLAND_BINS="sbin/fastboot sbin/halt sbin/init bin/sh sbin/sysctl" 300 301ISODIR=${HOME}/iso 302 303find_iso() { 304 local isotgt=${TARGET} 305 [ ${TARGET} != ${TARGET_ARCH} ] && isotgt="${isotgt}-${TARGET_ARCH}" 306 local isoname="${ISODIR}/FreeBSD-$(param freebsd_version)-RELEASE-${isotgt}-disc1.iso.xz" 307 [ -f "${isoname}" ] && echo "${isoname}" && return 308 die "No ISO found for ${ARCH}: ${isoname} not found" 309} 310 311install_minimal_userland() { 312 # Split the declaration from the assignment: `local iso=$(find_iso)` would 313 # swallow find_iso's exit status (local returns 0), so its die() -- which 314 # runs in the $() subshell -- would not stop this script. 315 local iso 316 iso=$(find_iso) || exit 1 317 echo " Extracting minimal userland from release ISO ${iso}..." 318 319 # Determine library paths from host equivalents of our binaries 320 local host_bins="" 321 for b in ${USERLAND_BINS}; do 322 host_bins="${host_bins} /${b}" 323 done 324 lib_paths=$(ldd ${host_bins} 2>/dev/null \ 325 | awk 'NF == 4 { print $3 }' | sort -u) 326 327 # Build the list of paths to extract: binaries + libraries + rtld 328 local extract_list="" 329 for b in ${USERLAND_BINS}; do 330 extract_list="${extract_list} ./${b}" 331 done 332 for l in ${lib_paths}; do 333 extract_list="${extract_list} .${l}" 334 done 335 extract_list="${extract_list} ./libexec/ld-elf.so.1" 336 337 # Some architectures (e.g., armv7) need libgcc_s.so.1 even though the host 338 # binaries don't. Always try to extract it. We'll ignore it if not there. 339 extract_list="${extract_list} ./lib/libgcc_s.so.1" 340 341 # Extract the files from the tarball. 342 tar -C ${DESTDIR} -xf ${iso} ${extract_list} \ 343 >> ${LOGDIR}/installuserland.log 2>&1 || true 344} 345 346# -------------------------------------------------------------------------- 347# Phase 1: Build the boot tree 348# -------------------------------------------------------------------------- 349 350build_tree() { 351 echo "=== Phase 1: Building boot tree ===" 352 353 rm -rf ${DESTDIR} 354 mkdir -p ${DESTDIR}/boot/defaults 355 mkdir -p ${DESTDIR}/boot/kernel 356 mkdir -p ${DESTDIR}/boot/uboot 357 mkdir -p ${DESTDIR}/sbin ${DESTDIR}/bin \ 358 ${DESTDIR}/lib ${DESTDIR}/libexec \ 359 ${DESTDIR}/etc ${DESTDIR}/dev 360 361 # Install kernel 362 # I'd prefer this to be MINIMAL, but GENERIC is needed until I work out what 363 # different devices we boot from... 364 (cd ${SRCTOP} && ${MK} installkernel \ 365 KERNCONF=$(param kernconf) \ 366 MODULES_OVERRIDE="ufs zfs acl_nfs4 crypto zlib cd9660" \ 367 DESTDIR=${DESTDIR} \ 368 MK_KERNEL_SYMBOLS=no \ 369 MK_INSTALL_AS_USER=yes) > ${LOGDIR}/installkernel.log 2>&1 \ 370 || die "Kernel install failed (see ${LOGDIR}/installkernel.log)" 371 372 # Install boot loaders 373 ${MK} buildenv \ 374 DESTDIR=${DESTDIR} \ 375 MK_MAN=no \ 376 MK_INSTALL_AS_USER=yes \ 377 MK_DEBUG_FILES=no \ 378 BUILDENV_SHELL="make all install" \ 379 >> ${LOGDIR}/installloader.log 2>&1 \ 380 || die "Boot loader install failed (see ${LOGDIR}/installloader.log)" 381 382 # Install minimal userland (works for both native and cross builds) 383 install_minimal_userland 384 385 # Remove default loader symlinks -- we add them back per-image via 386 # mtree overlays to test each loader variant individually. 387 # /boot/loader is the BIOS stage-3 (amd64 only). 388 # /boot/loader.efi is what boot1.efi chainloads (all EFI platforms). 389 # OFW/PReP are the exception: their /boot/loader is the one real loader 390 # (no lua/4th/simp variants are built), chainloaded by boot1.hfs (mac99) 391 # or boot1.elf (pseries PReP), so keep it in the tree. 392 has ofw || has prep || rm -f ${DESTDIR}/boot/loader 393 rm -f ${DESTDIR}/boot/loader.efi 394 395 # Serial console configuration. boot.config is consumed only by the 396 # BIOS boot blocks; its -h/-D/-S flags are meaningless on EFI/OFW, so 397 # only write it where BIOS booting is supported. 398 if has bios; then 399 echo -h -D -S115200 > ${DESTDIR}/boot.config 400 fi 401 # Unified loader.conf: always load ufs, zfs, and cd9660 402 cat > ${DESTDIR}/boot/loader.conf <<EOF 403boot_serial=YES 404comconsole_speed=115200 405autoboot_delay=1 406ufs_load="YES" 407zfs_load="YES" 408cd9660_load="YES" 409EOF 410 local hints=$(param hints) 411 if [ -n "${hints}" ] && [ -f "${SRCTOP}/${hints}" ]; then 412 cp "${SRCTOP}/${hints}" ${DESTDIR}/boot/device.hints 413 fi 414 415 # Test /etc/rc - prints success and halts 416 cat > ${DESTDIR}/etc/rc <<'RCEOF' 417#!/bin/sh 418 419sysctl machdep.bootmethod 420echo "RC COMMAND RUNNING -- SUCCESS!!!!!" 421halt -p 422RCEOF 423 chmod +x ${DESTDIR}/etc/rc 424 425 # Create fstab used by UFS mtree overlays 426 cat > ${OUTDIR}/fstab.ufs <<EOF 427/dev/ufs/root / ufs rw 1 1 428EOF 429 # Create fstab used by CD mtree overlays 430 cat > ${OUTDIR}/fstab.cd <<EOF 431/dev/iso9660/FBSDTEST / cd9660 ro 0 0 432EOF 433 434 echo "Boot tree built in ${DESTDIR}" 435} 436 437# -------------------------------------------------------------------------- 438# Phase 2: Create base filesystem images 439# 440# Uses mtree overlays to vary the /boot/loader and /etc/fstab 441# without copying the tree. The base tree has no /boot/loader or 442# /etc/fstab; each image adds what it needs via an mtree spec file 443# passed as a second source to makefs. 444# -------------------------------------------------------------------------- 445 446# Create a UFS image with a specific loader variant via mtree overlay 447make_one_ufs() { 448 variant=$1 449 img=${IMGDIR}/bootable-ufs-${variant}.img 450 mt=$(mktemp ${OUTDIR}/ufs-mtree.XXXXXX) 451 452 echo " Creating UFS image with loader_${variant}..." 453 echo "./etc/fstab type=file mode=0644 contents=${OUTDIR}/fstab.ufs" > ${mt} 454 # BIOS: /boot/loader -> loader_<variant> 455 if [ -n "$(param bios_loaders)" ]; then 456 echo "./boot/loader type=file mode=0644 contents=${DESTDIR}/boot/loader_${variant}" >> ${mt} 457 fi 458 # OFW: boot1.hfs chainloads the single /boot/loader already in the 459 # tree (no per-variant overlay needed). 460 # EFI: /boot/loader.efi -> loader_<variant>.efi (needed for boot1.efi chainload) 461 if has efi; then 462 echo "./boot/loader.efi type=file mode=0755 contents=${DESTDIR}/boot/loader_${variant}.efi" >> ${mt} 463 fi 464 makefs -t ffs -B $(param byte_order) -M 10m -o label=root -o version=2 \ 465 ${img} ${mt} ${DESTDIR} >> ${LOGDIR}/imagebuild.log 2>&1 466 rm -f ${mt} 467} 468 469# Create a ZFS image with a specific loader variant via mtree overlay 470make_one_zfs() { 471 variant=$1 472 img=${IMGDIR}/bootable-zfs-${variant}.img 473 mt=$(mktemp ${OUTDIR}/zfs-mtree.XXXXXX) 474 475 echo " Creating ZFS image with loader_${variant}..." 476 > ${mt} 477 # BIOS: /boot/loader -> loader_<variant> 478 if [ -n "$(param bios_loaders)" ]; then 479 echo "./boot/loader type=link link=loader_${variant}" >> ${mt} 480 fi 481 # OFW: boot1.hfs chainloads the single /boot/loader already in the 482 # tree (no per-variant overlay needed). 483 # EFI: /boot/loader.efi -> loader_<variant>.efi (needed for boot1.efi chainload) 484 if has efi; then 485 echo "./boot/loader.efi type=file mode=0755 contents=${DESTDIR}/boot/loader_${variant}.efi" >> ${mt} 486 fi 487 makefs -t zfs -s 100m \ 488 -o poolname=ztestroot -o bootfs=ztestroot -o rootpath=/ \ 489 ${img} ${mt} ${DESTDIR} >> ${LOGDIR}/imagebuild.log 2>&1 490 rm -f ${mt} 491} 492 493# Create an ESP with the given EFI loader using an mtree spec 494make_one_esp() { 495 loader_name=$1 496 esp=${IMGDIR}/${loader_name}.esp 497 mt=$(mktemp ${OUTDIR}/esp-mtree.XXXXXX) 498 499 echo " Creating ESP with ${loader_name}.efi..." 500 cat > ${mt} <<EOF 501./efi type=dir uname=root gname=wheel mode=0755 502./efi/boot type=dir uname=root gname=wheel mode=0755 503./efi/boot/$(param efi_bootname).efi type=file uname=root gname=wheel mode=0755 contents=${DESTDIR}/boot/${loader_name}.efi 504EOF 505 makefs -t msdos \ 506 -o fat_type=32 \ 507 -o sectors_per_cluster=1 \ 508 -o volume_label=EFISYS \ 509 -s ${espsize}k \ 510 ${esp} ${mt} >> ${LOGDIR}/imagebuild.log 2>&1 511 rm -f ${mt} 512} 513 514# Create a small ESP for CD hybrid boot using an mtree spec 515make_cd_esp() { 516 file=$1 517 loader=$2 518 mt=$(mktemp ${OUTDIR}/cd-esp-mtree.XXXXXX) 519 520 cat > ${mt} <<EOF 521./efi type=dir uname=root gname=wheel mode=0755 522./efi/boot type=dir uname=root gname=wheel mode=0755 523./efi/boot/$(param efi_bootname).efi type=file uname=root gname=wheel mode=0755 contents=${loader} 524EOF 525 makefs -t msdos \ 526 -o fat_type=12 \ 527 -o sectors_per_cluster=1 \ 528 -o volume_label=EFISYS \ 529 -s 2048k \ 530 ${file} ${mt} >> ${LOGDIR}/imagebuild.log 2>&1 531 rm -f ${mt} 532} 533 534# Find the pre-built Linux kernel EFI binary for linuxboot 535find_linux_kernel() { 536 target_arch=$(${MK} -v TARGET_ARCH) 537 linuxboot_dir=${ARCH_OBJDIR}/../../linuxboot/data/output 538 539 # amd64 has .efi suffix, others don't 540 for f in \ 541 ${linuxboot_dir}/${target_arch}.linux.v${LINUX_VERSION}.efi \ 542 ${linuxboot_dir}/${target_arch}.linux.v${LINUX_VERSION} \ 543 ${linuxboot_dir}/${target_arch}.v${LINUX_VERSION}.efi \ 544 ${linuxboot_dir}/${target_arch}.v${LINUX_VERSION}; do 545 if [ -f "$f" ]; then 546 echo "$f" 547 return 548 fi 549 done 550 return 1 551} 552 553# Build a linuxboot initrd using tar --format newc with an mtree spec. 554# No root/sudo required -- device nodes are written directly into the 555# cpio archive via mtree type=char entries. 556make_linuxboot_initrd() { 557 initrd=${IMGDIR}/linuxboot-initrd.cpio.gz 558 mt=$(mktemp ${OUTDIR}/initrd-mtree.XXXXXX) 559 560 echo " Creating linuxboot initrd..." 561 562 # Build mtree spec for the initrd contents 563 cat > ${mt} <<EOF 564./init type=file mode=0755 contents=${DESTDIR}/boot/loader.kboot 565./dev type=dir mode=0755 566./dev/console type=char mode=0600 device=freebsd,5,0 567./dev/tty type=char mode=0600 device=freebsd,5,1 568./dev/ttyS0 type=char mode=0600 device=freebsd,4,64 569./boot type=dir mode=0755 570./boot/defaults type=dir mode=0755 571./boot/defaults/loader.conf type=file mode=0644 contents=${DESTDIR}/boot/defaults/loader.conf 572./boot/lua type=dir mode=0755 573EOF 574 575 # Add all lua files 576 for f in ${DESTDIR}/boot/lua/*.lua; do 577 [ -f "$f" ] || continue 578 bn=$(basename $f) 579 echo "./boot/lua/${bn} type=file mode=0644 contents=$f" >> ${mt} 580 done 581 582 # Add loader.help.kboot if present 583 if [ -f "${DESTDIR}/boot/loader.help.kboot" ]; then 584 echo "./boot/loader.help.kboot type=file mode=0644 contents=${DESTDIR}/boot/loader.help.kboot" >> ${mt} 585 fi 586 587 # Create the kboot-specific loader.conf 588 kboot_conf=$(mktemp ${OUTDIR}/kboot-loader-conf.XXXXXX) 589 cat > ${kboot_conf} <<EOF 590# Kboot configuration -- FreeBSD ${ARCH} 591boot_serial="YES" 592EOF 593 if [ "${ARCH}" = "amd64" ]; then 594 cat >> ${kboot_conf} <<EOF 595hw.uart.console="io:1016,br:115200" 596EOF 597 fi 598 echo "./boot/loader.conf type=file mode=0644 contents=${kboot_conf}" >> ${mt} 599 600 # Create the initrd as a gzip-compressed newc cpio archive 601 tar --format newc -cf - @${mt} 2>> ${LOGDIR}/imagebuild.log | \ 602 gzip > ${initrd} 603 604 rm -f ${mt} ${kboot_conf} 605} 606 607# Build a linuxboot ESP containing the Linux kernel, initrd, and startup.nsh 608make_linuxboot_esp() { 609 linux_kernel=$1 610 esp=${IMGDIR}/linuxboot.esp 611 mt=$(mktemp ${OUTDIR}/linuxboot-esp-mtree.XXXXXX) 612 613 echo " Creating linuxboot ESP..." 614 615 # Generate startup.nsh 616 startup=${OUTDIR}/startup.nsh 617 cat > ${startup} <<EOF 618\\linux.efi $(param linux_console) initrd=\\initrd 619EOF 620 621 cat > ${mt} <<EOF 622./startup.nsh type=file mode=0644 contents=${startup} 623./linux.efi type=file mode=0755 contents=${linux_kernel} 624./initrd type=file mode=0644 contents=${IMGDIR}/linuxboot-initrd.cpio.gz 625EOF 626 makefs -t msdos \ 627 -o fat_type=32 \ 628 -o sectors_per_cluster=1 \ 629 -o volume_label=EFISYS \ 630 -s 100m \ 631 ${esp} ${mt} >> ${LOGDIR}/imagebuild.log 2>&1 632 rm -f ${mt} 633} 634 635make_base_images() { 636 echo "=== Phase 2: Creating base filesystem images ===" 637 638 # UFS images - one per BIOS loader variant, plus one for EFI (uses lua) 639 if [ -n "$(param bios_loaders)" ]; then 640 for v in $(param bios_loaders); do 641 make_one_ufs $v 642 done 643 else 644 # EFI-only arches still need one UFS image 645 make_one_ufs lua 646 fi 647 648 # ZFS images (if supported) 649 if has zfs; then 650 if [ -n "$(param bios_loaders)" ]; then 651 for v in $(param bios_loaders); do 652 make_one_zfs $v 653 done 654 else 655 make_one_zfs lua 656 fi 657 fi 658 659 # ESP images (if EFI is supported) 660 if has efi; then 661 for l in $(param efi_loaders); do 662 make_one_esp $l 663 done 664 fi 665 666 # Linuxboot initrd + ESP (if supported and Linux kernel is available) 667 if has linuxboot; then 668 linux_kernel=$(find_linux_kernel) || true 669 if [ -n "${linux_kernel}" ]; then 670 make_linuxboot_initrd 671 # EFI arches chainload the kernel+initrd off an ESP; platforms 672 # without EFI hand them to QEMU directly (-kernel/-initrd), so 673 # they need no ESP. 674 has efi && make_linuxboot_esp ${linux_kernel} 675 else 676 echo " WARNING: Linux kernel not found for linuxboot, skipping" 677 echo " Expected in: ${ARCH_OBJDIR}/../../linuxboot/data/output/" 678 fi 679 fi 680 681 echo "Base images created in ${IMGDIR}" 682} 683 684# -------------------------------------------------------------------------- 685# Phase 3: Assemble disk images and register tests 686# -------------------------------------------------------------------------- 687 688# Test registration - uses temp files since /bin/sh doesn't have arrays. 689# ${TESTLIST} and ${OUTDIR} are set per-arch by setup_arch_env; the list is 690# truncated in build_all before each arch's images are (re)assembled. 691register_test() { 692 name=$1 693 shift 694 echo "$*" > ${OUTDIR}/test-cmd-${name}.sh 695 echo "${name}" >> ${TESTLIST} 696} 697 698# Like register_test, but for tests that need a real tap(4) interface 699# (assigned later by netboot_network_setup, once every arch is built) instead 700# of QEMU's slirp net. The command is written out now with a placeholder tap 701# name; netboot_network_setup patches it in once the tap is assigned. 702# ${NETBOOT_PLAN} accumulates across every arch (unlike ${TESTLIST}, it is not 703# truncated per-arch), so it must already exist by the time build_all runs -- 704# see Main. $5 optionally names a different command builder than the default 705# qemu_netboot (e.g. qemu_netboot_ramdisk) -- it's always called as 706# "builder __NETBOOT_TAP__ ${fw}", so a non-default builder that doesn't need 707# ${fw} just ignores its second arg. 708register_netboot_test() { 709 name=$1 710 tftpdir=$2 711 bootfile=$3 712 fw=$4 713 builder=${5:-qemu_netboot} 714 cmdfile=${OUTDIR}/test-cmd-${name}.sh 715 echo "$(${builder} __NETBOOT_TAP__ ${fw})" > ${cmdfile} 716 echo "${name}" >> ${TESTLIST} 717 echo "${cmdfile} ${tftpdir} ${bootfile}" >> ${NETBOOT_PLAN} 718} 719 720assemble_efi_gpt() { 721 echo " Assembling EFI+GPT images..." 722 for loader in $(param efi_loaders); do 723 esp=${IMGDIR}/${loader}.esp 724 fstypes="ufs" 725 has zfs && fstypes="ufs zfs" 726 for fs in ${fstypes}; do 727 name="efi-gpt-${fs}-${loader}" 728 img=${IMGDIR}/${name}.img 729 730 # For EFI, the stage-3 in the filesystem doesn't matter, use lua variant 731 case ${fs} in 732 ufs) fsimg=${IMGDIR}/bootable-ufs-lua.img; ptype="freebsd-ufs" ;; 733 zfs) fsimg=${IMGDIR}/bootable-zfs-lua.img; ptype="freebsd-zfs" ;; 734 esac 735 736 mkimg -s gpt \ 737 -p efi:=${esp} \ 738 -p ${ptype}:=${fsimg} \ 739 -o ${img} >> ${LOGDIR}/imagebuild.log 2>&1 740 741 register_test ${name} $(qemu_efi ${img}) 742 done 743 done 744} 745 746assemble_efi_mbr() { 747 echo " Assembling EFI+MBR images..." 748 for loader in $(param efi_loaders); do 749 esp=${IMGDIR}/${loader}.esp 750 name="efi-mbr-ufs-${loader}" 751 img=${IMGDIR}/${name}.img 752 ufs=${IMGDIR}/bootable-ufs-lua.img 753 754 mkimg -s bsd -p freebsd-ufs:=${ufs} -o ${img}.s2 >> ${LOGDIR}/imagebuild.log 2>&1 755 mkimg -a 1 -s mbr -p efi:=${esp} -p freebsd:=${img}.s2 -o ${img} >> ${LOGDIR}/imagebuild.log 2>&1 756 rm -f ${img}.s2 757 758 register_test ${name} $(qemu_efi ${img}) 759 done 760} 761 762assemble_bios_gpt() { 763 echo " Assembling BIOS+GPT images..." 764 for variant in $(param bios_loaders); do 765 # UFS 766 name="bios-gpt-ufs-loader_${variant}" 767 img=${IMGDIR}/${name}.img 768 ufs=${IMGDIR}/bootable-ufs-${variant}.img 769 770 mkimg -s gpt -b ${DESTDIR}/boot/pmbr \ 771 -p freebsd-boot:=${DESTDIR}/boot/gptboot \ 772 -p freebsd-ufs:=${ufs} \ 773 -o ${img} >> ${LOGDIR}/imagebuild.log 2>&1 774 775 register_test ${name} $(qemu_bios ${img}) 776 777 # ZFS 778 if has zfs; then 779 name="bios-gpt-zfs-loader_${variant}" 780 img=${IMGDIR}/${name}.img 781 zfs=${IMGDIR}/bootable-zfs-${variant}.img 782 783 mkimg -s gpt -b ${DESTDIR}/boot/pmbr \ 784 -p freebsd-boot:=${DESTDIR}/boot/gptzfsboot \ 785 -p freebsd-zfs:=${zfs} \ 786 -o ${img} >> ${LOGDIR}/imagebuild.log 2>&1 787 788 register_test ${name} $(qemu_bios ${img}) 789 fi 790 done 791} 792 793assemble_bios_mbr() { 794 echo " Assembling BIOS+MBR images..." 795 for variant in $(param bios_loaders); do 796 name="bios-mbr-ufs-loader_${variant}" 797 img=${IMGDIR}/${name}.img 798 ufs=${IMGDIR}/bootable-ufs-${variant}.img 799 800 mkimg -s bsd -b ${DESTDIR}/boot/boot \ 801 -p freebsd-ufs:=${ufs} -o ${img}.s1 >> ${LOGDIR}/imagebuild.log 2>&1 802 # Note: boot0sio has a longish timeout, and does work but 803 # takes longer than 30s so we use mbr. 804 mkimg -a 1 -s mbr -b ${DESTDIR}/boot/mbr \ 805 -p freebsd:=${img}.s1 -o ${img} >> ${LOGDIR}/imagebuild.log 2>&1 806 rm -f ${img}.s1 807 808 register_test ${name} $(qemu_bios ${img}) 809 done 810} 811 812# Hybrid GPT images: ESP + freebsd-boot + filesystem, tested with both BIOS and EFI 813assemble_both_gpt() { 814 echo " Assembling hybrid BIOS+EFI GPT images..." 815 for l in $(param bios_loaders); do 816 loader="loader_${l}" 817 esp=${IMGDIR}/${loader}.esp 818 fstypes="ufs" 819 has zfs && fstypes="ufs zfs" 820 for fs in ${fstypes}; do 821 name_base="both-gpt-${fs}-${loader}" 822 img=${IMGDIR}/${name_base}.img 823 824 case ${fs} in 825 ufs) 826 fsimg=${IMGDIR}/bootable-ufs-lua.img 827 ptype="freebsd-ufs" 828 bootblk=${DESTDIR}/boot/gptboot 829 ;; 830 zfs) 831 fsimg=${IMGDIR}/bootable-zfs-lua.img 832 ptype="freebsd-zfs" 833 bootblk=${DESTDIR}/boot/gptzfsboot 834 ;; 835 esac 836 837 mkimg -b ${DESTDIR}/boot/pmbr -s gpt \ 838 -p efi:=${esp} \ 839 -p freebsd-boot:=${bootblk} \ 840 -p ${ptype}:=${fsimg} \ 841 -o ${img} >> ${LOGDIR}/imagebuild.log 2>&1 842 843 register_test "${name_base}-bios" $(qemu_bios ${img}) 844 register_test "${name_base}-efi" $(qemu_efi ${img}) 845 done 846 done 847} 848 849# Hybrid MBR images: ESP + freebsd(boot+ufs), tested with both BIOS and EFI 850assemble_both_mbr() { 851 echo " Assembling hybrid BIOS+EFI MBR images..." 852 for l in $(param bios_loaders); do 853 loader="loader_${l}" 854 esp=${IMGDIR}/${loader}.esp 855 name_base="both-mbr-ufs-${loader}" 856 img=${IMGDIR}/${name_base}.img 857 ufs=${IMGDIR}/bootable-ufs-lua.img 858 859 mkimg -s bsd -b ${DESTDIR}/boot/boot \ 860 -p freebsd-ufs:=${ufs} -o ${img}.s2 >> ${LOGDIR}/imagebuild.log 2>&1 861 mkimg -a 2 -s mbr -b ${DESTDIR}/boot/mbr \ 862 -p efi:=${esp} \ 863 -p freebsd:=${img}.s2 -o ${img} >> ${LOGDIR}/imagebuild.log 2>&1 864 rm -f ${img}.s2 865 866 register_test "${name_base}-bios" $(qemu_bios ${img}) 867 register_test "${name_base}-efi" $(qemu_efi ${img}) 868 done 869} 870 871assemble_cd() { 872 echo " Assembling CD images..." 873 874 # mtree to add loader and fstab to image 875 mt1=$(mktemp ${OUTDIR}/cd-mtree.XXXXXX) 876 variant=lua 877 cat > ${mt1} <<EOF 878./boot/loader type=file mode=0644 contents=${DESTDIR}/boot/loader_${variant} 879./etc/fstab type=file mode=0644 contents=${OUTDIR}/fstab.cd 880EOF 881 882 # cdboot - BIOS CD boot via El Torito 883 name="bios-cd-cdboot" 884 img=${IMGDIR}/${name}.iso 885 makefs -t cd9660 \ 886 -o bootimage=i386\;${DESTDIR}/boot/cdboot \ 887 -o no-emul-boot \ 888 -o rockridge \ 889 -o label=FBSDTEST \ 890 ${img} ${mt1} ${DESTDIR} >> ${LOGDIR}/imagebuild.log 2>&1 891 register_test ${name} $(qemu_bios_cdrom ${img}) 892 893 # isoboot - hybrid BIOS+EFI CD (tests isoboot for BIOS, loader.efi for EFI) 894 name="hybrid-cd-isoboot" 895 img=${IMGDIR}/${name}.iso 896 espfile=$(mktemp ${OUTDIR}/efiboot.XXXXXX) 897 make_cd_esp ${espfile} ${DESTDIR}/boot/loader_lua.efi 898 899 makefs -t cd9660 \ 900 -o bootimage=i386\;${DESTDIR}/boot/cdboot \ 901 -o no-emul-boot \ 902 -o bootimage=i386\;${espfile} \ 903 -o no-emul-boot \ 904 -o platformid=efi \ 905 -o rockridge \ 906 -o label=FBSDTEST \ 907 ${img} ${mt1} ${DESTDIR} >> ${LOGDIR}/imagebuild.log 2>&1 908 909 # Overlay hybrid GPT for isoboot 910 imgsize=$(stat -f %z "${img}") 911 912 # Find the EFI partition in the ISO to reference it 913 espstart="" 914 espsize_cd="" 915 for entry in $(etdump --format shell ${img}); do 916 eval ${entry} 917 if [ "${et_platform}" = "efi" ]; then 918 espstart=$(expr ${et_lba} \* 2048) 919 espsize_cd=$(expr ${et_sectors} \* 512) 920 break 921 fi 922 done 923 924 if [ -n "${espstart}" ]; then 925 hybrid=$(mktemp ${OUTDIR}/hybrid.XXXXXX) 926 mkimg -s gpt \ 927 --capacity ${imgsize} \ 928 -b ${DESTDIR}/boot/pmbr \ 929 -p freebsd-boot:=${DESTDIR}/boot/isoboot \ 930 -p efi::${espsize_cd}:${espstart} \ 931 -o ${hybrid} >> ${LOGDIR}/imagebuild.log 2>&1 932 dd if=${hybrid} of=${img} bs=32k count=1 conv=notrunc >> ${LOGDIR}/imagebuild.log 2>&1 933 rm -f ${hybrid} 934 fi 935 936 rm -f ${espfile} 937 938 # Test the hybrid ISO with both BIOS and EFI 939 register_test "${name}-bios" $(qemu_bios_cdrom ${img}) 940 register_test "${name}-efi" $(qemu_efi_cdrom ${img}) 941 942 rm -f ${mt1} 943} 944 945assemble_ofw() { 946 echo " Assembling Open Firmware images..." 947 # APM partitioned disk with boot1.hfs 948 fstypes="ufs" 949 has zfs && fstypes="ufs zfs" 950 for fs in ${fstypes}; do 951 name="ofw-apm-${fs}" 952 img=${IMGDIR}/${name}.img 953 954 case ${fs} in 955 ufs) fsimg=${IMGDIR}/bootable-ufs-lua.img; ptype="freebsd-ufs" ;; 956 zfs) fsimg=${IMGDIR}/bootable-zfs-lua.img; ptype="freebsd-zfs" ;; 957 esac 958 959 mkimg -a 1 -s apm \ 960 -p freebsd-boot:=${DESTDIR}/boot/boot1.hfs \ 961 -p ${ptype}:=${fsimg} \ 962 -o ${img} >> ${LOGDIR}/imagebuild.log 2>&1 963 964 register_test ${name} $(qemu_ofw ${img}) 965 done 966} 967 968# Open Firmware bootable CD. Mirrors release/powerpc/mkisoimages.sh: build the 969# Apple/OF "macppc" boot image by dd'ing /boot/loader into the hfs-boot block 970# at its "Loader START" offset, then hand that to makefs as the El Torito 971# no-emul boot image. OpenBIOS finds it via cd:,\\:tbxi. 972assemble_ofw_cd() { 973 echo " Assembling Open Firmware CD image..." 974 name="ofw-cd" 975 img=${IMGDIR}/${name}.iso 976 977 # cd9660 root fstab overlay 978 mt=$(mktemp ${OUTDIR}/ofwcd-mtree.XXXXXX) 979 echo "./etc/fstab type=file mode=0644 contents=${OUTDIR}/fstab.cd" > ${mt} 980 981 # Apple/OF boot block with the loader embedded at "Loader START". 982 bootblock=$(mktemp ${OUTDIR}/hfs-boot.XXXXXX) 983 uudecode -p ${SRCTOP}/release/powerpc/hfs-boot.bz2.uu | bunzip2 > ${bootblock} 984 offset=$(hd ${bootblock} | grep 'Loader START' | cut -f 1 -d ' ') 985 offset=$((0x${offset} / 512)) 986 dd if=${DESTDIR}/boot/loader of=${bootblock} seek=${offset} conv=notrunc \ 987 >> ${LOGDIR}/imagebuild.log 2>&1 988 989 makefs -t cd9660 \ 990 -o bootimage=macppc\;${bootblock} \ 991 -o no-emul-boot \ 992 -o rockridge \ 993 -o label=FBSDTEST \ 994 ${img} ${mt} ${DESTDIR} >> ${LOGDIR}/imagebuild.log 2>&1 995 996 rm -f ${bootblock} ${mt} 997 register_test ${name} $(qemu_ofw_cdrom ${img}) 998} 999 1000# pseries PReP disk boot: MBR with a PReP boot partition (boot1.elf) and the 1001# UFS root directly on an MBR partition -- no BSD label, matching freebsd-ci 1002# (whose BSD-slice container does not cross-build from amd64). SLOF runs 1003# boot1.elf from the PReP partition, which loads /boot/loader from the UFS. 1004assemble_prep() { 1005 echo " Assembling pseries PReP (MBR) images..." 1006 name="prep-mbr-ufs" 1007 img=${IMGDIR}/${name}.img 1008 ufs=${IMGDIR}/bootable-ufs-lua.img 1009 1010 mkimg -a 1 -s mbr \ 1011 -p prepboot:=${DESTDIR}/boot/boot1.elf \ 1012 -p freebsd:=${ufs} \ 1013 -o ${img} >> ${LOGDIR}/imagebuild.log 2>&1 1014 1015 register_test ${name} $(qemu_prep ${img}) 1016} 1017 1018# pseries CD boot. SLOF reads \ppc\bootinfo.txt (CHRP boot script) and runs 1019# the OF loader from the CD. Mirrors the chrp-boot half of 1020# release/powerpc/mkisoimages.sh (the macppc/Apple half is mac99-only). 1021assemble_pseries_cd() { 1022 echo " Assembling pseries CD image..." 1023 name="prep-cd" 1024 img=${IMGDIR}/${name}.iso 1025 1026 mt=$(mktemp ${OUTDIR}/pseriescd-mtree.XXXXXX) 1027 bootinfo=$(mktemp ${OUTDIR}/bootinfo.XXXXXX) 1028 cat > ${bootinfo} <<EOF 1029<chrp-boot> 1030<description>FreeBSD Install</description> 1031<os-name>FreeBSD</os-name> 1032<boot-script>boot &device;:,\ppc\chrp\loader</boot-script> 1033</chrp-boot> 1034EOF 1035 cat > ${mt} <<EOF 1036./etc/fstab type=file mode=0644 contents=${OUTDIR}/fstab.cd 1037./ppc type=dir mode=0755 1038./ppc/bootinfo.txt type=file mode=0644 contents=${bootinfo} 1039./ppc/chrp type=dir mode=0755 1040./ppc/chrp/loader type=file mode=0644 contents=${DESTDIR}/boot/loader 1041EOF 1042 makefs -t cd9660 \ 1043 -o chrp-boot \ 1044 -o rockridge \ 1045 -o label=FBSDTEST \ 1046 ${img} ${mt} ${DESTDIR} >> ${LOGDIR}/imagebuild.log 2>&1 1047 rm -f ${mt} ${bootinfo} 1048 register_test ${name} $(qemu_prep_cdrom ${img}) 1049} 1050 1051assemble_linuxboot() { 1052 echo " Assembling linuxboot images..." 1053 esp=${IMGDIR}/linuxboot.esp 1054 fstypes="ufs" 1055 has zfs && fstypes="ufs zfs" 1056 for fs in ${fstypes}; do 1057 name="linuxboot-gpt-${fs}" 1058 img=${IMGDIR}/${name}.img 1059 1060 case ${fs} in 1061 ufs) fsimg=${IMGDIR}/bootable-ufs-lua.img; ptype="freebsd-ufs" ;; 1062 zfs) fsimg=${IMGDIR}/bootable-zfs-lua.img; ptype="freebsd-zfs" ;; 1063 esac 1064 1065 mkimg -s gpt \ 1066 -p efi:=${esp} \ 1067 -p ${ptype}:=${fsimg} \ 1068 -o ${img} >> ${LOGDIR}/imagebuild.log 2>&1 1069 1070 register_test ${name} $(qemu_efi ${img}) 1071 done 1072} 1073 1074# Linuxboot for platforms without an ESP: the kernel+initrd go on the QEMU 1075# command line, and the disk is just the FreeBSD root filesystem in a GPT 1076# (no ESP partition). loader.kboot mounts root from that disk. 1077assemble_linuxboot_direct() { 1078 echo " Assembling linuxboot (direct kernel/initrd) images..." 1079 linux_kernel=$(find_linux_kernel) || return 0 1080 initrd=${IMGDIR}/linuxboot-initrd.cpio.gz 1081 fstypes="ufs" 1082 has zfs && fstypes="ufs zfs" 1083 for fs in ${fstypes}; do 1084 name="linuxboot-${fs}" 1085 img=${IMGDIR}/${name}.img 1086 1087 case ${fs} in 1088 ufs) fsimg=${IMGDIR}/bootable-ufs-lua.img; ptype="freebsd-ufs" ;; 1089 zfs) fsimg=${IMGDIR}/bootable-zfs-lua.img; ptype="freebsd-zfs" ;; 1090 esac 1091 1092 mkimg -s gpt \ 1093 -p ${ptype}:=${fsimg} \ 1094 -o ${img} >> ${LOGDIR}/imagebuild.log 2>&1 1095 1096 register_test ${name} $(qemu_linuxboot ${img} ${linux_kernel} ${initrd}) 1097 done 1098} 1099 1100# Netboot: stage a TFTP root and register PXE/EFI network-boot tests. The 1101# loader fetches the kernel + an md_image RAM root over TFTP and mounts 1102# /dev/md0. The bootable UFS image is reused as the RAM root, so the kernel 1103# must have "options MD_ROOT" (amd64/aarch64 do; riscv64/armv7 GENERIC do 1104# not -- see the plan). netboot-bios/netboot-efi go over a real tap(4) + 1105# dnsmasq (register_netboot_test / netboot_network_setup) so DHCP can carry a 1106# root-path. 1107assemble_netboot() { 1108 echo " Assembling netboot images..." 1109 tftp=${OUTDIR}/tftp 1110 rm -rf ${tftp} 1111 mkdir -p ${tftp} 1112 cp -a ${DESTDIR}/boot ${tftp}/boot 1113 cp ${IMGDIR}/bootable-ufs-lua.img ${tftp}/boot/mdroot.img 1114 1115 # Serve the kernel and RAM root as .xz: xzfs_fsops (stand/libsa/xzfs.c) 1116 # transparently retries "<name>.xz" whenever "<name>" isn't found, 1117 # exactly like gzipfs already does for kernel.gz, so this cuts the bytes 1118 # that have to cross TFTP with no loader.conf change -- mdroot_name below 1119 # still names the plain, unsuffixed "mdroot.img", and pxeboot/loader.efi 1120 # (fetched directly by firmware, not through libsa) are untouched. Both 1121 # the amd64 BIOS loader (i386/loader/Makefile: LOADER_XZ_SUPPORT) and 1122 # every EFI loader (efi/loader/conf.c: unconditional) have xzfs compiled 1123 # in, so netboot-bios and netboot-efi -- which share this tftp tree -- 1124 # both exercise the compressed path. 1125 # 1126 # -6 (xz's own default), not -9: -9's 64MB LZMA2 dictionary needs one 1127 # contiguous vmalloc() of that size before xz_dec_run() can decode 1128 # anything, and the BIOS loader's *entire* heap is a fixed 64MB 1129 # (HEAP_MIN in i386/libi386/biosmem.c) regardless of VM memory -- so a 1130 # 64MB dictionary request against a 64MB heap that already holds other 1131 # loader state can never succeed (XZ_MEM_ERROR, every read, forever). 1132 # -6's 8MB dictionary leaves ample headroom and loses ~nothing on data 1133 # this size. 1134 xz -6 -f ${tftp}/boot/kernel/kernel 1135 xz -6 -f ${tftp}/boot/mdroot.img 1136 1137 # Boot the kernel + RAM root off the network instead of a disk (appended to 1138 # the arch-correct loader.conf build_tree already wrote). 1139 cat >> ${tftp}/boot/loader.conf <<EOF 1140mdroot_load="YES" 1141mdroot_type="md_image" 1142mdroot_name="/boot/mdroot.img" 1143vfs.root.mountfrom="ufs:/dev/md0" 1144EOF 1145 1146 # BIOS PXE: the NIC option ROM chainloads pxeboot (the DHCP bootfile). 1147 # Registered via register_netboot_test, not register_test: these need a 1148 # real tap(4) + dnsmasq root-path (see netboot_network_setup) rather than 1149 # slirp, which can't send one. 1150 # 1151 # netboot-bios legitimately runs past the default timeout: it's TFTP 1152 # (not xzfs decode) that's the bottleneck here -- EFI's memdisk=<url> 1153 # path decodes 150MB .xz images in about a minute, so the loader's xz 1154 # decoder isn't the bottleneck; this is plain TFTP bandwidth for the 1155 # kernel+mdroot fetch. Measured wins on real hardware: whole-suite boot 1156 # time dropped from ~25 min to ~18 min with xzfs in place. TFTP-level 1157 # fixes (e.g. HTTP instead, once BIOS can do that) are the only further 1158 # lever; don't chase this again without one. 1159 if has bios; then 1160 cp ${DESTDIR}/boot/pxeboot ${tftp}/pxeboot 1161 register_netboot_test netboot-bios ${tftp} pxeboot bios 1162 fi 1163 # EFI: stage loader.efi for the iPXE-chainload test below. This can't use 1164 # the NIC's own PXE ROM the way netboot-bios does -- our installed OVMF 1165 # build (qemu's bundled edk2-x86_64-code.fd) has no NetworkPkg/PXE driver 1166 # at all (confirmed: zero PXE/HTTPBoot strings in the firmware, and 1167 # BdsDxe drops straight to "EFI Internal Shell" with no network boot 1168 # option ever offered, independent of vars pflash/bootindex/-boot n). So 1169 # EFI netboot instead brings its own iPXE (see the ramdisk block below) 1170 # to do DHCP/TFTP; netboot-efi's own chain script (also below) omits 1171 # memdisk= so loader.efi does its own BOOTP call and gets a real 1172 # DHCP root-path, unlike the RAM-disk tests. 1173 if has efi; then 1174 cp ${DESTDIR}/boot/loader_lua.efi ${tftp}/loader.efi 1175 fi 1176 1177 # RAM-disk netboot: iPXE (from -hda) chains loader.efi with memdisk=<url>, 1178 # which the loader boots entirely from RAM -- sidestepping the NFS/root-path 1179 # problem above. iPXE EFI is x86-only in the install, so this is gated on 1180 # netboot_ipxe (amd64 today). See qemu_netboot_ramdisk / do-memddisk-efi. 1181 # netboot-efi (real root-path, no memdisk=) piggybacks on the same iPXE 1182 # image/vars, since it needs the same OVMF-has-no-PXE workaround. 1183 if [ -n "$(param netboot_ipxe)" ] && have_file "$(param netboot_ipxe)" && \ 1184 have_file "$(param netboot_efi_vars)"; then 1185 # Writable copies: QEMU opens -hda and the edk2 vars pflash read-write, 1186 # but the installed originals are root-owned. 1187 cp -f $(param netboot_ipxe) ${OUTDIR}/netboot-ipxe.img 1188 cp -f $(param netboot_efi_vars) ${OUTDIR}/netboot-vars.fd 1189 1190 if has efi; then 1191 cat > ${tftp}/boot-efi.ipxe <<EOF 1192#!ipxe 1193chain tftp://\${next-server}/loader.efi 1194EOF 1195 register_netboot_test netboot-efi ${tftp} /boot-efi.ipxe "" qemu_netboot_ramdisk 1196 fi 1197 1198 cat > ${tftp}/boot.ipxe <<EOF 1199#!ipxe 1200chain tftp://\${next-server}/loader.efi memdisk=tftp://\${next-server}/boot/mdroot.img 1201EOF 1202 register_netboot_test netboot-ramdisk ${tftp} /boot.ipxe "" qemu_netboot_ramdisk 1203 1204 # Same mechanism, but the image is compressed, exercising each codec 1205 # in stand/efi/loader/decompress.c. Compress a throwaway copy rather 1206 # than ${IMGDIR}/bootable-ufs-lua.img itself: gzip/bzip2/xz remove 1207 # their input on success, and this image is shared with other tests. 1208 # Compressing a real file (not a pipe) lets zstd embed the frame 1209 # content size, so zstd_init() can size the output buffer exactly 1210 # instead of guessing 4x and growing/copying as it decompresses. 1211 for spec in gzip:gz bzip2:bz2 xz:xz zstd:zst; do 1212 tool=${spec%%:*} 1213 ext=${spec#*:} 1214 img=${tftp}/boot/mdroot-${tool}.img 1215 cp ${IMGDIR}/bootable-ufs-lua.img ${img} 1216 case ${tool} in 1217 zstd) zstd -f --rm ${img} >> ${LOGDIR}/imagebuild.log 2>&1 ;; 1218 *) ${tool} -f ${img} >> ${LOGDIR}/imagebuild.log 2>&1 ;; 1219 esac 1220 cat > ${tftp}/boot-${tool}.ipxe <<EOF 1221#!ipxe 1222chain tftp://\${next-server}/loader.efi memdisk=tftp://\${next-server}/boot/mdroot-${tool}.img.${ext} 1223EOF 1224 register_netboot_test netboot-ramdisk-${tool} ${tftp} /boot-${tool}.ipxe "" qemu_netboot_ramdisk 1225 done 1226 fi 1227 1228 # BIOS RAM-disk netboot: iPXE loads syslinux memdisk with a *bootable* disk 1229 # image as its initrd. memdisk boots the image's MBR and installs a MEMDISK 1230 # memory disk; the BIOS loader detects it (biosmemdisk.c -> hint.md.0.*), so 1231 # the kernel gets md0 and roots via the UFS label. The BIOS loader has no 1232 # memdisk= arg, hence this different mechanism. 1233 if has bios && [ -n "$(param netboot_memdisk)" ] && \ 1234 have_file "$(param netboot_memdisk)"; then 1235 cp $(param netboot_memdisk) ${tftp}/memdisk 1236 cp ${IMGDIR}/bios-mbr-ufs-loader_lua.img ${tftp}/bootdisk.img 1237 cat > ${tftp}/boot-bios.ipxe <<EOF 1238#!ipxe 1239kernel tftp://\${next-server}/memdisk 1240initrd tftp://\${next-server}/bootdisk.img 1241boot 1242EOF 1243 register_netboot_test netboot-bios-memdisk ${tftp} /boot-bios.ipxe bios 1244 fi 1245} 1246 1247# -------------------------------------------------------------------------- 1248# Netboot host networking: vmnet(4) + dnsmasq 1249# 1250# QEMU's slirp/user-mode net cannot send DHCP root-path (option 17); without 1251# one, the loader's netproto defaults to NFS for every file fetch after the 1252# initial TFTP-delivered bootfile (net_parse_rootpath()/NETPROTO_DEFAULT), which 1253# is why netboot-bios/ netboot-efi otherwise fail here (no NFS server). 1254# 1255# This uses vmnet(4), not tap(4), even though both are clones of the same 1256# if_tuntap(4) driver and QEMU treats them identically (-netdev tap,ifname=). 1257# tap(4)'s close handler unconditionally runs if_down()+if_purgeaddrs() on last 1258# close -- so the interface would lose its address and go down every time QEMU 1259# exits, and re-adding it needs root. vmnet(4) explicitly skips that, so the 1260# address assigned once survives every subsequent QEMU open/close. 1261# 1262# Creating the interface and giving it an address both require root regardless 1263# of net.link.tap.user_open (that sysctl only gates opening the cloning device 1264# itself). So the root setup below runs once, chowns the resulting /dev/vmnetN 1265# nodes to the invoking user (so QEMU, unprivileged, can open them directly -- 1266# see net/tap-bsd.c: it opens /dev/<ifname> directly when that device already 1267# exists, only falling back to the /dev/tap cloning device otherwise), and 1268# leaves everything running. Later invocations detect the existing state via a 1269# content hash and skip sudo entirely: "once per boot", not "once per run". 1270# -------------------------------------------------------------------------- 1271 1272# Deterministically slice a /30 out of $(param netboot_subnet_base) (a /16, 1273# e.g. 198.18.0.0) for plan line ${1} (0-based). Prints "gw client". 1274netboot_subnet_for() { 1275 idx=$1 1276 base=$(param netboot_subnet_base) 1277 o1o2=$(echo ${base} | cut -d. -f1-2) 1278 o3=$(($(echo ${base} | cut -d. -f3) + idx / 64)) 1279 o4=$(((idx % 64) * 4)) 1280 echo "${o1o2}.${o3}.$((o4 + 1)) ${o1o2}.${o3}.$((o4 + 2))" 1281} 1282 1283# Runs after every arch is built (so ${NETBOOT_PLAN} is complete) and before 1284# run_all_tests. Assigns each registered netboot test its own vmnet + /30, 1285# escalates via sudo only if the host doesn't already match that plan, then 1286# patches the __NETBOOT_TAP__ placeholder in each test's command file. 1287netboot_network_setup() { 1288 [ -s "${NETBOOT_PLAN}" ] || return 0 1289 echo "=== Netboot: configuring vmnet(4) + dnsmasq ===" 1290 mkdir -p "${NETBOOT_STATE_DIR}" 1291 1292 resolved=${NETBOOT_STATE_DIR}/plan.new 1293 : > "${resolved}" 1294 idx=0 1295 while read cmdfile tftpdir bootfile; do 1296 vmnet="vmnet$((100 + idx))" # offset away from any operator-managed vmnets 1297 set -- $(netboot_subnet_for ${idx}) 1298 gw=$1 1299 client=$2 1300 echo "${vmnet} ${gw} ${client} 30 ${tftpdir} ${bootfile} ${cmdfile}" >> "${resolved}" 1301 idx=$((idx + 1)) 1302 done < "${NETBOOT_PLAN}" 1303 1304 newhash=$(md5 -q "${resolved}") 1305 oldhash="" 1306 [ -f "${NETBOOT_STATE_DIR}/state.hash" ] && oldhash=$(cat "${NETBOOT_STATE_DIR}/state.hash") 1307 1308 # dnsmasq runs as "nobody" (it drops root after binding), so kill -0 1309 # from this unprivileged process would always fail with EPERM even when 1310 # it's alive; check process existence via ps instead, which doesn't 1311 # require signal permission. 1312 converged=false 1313 if [ "${newhash}" = "${oldhash}" ] && [ -s "${NETBOOT_STATE_DIR}/dnsmasq.pid" ] \ 1314 && ps -p "$(cat ${NETBOOT_STATE_DIR}/dnsmasq.pid)" > /dev/null 2>&1; then 1315 converged=true 1316 while read vmnet gw client prefix tftpdir bootfile cmdfile; do 1317 ifconfig "${vmnet}" > /dev/null 2>&1 || { converged=false; break; } 1318 done < "${resolved}" 1319 fi 1320 1321 if ${converged}; then 1322 echo " Existing vmnet(4)/dnsmasq setup already matches -- no sudo needed." 1323 else 1324 echo " Host networking missing or stale; requesting sudo once to (re)create it..." 1325 cp "${resolved}" "${NETBOOT_STATE_DIR}/plan" 1326 sudo "$0" --netboot-helper "${NETBOOT_STATE_DIR}/plan" \ 1327 || die "netboot vmnet(4)/dnsmasq setup (sudo) failed" 1328 fi 1329 1330 while read vmnet gw client prefix tftpdir bootfile cmdfile; do 1331 sed -i '' "s/__NETBOOT_TAP__/${vmnet}/" "${cmdfile}" 1332 done < "${resolved}" 1333} 1334 1335# Root-side setup, only reached via `sudo "$0" --netboot-helper <planfile>` 1336# (see netboot_network_setup). ${1} has the same "vmnet gw client prefix 1337# tftpdir bootfile cmdfile" lines as netboot_network_setup's resolved plan. 1338netboot_helper() { 1339 planfile=$1 1340 [ -r "${planfile}" ] || die "netboot helper: cannot read plan ${planfile}" 1341 invoker=${SUDO_UID:-$(id -u)} 1342 mkdir -p "${NETBOOT_STATE_DIR}" 1343 1344 # Converge from scratch rather than trusting the caller's hash check: 1345 # tear down anything left over from a previous (possibly interrupted) run. 1346 if [ -s "${NETBOOT_STATE_DIR}/dnsmasq.pid" ]; then 1347 kill "$(cat ${NETBOOT_STATE_DIR}/dnsmasq.pid)" 2>/dev/null || true 1348 rm -f "${NETBOOT_STATE_DIR}/dnsmasq.pid" 1349 fi 1350 for vmnet in $(ifconfig -g boot-test 2>/dev/null); do 1351 ifconfig "${vmnet}" destroy 1352 done 1353 1354 conf=${NETBOOT_STATE_DIR}/dnsmasq.conf 1355 cat > "${conf}" <<EOF 1356port=0 1357bind-interfaces 1358enable-tftp 1359log-dhcp 1360pid-file=${NETBOOT_STATE_DIR}/dnsmasq.pid 1361dhcp-leasefile=${NETBOOT_STATE_DIR}/dnsmasq.leases 1362 1363# QEMU's PXE ROM is iPXE; it self-identifies as vendor-class 1364# "PXEClient:Arch:00000:UNDI:002001" (stand/libsa/bootp.c's own request just 1365# says "PXEClient", no ":Arch:..." suffix, so this substring match is 1366# iPXE-only). iPXE's autoboot() gives DHCP root-path priority over 1367# chainloading the DHCP filename -- if root-path is set it tries to 1368# sanboot(8) it instead, which fails outright for a tftp:// URI ("Could not 1369# open SAN device"). So root-path below is withheld from iPXE's own 1370# negotiation and only given once stand/libsa/bootp.c does its own separate 1371# BOOTP call after pxeboot/loader.efi has already been chainloaded. 1372dhcp-vendorclass=set:ipxerom,PXEClient:Arch 1373EOF 1374 1375 while read vmnet gw client prefix tftpdir bootfile cmdfile; do 1376 ifconfig "${vmnet}" create group boot-test 1377 ifconfig "${vmnet}" inet "${gw}/${prefix}" up 1378 chown "${invoker}" "/dev/${vmnet}" 1379 1380 # dnsmasq's dhcp-boot only sets DHCP option 67 (bootfile-name), never 1381 # the classic fixed-length BOOTP "file" field -- confirmed by comparing 1382 # against QEMU's own slirp DHCP server, which sets that field directly 1383 # and gets a plain "Filename: pxeboot" chainload with no further 1384 # ceremony. Without it, and since iPXE requested option 43 (PXE vendor 1385 # info) in its Parameter-Request list, iPXE assumes it must run the full 1386 # PXE boot-server-discovery dance instead of trusting option 67 -- seen 1387 # on the wire as a TFTP RRQ with an empty filename, then "No 1388 # configuration methods succeeded" / "PXEBS ... Connection timed 1389 # out". Telling it not to via PXE discovery-control (option 43 1390 # sub-option 6, value 8 = "use bootfile name from the DHCP packet, don't 1391 # discover") fixes it directly, without dnsmasq's heavier --pxe-service 1392 # boot-menu machinery (which defaults discovery-control to 3, still 1393 # triggering discovery). 1394 cat >> "${conf}" <<EOF 1395interface=${vmnet} 1396dhcp-range=set:${vmnet},${client},${client},255.255.255.252,1h 1397dhcp-boot=tag:${vmnet},${bootfile} 1398dhcp-option=tag:${vmnet},tag:ipxerom,encap:43,6,8 1399dhcp-option=tag:${vmnet},tag:!ipxerom,17,"tftp://${gw}/" 1400tftp-root=${tftpdir},${vmnet} 1401EOF 1402 done < "${planfile}" 1403 1404 dnsmasq --conf-file="${conf}" 1405 1406 # dnsmasq daemonizes itself; wait for the pidfile before trusting it. 1407 tries=0 1408 while [ ! -s "${NETBOOT_STATE_DIR}/dnsmasq.pid" ] && [ ${tries} -lt 5 ]; do 1409 sleep 1 1410 tries=$((tries + 1)) 1411 done 1412 [ -s "${NETBOOT_STATE_DIR}/dnsmasq.pid" ] || die "dnsmasq did not start" 1413 1414 md5 -q "${planfile}" > "${NETBOOT_STATE_DIR}/state.hash" 1415 chown "${invoker}" "${NETBOOT_STATE_DIR}" "${conf}" "${planfile}" \ 1416 "${NETBOOT_STATE_DIR}/dnsmasq.pid" "${NETBOOT_STATE_DIR}/state.hash" 1417} 1418 1419# Manual cleanup: `sudo sh boot-test.sh --netboot-teardown`. Not run 1420# automatically -- leaving the setup running is what makes it "once per 1421# boot" instead of "once per run" (see netboot_network_setup). 1422netboot_teardown() { 1423 if [ -s "${NETBOOT_STATE_DIR}/dnsmasq.pid" ]; then 1424 kill "$(cat ${NETBOOT_STATE_DIR}/dnsmasq.pid)" 2>/dev/null || true 1425 fi 1426 for vmnet in $(ifconfig -g boot-test 2>/dev/null); do 1427 ifconfig "${vmnet}" destroy 1428 done 1429 rm -rf "${NETBOOT_STATE_DIR}" 1430 echo "Netboot vmnet(4)/dnsmasq setup torn down." 1431} 1432 1433assemble_all_images() { 1434 echo "=== Phase 3: Assembling disk images ===" 1435 1436 if has efi; then 1437 if [ -r "$(param efi_firmware)" ]; then 1438 assemble_efi_gpt 1439 if has mbr; then 1440 assemble_efi_mbr 1441 fi 1442 elif [ -n "$(param efi_firmware)" ]; then 1443 echo "WARNING: EFI firmware not found at $(param efi_firmware), skipping EFI tests" 1444 else 1445 # riscv64 uses u-boot with EFI payload 1446 assemble_efi_gpt 1447 fi 1448 fi 1449 1450 if has bios; then 1451 assemble_bios_gpt 1452 if has mbr; then 1453 assemble_bios_mbr 1454 fi 1455 fi 1456 1457 if has bios && has efi && [ -r "$(param efi_firmware)" ]; then 1458 assemble_both_gpt 1459 if has mbr; then 1460 assemble_both_mbr 1461 fi 1462 fi 1463 1464 if has cd; then 1465 if has prep; then 1466 assemble_pseries_cd # pseries SLOF CHRP CD 1467 elif has ofw; then 1468 assemble_ofw_cd # mac99 Apple/OF CD 1469 else 1470 assemble_cd # x86 El Torito 1471 fi 1472 fi 1473 1474 if has prep; then 1475 assemble_prep 1476 fi 1477 1478 # Used for mac99 emulation, though kernel issues prevent testing 1479 if has ofw; then 1480 assemble_ofw 1481 fi 1482 1483 if has linuxboot; then 1484 if [ -f "${IMGDIR}/linuxboot.esp" ]; then 1485 assemble_linuxboot 1486 elif [ -f "${IMGDIR}/linuxboot-initrd.cpio.gz" ]; then 1487 assemble_linuxboot_direct 1488 fi 1489 fi 1490 1491 if has netboot; then 1492 assemble_netboot 1493 fi 1494} 1495 1496# -------------------------------------------------------------------------- 1497# Phase 4: Run tests in parallel 1498# -------------------------------------------------------------------------- 1499 1500run_one_test() { 1501 name=$1 1502 log="${LOGDIR}/${name}.log" 1503 cmd=$(cat ${OUTDIR}/test-cmd-${name}.sh) 1504 1505 expect -c " 1506 set timeout ${TIMEOUT} 1507 log_file -noappend \"${log}\" 1508 spawn {*}${cmd} 1509 expect { 1510 \"SUCCESS\" { exit 0 } 1511 timeout { exit 1 } 1512 eof { exit 2 } 1513 } 1514 " >/dev/null 2>&1 1515 return $? 1516} 1517 1518wait_for_slot() { 1519 # Wait until fewer than MAX_JOBS are running 1520 while true; do 1521 running=0 1522 for p in ${active_pids}; do 1523 if kill -0 $p 2>/dev/null; then 1524 running=$((running + 1)) 1525 fi 1526 done 1527 [ ${running} -lt ${MAX_JOBS} ] && break 1528 sleep 1 1529 done 1530} 1531 1532# Launch every arch's tests together and wait once, so a run of N arches costs 1533# roughly one timeout rather than N. Each `run_one_test &` snapshots the 1534# per-arch OUTDIR/LOGDIR/TIMEOUT that setup_arch_env just set, so backgrounded 1535# jobs keep their own arch context even as we move on to the next arch. 1536run_all_tests() { 1537 echo "=== Phase 4: Running tests ===" 1538 1539 total=0 1540 skipped=0 1541 active_pids="" 1542 results_map=$(mktemp -t boot-test-results) 1543 1544 for arch in ${ARCHES}; do 1545 setup_arch_env "${arch}" 1546 [ -s "${TESTLIST}" ] || continue 1547 1548 while read name; do 1549 # Apply test filter if given 1550 if [ -n "${TEST_FILTER}" ]; then 1551 echo "${name}" | grep -qE "${TEST_FILTER}" || { 1552 skipped=$((skipped + 1)) 1553 continue 1554 } 1555 fi 1556 1557 total=$((total + 1)) 1558 1559 # Job throttling (global across all arches) 1560 if [ ${MAX_JOBS} -gt 0 ]; then 1561 wait_for_slot 1562 fi 1563 1564 run_one_test "${name}" & 1565 pid=$! 1566 active_pids="${active_pids} ${pid}" 1567 echo "${pid} ${arch} ${name}" >> ${results_map} 1568 done < ${TESTLIST} 1569 done 1570 1571 [ ${total} -gt 0 ] || die "No tests registered. Run without -B first." 1572 echo " ${total} tests launched (${skipped} skipped by filter), waiting..." 1573 echo "" 1574 1575 # Collect results - disable errexit since wait returns the child's exit status 1576 set +e 1577 pass=0 1578 fail=0 1579 timeout_count=0 1580 while read pid arch name; do 1581 wait ${pid} 1582 rc=$? 1583 case ${rc} in 1584 0) 1585 result="PASS" 1586 pass=$((pass + 1)) 1587 ;; 1588 1) 1589 result="TIMEOUT" 1590 timeout_count=$((timeout_count + 1)) 1591 ;; 1592 *) 1593 result="FAILED" 1594 fail=$((fail + 1)) 1595 ;; 1596 esac 1597 printf " %-12s %-40s %s\n" "${arch}" "${name}" "${result}" 1598 done < ${results_map} 1599 1600 echo "" 1601 echo "=== Results: ${pass} passed, ${fail} failed, ${timeout_count} timed out (of ${total}) ===" 1602 1603 rm -f ${results_map} 1604 [ ${fail} -eq 0 ] && [ ${timeout_count} -eq 0 ] 1605} 1606 1607# Build the tree and images for each arch, one arch at a time. Each arch runs 1608# in a subshell so its setup_arch_env globals stay isolated and a build failure 1609# (set -e) is caught here instead of aborting the whole run. 1610build_all() { 1611 for arch in ${ARCHES}; do 1612 echo "############################################################" 1613 echo "# ${arch}: building" 1614 echo "############################################################" 1615 if ! ( 1616 setup_arch_env "${arch}" 1617 need_cmd "$(param qemu_bin)" 1618 ${SKIP_BUILD} || build_tree 1619 if ! ${SKIP_IMAGES}; then 1620 [ -d "${DESTDIR}" ] || \ 1621 die "No boot tree at ${DESTDIR}. Run without -b first." 1622 : > ${TESTLIST} # fresh test list before (re)assembling 1623 make_base_images 1624 assemble_all_images 1625 fi 1626 ); then 1627 echo " ${arch}: BUILD FAILED" 1628 fi 1629 done 1630} 1631 1632# -------------------------------------------------------------------------- 1633# Main 1634# -------------------------------------------------------------------------- 1635 1636# --netboot-helper/--netboot-teardown are internal/manual entry points that 1637# skip the whole build+test flow below; see netboot_network_setup. 1638case "${NETBOOT_MODE}" in 1639 helper) netboot_helper "${NETBOOT_HELPER_PLAN}"; exit $? ;; 1640 teardown) netboot_teardown; exit $? ;; 1641esac 1642 1643# Preflight: universal tools (per-arch qemu binaries are checked in build_all). 1644for prog in jq expect makefs mkimg; do 1645 need_cmd "${prog}" 1646done 1647 1648echo "FreeBSD boot loader test suite: ${ARCHES}" 1649echo "" 1650 1651# Accumulates netboot test plan lines across every arch (unlike ${TESTLIST}, 1652# which build_all truncates per-arch) -- see register_netboot_test. 1653NETBOOT_PLAN=$(mktemp -t boot-test-netboot-plan) 1654 1655if $do_report_dirs; then 1656 for arch in ${ARCHES}; do 1657 setup_arch_env "${arch}" 1658 echo "${arch} settings:" 1659 echo " TIMEOUT=${TIMEOUT}" 1660 echo " ARCH_OBJDIR=${ARCH_OBJDIR}" 1661 echo " OUTDIR=${OUTDIR}" 1662 echo " LOGDIR=${LOGDIR}" 1663 echo " DESTDIR=${DESTDIR}" 1664 echo " TESTLIST=${TESTLIST}" 1665 done 1666 exit 0 1667fi 1668 1669build_all 1670netboot_network_setup 1671if run_all_tests; then 1672 rc=0 1673else 1674 rc=$? 1675fi 1676rm -f "${NETBOOT_PLAN}" 1677exit ${rc} 1678