--- 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/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.