foundry
Foundry OS: the operating system built on the Rustbelt kernel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | #!/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.
IMAGE= IMG= KT= OUT=
CONTRACT=
STAGE=
done
for; do
case in file=*) f=; f=
[ || { ; ; } ;; esac
done
PIDFILE=/qemu-headless.pid
# 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=
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=",hostfwd=tcp:127.0.0.1:-:80"
CURL_OUT=/headless-curl.txt
( for; do
&& [ && 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=/headless-hvc0.txt
VIRTIO_DEVICES=(-vga none -device virtio-vga -device virtio-rng-pci
-device virtio-serial-pci -device virtconsole,chardev=hvc
-chardev "file,id=hvc,path=,input-path=scripts/hvc0-session.txt")
LOG=/headless-serial.log
|
code=
;
# Lines the shell self-test must have printed (same list as the kernel repo at the pinned commit).
if [ && [; 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=("" 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 [; then
PIPE_RESULTS=(1 "" 3 4 20000)
FRAMES_RE='^\[memory\] ([0-9]+) frames in use before the shell, \1 after'
expect=(
" 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 " # a no-libc gcc binary
"rawabi: all checks passed"
"sh: nosuchprogram: command not found"
"cd: there is no drive Q:"
"A:/tmp $ pwd"
)
fi
if [; then
if [; then
while ; do
case in ''|'#'*) continue ;; esac
line=
line=
line=
line=
expect+=("")
done
fi
missing=0
for; do
|| { ; missing=1; }
done
# Pipe results (v1): `... | grep -i EXT | wc -l` -> 1, `dir bin | wc -l` -> $BIN_COUNT (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. v2: see above.
for; do
|| { ; 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 ; then
else
; missing=1
fi
# The other direction: the host fetched a page from httpd in the guest.
if ; then
else
; missing=1
fi
# random -n 8: 16 hex digits; random -d 6 5: five numbers 1-6.
|| { ; missing=1; }
|| { ; missing=1; }
# hvc0 is a tty now: its output turns "\n" into "\r\n" (ONLCR), so
# drop the '\r's before comparing whole lines.
&&
# The shell on hvc0 ran the host's commands and answered over hvc0.
for; do
|| { ; missing=1; }
done
|| { ; missing=1; }
if [; then
# init adopted and reaped at least one orphan (c/orphan + v2-test.sh make some).
if ; then
else
; missing=1
fi
fi
[ && || code=35
fi
# isa-debug-exit: QEMU exit status = (value << 1) | 1, so Success(0x10) -> 33.
case in
33) ; ;;
35) ; ;;
124) ; ;;
*) ; ;;
esac
|