foundry
Foundry OS: the operating system built on the Rustbelt kernel
| author | Grok Bot <grokbot@localhost> | 2026-10-31 19:22:37 -0700 |
|---|---|---|
| commit | 3ca471d9454c01b98f035fe0ee9b070c1c66b9ee | |
| parent | baf7b2d1a4ccb7e2240203ce9b7601225dd6bf12 | |
| tree | browse files at this commit | |
Install dash as /bin/dash (not /bin/sh) and run 15 dash script checks in make test - make ports (config.mk FOUNDRY_PORTS); the image gets /bin/dash: 50 programs. - rootfs/scripts/dash-test.sh, run from selftest.sh's Foundry OS block. - expect-foundry.txt: dash-test lines; @BIN_COUNT@ checks the glob sees all of /bin. - compare-image.sh: FOUNDRY_FILES (scripts/dash-test.sh) left out. - docs: ports/README (dash), plan (M2b done), README make targets.
| M | Makefile | +15 -4 |
| M | README.md | +6 -4 |
| M | config.mk | +5 -0 |
| M | docs/bumping-the-kernel.md | +1 -1 |
| M | docs/plan.md | +1 -0 |
| A | rootfs/scripts/dash-test.sh | +54 -0 |
| M | rootfs/scripts/selftest.sh | +3 -0 |
| M | scripts/compare-image.sh | +9 -3 |
| M | scripts/expect-foundry.txt | +11 -1 |
| M | scripts/run-headless.sh | +1 -0 |
10 files changed, 106 insertions, 13 deletions
@@ -18,10 +18,11 @@ SYSROOT := $(OUT)/sysroot FCC := $(CURDIR)/toolchain/foundry-cc C_OUT := $(OUT)/c # Everything in /bin, in order: the kernel's list, then Foundry OS's own.-ALL_BIN := $(BIN_ORDER) $(FOUNDRY_C)+ALL_BIN := $(BIN_ORDER) $(FOUNDRY_C) $(FOUNDRY_PORTS)+PORTS_OUT := $(OUT)/ports .DEFAULT_GOAL := image-.PHONY: help image rust fixtures cprogs staging kernel kernel-src run test test-reference compare musl clean distclean+.PHONY: help image rust fixtures cprogs ports staging kernel kernel-src run test test-reference compare musl clean distclean help: @echo "Foundry OS (userland for the Rustbelt kernel $(KERNEL_VERSION) @ $(KERNEL_REV))"@@ -30,6 +31,7 @@ help: @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 cprogs build our C programs (c/) as static-PIEs with toolchain/foundry-cc"+ @echo " make ports fetch (cache/) + build the ports ($(FOUNDRY_PORTS)) the same way" @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"@@ -61,16 +63,25 @@ cprogs: $(SYSROOT)/lib/musl-pie.specs done @echo "[cprogs] built $(FOUNDRY_C) into $(C_OUT)" +# Ports: each one is fetched into cache/ (SHA256-checked) and built once;+# delete out/ports/bin/NAME to rebuild it.+ports: $(addprefix $(PORTS_OUT)/bin/,$(FOUNDRY_PORTS))++$(PORTS_OUT)/bin/%: $(SYSROOT)/lib/musl-pie.specs ports/%/build.sh+ ports/fetch.sh $* cache+ ports/$*/build.sh cache $(PORTS_OUT)/$*-build $@+ # ---- 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 cprogs+staging: rust fixtures cprogs ports 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/ for p in $(FOUNDRY_C); do cp $(C_OUT)/$$p $(STAGING)/bin/; done+ for p in $(FOUNDRY_PORTS); do cp $(PORTS_OUT)/bin/$$p $(STAGING)/bin/; done image: staging scripts/mkimage.sh $(STAGING) $(IMAGE) $(IMAGE_MIB) $(IMAGE_LABEL) "$(ALL_BIN)"@@ -93,7 +104,7 @@ test-reference: kernel scripts/run-headless.sh $(KTARGET)/disk.img $(KBOOT_HL) $(KTARGET) $(OUT)/reference compare: image kernel- FOUNDRY_BIN="$(FOUNDRY_C)" scripts/compare-image.sh $(IMAGE) $(KTARGET)/disk.img $(OUT)+ FOUNDRY_BIN="$(FOUNDRY_C) $(FOUNDRY_PORTS)" FOUNDRY_FILES="$(FOUNDRY_FILES)" scripts/compare-image.sh $(IMAGE) $(KTARGET)/disk.img $(OUT) # ---- toolchain --------------------------------------------------------------- # musl is built once; delete out/sysroot (or run `make musl`) to rebuild it.
@@ -27,15 +27,17 @@ the packages the kernel needs plus `make` (all already on the box): | Target | What it does | |---|---|-| `make image` | builds the Rust programs, the kernel's test fixtures, assembles `out/staging/`, and writes `out/foundry.img` |+| `make image` | builds the Rust programs, our C programs and ports, the kernel's test fixtures, assembles `out/staging/`, and writes `out/foundry.img` | | `make rust` | `cargo build --release` in `rust/` | | `make fixtures` | builds the kernel's ABI test programs from the pinned kernel source |+| `make cprogs` | builds our C programs (`c/`: hello-musl, fprobe) as static-PIEs with `toolchain/foundry-cc` |+| `make ports` | downloads (SHA256-checked, into `cache/`) and builds the ports (dash) the same way | | `make kernel` | builds the pinned Rustbelt kernel into `out/kernel-target` | | `make run` | boots the image in a QEMU window (`NOGRAPHIC=1` for serial only) |-| `make test` | headless boot, self-test, ~90 expected-output checks; exit 0 = pass |+| `make test` | headless boot, self-test, ~100 expected-output checks (the kernel's, plus ours from `scripts/expect-foundry.txt`); exit 0 = pass | | `make test-reference` | the same test on the kernel's *own* disk image (the baseline) | | `make compare` | compares our image with the kernel's, file by file |-| `make musl` | downloads (into `cache/`) and builds musl into `out/sysroot`; then `toolchain/foundry-cc` builds static-PIE C programs (M2) |+| `make musl` | downloads (into `cache/`) and builds musl into `out/sysroot`; `make cprogs`/`make ports` run it when needed | | `make clean` | `rm -rf out` (`make distclean` also empties `cache/`) | ## What is where@@ -54,7 +56,7 @@ foundry/ │ └── etc/ passwd, hosts... for the later ext2 root (not in the image yet) ├── toolchain/ foundry-cc (gcc + musl, static-PIE); musl/ pinned by SHA256, downloaded into cache/ ├── c/ our own C programs: hello-musl, fprobe (the libc self-check)-├── ports/ other people's C programs, one directory each (dash first)+├── ports/ other people's C programs, one directory each (dash: /bin/dash) ├── scripts/ mkimage, build-kernel, run-qemu, run-headless, compare-image... ├── docs/ how it all fits together (start with docs/glossary.md) ├── out/ everything built (git-ignored)
@@ -58,6 +58,11 @@ BIN_ORDER := sh ls cat wc grep mkdir rm touch cp sleep \ # programs above, and the self-test runs them (rootfs/scripts/selftest.sh, # the "Foundry OS" block; expected lines in scripts/expect-foundry.txt). FOUNDRY_C := hello-musl fprobe+# Third-party programs from ports/NAME (pinned tarball in cache/, built+# static-PIE with foundry-cc), installed as /bin/NAME.+FOUNDRY_PORTS := dash+# Files outside /bin that only Foundry OS's image has (make compare skips them).+FOUNDRY_FILES := scripts/dash-test.sh # ---- Toolchain pins (downloaded into cache/, checked by SHA256) ----------- MUSL_VERSION ?= 1.2.5
@@ -60,7 +60,7 @@ there, with its own target dir (`out/kernel-target`). `BIN_COUNT` (the `dir bin | wc -l` result) from the Makefile, so the expected lines don't need hand edits for a plain version bump. - `scripts/expect-foundry.txt`: Foundry OS's own expected lines; it may use- `@KERNEL_VERSION@`.+ `@KERNEL_VERSION@` and `@BIN_COUNT@`. ## History of bumps
@@ -38,6 +38,7 @@ Inside the ext2 root: | **M1** | Foundry OS builds the same image; the kernel's self-test passes on it | **done**: `make test` passes; `make compare` shows the same 44 programs (47 since the 0.20.0 pin) and identical non-program files | | M1b | `rbrt` becomes a real library crate; rbsh's parser tests run on the host | next | | **M2** | musl hello + probes in `/bin`, built static-PIE with `toolchain/foundry-cc` | **done**: `hello-musl` and `fprobe` (40 checks incl. timerfd) in `/bin`, checked by `make test` |+| M2b | dash built from ports/ as `/bin/dash`, with script checks in `make test` | **done** | | M3 | dash as `/bin/sh`, ext2 root (contract v2), Rust `/sbin/init` | needs kernel K5 | | M4 | sbase tools | needs testing per tool (docs/syscall-gaps.md) | | M5 | busybox in `/usr/lib/busybox` | `bzip2` for the tarball |
@@ -0,0 +1,54 @@+# dash-test.sh: POSIX shell checks for dash (Foundry OS, milestone M2).+# The self-test runs it as `dash /mnt/disk0/scripts/dash-test.sh`. Each+# check prints "dash-test: ok NAME" or "dash-test: FAIL NAME"; the exit+# status is the number of failures.+fails=0 checks=0+check() { # check NAME COMMAND... (passes when COMMAND succeeds)+ name=$1; shift+ checks=$((checks + 1))+ if "$@"; then echo "dash-test: ok $name"+ else echo "dash-test: FAIL $name"; fails=$((fails + 1)); fi+}+eq() { [ "$1" = "$2" ] || { echo " got '$1', want '$2'"; return 1; }; }++echo "dash-test: dash running, pid $$"+x=6+check arithmetic eq "$((x * 7))" 42+s=; for i in 1 2 3; do s="$s$i"; done+check for-loop eq "$s" 123+f() { echo "$#:$*"; return 3; }+out=$(f a b c); st=$?+check function-and-return eq "$out/$st" "3:a b c/3"+case foo.txt in *.txt) c=yes ;; *) c=no ;; esac+check case-glob eq "$c" yes+n=0; for p in /mnt/disk0/bin/*; do n=$((n + 1)); done+echo "dash-test: $n programs in /mnt/disk0/bin"+check pipe-to-rust-grep eq "$(printf 'apple\nbanana\n' | grep ban)" banana+echo "written by dash" > /tmp/dash-test.txt+read line < /tmp/dash-test.txt+check redirect-and-read eq "$line" "written by dash"+cat /tmp/no-such-file 2>/dev/null; st=$?+check missing-file-status [ "$st" -ne 0 ]+heredoc=$(cat <<END+here $x+END+)+check here-document eq "$heredoc" "here 6"+(exit 7); st=$?+check subshell-exit-status eq "$st" 7+sleep 1 &+bg=$!+wait "$bg"; st=$?+check background-and-wait eq "$st" 0+tests() { [ -d /tmp ] && [ -x /mnt/disk0/bin/dash ] && [ ! -e /tmp/nope ]; }+check test-builtins tests+got=; trap 'got=USR1' USR1; kill -USR1 $$+check trap-signal eq "$got" USR1+rm /tmp/dash-test.txt+check rm-rust-tool [ ! -e /tmp/dash-test.txt ]+v=$(sh_var=inner; echo "$sh_var"); check command-substitution eq "$v" inner+set -- one "two words" three+check positional-params eq "$#:$2" "3:two words"+if [ $fails = 0 ]; then echo "dash-test: all $checks checks passed"+else echo "dash-test: $fails of $checks checks FAILED"; fi+exit $fails
@@ -124,5 +124,8 @@ pwd # threads, mmap, clocks, timerfd) and prints a summary line at the end. hello-musl fprobe+# dash (ports/dash), a POSIX sh built the same way, runs a script of checks.+# It is /bin/dash, not /bin/sh: the kernel starts /bin/sh, which is rbsh.+dash /mnt/disk0/scripts/dash-test.sh # ---- end Foundry OS ---- exit 0
@@ -10,10 +10,12 @@ # metadata), so only list equality and non-program files fail the check. # # Since M2 our image has more than the kernel's: Foundry OS's own programs-# (env FOUNDRY_BIN, left out of the list comparison) and a "Foundry OS"-# block in scripts/selftest.sh (cut out before comparing that file).+# (env FOUNDRY_BIN, left out of the list comparison), files of its own+# outside /bin (env FOUNDRY_FILES, paths relative to the image root, left+# out) and a "Foundry OS" block in scripts/selftest.sh (cut out before+# comparing that file). #-# Usage: [FOUNDRY_BIN="prog ..."] scripts/compare-image.sh OURS KERNELS OUT_DIR+# Usage: [FOUNDRY_BIN="prog ..."] [FOUNDRY_FILES="path ..."] scripts/compare-image.sh OURS KERNELS OUT_DIR set -euo pipefail fail=0 OURS=$1 THEIRS=$2 OUT=$(realpath -m "$3")/compare@@ -26,6 +28,10 @@ mcopy -s -n -i "$THEIRS@@1M" '::/*' "$OUT/kernel/" grep -vxF -f <(printf '%s\n' ${FOUNDRY_BIN:-} '') "$OUT/bin-ours-all.txt" > "$OUT/bin-ours.txt" || true echo "== Foundry OS's own programs (not in the kernel's image): ${FOUNDRY_BIN:-none}" for p in ${FOUNDRY_BIN:-}; do [ -f "$OUT/ours/bin/$p" ] || { echo " MISSING /bin/$p"; fail=1; }; done+for f in ${FOUNDRY_FILES:-}; do+ if [ -f "$OUT/ours/$f" ]; then echo " Foundry OS file: $f (not compared)"; rm "$OUT/ours/$f"+ else echo " MISSING $f"; fail=1; fi+done # Our selftest.sh = the kernel's + a "Foundry OS" block; compare without it. sed -i '/^# ---- Foundry OS ----$/,/^# ---- end Foundry OS ----$/d' "$OUT/ours/scripts/selftest.sh" (cd "$OUT/kernel/bin" && ls | LC_ALL=C sort) > "$OUT/bin-kernel.txt"
@@ -2,7 +2,8 @@ # block at the end of rootfs/scripts/selftest.sh). `make test` passes this # file to scripts/run-headless.sh as EXTRA_EXPECT; `make test-reference` # (the kernel's own image) doesn't. One line per check, matched as a fixed-# string anywhere in the serial log. @KERNEL_VERSION@ = config.mk's value.+# string anywhere in the serial log. @KERNEL_VERSION@ = config.mk's value,+# @BIN_COUNT@ = the number of programs in /bin. # Blank lines and lines starting with # are skipped. # hello-musl: a static-PIE C program on our musl.@@ -18,3 +19,12 @@ fprobe: Foundry OS libc self-check on Rustbelt @KERNEL_VERSION@ timerfd wakes epoll_wait ok TFD_TIMER_CANCEL_ON_SET -> EINVAL (Rustbelt) ok fprobe: all 40 checks passed++# dash (ports/dash) running rootfs/scripts/dash-test.sh. @BIN_COUNT@ = the+# number of programs in /bin; the glob in the script must see all of them.+dash-test: ok test-builtins+dash-test: ok background-and-wait+dash-test: ok trap-signal+dash-test: ok pipe-to-rust-grep+dash-test: @BIN_COUNT@ programs in /mnt/disk0/bin+dash-test: all 15 checks passed
@@ -167,6 +167,7 @@ if [ "$code" = 33 ]; then while IFS= read -r line; do case $line in ''|'#'*) continue ;; esac line=${line//@KERNEL_VERSION@/$KERNEL_VERSION}+ line=${line//@BIN_COUNT@/$BIN_COUNT} expect+=("$line") done < "$EXTRA_EXPECT" fi