RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

foundry / docs / kernel-requests.md
7436 bytes ยท raw
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
# Requests to the kernel side (Rustbelt)

The kernel is Edward's lane; Foundry OS never edits `/workspace/rust-kernel`.
These are requests. The full list (K0-K11) with background is in
[proposal.md]proposal.md, section 8. K12 (below) is new since M3.

## How M1 works without any kernel change

The kernel's `build.rs` always builds *all* programs in its `user/` and its
own `target/disk.img`. That does **not** stop us: the disk image is not
inside the kernel's boot image. It is just a `-drive` argument to QEMU. So
`make test`/`make run` boot the kernel's unchanged boot image with **our**
`out/foundry.img` as the first virtio disk. The kernel's own `disk.img`
(built into our `out/kernel-target`) is only used as the reference for
`make compare` and `make test-reference`.

No workaround in the kernel build was needed. The only costs today:

- the kernel build still compiles the moved programs (wasted seconds), and
- the kernel's own `cargo run`/`cargo test` use the kernel's `disk.img`, not
  Foundry OS's. `Cargo.toml`'s `run-args`/`test-args` say
  `file=target/disk.img`, a relative path that also assumes the default
  target dir.

## K1: `RUSTBELT_DISK0` (small, optional for M1, wanted before the kernel deletes the moved programs)

> **Status: landed in Rustbelt 0.21.0 (50476f4).** Edward took this patch
> and went further: `CARGO_TARGET_DIR` is honored everywhere (his scripts
> and `Cargo.toml` paths, via the new `scripts/qemu.sh` runner), and the 19
> moved programs aren't even built when `RUSTBELT_DISK0` is set.
> `make k1-check` builds the pinned kernel that way in its own target dir
> (`out/kernel-k1-target`) and checks both: the kernel's `disk.img` is
> byte-identical to `out/foundry.img`, and no moved program was built.
> We still build the reference kernel *without* the variable, because
> `make test-reference` and `make compare` need the kernel's own disk.img.

**Request:** in `build.rs`, if the environment variable `RUSTBELT_DISK0` is
set to an image file, copy that file to `$CARGO_TARGET_DIR/disk.img` (or
`target/disk.img`) instead of building the FAT32 disk. Nothing changes when
it is unset.

```sh
RUSTBELT_DISK0=/workspace/foundry/out/foundry.img cargo test   # kernel tests on the Foundry OS image
```

The exact change, 16 added lines in `build.rs` only. Tested in a scratch
clone at a55b41d (`/workspace/foundry-scratch/rk`, branch `k1-experiment`,
own target dir): `cargo build` with the variable set produced a
`disk.img` byte-identical to `out/foundry.img`.

```diff
diff --git a/build.rs b/build.rs
index cc5505b..1f432dc 100644
--- a/build.rs
+++ b/build.rs
@@ -130,6 +130,9 @@ fn main() {
     println!("cargo:rerun-if-changed=vdso");
 
     build_vdso(&manifest, &out_dir);
+    // K1: an OS image from outside (Foundry OS's `make image`) as disk 0.
+    println!("cargo:rerun-if-env-changed=RUSTBELT_DISK0");
+    let external_disk0 = env::var_os("RUSTBELT_DISK0").map(PathBuf::from);
     for program in PROGRAMS {
         build_program(&user_dir, &out_dir, program);
     }
@@ -139,7 +142,10 @@ fn main() {
     for &(program, kind) in MUSL_PROGRAMS {
         build_musl_program(&user_dir, &out_dir, program, kind);
     }
-    build_disk(&manifest, &out_dir);
+    match &external_disk0 {
+        Some(img) => use_external_disk(&manifest, img),
+        None => build_disk(&manifest, &out_dir),
+    }
     build_ext2_disks(&manifest, &out_dir);
     build_exfat_disks(&manifest, &out_dir);
     build_btrfs_disks(&manifest, &out_dir);
@@ -263,6 +269,16 @@ fn run(cmd: &mut Command, stdin: Option<&str>) {
     assert!(status.success(), "{cmd:?} failed");
 }
 
+/// K1: copy an OS image built elsewhere to target/disk.img (instead of
+/// building one), so `cargo run`/`cargo test` boot it as disk 0.
+fn use_external_disk(manifest: &Path, img: &Path) {
+    println!("cargo:rerun-if-changed={}", img.display());
+    let target_dir = env::var("CARGO_TARGET_DIR").map(PathBuf::from).unwrap_or_else(|_| manifest.join("target"));
+    fs::create_dir_all(&target_dir).unwrap();
+    fs::copy(img, target_dir.join("disk.img"))
+        .unwrap_or_else(|e| panic!("RUSTBELT_DISK0={}: {e}", img.display()));
+}
+
 fn build_disk(manifest: &Path, out_dir: &Path) {
     let target_dir = env::var("CARGO_TARGET_DIR").map(PathBuf::from).unwrap_or_else(|_| manifest.join("target"));
     fs::create_dir_all(&target_dir).unwrap();
```

