SchOS/.claude/memory/schos-schinit.md
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:05:12 +02:00

63 lines
3.1 KiB
Markdown

---
name: schos-schinit
description: "schinit is process 1, launchd-shaped; job manifests, the ControllingTerminal trap, and what milestones 2 and 3 are"
metadata:
node_type: memory
type: project
originSessionId: 9d8a2aef-bcf0-47a0-96c6-f706efab4a95
modified: 2026-08-09T14:36:32.587Z
---
**`cmd/schinit/` is process 1**, chosen by Nexoniarz to be "close to Apple's
launchd, the king of inits". `lib/initcode.S` execs `/bin/schinit`. The old
`cmd/init` is still in the tree but nothing runs it.
**Milestone 1 (done, 2026-08-09):** manifests, `RunAtLoad`, `KeepAlive` with
`ThrottleInterval`, reaping, `ControllingTerminal`.
**Milestone 2:** socket activation — schinit holds the listener and starts the
job on first connect. `[Socket name]` / `Path` is already parsed and stored in
`job.sock_path`, so manifests do not have to change.
**Milestone 3:** `schinitctl` (list/start/stop/reload), `WatchPaths`, per-user
agents.
**The format is sectioned `key = value`**, not plists. Decided deliberately:
the roadmap's own words are that "the point of copying launchd rather than
sysvinit is not the config format, it is *demand launching*". Keys keep
launchd's names so the model transfers. Files live in `etc/schinit/*.job` in the
repo and are installed to `/etc/schinit/` by the `etc-install` target — **not
written straight into `sysroot/`, which is gitignored**; that is why a fresh
clone has no `/etc/passwd` or `/etc/motd` (still an open gap).
**No libc in `cmd/`.** These programs link `lib/` — raw syscall stubs. No
malloc, no printf, no strlen. Static storage and hand-rolled helpers only.
**`getdents64` was exposed for this.** The kernel has had syscall 96/217 since
the Linux ABI landed, but nothing declared it in `include/uapi/user.h` or
`lib/usys.S`, so first-party programs could not list a directory at all. Step
records by `d_reclen`, never `sizeof`.
## The trap worth remembering: setsid() costs you the terminal
`setsid()` puts a job in its own session, and **a new session has no controlling
terminal**. The console delivers input only to the foreground process group it
tracks (`tty_fg_pgrp`), so the shell started, echoed every keystroke through the
line discipline, and executed nothing. It looks exactly like a hung shell and is
really a terminal that is not listening to it. See
[[schunix-tty-fg-pgrp-coupling]].
`ControllingTerminal = yes` claims it in the child: `ioctl(0, TIOCSCTTY, 0)`
then `ioctl(0, TIOCSPGRP, &pg)` where `pg = getpid()` (after `setsid()`, pgid ==
pid). **Off by default** — a daemon has no business seizing the console.
`TIOCSCTTY` is currently a no-op in the kernel; `TIOCSPGRP` does the real work.
## Do not transcribe bootstrap() from memory
The first version dropped `/dev/input/event0-1` and most of `/proc`, because it
was copied from the part of the old `cmd/init/main.c` that had been read on
screen rather than all 124 lines. `-Evdev` caught it with
`inputtest: cannot open /dev/input/event0`. **Read the whole file before
replacing it.**
Also: `build/usbstick.img` must be built by `all`. Without it QEMU refuses the
`-drive` and the entire `-Usb` run produces no output — which reads as a broken
USB driver, not a missing file.