#!/usr/bin/env bash # Build the kernel with the `headless` feature and boot it in QEMU with no # window. Serial output goes to this terminal; the kernel powers QEMU off via # the isa-debug-exit device when done. A timeout guards against hangs. # # A normal boot runs the shell self-test (disk/scripts/selftest.sh); the # shell's exit code decides success, and this script then also checks that # the expected lines appear in the output. # # Usage: ./scripts/run-headless.sh # boot + shell self-test # ./scripts/run-headless.sh stack-overflow # extra cargo features, comma separated # EXT2_IMG=copy.img ./scripts/run-headless.sh # use copy.img as the ext2 disk *without* snapshot=on, so the # self-test's writes land in that file (scripts/ext2-check.sh # then runs e2fsck on it) # EXFAT_IMG=copy.img ./scripts/run-headless.sh # the same for the exFAT disk (scripts/exfat-check.sh runs # fsck.exfat on it) # BTRFS_IMG=other.img ./scripts/run-headless.sh # use other.img as drive E: (read-only, so always snapshot=on) set -euo pipefail cd "$(dirname "$0")/.." cargo build --features "headless${1:+,$1}" # Force bootimage to repack the disk image (see scripts/runner.sh for why). touch target/x86_64-rust_kernel/debug/rustbelt cargo bootimage --features "headless${1:+,$1}" IMG=target/x86_64-rust_kernel/debug/bootimage-rustbelt.bin if [ -n "${EXT2_IMG:-}" ]; then EXT2_DRIVE="file=$EXT2_IMG,format=raw,if=virtio" else EXT2_DRIVE="file=target/ext2.img,format=raw,if=virtio,snapshot=on" fi if [ -n "${EXFAT_IMG:-}" ]; then EXFAT_DRIVE="file=$EXFAT_IMG,format=raw,if=virtio" else EXFAT_DRIVE="file=target/exfat.img,format=raw,if=virtio,snapshot=on" fi BTRFS_DRIVE="file=${BTRFS_IMG:-target/btrfs.img},format=raw,if=virtio,snapshot=on" LOG=target/headless-serial.log set +e timeout 90 qemu-system-x86_64 -smp 4 \ -drive format=raw,file="$IMG" \ -drive file=target/disk.img,format=raw,if=virtio,snapshot=on \ -drive "$EXT2_DRIVE" \ -drive "$EXFAT_DRIVE" \ -drive "$BTRFS_DRIVE" \ -device isa-debug-exit,iobase=0xf4,iosize=0x04 \ -serial stdio -display none -no-reboot | tee "$LOG" code=${PIPESTATUS[0]} set -e # Lines the shell self-test must have printed (only for a plain boot). if [ -z "${1:-}" ] && [ "$code" = 33 ]; then expect=( "Rustbelt 0.11.0 (stage 11: core polish) x86_64" # ver "[smp] 4 CPUs online" # smp.rs started 3 more cores "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 made-here/" "D:/ $ ls" # the exFAT disk "This file has a long name with spaces in it." # quoted name, other case "MixedCase.TXT: exFAT keeps the case" # cp D: -> C:, cat "hi from exFAT" # md + echo >, other case " 125 todo.txt" # cp B: -> D:, ls -l D:/ " New Folder/" " 340000 numbers.txt" "E:/ $ ls -l" # the btrfs disk "lrwxrwxrwx hello-link -> hello.txt" "Hello from btrfs! (an inline extent" # inline, through a symlink "btrfs never overwrites a block in place" # compressed inline extent " 120000 360000 2160000 big.txt" # 17 zlib extents "btrfs line 119999" "Stage 10: a read-only btrfs driver" # inside subvolume projects "sh: new.txt: Read-only file system" # writes refused "rm: hello.txt: Read-only file system" "mkdir: newdir: Read-only file system" "[exit code 139]" # run evil "(overflow) killed: stack overflow" # user stack guard zone "4 CPUs online (this program ran on CPU" # sysinfo " CPU3 [" "sh: nosuchprogram: command not found" "cd: there is no drive Q:" "A:/tmp $ pwd" ) missing=0 for line in "${expect[@]}"; do grep -qF -- "$line" "$LOG" || { echo "[run-headless] MISSING: $line"; missing=1; } done # Pipe results: `... | grep -i EXT | wc -l` -> 1, `dir bin | wc -l` -> 17, # `cat C:/chunks.txt | wc -l` (copied from E:) -> 3, and # `type D:/COW.TXT | wc -l` (copied from E:) -> 4. for n in 1 17 3 4; do grep -qx -- "$n" "$LOG" || { echo "[run-headless] MISSING pipe result: $n"; missing=1; } done # No memory leaks: once the shell and all ~60 programs it ran have # exited, exactly as many frames must be in use as before it started. if grep -qE '^\[memory\] ([0-9]+) frames in use before the shell, \1 after' "$LOG"; then echo "[run-headless] frames in use returned to the baseline" else echo "[run-headless] MISSING: frames back at the baseline after the shell"; missing=1 fi [ $missing = 0 ] && echo "[run-headless] all ${#expect[@]}+4 expected shell lines found" || code=35 fi # isa-debug-exit: QEMU exit status = (value << 1) | 1, so Success(0x10) -> 33. case $code in 33) echo "[run-headless] kernel exited OK"; exit 0 ;; 35) echo "[run-headless] kernel reported FAILURE"; exit 1 ;; 124) echo "[run-headless] timed out (kernel hung?)"; exit 124 ;; *) echo "[run-headless] QEMU exited with $code"; exit "$code" ;; esac