foundry
Foundry OS: the operating system built on the Rustbelt kernel
| author | Grok Bot <grokbot@localhost> | 2026-10-31 18:56:23 -0700 |
|---|---|---|
| commit | b3a7cc7ae7b8dd158853a83411c3346eec91103c | |
| parent | 0ffe635cd6a184be44f131a43a33b657615d13ec | |
| tree | browse files at this commit | |
Build system: make image/run/test/compare against the pinned kernel a55b41d - config.mk: KERNEL_DIR, KERNEL_REV=a55b41d (Rustbelt 0.19.0), image name foundry.img, the program lists and their /bin order. - scripts/build-kernel.sh: git archive the pinned commit into out/kernel-src and build it with CARGO_TARGET_DIR=out/kernel-target (never touches the kernel repo's tree or target/). - scripts/build-fixtures.sh: the kernel's ABI test programs, compiled from the pinned kernel source with build.rs's exact commands. - scripts/mkimage.sh: GPT + FAT32 with sfdisk/mkfs.vfat/mtools, no root. - scripts/run-qemu.sh, run-headless.sh (moved from the kernel, same expected lines; QEMU PID tracked with -pidfile), compare-image.sh, http-reply.sh and hvc0-session.txt (copied from the kernel at a55b41d).
| A | Makefile | +81 -0 |
| A | config.mk | +50 -0 |
| A | scripts/build-fixtures.sh | +45 -0 |
| A | scripts/build-kernel.sh | +50 -0 |
| A | scripts/compare-image.sh | +51 -0 |
| A | scripts/http-reply.sh | +26 -0 |
| A | scripts/hvc0-session.txt | +5 -0 |
| A | scripts/mkimage.sh | +51 -0 |
| A | scripts/run-headless.sh | +203 -0 |
| A | scripts/run-qemu.sh | +27 -0 |
10 files changed, 589 insertions, 0 deletions
@@ -0,0 +1,81 @@+# Foundry OS -- the single entry point. `make help` lists the targets.+#+# Everything generated goes to out/ (git-ignored); `make clean` removes it.+# Downloads (musl source etc.) go to cache/ (git-ignored, SHA256-checked).++include config.mk++OUT := $(CURDIR)/out+IMAGE := $(OUT)/$(IMAGE_NAME)+STAGING := $(OUT)/staging+RUST_OUT := $(OUT)/rust-target/x86_64-unknown-none/release+FIXTURES := $(OUT)/fixtures+KSRC := $(OUT)/kernel-src+KTARGET := $(OUT)/kernel-target+KBOOT := $(OUT)/kernel/bootimage.bin+KBOOT_HL := $(OUT)/kernel/bootimage-headless.bin++.DEFAULT_GOAL := image+.PHONY: help image rust fixtures staging kernel kernel-src run test compare musl clean distclean++help:+ @echo "Foundry OS (userland for the Rustbelt kernel $(KERNEL_VERSION) @ $(KERNEL_REV))"+ @echo ""+ @echo " make / make image build out/$(IMAGE_NAME), the disk image (contract $(CONTRACT))"+ @echo " make rust cargo build the Rust programs in rust/"+ @echo " make fixtures build the kernel's ABI test programs from the pinned kernel"+ @echo " make staging assemble out/staging (rootfs/ + /bin)"+ @echo " make kernel build the pinned kernel into out/kernel-target"+ @echo " make run boot the image in a QEMU window"+ @echo " make test headless boot + the shell self-test + expected-line checks"+ @echo " make compare compare out/$(IMAGE_NAME) with the kernel's own disk.img"+ @echo " make musl fetch (cache/) + build musl $(MUSL_VERSION) into out/sysroot (not in the image yet)"+ @echo " make clean rm -rf out (distclean also removes cache/)"++# ---- the pinned kernel -------------------------------------------------------+kernel-src:+ scripts/build-kernel.sh $(KERNEL_DIR) $(KERNEL_REV) $(OUT) --export-only++kernel:+ scripts/build-kernel.sh $(KERNEL_DIR) $(KERNEL_REV) $(OUT)++# ---- programs ---------------------------------------------------------------+rust:+ cd rust && CARGO_TARGET_DIR=$(OUT)/rust-target cargo build --release++fixtures: kernel-src+ scripts/build-fixtures.sh $(KSRC) $(FIXTURES) "$(FIXTURE_RUST)" "$(FIXTURE_C)" "$(FIXTURE_MUSL)"++# ---- the image ---------------------------------------------------------------+# Contract v1: rootfs/ minus etc/ (etc/ is for the contract-v2 ext2 root,+# see docs/plan.md) goes to the top of the FAT32 volume, programs to /bin.+staging: rust fixtures+ rm -rf $(STAGING) && mkdir -p $(STAGING)/bin+ cd rootfs && for e in $$(ls -A); do [ "$$e" = etc ] && [ "$(CONTRACT)" = v1 ] && continue; cp -r "$$e" $(STAGING)/; done+ for p in $(filter-out sh,$(RUST_PROGRAMS)); do cp $(RUST_OUT)/$$p $(STAGING)/bin/; done+ cp $(RUST_OUT)/rbsh $(STAGING)/bin/sh # contract v1: the kernel runs /mnt/disk0/bin/sh+ cp $(FIXTURES)/* $(STAGING)/bin/++image: staging+ scripts/mkimage.sh $(STAGING) $(IMAGE) $(IMAGE_MIB) $(IMAGE_LABEL) "$(BIN_ORDER)"++# ---- booting ----------------------------------------------------------------+run: image kernel+ scripts/run-qemu.sh $(IMAGE) $(KBOOT) $(KTARGET) $(OUT)++test: image kernel+ scripts/run-headless.sh $(IMAGE) $(KBOOT_HL) $(KTARGET) $(OUT)++compare: image kernel+ scripts/compare-image.sh $(IMAGE) $(KTARGET)/disk.img $(OUT)++# ---- toolchain (M2 groundwork; not used by the v1 image) ---------------------+musl:+ toolchain/musl/fetch.sh cache+ toolchain/musl/build.sh cache $(OUT)/musl-build $(OUT)/sysroot++clean:+ rm -rf out++distclean: clean+ rm -rf cache
@@ -0,0 +1,50 @@+# Foundry OS build settings. The Makefile includes this file; you can also+# override any value on the command line: `make KERNEL_DIR=~/rk test`.++# ---- The kernel we build against ------------------------------------------+# Foundry OS runs on the Rustbelt kernel. We never build "whatever is checked+# out": we export one exact commit (KERNEL_REV) from the kernel's git store+# into out/kernel-src and build it there, with its own target dir. Bump+# KERNEL_REV deliberately (and re-run `make test`) when the kernel moves on.+KERNEL_DIR ?= /workspace/rust-kernel+KERNEL_REV ?= a55b41d+KERNEL_VERSION ?= 0.19.0++# ---- The image -------------------------------------------------------------+ARCH ?= x86_64+# Boot contract version (docs/boot-contract.md). v1 = today's layout:+# GPT + one FAT32 partition, programs in /bin, kernel runs /mnt/disk0/bin/sh.+CONTRACT ?= v1+IMAGE_NAME ?= foundry.img+IMAGE_MIB ?= 64+# FAT volume label. Kept as the kernel's TYLERDISK under contract v1 so the+# image is the same as the kernel's own disk.img.+IMAGE_LABEL ?= TYLERDISK++# ---- Programs in /bin ------------------------------------------------------+# Built from this repo (rust/):+RUST_PROGRAMS := sh ls cat wc grep mkdir rm touch cp sleep \+ ifconfig ping host fetch httpd getty tile random sysinfo+# ABI test fixtures that stay in the kernel repo (proposal section 7). For+# contract v1 the image still carries them (the self-test runs them), so+# they are compiled from the pinned kernel source in out/kernel-src/user.+FIXTURE_RUST := hello evil spin demo child overflow forktest cowtest \+ mmaptest sigtest threads echoserver ptytest nbtest \+ fputest pathtest libctest coretest unixtest+FIXTURE_C := rawabi+# name:kind, kind = pie (musl-gcc -static-pie), exec (static ET_EXEC at the+# user base) or dyn (dynamic; Rustbelt must refuse it).+FIXTURE_MUSL := vdsotest:exec piehello:pie piethreads:pie dynhello:dyn remaptest:pie++# The order programs are copied into /bin: the same order as the kernel's+# build.rs (PROGRAMS, then C_PROGRAMS, then MUSL_PROGRAMS), so `ls` lists+# them the same way.+BIN_ORDER := sh ls cat wc grep mkdir rm touch cp sleep \+ hello evil spin demo child sysinfo overflow \+ ifconfig ping host fetch httpd random getty tile \+ forktest cowtest mmaptest sigtest threads echoserver ptytest nbtest \+ fputest pathtest libctest coretest unixtest \+ rawabi vdsotest piehello piethreads dynhello remaptest++# ---- Toolchain pins (downloaded into cache/, checked by SHA256) -----------+MUSL_VERSION ?= 1.2.5
@@ -0,0 +1,45 @@+#!/usr/bin/env bash+# Build the kernel's ABI test programs ("fixtures") from the pinned kernel+# source (out/kernel-src/user, exported by build-kernel.sh), with exactly the+# commands the kernel's build.rs uses. Under boot contract v1 the image+# carries them in /bin because the self-test runs them; they are *not* part+# of Foundry OS and their source stays in the kernel repo.+#+# Usage: scripts/build-fixtures.sh KERNEL_SRC OUT_DIR "rust names" "c names" "musl name:kind ..."+set -euo pipefail+SRC=$(realpath "$1")/user OUT=$(realpath -m "$2")+RUST=$3 C=$4 MUSL=$5+mkdir -p "$OUT"+RUSTC=${RUSTC:-rustc}+# Use the kernel's toolchain (rust-toolchain.toml in KERNEL_SRC): run from there.+cd "$SRC/.."+for p in $RUST; do+ "$RUSTC" --edition 2024 --target x86_64-unknown-none \+ -C panic=abort -C opt-level=2 -C debuginfo=0 -C strip=debuginfo \+ -C relocation-model=static -C code-model=large \+ -C "link-arg=-T$SRC/link.ld" -o "$OUT/$p" "$SRC/$p.rs"+done+# No libc at all: raw Linux syscalls, an ordinary static ET_EXEC.+for p in $C; do+ ${CC:-cc} -O2 -static -nostdlib -ffreestanding -fno-stack-protector \+ -fpie -no-pie -Wall -Wextra \+ -Wl,-Ttext-segment=0x8000400000 -Wl,--build-id=none -z noexecstack \+ -o "$OUT/$p" "$SRC/$p.c"+done+# musl (Debian's musl-tools). Rustbelt 0.19.0 loads static-PIE, so "pie"+# is plain `musl-gcc -static-pie` with no tricks; "exec" is the old+# ET_EXEC-at-the-user-base recipe (kept as a fixture for the loader).+MUSL_LIB=/usr/lib/x86_64-linux-musl+for entry in $MUSL; do+ p=${entry%%:*} kind=${entry#*:}+ common=(-O2 -Wall -Wextra -pthread -Wl,--build-id=none)+ case $kind in+ pie) musl-gcc "${common[@]}" -static-pie "$SRC/$p.c" -o "$OUT/$p" ;;+ dyn) musl-gcc "${common[@]}" -fPIE -pie "$SRC/$p.c" -o "$OUT/$p" ;;+ exec) musl-gcc "${common[@]}" -static -no-pie -nostartfiles \+ -Wl,-Ttext-segment=0x8000400000 -Wl,--defsym=_DYNAMIC=0x8000400000 -Wl,--no-relax \+ $MUSL_LIB/crt1.o $MUSL_LIB/crti.o "$SRC/$p.c" $MUSL_LIB/crtn.o -o "$OUT/$p" ;;+ *) echo "unknown musl kind $kind for $p"; exit 1 ;;+ esac+done+echo "[fixtures] $(ls "$OUT" | wc -l) programs in $OUT"
@@ -0,0 +1,50 @@+#!/usr/bin/env bash+# Build the pinned Rustbelt kernel for Foundry OS, without touching the+# kernel repo's working tree or its target/ directory.+#+# 1. `git archive KERNEL_REV` reads the commit straight from the kernel+# repo's git object store and unpacks it into out/kernel-src. Nothing in+# KERNEL_DIR is written.+# 2. cargo builds it there with CARGO_TARGET_DIR=out/kernel-target.+# (Cargo reads .cargo/config.toml and rust-toolchain.toml from the+# directory it runs in, so we must cd into the kernel source first.)+# 3. Two boot images are kept: out/kernel/bootimage-headless.bin (runs the+# self-test, then powers QEMU off) and out/kernel/bootimage.bin+# (interactive).+#+# Side effect we rely on: the kernel's build.rs also writes its own disk+# images into out/kernel-target: disk.img (the reference for `make compare`)+# and the kernel-owned test disks ext2.img, exfat.img, btrfs.img, which+# `make run`/`make test` attach as drives C:, D:, E: (contract v1, point 5).+#+# Usage: scripts/build-kernel.sh KERNEL_DIR KERNEL_REV OUT_DIR [--export-only]+set -euo pipefail+KERNEL_DIR=$1 KERNEL_REV=$2 OUT=$(realpath -m "$3")+SRC=$OUT/kernel-src TARGET=$OUT/kernel-target DEST=$OUT/kernel++want=$(git -C "$KERNEL_DIR" rev-parse --verify "$KERNEL_REV^{commit}")+if [ "$(cat "$SRC/.foundry-kernel-rev" 2>/dev/null)" != "$want" ]; then+ echo "[kernel] exporting $KERNEL_REV ($want) from $KERNEL_DIR into $SRC"+ rm -rf "$SRC" && mkdir -p "$SRC"+ git -C "$KERNEL_DIR" archive "$want" | tar -x -C "$SRC"+ echo "$want" > "$SRC/.foundry-kernel-rev"+fi+[ "${4:-}" = --export-only ] && exit 0+mkdir -p "$TARGET" "$DEST"+cd "$SRC"+export CARGO_TARGET_DIR=$TARGET+ELF=$TARGET/x86_64-rust_kernel/debug/rustbelt+BOOT=$TARGET/x86_64-rust_kernel/debug/bootimage-rustbelt.bin+for variant in headless interactive; do+ if [ $variant = headless ]; then feat=(--features headless); out=$DEST/bootimage-headless.bin+ else feat=(); out=$DEST/bootimage.bin; fi+ echo "[kernel] cargo bootimage ${feat[*]:-} (target dir $TARGET)"+ cargo build "${feat[@]}"+ # bootimage caches by timestamp; switching features can reuse an old+ # ELF, so touch it (the kernel's scripts/runner.sh does the same).+ touch "$ELF"+ cargo bootimage "${feat[@]}"+ cp "$BOOT" "$out"+done+echo "$want" > "$DEST/rev"+echo "[kernel] done: $DEST/bootimage{,-headless}.bin, test disks in $TARGET"
@@ -0,0 +1,51 @@+#!/usr/bin/env bash+# M1 check: compare our image with the kernel's own disk.img, file by file.+# Both are GPT + FAT32; mtools reads the partition at byte offset 1 MiB+# (`-i image@@1M`), so nothing is mounted.+#+# Prints: the /bin program lists side by side (must be equal), the other+# files (must be equal, byte for byte), and for each program whether the+# bytes are identical. Rust programs built by Cargo are expected to differ+# in bytes from the kernel's single-file rustc builds (Cargo adds crate+# metadata), so only list equality and non-program files fail the check.+#+# Usage: scripts/compare-image.sh OURS KERNELS OUT_DIR+set -euo pipefail+OURS=$1 THEIRS=$2 OUT=$(realpath -m "$3")/compare+export MTOOLS_SKIP_CHECK=1+rm -rf "$OUT" && mkdir -p "$OUT/ours" "$OUT/kernel"+mcopy -s -n -i "$OURS@@1M" '::/*' "$OUT/ours/"+mcopy -s -n -i "$THEIRS@@1M" '::/*' "$OUT/kernel/"++(cd "$OUT/ours/bin" && ls | LC_ALL=C sort) > "$OUT/bin-ours.txt"+(cd "$OUT/kernel/bin" && ls | LC_ALL=C sort) > "$OUT/bin-kernel.txt"+fail=0+echo "== /bin program list: ours $(wc -l < "$OUT/bin-ours.txt"), kernel $(wc -l < "$OUT/bin-kernel.txt")"+if diff -u "$OUT/bin-kernel.txt" "$OUT/bin-ours.txt"; then+ echo " same list"+else+ echo " LISTS DIFFER (- kernel, + ours)"; fail=1+fi++echo "== files outside /bin (must be byte-identical)"+(cd "$OUT/ours" && find . -path ./bin -prune -o -type f -print | LC_ALL=C sort) > "$OUT/files-ours.txt"+(cd "$OUT/kernel" && find . -path ./bin -prune -o -type f -print | LC_ALL=C sort) > "$OUT/files-kernel.txt"+if ! diff -u "$OUT/files-kernel.txt" "$OUT/files-ours.txt"; then fail=1; fi+while read -r f; do+ if [ -f "$OUT/kernel/$f" ] && cmp -s "$OUT/ours/$f" "$OUT/kernel/$f"; then echo " same $f"+ else echo " DIFFERENT $f"; fail=1; fi+done < "$OUT/files-ours.txt"++echo "== /bin bytes (Rust programs from rust/ may differ: Cargo vs plain rustc)"+same=0 diff=0+while read -r p; do+ [ -f "$OUT/kernel/bin/$p" ] || continue+ a=$(stat -c %s "$OUT/kernel/bin/$p") b=$(stat -c %s "$OUT/ours/bin/$p")+ if cmp -s "$OUT/kernel/bin/$p" "$OUT/ours/bin/$p"; then+ printf ' same %-12s %8d bytes\n' "$p" "$a"; same=$((same + 1))+ else+ printf ' different %-12s %8d -> %8d bytes\n' "$p" "$a" "$b"; diff=$((diff + 1))+ fi+done < "$OUT/bin-ours.txt"+echo "== $same identical, $diff different in bytes; extracted trees in $OUT"+[ $fail = 0 ] && echo "[compare] OK: same program list and same non-program files" || { echo "[compare] FAILED"; exit 1; }
@@ -0,0 +1,26 @@+#!/bin/sh+# A one-shot HTTP/1.0 server on stdin/stdout, for QEMU's guestfwd:+# -netdev user,...,guestfwd=tcp:10.0.2.100:80-cmd:sh scripts/http-reply.sh+# QEMU runs this once per connection the guest makes to 10.0.2.100:80, with+# the connection as stdin/stdout. No host port is opened, so tests can't+# collide with anything else on the machine.+# GET / -> a short greeting+# GET /big -> `seq 1 20000` (108894 bytes: exercises TCP flow control)+# other -> 404+cr=$(printf '\r')+read -r method path version+while IFS= read -r line; do # skip the request headers+ [ "$line" = "$cr" ] || [ -z "$line" ] && break+done+reply() { # status, body+ printf 'HTTP/1.0 %s\r\nContent-Type: text/plain\r\nContent-Length: %s\r\nServer: rustbelt-test\r\n\r\n%s' \+ "$1" "$(printf '%s' "$2" | wc -c)" "$2"+}+case "$path" in+ /) reply "200 OK" "Hello from the host, via QEMU guestfwd!+" ;;+ /big) printf 'HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 108894\r\n\r\n'+ seq 1 20000 ;;+ *) reply "404 Not Found" "not found+" ;;+esac
@@ -0,0 +1,5 @@+ver+echo hi over hvc0+random -d 20 3+ls /dev+exit 0
@@ -0,0 +1,51 @@+#!/usr/bin/env bash+# Turn the staging directory into a disk image (boot contract v1):+# * a raw file of IMAGE_MIB MiB,+# * a GPT partition table (sfdisk) with one partition at sector 2048,+# * a FAT32 file system in it (mkfs.vfat), filled with mtools.+# mtools (mmd/mcopy) write straight into the image file, so no root and no+# loop-mounting is needed. This is what the kernel's build.rs does.+#+# Usage: scripts/mkimage.sh STAGING IMAGE MIB LABEL "bin order..."+# The /bin files are copied in the given order (same as the kernel's+# build.rs), then any other file in STAGING/bin.+set -euo pipefail+STAGING=$1 IMAGE=$2 MIB=$3 LABEL=$4 ORDER=$5+PATH=$PATH:/usr/sbin:/sbin+export MTOOLS_SKIP_CHECK=1++SECTORS=$((MIB * 1024 * 1024 / 512))+START=2048+PART_SECTORS=$((SECTORS - 2 * START)) # 1 MiB free at the end for the backup GPT+PART=$IMAGE.part+rm -f "$PART" "$IMAGE"++# 1. The FAT32 file system as its own file first. -s 1: one sector per+# cluster (FAT32 needs >= 65525 clusters). -i: a fixed volume serial+# number, so two builds of the same tree give the same bytes.+mkfs.vfat -F 32 -s 1 -n "$LABEL" -i 466f6e64 -C "$PART" $((PART_SECTORS / 2)) >/dev/null++# Top-level entries (everything except bin/) in sorted order...+for entry in $(cd "$STAGING" && ls -A | LC_ALL=C sort); do+ [ "$entry" = bin ] && continue+ mcopy -s -i "$PART" "$STAGING/$entry" ::/+done+# ...then the programs.+mmd -i "$PART" ::/bin+done_list=" "+for p in $ORDER; do+ [ -f "$STAGING/bin/$p" ] || { echo "[mkimage] missing $STAGING/bin/$p"; exit 1; }+ mcopy -i "$PART" "$STAGING/bin/$p" "::/bin/$p"+ done_list+="$p "+done+for f in $(cd "$STAGING/bin" && ls | LC_ALL=C sort); do+ case "$done_list" in *" $f "*) ;; *) mcopy -i "$PART" "$STAGING/bin/$f" "::/bin/$f" ;; esac+done++# 2. The whole disk: zeros + GPT, then the FAT32 file system in the partition.+truncate -s $((SECTORS * 512)) "$IMAGE"+printf 'label: gpt\nstart=%d, size=%d, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, name="fat32"\n' \+ $START $PART_SECTORS | sfdisk --quiet "$IMAGE"+dd if="$PART" of="$IMAGE" bs=512 seek=$START conv=notrunc status=none+rm -f "$PART"+echo "[mkimage] $IMAGE: $MIB MiB, GPT + FAT32 '$LABEL', $(ls "$STAGING/bin" | wc -l) programs in /bin"
@@ -0,0 +1,203 @@+#!/usr/bin/env bash+# Boot the Foundry OS image headless on the pinned Rustbelt kernel, let the+# kernel run the shell self-test (B:/scripts/selftest.sh from rootfs/), and+# check the output. Moved from the kernel's scripts/run-headless.sh; the+# expected lines are the same, so a pass here means "same behaviour as the+# kernel's own image".+#+# Usage: scripts/run-headless.sh IMAGE KERNEL_BOOTIMAGE KERNEL_TARGET OUT_DIR+# IMAGE our disk image (drive B:, /mnt/disk0)+# 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: 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+mkdir -p "$OUT"+for f in "$IMAGE" "$IMG" "$KT/ext2.img" "$KT/exfat.img" "$KT/btrfs.img"; do+ [ -f "$f" ] || { echo "[run-headless] missing $f (run make image / make kernel)"; exit 2; }+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] disk 0 (B:) = $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 \+ -drive file="$IMAGE",format=raw,if=virtio,snapshot=on \+ -drive file="$KT/ext2.img",format=raw,if=virtio,snapshot=on \+ -drive file="$KT/exfat.img",format=raw,if=virtio,snapshot=on \+ -drive file="$KT/btrfs.img",format=raw,if=virtio,snapshot=on \+ -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 ]; then+ expect=(+ "Rustbelt 0.19.0 (stage 19: vDSO and static PIE) 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 <DIR> 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:/+ " <DIR> 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 ["+ "[net] DHCP lease: 10.0.2.15/24, gateway 10.0.2.2, DNS 10.0.2.3" # net/+ " inet 10.0.2.15/24 gateway 10.0.2.2 DNS 10.0.2.3 (DHCP)" # ifconfig+ "64 bytes from 10.0.2.2: icmp_seq=3" # ping the slirp gateway+ "3 packets transmitted, 3 received, 0% packet loss"+ "Hello from the host, via QEMU guestfwd!" # fetch (TCP + HTTP)+ "fetch: HTTP/1.0 200 OK, saved 108894 bytes to /mnt/disk2/seq.txt" # wget -o D:+ "[httpd] served 1 request, bye" # httpd, reached via hostfwd+ "[virtio] rng: virtio-rng at 00:04.0" # stage 13: virtio/+ "[virtio] hvc0: virtio-console at 00:05.0"+ "[virtio] gpu: virtio-gpu 2D at 00:03.0, display 1280x800, framebuffer 1024x768"+ "[virtio] fbcon: text console on the framebuffer, 128x48 characters"+ "crw-rw---- <DEV> fb0" # ls -l /dev (Linux modes since stage 19)+ "crw------- <DEV> hvc0"+ "tile: 4 windows on 1024x768, console 61x46" # the tiling demo+ "console: full screen, 128x48 characters" # tile -r+ "[forktest] all checks passed" # stage 14: processes+ "[forktest args] all checks passed" # exec'd with argv/envp/auxv+ "[cowtest] all checks passed"+ "[mmaptest] all checks passed"+ "[sigtest] all checks passed"+ "(start spin 1000), exit code 143" # kill %1 (SIGTERM)+ "15) SIGTERM" # kill -l+ "[threads] all checks passed" # stage 15: libc plumbing+ "[echoserver] all checks passed"+ "[ptytest] all checks passed"+ "[nbtest] all checks passed"+ "[fputest] all checks passed" # stage 15.1: musl prerequisites+ "[pathtest] all checks passed"+ "rawabi: uname: Rustbelt 0.19.0" # a no-libc gcc binary+ "rawabi: all checks passed"+ "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` -> 44 (the programs),+ # `cat C:/chunks.txt | wc -l` (copied from E:) -> 3,+ # `type D:/COW.TXT | wc -l` (copied from E:) -> 4, and+ # `cat D:/seq.txt | wc -l` (downloaded by wget) -> 20000.+ for n in 1 44 3 4 20000; 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+ # The other direction: the host fetched a page from httpd in the guest.+ if grep -qF "Hello from Rustbelt! Rustbelt 0.19.0" "$CURL_OUT" 2>/dev/null; then+ echo "[run-headless] host curl got httpd's page: $(head -1 "$CURL_OUT")"+ else+ echo "[run-headless] MISSING: host curl through hostfwd to httpd"; missing=1+ fi+ # random -n 8: 16 hex digits; random -d 6 5: five numbers 1-6.+ grep -qxE -- "[0-9a-f]{16}" "$LOG" || { echo "[run-headless] MISSING: random -n 8 output"; missing=1; }+ grep -qxE -- "[1-6]( [1-6]){4}" "$LOG" || { echo "[run-headless] MISSING: random -d 6 5 output"; missing=1; }+ # hvc0 is a tty now: its output turns "\n" into "\r\n" (ONLCR), so+ # drop the '\r's before comparing whole lines.+ tr -d '\r' < "$HVC_OUT" > "$HVC_OUT.txt" 2>/dev/null && mv "$HVC_OUT.txt" "$HVC_OUT"+ # The shell on hvc0 ran the host's commands and answered over hvc0.+ for line in "Rustbelt getty: shell on this terminal" "Rustbelt 0.19.0 (stage 19" \+ "$ echo hi over hvc0" "hi over hvc0" "fb0" "hvc0"; do+ grep -qF -- "$line" "$HVC_OUT" 2>/dev/null || { echo "[run-headless] MISSING on hvc0: $line"; missing=1; }+ done+ grep -qxE -- "[0-9]+ [0-9]+ [0-9]+" "$HVC_OUT" || { echo "[run-headless] MISSING on hvc0: random -d 20 3"; missing=1; }+ [ $missing = 0 ] && echo "[run-headless] all ${#expect[@]}+12 expected shell and hvc0 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
@@ -0,0 +1,27 @@+#!/usr/bin/env bash+# Boot the Foundry OS image interactively on the pinned Rustbelt kernel:+# a QEMU window (or add NOGRAPHIC=1 for serial only), our image as disk 0+# (B:), the kernel's test disks as C:, D:, E:. Same devices as the kernel's+# `cargo run`. The hvc0 console is on the unix socket OUT/hvc0.sock:+# socat - UNIX-CONNECT:out/hvc0.sock (then `getty /dev/hvc0 &` in Foundry)+#+# Usage: scripts/run-qemu.sh IMAGE KERNEL_BOOTIMAGE KERNEL_TARGET OUT_DIR+set -euo pipefail+cd "$(dirname "$0")/.."+IMAGE=$1 IMG=$2 KT=$3 OUT=$4+display=()+[ -n "${NOGRAPHIC:-}" ] && display=(-display none)+exec qemu-system-x86_64 -smp 4 -cpu "${QEMU_CPU:-max}" \+ -pidfile "$OUT/qemu-run.pid" \+ -drive format=raw,file="$IMG",snapshot=on \+ -drive file="$IMAGE",format=raw,if=virtio,snapshot=on \+ -drive file="$KT/ext2.img",format=raw,if=virtio,snapshot=on \+ -drive file="$KT/exfat.img",format=raw,if=virtio,snapshot=on \+ -drive file="$KT/btrfs.img",format=raw,if=virtio,snapshot=on \+ -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,hostfwd=tcp:127.0.0.1:5580-:80" \+ -device virtio-net-pci,netdev=net0 \+ -vga none -device virtio-vga -device virtio-rng-pci \+ -device virtio-serial-pci -device virtconsole,chardev=hvc \+ -chardev "socket,id=hvc,path=$OUT/hvc0.sock,server=on,wait=off" \+ -device isa-debug-exit,iobase=0xf4,iosize=0x04 \+ -serial stdio "${display[@]}"