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