diff --git a/issues/build/doom-jpg-shows-ids-art-under-no-recorded-terms.md b/issues/build/doom-jpg-shows-ids-art-under-no-recorded-terms.md deleted file mode 100644 index 518f966df5f..00000000000 --- a/issues/build/doom-jpg-shows-ids-art-under-no-recorded-terms.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -status: owner -kind: question -opened: 2026-09-26 ---- - -# `doom.jpg` shows id's art, and nothing records the terms it is under - -`doom.jpg`, the screenshot `README.md` shows, is a picture of this system -running doom, so most of its pixels are `assets/DOOM1.WAD`'s graphics. -Its row in `COMMITTED_FILES` (`src/licence.rs`) declares `NOASSERTION`, -because the tree's `MIT OR Apache-2.0` does not reach id's art, and the -shareware terms in `NOTICE` do not name screenshots. No image -ships the file, so the licence gate does not judge it. - -**Exit**: the owner rules on the terms, and the row says them. Or the -screenshot is replaced by one that shows only this tree's own work. diff --git a/issues/build/whether-doomgeneric-becomes-a-submodule-is-the-owners.md b/issues/build/whether-doomgeneric-becomes-a-submodule-is-the-owners.md deleted file mode 100644 index 82fa7f73378..00000000000 --- a/issues/build/whether-doomgeneric-becomes-a-submodule-is-the-owners.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -status: owner -kind: question -opened: 2026-09-29 ---- - -# Whether doomgeneric becomes a submodule is the owner's - -`userland/doom/build.rs` fetches doomgeneric's commit `fc601639` as a GitHub -archive into the gitignored `userland/doom/doomgeneric/` and compiles it. The -fetch costs five build-dependencies in `userland/doom/Cargo.toml` — `ureq`, -`rustls-rustcrypto`, `webpki-roots`, `flate2`, `tar` — and leaves a ToyOS -change to the C nowhere to live but that untracked directory, which the build -replaces whenever its stamp disagrees with the pin. A fork repository of -doomgeneric as a submodule at that path deletes the fetch and the five. Neither -`ToyOSOrg/doomgeneric` nor `Japabu/doomgeneric` exists (`gh repo view`), and -creating one is the owner's. - -**Exit**: the owner rules — the fetch stays, or a submodule on a repository he -creates replaces it. diff --git a/issues/design-debt/a-boot-start-refused-a-present-device-runs-without-it.md b/issues/design-debt/a-boot-start-refused-a-present-device-runs-without-it.md index 14ee6cc58f9..d252767e3e6 100644 --- a/issues/design-debt/a-boot-start-refused-a-present-device-runs-without-it.md +++ b/issues/design-debt/a-boot-start-refused-a-present-device-runs-without-it.md @@ -1,26 +1,21 @@ --- -status: owner -kind: question +status: open +kind: defect opened: 2026-09-24 --- # A boot start refused a device the machine has runs without it -`/system/bin/init`'s `start` mints every device a `[programs]` row names. At a -swap or the restart that rolls one back, a device the process being replaced -held and the new one is refused fails the start (`Served::Restart`'s `owed`). -At boot there is no process before it, and a refusal is said in the kernel's -own word (`init: netd: pci:8086:10c9 is on this machine and could not be -handed over`) and the program is started without that device. +A boot start's device refusal is fatal only for a device its `[programs]` row +marks as required; for any other, init logs the refusal loudly and starts the +program without that device (owner, 2026-09-30). No row can mark a device +required, so every boot start refused a device the machine has — +`NotSupported`, `AlreadyExists`, `ResourceExhausted` — runs without it: +`/system/bin/init`'s `start` says the refusal in the kernel's own word +(`init: netd: pci:8086:10c9 is on this machine and could not be handed over`) +and starts the program. `refused_claim` (`tests/common/faults.rs`) and +`pci_function_is_exclusive` assert that behaviour today. -That is not the swap's defect — init claims nothing about a boot start beyond -`started`, and a row names every device its program can drive, so a machine -with a subset is a configuration, not a fault — but a refusal other than -`NotFound` is a fault, and the program then runs on less than the machine -has. `tests/common/faults.rs`'s `refused_claim` and `pci_function_is_exclusive` -assert exactly this behaviour today. - -The question for the owner: should a boot start refused a device the machine -has — `NotSupported`, `AlreadyExists`, `ResourceExhausted` — be fatal? A failed -boot start is an init panic, so the machine would not boot over one device; -and a row cannot yet say which of its devices its program needs. +**Exit**: a `[programs]` row can mark a device required, and guest tests show +a boot start refused a required device is fatal, and one refused a device its +row does not mark logs it loudly and starts the program without it. diff --git a/issues/design-debt/os-toyos-io-traits-keep-a-posix-name.md b/issues/design-debt/os-toyos-io-traits-keep-a-posix-name.md index d76b3aeaa2e..df9885c522c 100644 --- a/issues/design-debt/os-toyos-io-traits-keep-a-posix-name.md +++ b/issues/design-debt/os-toyos-io-traits-keep-a-posix-name.md @@ -1,37 +1,18 @@ --- -status: owner -kind: question +status: open +kind: defect opened: 2026-08-24 --- -# Does `std::os::toyos::io` get to keep `AsRawFd`/`FromRawFd`? +# `std::os::toyos::io` names its raw-handle traits `AsRawFd` and `FromRawFd` -The owner ruled on 2026-08-19 that **"fds belong only in libc jargon"**: the -kernel has no file descriptors, a process holds typed handles -(`toyos-abi/src/handle.rs`), and `userland/libc` is the one layer whose -interface the word describes. The sweep that followed took the kernel, the ABI, -the SDK, every forced call site and both fork pins. One name it did not settle -is left, and it is the owner's because it is a fork-repo name (`Japabu/rust`) -and not this tree's to rename alone. +`os::toyos::*` is ToyOS's own extension API and speaks ToyOS, and "fds belong +only in libc jargon" (owner, 2026-08-19). The `rust/` fork's +`library/std/src/os/toyos/io.rs` re-exports `std::os::fd`, so +`std::os::toyos::io::{AsRawFd, FromRawFd}` still speak POSIX. +`tests/toyos-rust-tests/src/bin/std_fs.rs` is their one caller in this +repository. -`std::os::toyos::io::{AsRawFd, FromRawFd}`. The naming table ruled that -`os::toyos::*` is ToyOS's own extension API and must speak ToyOS, and it named -every `process` row — but not this one. std's *POSIX* surface keeps the word by -charter; whether an `os::toyos` trait carrying a POSIX name is that surface or -the extension API is what nobody has decided. - -The two answers, both defensible: - -- **It stays**, as a deliberate mirror of std's own - `std::os::unix::io::{AsRawFd, FromRawFd}` convention — the trait names an - `os::toyos` caller reaches for by muscle memory, and diverging costs every - such caller a lookup. -- **It is renamed the next time the trait is touched in the fork**, to whatever - `os::toyos` decides its own non-POSIX vocabulary for a raw handle is, because - the extension API speaking POSIX is precisely what the ruling forbade - everywhere else. - -`tests/toyos-rust-tests/src/bin/std_fs.rs:4` is the only caller in this -repository (`use std::os::toyos::io::{AsRawFd, FromRawFd};`, used at `:59`). -Nothing is blocked on the answer; the word is legal in the two places it is -still spoken here, so this is a naming decision and not a defect. +**Exit**: the next time the trait is touched in the fork, `AsRawFd` and +`FromRawFd` in `std::os::toyos::io` are renamed to `os::toyos`'s own word for +a raw handle, and `std_fs.rs` uses the new names (owner, 2026-09-30). diff --git a/issues/isolation/the-supervisor-is-host-tested-and-owns-the-stop.md b/issues/isolation/the-supervisor-is-host-tested-and-owns-the-stop.md index a68d6923f80..9225b95b0c5 100644 --- a/issues/isolation/the-supervisor-is-host-tested-and-owns-the-stop.md +++ b/issues/isolation/the-supervisor-is-host-tested-and-owns-the-stop.md @@ -84,7 +84,5 @@ A deleted or disabled test covers nothing. ## Open with the owner -- Before stage 3: the ask's ABI, which is Q6a of - `issues/kernel/the-child-process-track-waits-on-the-owners-rulings.md`; whether a program - started through `launcher` is asked or only stopped; whether - `SYS_SHUTDOWN`/`SYS_REBOOT` change at all. +- Before stage 3: whether a program started through `launcher` is asked or + only stopped; whether `SYS_SHUTDOWN`/`SYS_REBOOT` change at all. diff --git a/issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md b/issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md index 8a725701db4..f22a4a92526 100644 --- a/issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md +++ b/issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md @@ -10,8 +10,7 @@ libc cannot start a child: `fork` and `execvp` answer `ENOSYS`, `waitpid` `ECHILD` and `system` `-1` (`userland/libc/src/misc.rs`, `userland/libc/src/stdio.rs`), and there is no `posix_spawn`. M2 and M4 of `issues/build/toyos-builds-itself.md` and the exit of -`issues/kernel/toyos-runs-on-arm64.md` need it. Stages 2, 3, 5, 6 and 7 wait -on `issues/kernel/the-child-process-track-waits-on-the-owners-rulings.md`. +`issues/kernel/toyos-runs-on-arm64.md` need it. **Constraints.** libc is built on ToyOS and never ToyOS on libc (owner, 2026-09-30: "toyos moderness and anti legacy may never be compromised because @@ -30,17 +29,15 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md — exit, crash or kill — kills its whole subtree at once; quitting gracefully is the parent's job before it exits, never the kernel's. The kernel starts init and init starts everything else, with no rule for services: a server - is init's child and lives as long as init. Each login, desktop or SSH, is a - session process under init that parents everything its user starts; an app - uses a server through handles and is never its child; logging out ends the - session. "Keep running after I close this" asks init, through `launcher`, to - be the parent, and nothing else reparents. A kill is cleanup without - cooperation, run on the causing path in bounded steps. init never dies and - stays tiny. + is init's child and lives as long as init. An app uses a server through + handles and is never its child. "Keep running after I close this" asks init, + through `launcher`, to be the parent, and nothing else reparents. A kill is + cleanup without cooperation, run on the causing path in bounded steps. init + never dies and stays tiny. ## Stages -0. **`SYS_PROCESS_OPEN` goes** (ruled). Deleted: `sys_process_open` and every +0. **`SYS_PROCESS_OPEN` goes**. Deleted: `sys_process_open` and every name only it reaches — `process_open`, `SysCap::open_process`, `MANAGE` in init's `SysCap`, the `reopenable` column, `process::process_object`, `reopen_selftest` and `sched::kthread::open_selftest` with their actuator, @@ -54,7 +51,7 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md whose symbols are test data. Negative control: the stage reverted whole, where `sys_process_open` answers 110. Oracle: rustc's name resolution, which fails the build of any caller left. -1. **An end is an event** (ruled). `read_watch` and `has_data` answer for a +1. **An end is an event**. `read_watch` and `has_data` answer for a `Process`, whose watch becomes an `Arc` as an `Acceptor`'s is, and `close_ends_polls` answers `false` for one; init's waiter threads go. *Exit*: children held on their stdin (`process_lifecycle`'s `held` role), @@ -64,33 +61,36 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md handles to a held child closes, a non-blocking submit finds nothing. Negative control: the stage reverted whole (`NotSupported`); mutation: `close_ends_polls` at `true` reds the last arm. Oracle: pidfd_open(2). -2. **An end says how** (Q2, Q6d). *Exit*: exited 137, killed, and each fault +2. **An end says how**. An end reads as an exit, a kill or a fault kind + alike on every architecture, and a bare code reads the last two as failures. + No end reads as a quit's reason. *Exit*: exited 137, killed, and each fault kind the architecture raises, each read from a child that did it. Negative control: the stage reverted whole, where the first two read alike. Oracle: POSIX ``'s classes. -3. **libc starts and waits for children** (Q3a–Q3c; blocked on +3. **libc starts and waits for children** (blocked on `issues/isolation/a-childs-stdio-handle-is-not-the-one-command-named.md`). - `posix_spawn` and `posix_spawnp` with file actions and attributes, routed by - std's launch-or-spawn rule moved into `toyos`; a descriptor table with - close-on-exec, a child getting descriptors 0–2 and exactly what its file - actions name; `waitpid`, `wait`, `wait4`; `ppoll`, `sigpending` and the + libc imitates `SIGCHLD`. A C child starts with descriptors 0–2 and exactly + what its file actions name, a stated departure from POSIX. `posix_spawn` and + `posix_spawnp` with file actions and attributes, routed by std's + launch-or-spawn rule moved into `toyos`; a descriptor table with + close-on-exec; `waitpid`, `wait`, `wait4`; `ppoll`, `sigpending` and the signal-set calls; `environ`, `setenv`, `unsetenv`; `kill` with `SIGKILL` for a child or a `-pgid` of its children, 0 as a probe, `EINVAL` for other signals until stage 6; `system`, `popen` and `pclose` through `/system/bin/shell -c`. No `select`: a descriptor is a handle and passes - `FD_SETSIZE`. A handler for any signal libc imitates runs inside a blocking - call of a thread that leaves the signal unblocked (`ppoll` and `sigsuspend` - under their mask); after it `read`, `recv`, `accept`, `wait`, `waitpid` and - `wait4` restart if it was installed with `SA_RESTART` and answer `EINTR` if - not, and `poll`, `ppoll`, `pause`, `sigsuspend`, `nanosleep` and `usleep` - answer `EINTR`, `sleep` the time left. While no thread is in one, it runs at - once on libc's own thread; while every thread blocks the signal, it stays - pending, as `sigpending` reports, until one unblocks it. libc's own thread - watches at most 255 unwaited children on its own inbox, the 256th watch - being stage 6's notice, and wakes a thread in a blocking call through one - pipe, so `poll` takes at most 255 descriptors and children never count - against them. *Exit*, C corpus files, the mutation that reds an arm after - it: a piped stdout read to EOF through `poll`, then `waitpid`; + `FD_SETSIZE`. A handler for any signal libc imitates runs at once, beside the + program: inside a blocking call of a thread that leaves the signal unblocked + (`ppoll` and `sigsuspend` under their mask), and, while no thread is in one, + on libc's own thread. After it `read`, `recv`, `accept`, `wait`, `waitpid` + and `wait4` restart if it was installed with `SA_RESTART` and answer `EINTR` + if not, and `poll`, `ppoll`, `pause`, `sigsuspend`, `nanosleep` and `usleep` + answer `EINTR`, `sleep` the time left; while every thread blocks the signal, + it stays pending, as `sigpending` reports, until one unblocks it. libc's own + thread watches at most 255 unwaited children on its own inbox, the 256th + watch being stage 6's notice, and wakes a thread in a blocking call through + one pipe, so `poll` takes at most 255 descriptors and children never count + against them. *Exit*, C corpus files, the mutation that reds an arm after it: + a piped stdout read to EOF through `poll`, then `waitpid`; `waitpid(-1)` answering three children once each in the order they end, then `ECHILD` (waiting on the first alone), and `wait` and `wait4` alike (either answering `ECHILD` as `waitpid` does today); a killed child `WIFSIGNALED` @@ -117,7 +117,7 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md the pipe answering (passed to the kernel, which ends the caller). Negative control: today's libc, against which the corpus does not link. Oracle: POSIX's `posix_spawn`, `waitpid`, `poll` and `SA_RESTART`, and signal(7). -4. **A parent takes its children down** (ruled). A spawn's parent is its +4. **A parent takes its children down**. A spawn's parent is its spawner. A launch names its parent in its request, by one of two words: the caller, whose place is a copy of the handle to itself every process starts holding under the label `self` (`WRITE`, `DUP`, `TRANSFER`), carried as one @@ -168,20 +168,21 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md place spawned under init (it starts); std's `NotDeclared` fallback, or its early return for an endowment or extra slot, kept for init (it starts). Oracle: cgroup v2's `cgroup.kill` and `cgroup.max.depth`. -5. **A login is a session under init** (ruled; Q5a–Q5c). init starts a session - process per login — the desktop's at boot, one per SSH connection at sshd's - request — and the compositor and sshd start a login's programs under it. Its - namespace is `issues/filesystem/a-user-is-a-home-tree-and-a-login-row.md`'s - login row. *Exit*: killing the desktop session ends every program the - compositor started and leaves the compositor running; killing the compositor - ends none of them; a dropped SSH connection ends its session and all under - it. Negative control: the stage reverted whole, where the compositor's - programs die with it. Oracle: systemd-logind, whose session scope ends every - process of a login. -6. **A program is asked to quit** (Q3b, Q6a–Q6f). `SYS_PROCESS_QUIT` (124, - never assigned), `(process, reason)`, needs `MANAGE` as the kill does and - reaches the process's subtree by stage 4's walk; the reason is interrupt, - hang-up or terminate. Each process is started holding its notice, a new +5. **A login is a session under init**. Per login init starts a session + program that logout ends and that only parents what its user starts — the + desktop's at boot, one per SSH connection at sshd's request. As it starts + one, init hands the compositor or sshd a right to start programs in that + session alone. sshd's right also quits and kills it. Its namespace is + `issues/filesystem/a-user-is-a-home-tree-and-a-login-row.md`'s login row. + *Exit*: killing the desktop session ends every program the compositor started + and leaves the compositor running; killing the compositor ends none of them; + a dropped SSH connection ends its session and all under it. Negative control: + the stage reverted whole, where the compositor's programs die with it. + Oracle: systemd-logind, whose session scope ends every process of a login. +6. **A program is asked to quit**. `SYS_PROCESS_QUIT` (124, never + assigned), `(process, reason)`, needs `MANAGE` as the kill does, and the + reason is interrupt, hang-up or terminate. A quit reaches the process's + subtree by stage 4's walk. Each process is started holding its notice, a new object kind under the label `quit`, `READABLE` while a reason is asked and not yet read, whose read takes it. A quit kills a process that has never watched its notice or whose notice still holds a reason, whoever asks, in @@ -192,8 +193,8 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md Unix arm maps; rustc stops skipping its handler on ToyOS (`rust/compiler/rustc_driver_impl/src/lib.rs`, `install_ctrlc_handler`). Stage 5's session, `/system/bin/terminal` and `/system/bin/shell` listen and - relay nothing, the walk having asked what they run: each ends once that has, - `-c` with its status, but an interactive shell does not end on an interrupt. + relay nothing, each ending once what it runs has, `-c` with its status, but + an interactive shell does not end on an interrupt. libc takes the three reasons as `SIGINT`, `SIGHUP` and `SIGTERM`, watching the notice once one is handled, ignored or blocked: an ignored reason is dropped and an unhandled one ends the process with exit code 128 plus the @@ -236,25 +237,24 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md watching only from a handler's install (the ignoring child is killed); a spawn carrying no ignore, no mask or no `SETSIGDEF` (each spawned child in turn). Oracle: POSIX's signal actions (XSH 2.4.3) and `posix_spawn`. -7. **Job control** (Q6a–Q6c, Q7). The shell hands its terminal a `MANAGE`-only - duplicate of each process of the foreground line over its `surface` port. - The terminal turns each Ctrl+C into their interrupt, and before it closes it - asks its shell's tree to hang up and kills what is left at its deadline. - sshd does that to a dropped connection's session, and init at shutdown and - test-runner at a test's deadline ask with terminate. `&` is a line the shell - watches. sshd awaits exits through a ToyOS `tokio/src/process` arm in the - tokio fork, shaped as its Linux pidfd arm (`process/unix/pidfd_reaper.rs`). - Stop and continue need a suspend primitive and are not proposed. *Exit*: +7. **Job control**. The shell hands its terminal a `MANAGE`-only + duplicate of each process of the foreground line over its `surface` port. The + terminal turns each Ctrl+C into their interrupt, and a second kills only a + program that has not yet taken the first. Before it closes, the terminal asks + its shell's tree to hang up and kills what is left at its deadline; sshd does + that to a dropped connection's session, and init at shutdown and test-runner + at a test's deadline ask with terminate. `&` is a line the shell watches. + sshd awaits exits through a ToyOS `tokio/src/process` arm in the tokio fork, + shaped as its Linux pidfd arm (`process/unix/pidfd_reaper.rs`). *Exit*: Ctrl+C ends a pipeline whose stage started a child, none of it left and the prompt back, and ends no shell nested on the foreground line; a child that reads each interrupt survives three Ctrl+C, each pressed once it reports its - watch armed or the one before read, and one that reports its watch armed - and never reads dies on the second; a dropped SSH connection whose session - runs a watching child sees it write its file before it ends; tokio's - `Child::wait` returns with no loop polling `try_wait`. Negative control: - today's terminal and shell, where the pipeline runs on. Mutations: a quit - that kills on a reason already read (the reading child dies); a session - that never listens (the file unwritten); a shell that ends on an interrupt - (the nested shell). Oracle: POSIX's INTR, a `SIGINT` "sent to all processes - in the foreground process group" (XBD 11.1.9), and tokio's - `tests/process_smoke.rs`. + watch armed or the one before read, and one that reports its watch armed and + never reads dies on the second; a dropped SSH connection whose session runs a + watching child sees it write its file before it ends; tokio's `Child::wait` + returns with no loop polling `try_wait`. Negative control: today's terminal + and shell, where the pipeline runs on. Mutations: a quit that kills on a + reason already read (the reading child dies); a session that never listens + (the file unwritten); a shell that ends on an interrupt (the nested shell). + Oracle: POSIX's INTR, a `SIGINT` "sent to all processes in the foreground + process group" (XBD 11.1.9), and tokio's `tests/process_smoke.rs`. diff --git a/issues/kernel/the-child-process-track-waits-on-the-owners-rulings.md b/issues/kernel/the-child-process-track-waits-on-the-owners-rulings.md deleted file mode 100644 index a6d2f66ca93..00000000000 --- a/issues/kernel/the-child-process-track-waits-on-the-owners-rulings.md +++ /dev/null @@ -1,283 +0,0 @@ ---- -status: owner -kind: question -opened: 2026-09-30 ---- - -# The child-process track waits on the owner's rulings - -Stages 2, 3, 5, 6 and 7 of -`issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md` -wait on these, and are written there as recommended here. Each question is one -decision. Its ruling goes into the track as one line, and its entry here is -deleted. - -## Q2. Can a parent tell a failure from a kill or a crash? (stage 2) - -When a child ends, its parent reads one number. Today a child that exits with -137 and one that is killed both read 137, and a crash reads 139 or -1, numbers -a program can exit with too. - -- **Yes.** The number says whether the program ended itself or the kernel - ended it, and if the kernel did, why: killed, or which kind of crash. A crash - reads the same on x86-64 and on ARM64. A program that reads the number the - old way still sees a kill or a crash as a failure, never as a success. -- **No.** A tool that retries a crashed job cannot tell it from one that - failed, and a program that exits with 137 on purpose reads as killed. - -*Recommended: yes.* - -## Q3a. How does a C program learn that its child ended? (stage 3) - -C programs written for Unix learn it from a Unix signal, `SIGCHLD`: Ninja -waits for one (v1.13.1 `src/subprocess-posix.cc`), and so does libuv where it -has no kqueue (v1.53.0 `src/unix/process.c`). ToyOS has no signals, and its -kernel never will. - -- **libc imitates the signal.** libc, the C library, learns of the end from - ToyOS's own event and runs the program's handler for it. Ninja runs - unchanged, and the kernel gains nothing. -- **Each such program is changed for ToyOS.** Ninja, libuv and every other - program that waits for its children carries a ToyOS change of its own, as a - fork, for what one part of libc would serve. - -*Recommended: libc imitates it.* - -## Q3b. When does a C program's signal handler run? (stages 3 and 6) - -On Unix a signal interrupts the program wherever it is, and the handler runs -in its place. ToyOS never does that; the owner ruled it out as legacy. libc can -run a handler only on a thread it controls. - -- **At once, beside the program.** While one of the program's threads is - waiting inside libc — for input, for a child, for time to pass or for a - signal — the handler runs on that thread and cuts the wait short, as on - Unix: a C prompt waiting for a line comes back on Ctrl+C. As on Unix, a - program can ask that a wait for input or for a child carry on after its - handler instead, and a wait for time or for a signal is always cut short. - While no thread is waiting, libc runs the handler on a thread of its own, and - the program's own code goes on running at the same time. Ctrl+C stops clang - at once, and clang deletes the file it was writing. The cost: the handler and - the program's own code can then work on the same thing at once and collide. - GNU make's Ctrl+C handler collects the compiles that have ended, which make's - own code also does; on Windows, the one system where make's handler already - runs beside its code, make stops its own code first for exactly that reason - (make 4.4.1 `src/commands.c:508-510`). On ToyOS it collides only when Ctrl+C - comes while make is working rather than waiting. A handler that jumps back - into the program's main loop, as some C programs' do, lands on the wrong - thread and breaks the program; and clang can delete that file while its main - code is still writing into it. POSIX lets a handler run on any of a program's - threads that does not block the signal; this one is a thread the program - never made. -- **Only when the program next waits inside libc.** Nothing runs beside the - program. A compiler that is computing does not wait, so Ctrl+C does not stop - clang until it is killed, and its half-written file stays behind. - -*Recommended: at once, beside the program.* - -## Q3c. What does a C child start with? (stage 3) - -On Unix a child starts holding every file its parent has open, unless the -parent marked the file not to be passed on. - -- **Only what the parent names**, a stated departure from POSIX. A C child - starts with its input, output and error, and exactly what its parent names - when it starts it. A program started from C then holds what it holds when - started from Rust: what it is declared to hold. The cost: a C program that - hands its child a file by leaving it open, without naming it, loses it. GNU - make does that to share its limit on how many jobs run at once, with a pipe - it leaves open to a make it starts; LLVM's tools, told to share the limit, - take the same pipe (`llvm/lib/Support/Unix/Jobserver.inc`). Without it, a - make that make starts warns and runs one job at a time (make 4.4.1 - `src/main.c:1849`), and an LLVM tool warns and runs as many as it would - alone. make shares the limit by a named pipe instead where the system has - one, and ToyOS has none. -- **Unix's rule.** A C parent with any other file open passes it on, and every - program it starts runs with what that parent holds, where the same program - started from Rust runs with what it is declared to hold. - -*Recommended: only what the parent names.* - -## Q5a. What program is a login? (stage 5) - -Each login, on the desktop or over SSH, has one program that is the parent of -everything its user starts, so that logging out ends all of it. - -- **A program of its own that does nothing else.** init starts a small session - program for each login, and everything the user starts runs under it. It - listens: when the login is asked to end, its programs are asked too, and it - ends once they have. A crash of any program the user started leaves the - login and the rest of its programs running. -- **The login's first program.** An SSH login's session is the shell sshd - starts for it, and the desktop's is the first program the desktop starts for - the user. When that program ends or crashes, everything the user started - ends with it, as on Unix when the login shell exits. One program fewer runs - per login. - -*Recommended: a program of its own.* - -## Q5b. Who lets the compositor and sshd start programs in a login? (stage 5) - -A login's programs are started under its session, never under the compositor -or sshd, so each of them needs a right to start programs there. - -- **init hands it over when it starts the session.** At boot init starts the - desktop's session and gives the compositor the right to start programs in - it; for SSH, sshd asks init for a session per connection and gets that right - back. Each right reaches one session. -- **The session program hands it over itself**, over a connection init sets up - between them. The session program then has a second job besides being a - parent, and each login costs one connection more. - -*Recommended: init hands it over.* - -## Q5c. Can sshd end a login it asked for? (stage 5) - -When an SSH connection drops, its login ends: its programs are asked to hang -up, which reaches them because the session program listens (Q5a), and what is -left is killed. - -- **Yes, only its own.** The right sshd gets for a connection's session also - lets it ask that session to quit and kill it, so a dropped connection ends - its login with nobody else involved. sshd can end no other login. -- **No, it asks init.** Only init can end a login: sshd tells init that the - connection dropped, and init ends it. init does one more job, and stays the - only program that can end a login. - -*Recommended: yes, only its own.* - -## Q6a. Can one program ask another to quit? (stage 6) - -Today the only way one program can end another is a kill, which leaves it no -chance to save anything. The same answer decides how init asks each service to -finish when the machine shuts down -(`issues/isolation/the-supervisor-is-host-tested-and-owns-the-stop.md`). - -- **Yes, with a reason.** A program allowed to end another — its parent, or - the terminal it runs in — can ask it to quit and say why: interrupt - (Ctrl+C), hang-up (a window closed, a connection dropped) or terminate - (shutdown, a deadline). The program hears it among its other events and - decides what to do: an editor saves, a compiler deletes its half-written - output, Ctrl+C in a shell or an editor stops what it is doing and ends - nothing, and a server may take a hang-up as the signal to reload its - settings. The shell, the terminal and a login's session program listen, so - the programs under them are asked too. Whoever asked still kills if the end - it wants does not come. The kernel gains one call and one kind of object. -- **Yes, without a reason.** A server that reloads on a hang-up and exits on - terminate would exit when asked to reload, and no program could tell Ctrl+C - from shutdown. -- **No: a kill only, as today.** Nothing saves its work when its window closes - or the machine shuts down. - -*Recommended: yes, with a reason.* Rejected: a quit channel std and libc would -make at every start, which a program started any other way would not have; and -a handler that interrupts the program's own code wherever it is, the legacy -the owner ruled out. - -## Q6b. What does a quit do to a program that never listens for one? (stage 6) - -Most programs never listen: `ls`, `cat`, a Rust program that does not ask. - -- **It is killed at once, with everything it started**, as a Unix program - that handles nothing dies of Ctrl+C. Ctrl+C stops such a program on the - first press. The cost: a program that listens still dies at once, without - cleaning up, when the program that started it does not listen, because it - dies with that program; on Unix only the one that does not listen would die. - ToyOS's own shell and terminal and each login's session program listen, so - a compiler run from a login cleans up. -- **Nothing, until whoever asked gives up and kills it.** Ctrl+C does nothing - to `cat` until a second press, and shutdown waits out its whole deadline for - every program that never listens. - -*Recommended: killed at once.* - -## Q6c. How far does a quit reach? (stages 6 and 7) - -- **The program and everything it started**, as a kill does. Ctrl+C reaches - `make` and every compile it is running at once, as on Unix, where Ctrl+C - reaches every program of the job in front. A closing terminal asks its shell - and everything the shell started to hang up. The cost: a program cannot keep - a helper it started out of a Ctrl+C meant for itself, as a Unix program can - by putting the helper in a job of its own. Ninja does that with every - compile and passes the Ctrl+C on to them itself (v1.13.1 - `src/subprocess-posix.cc:102`, `:451-457`), so here each compile is asked - twice, and one that has not yet taken the first when Ninja's comes is - killed (Q7), leaving its half-written file. The shell and the session - program pass nothing on, since the quit already reached what they run. -- **The program alone.** Ctrl+C reaches `make` and not its compiles, and make, - which expects its compiles to have been interrupted with it, waits for every - running one to finish before it stops (GNU make's `fatal_error_signal`). The - shell and the session program must then pass each quit on to what they run, - or a closing terminal reaches its shell and not what the shell started. - -*Recommended: the program and everything it started.* - -## Q6d. Does a program that Ctrl+C ended read as interrupted? (stages 2 and 6) - -Under the answers recommended here, no child that Ctrl+C ended ever reads to -its parent as interrupted. One that never listened reads as killed; a C -program that lets the interrupt end it once it has cleaned up, as clang does, -reads as having exited with 130, which is a failure. - -- **No.** The number keeps meaning only whether a program ended itself or the - kernel ended it. A build tool that tells an interrupt from a failure, as - Ninja does, may report a compile the user interrupted as failed; Ninja still - stops the build, because the Ctrl+C reached it too. -- **Yes.** Q2's reasons gain three — interrupted, hung up, terminated — so a - program a quit ended reads as ended for that reason, and a C program that - started it sees what it would on Unix. For clang to read that way, a program - must be able to end itself for one of those reasons, as a Unix program can - by sending the signal to itself, so the number no longer says only what the - kernel did. - -*Recommended: no.* - -## Q6e. Does an unchanged Rust program's Ctrl+C handler run? (stage 6) - -Rust programs that handle Ctrl+C mostly do it through one widely used library, -`ctrlc`; rustc is one of them. - -- **Yes.** Rust's standard library hands a program its quit notice, and the - `ctrlc` fork ToyOS already carries listens to it, so a Rust program's handler - runs on a quit as it does on Unix, with no change to the program. rustc - stops leaving its handler out on ToyOS. -- **No.** A Rust program hears a quit only through a call written for ToyOS; - rustc and every other `ctrlc` user is treated as a program that never - listens (Q6b). - -*Recommended: yes.* - -## Q6f. Does a C program hear a quit as the Unix signal for it? (stage 6) - -- **Yes.** libc turns interrupt into `SIGINT`, hang-up into `SIGHUP` and - terminate into `SIGTERM`, so a C program written for Unix acts as it does - there: clang deletes the file it was writing and stops, a server reloads on - a hang-up, and a program that says only to ignore Ctrl+C runs on through - it. A C program's `kill` with one of those signals asks for a quit. -- **No.** A C program never hears a quit, and is treated as a program that - never listens (Q6b): clang leaves its half-written file behind, and a server - asked to reload ends. - -*Recommended: yes.* - -## Q7. What does a second Ctrl+C do? (stage 7) - -A program that listens may stop what it is doing on Ctrl+C and carry on: a -Python prompt goes back to its prompt, an editor cancels a command. - -- **It kills only a program that has not yet taken the first.** A program - that takes each Ctrl+C and carries on survives any number of them; one stuck - so that it never takes the first dies on the second. The same holds whoever - asks: a program asked to quit again before it took the last ask is killed. - Two quick presses: a C program, or a Rust program using `ctrlc`, takes each - on a thread of its own as it comes, so it survives them unless the machine - is too busy to run that thread between the two; a program that takes its - Ctrl+C in its own loop and is busy across both presses dies, where on Unix - its handler would have run twice. A program that takes a Ctrl+C and then - hangs is not ended by Ctrl+C: closing its window or a kill ends it, as on - Unix. -- **It always kills.** A hung program always dies on the second press, but a - Python prompt or an editor dies on the second Ctrl+C of its life, and loses - what was not saved. - -*Recommended: only a program that has not yet taken the first.* diff --git a/issues/kernel/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md b/issues/kernel/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md index 6d7aa2f67a0..72bb264b81a 100644 --- a/issues/kernel/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md +++ b/issues/kernel/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md @@ -23,7 +23,7 @@ probe is a `boot-actuators` arm or a `test-actuators` `SYS_DEBUG` action. - `issues/kernel/user-pointer-checks-have-no-spectre-v1-fence-and-smap-is-optional.md` - `issues/kernel/indirect-branches-and-returns-run-without-thunks.md` - `issues/kernel/tsx-stays-as-firmware-left-it.md` -- `issues/kernel/whether-the-kernel-loads-cpu-microcode-is-the-owners.md` +- `issues/kernel/the-kernel-loads-no-cpu-microcode.md` - `issues/kernel/no-user-address-is-drawn-per-spawn.md` - `issues/kernel/the-kernel-has-no-stack-protector.md` - `issues/kernel/no-kernel-address-is-drawn-per-boot.md` diff --git a/issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md b/issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md index e7065240b7c..5e90f87e89e 100644 --- a/issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md +++ b/issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md @@ -144,15 +144,14 @@ times: written once as straight-line code. **Exit**: no interrupts-off window longer than a register access, and keyboard input keeps flowing while a stick misbehaves. -6. **The scheduler knows nothing about devices.** A handler posts its +6. A handler posts its device's `Watch` and ends its interrupt; the thread waiting on that watch does the work, and no step creates a kernel thread. `irq_ring`, the driver list in `drain_irqs` and the idle loop's device checks are gone by step 5. Each step measures the kernel's lines, and from step 2 the longest interrupts-off and preemption-off windows, against stage 6's first commit. Steps 3 and 4 do not land alone: they land with #592's i8042 stage and - with usbd. Stage 6 stays open past step 5 until the owner rules on the - panel the dump paints. + with usbd. 1. **Interrupts post.** A post is legal in a handler: the watches a handler posts, and the completions of a ring they complete into, sit behind interrupts-off locks nothing allocates or frees under, and every other @@ -182,9 +181,9 @@ times: them. **Exit**: step 10's. 5. **The pass is the scheduler's.** `drain_irqs` goes: the blocked-task dump and the heartbeat become `pass`'s own, and the TCO feed stays, - since what it proves is that passes run. The dump still paints its - report on the panel and holds it there, a device the pass reaches; - whether that stays is the owner's ruling. **Exit**: `drain_irqs` and the + since what it proves is that passes run. The dump keeps painting its + report on the panel and holding it there, a device the pass reaches + (owner, 2026-09-30). **Exit**: `drain_irqs` and the idle loop's device checks are gone, both windows are measured against stage 6's start, and the exits of `issues/kernel/an-irq-watchs-freeing-cancel-compiles-in-a-handler.md` diff --git a/issues/kernel/the-kernel-loads-no-cpu-microcode.md b/issues/kernel/the-kernel-loads-no-cpu-microcode.md new file mode 100644 index 00000000000..5b2513c2134 --- /dev/null +++ b/issues/kernel/the-kernel-loads-no-cpu-microcode.md @@ -0,0 +1,17 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# The kernel loads no CPU microcode + +The kernel is to load CPU microcode signed by the CPU's maker, pinned by +version and hash the way vendor device firmware is (owner, 2026-09-30). It +loads none, so a CPU whose BIOS ships stale microcode stays below Linux at +`Ubuntu-6.8.0-142.142` on every line that rests on microcode. + +Root `CLAUDE.md`'s firmware rule admits this microcode once PR #636 lands. + +**Exit**: the kernel loads current microcode early on every CPU, at least as +current as Linux's. diff --git a/issues/kernel/whether-the-kernel-loads-cpu-microcode-is-the-owners.md b/issues/kernel/whether-the-kernel-loads-cpu-microcode-is-the-owners.md deleted file mode 100644 index 1e6b8e4fef2..00000000000 --- a/issues/kernel/whether-the-kernel-loads-cpu-microcode-is-the-owners.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -status: owner -kind: question -opened: 2026-09-29 ---- - -# Whether the kernel loads CPU microcode is the owner's - -Microcode is firmware that runs on the CPU, which root `CLAUDE.md` does not -carve out: it admits only device firmware that never executes on the CPU. Until -the kernel loads it, a CPU whose BIOS ships stale microcode stays below Linux -at `Ubuntu-6.8.0-142.142` on every line that rests on microcode. - -**Exit**: the owner rules. diff --git a/src/licence.rs b/src/licence.rs index 437c9ac1924..d12eea314a9 100644 --- a/src/licence.rs +++ b/src/licence.rs @@ -396,8 +396,8 @@ pub const COMMITTED_FILES: &[(&str, &str, &str, Terms)] = &[ ( "doom.jpg", "ae22f71dc732580bd4f789937c9fe564969029413fc2092f27bdae8d1ceaf8e3", - "a screenshot of this system running doom, in README.md; \ - issues/build/doom-jpg-shows-ids-art-under-no-recorded-terms.md", + "the owner's own screenshot of this system running doom, in README.md; \ + no licence question applies", Terms::Spdx("NOASSERTION"), ), (