#!/usr/bin/env bash # Boot a Foundry OS image headless on the pinned Rustbelt kernel and check # the self-test's output on the serial console. Two boot contracts # (docs/boot-contract.md), picked with CONTRACT=v1|v2 (default v1): # # v1: the kernel runs the shell self-test (B:/scripts/selftest.sh from # rootfs/) itself. Our image is disk 0, the kernel's three test disks # C:, D:, E: (snapshot: nothing is written back). The expected lines # are the kernel repo's own (moved from its scripts/run-headless.sh), # so a pass means "same behaviour as the kernel's own image". # v2: our /sbin/init runs /scripts/selftest.sh (from root/) and ends the # run with its exit status. Only our image is attached (A:, B:, C: # are all partitions of it), WITHOUT snapshot, so the caller can # check afterwards what was written (make test-v2 boots a copy). # The expected lines are all in EXTRA_EXPECT (scripts/expect-v2.txt). # # Usage: [CONTRACT=v2] scripts/run-headless.sh IMAGE KERNEL_BOOTIMAGE KERNEL_TARGET OUT_DIR # IMAGE our disk image # KERNEL_BOOTIMAGE out/kernel/bootimage-headless.bin # KERNEL_TARGET out/kernel-target (has the kernel-owned test disks # ext2.img, exfat.img, btrfs.img: drives C:, D:, E:) # OUT_DIR where logs go (out/) # Env (the Makefile sets these from config.mk; they change with the kernel pin): # KERNEL_BANNER the kernel's first line, e.g. "Rustbelt 0.21.0 (stage 21: ...)" # KERNEL_VERSION e.g. 0.21.0 # BIN_COUNT how many programs the image has in B:/bin (`dir bin | wc -l`) # ROOT_BIN_COUNT v2: how many programs are in A:/bin (@ROOT_BIN_COUNT@) # EXTRA_EXPECT optional file: more lines the log must contain, one per # line (Foundry OS's own checks; # starts a comment) # QEMU_CPU (default max), QEMU_TIMEOUT (default 120 s). # # Only the QEMU this script starts is ever killed: QEMU writes its PID to # OUT_DIR/qemu-headless.pid and the exit trap kills exactly that PID. set -euo pipefail cd "$(dirname "$0")/.." # repo root: QEMU's guestfwd runs scripts/http-reply.sh IMAGE=$1 IMG=$2 KT=$3 OUT=$4 CONTRACT=${CONTRACT:-v1} : "${KERNEL_BANNER:?set KERNEL_BANNER (see config.mk)}" "${KERNEL_VERSION:?}" "${BIN_COUNT:?}" STAGE=${KERNEL_BANNER#*(}; STAGE=${STAGE%%:*} # "stage 20" mkdir -p "$OUT" if [ "$CONTRACT" = v2 ]; then DISKS=(-drive file="$IMAGE",format=raw,if=virtio) else DISKS=(-drive file="$IMAGE",format=raw,if=virtio,snapshot=on) for d in ext2 exfat btrfs; do DISKS+=(-drive file="$KT/$d.img",format=raw,if=virtio,snapshot=on) done fi for f in "$IMAGE" "$IMG"; do [ -f "$f" ] || { echo "[run-headless] missing $f (run make image / make kernel)"; exit 2; } done for d in "${DISKS[@]}"; do case $d in file=*) f=${d#file=}; f=${f%%,*} [ -f "$f" ] || { echo "[run-headless] missing $f (run make image / make kernel)"; exit 2; } ;; esac done PIDFILE=$OUT/qemu-headless.pid rm -f "$PIDFILE" cleanup() { if [ -s "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then kill "$(cat "$PIDFILE")" 2>/dev/null || true fi [ -n "${CURL_PID:-}" ] && kill "$CURL_PID" 2>/dev/null || true } trap cleanup EXIT # Network: QEMU's user-mode NAT. guestfwd runs a tiny test web server for # 10.0.2.100:80 (and `cat` as an echo server on :7); hostfwd forwards a free # host port to the guest's port 80, where the self-test's `httpd -n 1` # waits, and a background curl below fetches its page. HTTP_PORT=$(python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1])') NETDEV="user,id=net0,guestfwd=tcp:10.0.2.100:80-cmd:sh scripts/http-reply.sh,guestfwd=tcp:10.0.2.100:7-cmd:cat" NETDEV="$NETDEV,hostfwd=tcp:127.0.0.1:$HTTP_PORT-:80" CURL_OUT=$OUT/headless-curl.txt rm -f "$CURL_OUT" ( for _ in $(seq 1 80); do sleep 1 curl -s -m 5 -o "$CURL_OUT" "http://127.0.0.1:$HTTP_PORT/" 2>/dev/null && [ -s "$CURL_OUT" ] && break done ) & CURL_PID=$! # virtio-gpu, virtio-rng, and a virtio console (hvc0) fed from # scripts/hvc0-session.txt; its output goes to OUT/headless-hvc0.txt. HVC_OUT=$OUT/headless-hvc0.txt rm -f "$HVC_OUT" VIRTIO_DEVICES=(-vga none -device virtio-vga -device virtio-rng-pci -device virtio-serial-pci -device virtconsole,chardev=hvc -chardev "file,id=hvc,path=$HVC_OUT,input-path=scripts/hvc0-session.txt") LOG=$OUT/headless-serial.log echo "[run-headless] kernel $IMG" echo "[run-headless] contract $CONTRACT, disk 0 = $IMAGE" set +e timeout "${QEMU_TIMEOUT:-120}" qemu-system-x86_64 -smp 4 -cpu "${QEMU_CPU:-max}" \ -pidfile "$PIDFILE" \ -drive format=raw,file="$IMG",snapshot=on \ "${DISKS[@]}" \ -netdev "$NETDEV" -device virtio-net-pci,netdev=net0 \ "${VIRTIO_DEVICES[@]}" \ -device isa-debug-exit,iobase=0xf4,iosize=0x04 \ -serial stdio -display none -no-reboot | tee "$LOG" code=${PIPESTATUS[0]} kill $CURL_PID 2>/dev/null; wait $CURL_PID 2>/dev/null set -e # Lines the shell self-test must have printed (same list as the kernel repo at the pinned commit). if [ "$code" = 33 ] && [ "$CONTRACT" = v2 ]; then expect=() # all in EXTRA_EXPECT # Pipe results: `dir bin | wc -l` on B: -> $BIN_COUNT, the todo.txt grep -> 1, # `cat C:/seq.txt | wc -l` (downloaded by wget) -> 20000. PIPE_RESULTS=("$BIN_COUNT" 1 20000) # init's own memory check: frames before the self-test == after it and # every process it started (orphans included) were gone. FRAMES_RE='^\[init\] memory: ([0-9]+) frames in use before the self-test, \1 after' elif [ "$code" = 33 ]; then PIPE_RESULTS=(1 "$BIN_COUNT" 3 4 20000) FRAMES_RE='^\[memory\] ([0-9]+) frames in use before the shell, \1 after' expect=( "$KERNEL_BANNER x86_64" # ver "[smp] 4 CPUs online" # smp.rs started 3 more cores "[fpu] " # XSAVE (-cpu max) or FXSAVE (qemu64) "B: /mnt/disk0 fat32" # drives "C: /mnt/disk1 ext2" "D: /mnt/disk2 exfat" "E: /mnt/disk3 btrfs" "B:/ $ ls" # cd B: changed the prompt "readme.txt scripts/" # ls in columns "In the Rustbelt shell this disk is drive B:" # type B:/readme.txt "second line" # >> and < " 125 todo.txt" # cp + ls -l '[trace 18] open("/mnt/disk0/notes/todo.txt", 0o0) = 3' # run --trace "sleep started in the background" # start "[done] pid 19 (start sleep 1), exit code 0" # wait "C:/ $ ls" # the ext2 disk "Hello from the ext2 disk!" # cat through a symlink "big.txt line 12345" # double-indirect block "written by Rustbelt" # mkdir + create on ext2 "-rw-r--r-- 125 todo.txt" # cp B: -> C:, ls -l "lrwxrwxrwx hello-link -> hello.txt" "drwxr-xr-x