Follow-ups, in order (each optional):

1. When `RUSTBELT_DISK0` is set, also skip `build_program` for the moved
   programs (`sh ls cat wc grep mkdir rm touch cp sleep ifconfig ping host
   fetch httpd getty tile random sysinfo`). Their binaries only go into the
   disk the kernel no longer builds.
2. Proposal step 9: once both repos have been green for a while, delete
   the moved programs from `user/`. The kernel keeps its fixtures and a
   frozen `ulib.rs`. `tests/shell.rs` includes `user/shparse.rs`; it would
   either keep a copy or test only through the booted shell.
3. K5 (contract v2: ext2 root, `/sbin/init` as pid 1). **Landed in
   0.21.0** and used since M3 (docs/boot-contract.md). That is what makes
   Foundry OS the owner of what runs at boot.

## K12: clean shutdown when pid 1 exits (small, new in M3)

Found by `make test` (v2), which boots a writable copy of our image and
checks it with `fsck` afterwards. ext2 (A:) and exFAT (C:) come back
clean. The FAT32 data disk (B:) comes back with its **dirty bit set**:

```
$ fsck.fat -n B-partition-after-the-run
There are differences between boot sector and its backup.
  65:01/00
Dirty bit is set. Fs was not properly unmounted and some data may be corrupt.
B-partition-after-the-run: 33 files, 2676/80628 clusters
```

The image before the run is clean (`fsck.fat -n` exit 0), and only byte 65
of the boot sector differs in that area. The `fatfs` crate sets this bit on
the first write and clears it in `FileSystem::unmount()` (or on drop);
Rustbelt never unmounts, and `run_init_v2` calls `exit_qemu_with` right
after pid 1 exits. Everything else on the volume is consistent (fsck is
clean once the bit is cleared), so no data is lost. But Windows, macOS and
Linux will all offer to "repair" the disk.

**Request:** when pid 1 exits (and in `reboot`/power-off later, K7),
unmount or flush the mounted file systems before QEMU exits. For FAT that
means calling fatfs's `unmount()`. Our host check
(scripts/check-v2-image.sh) prints a WARN for now instead of failing.

## What Foundry OS still takes from the kernel repo

| What | How | Goes away when |
|---|---|---|
| the kernel itself | `git archive $(KERNEL_REV)` (now 50476f4) โ†’ `out/kernel-src`, built in `out/kernel-target` | never (that's the point), but bumps are deliberate |
| the 28 ABI fixtures (v2: in `B:/bin`, i.e. `/mnt/disk0/bin`, last on PATH) | compiled from `out/kernel-src/user` | the self-test stops running them (they move to the kernel's own tests) |
| the C:/D:/E: test disks (v1 tests only) | `ext2.img`, `exfat.img`, `btrfs.img` from the kernel build | stays: they are kernel-owned fixtures |
| the expected self-test lines | v1: copied into `scripts/run-headless.sh` (same 76 + 12 checks); v2: ours, in `scripts/expect-v2.txt` | โ€” (ours now; kernel-version lines must be updated on a bump) |
| `scripts/http-reply.sh`, `scripts/hvc0-session.txt` | copied at a55b41d | โ€” (ours now) |
| Debian's `musl-gcc` | builds the 5 musl fixtures, as the kernel does | the fixtures leave the image |