summaryrefslogtreecommitdiffstats
path: root/docs/adr/0006-executable-binaries-and-bin-tree.md
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-21 17:18:09 +0200
committersalmonik <salmonik@tuta.io>2026-08-21 17:18:09 +0200
commitfc4c563ca14ddec3de3aeef5a861f9707eaae389 (patch)
treee88483e79ca3ca6d980cdbf984cf9278ccef1d27 /docs/adr/0006-executable-binaries-and-bin-tree.md
parentwm: fix usize underflow in move_window at screen edges (diff)
downloadrs-dos-fc4c563ca14ddec3de3aeef5a861f9707eaae389.tar.gz
rs-dos-fc4c563ca14ddec3de3aeef5a861f9707eaae389.zip
docs: ADR 0006 (executable binaries) + implementation plan + glossary terms
ADR 0006: PE32+ loader in rsnix, versioned exports-table ABI constructed per invocation, task-per-binary, single dd-able system image with /bin/cli and /bin/gui, shell-owned grammar with injected line streams, per-terminal input discipline as prerequisite phase, frozen built-in core, 0.6.0 at first exec. Plan: five phases (image → input discipline → loader/exec → streams + migration → GUI binaries), each with a QEMU verification gate. Glossary: Binary, Loader, Exports table, System image; kind = launch surface.
Diffstat (limited to '')
-rw-r--r--docs/adr/0006-executable-binaries-and-bin-tree.md115
1 files changed, 115 insertions, 0 deletions
diff --git a/docs/adr/0006-executable-binaries-and-bin-tree.md b/docs/adr/0006-executable-binaries-and-bin-tree.md
new file mode 100644
index 0000000..17109c4
--- /dev/null
+++ b/docs/adr/0006-executable-binaries-and-bin-tree.md
@@ -0,0 +1,115 @@
1# ADR 0006 — Executable binaries: PE loader, exports-table ABI, the /bin tree, and the system image
2
3## Status
4
5Accepted.
6
7## Context
8
9Nothing can be executed inside raisernics that was not compiled into the
10kernel image: every command is a built-in function inside the `shell`
11crate, dispatched by a match statement. The kernel has no program loader,
12no exec concept, and (per design) no address-space separation — all tasks
13share one identity-mapped space. We want commands to become real,
14loadable binaries so the system can gain software without recompiling the
15kernel, so GUI apps and CLI tools are structurally the same kind of thing,
16and so a launcher can enumerate launchable apps by listing a directory.
17
18## Decision
19
20- **Format: PE32+.** Binaries are built with the existing
21 `x86_64-unknown-uefi` toolchain into standalone PE images. The loader
22 allocates pages, copies segments, applies `.reloc` base relocations at
23 load time, and jumps to the entry point. No page-table changes —
24 loading is alloc-copy-relocate-jump in the one shared address space.
25- **Loader lives in `rsnix`** as a kernel facility (alongside `fat`,
26 `ahci`).
27- **ABI: versioned exports table passed at entry** — not syscalls, not
28 well-known addresses. The kernel hands the binary a table of function
29 pointers (filesystem, memory, window/event, streams) with a version
30 field; both sides share the type definitions in-workspace, so the
31 contract is compile-time checked and old on-disk binaries fail loudly on
32 a version mismatch.
33- **The caller constructs the table per invocation.** Output injection:
34 `put_line` points either at the terminal or at the shell's filter
35 pipeline head. Input injection: `get_line` reads from wherever the
36 shell resolved the grammar's input side (keyboard, file, TIL-truncated
37 source). Redirection is dependency injection, not global state.
38- **Task per binary.** Every binary runs as its own task. CLI binaries
39 are waited on by the calling terminal's shell (completion via message);
40 GUI binaries are fire-and-forget. A faulting binary kills only its own
41 task, honouring ADR 0003's graceful-death guarantee.
42- **System image.** The build produces one dd-able FAT32 image containing
43 bootloader, kernel, `\bin\cli\*`, `\bin\gui\*`, `\etc\*`, and seed
44 content. Imaging it onto a disk yields a complete installed system.
45 The volume mounts through the ordinary `/disk/<UID>/` mechanism with
46 full read-write; overwriting a command on the image persists across
47 reboots.
48- **Resolution rule.** For name N: run `\bin\<kind>\N` from the first
49 mounted volume that has it; else the built-in N; else "unknown
50 command". No volume is privileged; no boot-volume identity exists.
51- **Kind = launch surface.** `/bin/cli` holds programs that run inside a
52 terminal session; `/bin/gui` holds programs launchable with no terminal
53 (they own their windows). A future launcher enumerates `/bin/gui`.
54- **The shell owns 100% of the grammar (ADR 0002).** Binaries never see
55 USE/TIL/filters/sinks — they receive clean argv and injected line
56 streams. Any binary gets stdin/stdout for free; which commands accept
57 input keywords stays a policy choice.
58- **^C and stop flow through the stream functions.** `put_line -> false`
59 means stop (head kill or pager ^C); `get_line` returns Eof (^D) or
60 Abort (^C) distinctly. The `-p` pager blocks inside the stream call in
61 the command task's context. No signals, no abort flags.
62- **Per-terminal input discipline is a prerequisite phase.** Keyboard
63 routing becomes total: IRQ ring → WM pump → focused window's queue,
64 always. Each terminal's shell owns canonical mode from its own queue;
65 the home terminal becomes an ordinary focusable window and the
66 `FOCUSED == CONSOLE` sentinel retires. Binaries consume assembled
67 lines through a bounded per-invocation channel fed by the owning shell.
68- **Frozen built-in core**: `help`, `clear`, `echo`, `poweroff`/`exit`,
69 `halt`, `rescan` stay built-ins permanently — needed before disks exist
70 or existing to fix them. Everything else migrates (`cat` last; it
71 exercises every grammar corner).
72- **Version 0.6.0 lands when exec first works** (loader + ABI + one
73 binary end-to-end).
74
75## Alternatives considered
76
77- **Flat raw binary at a fixed address** — trivial loader, but fixed
78 addresses forbid concurrent resident binaries (fatal under preemptive
79 tasks).
80- **ELF** — familiar but fights the toolchain for zero benefit here.
81- **Syscall gate (`int` + numbers)** — more OS-flavored, but stdout would
82 become "the current command's sink" global state — the exact bug class
83 the ACTIVE-terminal incident came from — and buys nothing without
84 address spaces.
85- **Static linking of rsnix into binaries** — forbidden for stateful
86 modules: each binary would get private copies of mount tables and node
87 pools.
88- **Synchronous execution in the caller's task** — simplest, but a
89 faulting binary halts task 0 and the machine.
90- **ramfs-seeded /bin** — rejected in favour of the system image: rw
91 through FAT write-back persists across reboots, and the image doubles
92 as the distribution artifact.
93- **Boot-volume identity / root-path unification** (`\etc` visible at
94 `/etc`) — deferred; needs VFS bind/alias machinery.
95- **A third "TUI" binary kind for rsdosedit** — rejected; `edit` is
96 classified CLI (it lives in a terminal session) and migrates only after
97 the CLI ABI grows raw-mode surface access. Until then it stays a
98 built-in.
99
100## Consequences
101
102- Positive: raisernics gains an exec story without abandoning the
103 single-address-space philosophy; commands become hot-swappable at
104 runtime (`cat x WRITING /bin/cli/ls`) and persistent across reboots;
105 CLI and GUI programs share one spawn path, loader, and ABI.
106- Positive: the grammar becomes enforceable by construction — binaries
107 cannot break ADR 0002 because they never see it.
108- Negative: build orchestration grows a stage (build `/bin` crates →
109 stage artifacts → build image); cargo does not chain these natively.
110- Negative: streaming output crosses a task boundary; `-t` flush and ^C
111 latency gain one scheduling round trip.
112- Negative: a compute-bound binary that never calls the streams cannot be
113 ^C'd (policy, same blind spot `stress` has today).
114- Deferred: raw-mode CLI ABI extension (for `edit`), FAT shadowing of
115 `/bin`, boot-volume identity, launcher.