# Foundations The kernel work that [wayland.md](wayland.md) and [hybrid.md](hybrid.md) both stop at. **This is the critical path**, and it is the reason those two are one plan rather than two. The insight is small and it decides the schedule: the substrate for passing a buffer to a compositor and the substrate for handing a device to a driver process are the same substrate. Descriptor passing and shared memory, built once. --- ## What is actually in the tree Measured against the source, not against a README. Each of these was checked before anything below was planned around it: | | | |---|---| | ~~`sendmsg`/`recvmsg`, and therefore **`SCM_RIGHTS`**~~ | **Built.** See item 1 below. | | Writable `MAP_SHARED` of a file | **Refused** — `kernel/sys/sys_mem.c:131` returns `-ENODEV`. | | `memfd_create` | Does not exist. | | `AF_UNIX` names | In a table in `kernel/net/unix.c`, not in the filesystem. | | `setsockopt` | `return 0;` unconditionally — `kernel/sys/sys_net.c:436`. | | `exec` | `ET_EXEC` only. No `PT_INTERP`, no `ET_DYN`. | > **`projects/schcom/README.md` said `SCM_RIGHTS` was done while nothing in the > kernel implemented it** — the most load-bearing wrong line in the tree, with an > entire GUI plan drawn on top of it. The line is now true, which is not the same > as it having been right, and the README says so. The `MAP_SHARED` refusal is worth defending rather than fixing carelessly. The code returns `-ENODEV` *honestly*: the contract of a writable shared mapping is that stores land in the file and other mappers see them, and there is no page cache that could make that true. Quietly handing back a private copy would be the same lie in a new place. --- ## The six items In dependency order. The first three are what Wayland cannot start without. All but item 4 are built. ### 1. `sendmsg` / `recvmsg` with `SCM_RIGHTS` — **done** Not optional and not substitutable. `wl_shm_create_pool` passes a **file descriptor**; there is no path in the Wayland protocol that passes an integer instead. The `handle_kind` field in `projects/schcom/proto.h` was good foresight and it cannot help here — *our* protocol is the flexible one, and Wayland's is not. Built as described: `struct msghdr`/`cmsghdr` in Linux's layout, ancillary data queued alongside the byte stream in `kernel/net/unix.c`, descriptor installation into the receiver's file table, and the three ways a message can end — received, received without room, never received — each releasing the kernel's reference. Fourteen checks in `cmd/socktest`, every one of them moving data through the descriptor rather than inspecting it. Documented in [../architecture/networking.md](../architecture/networking.md#descriptor-passing). The delivery rule is the part worth knowing before building on it: **descriptors are pinned to the stream position of the first byte of the message that carried them**, and a read that has not reached that position is cut short so it stops before it. That is what keeps "which message did these belong to" answerable. **This was also item 1 of the hybrid work**, where it is how a driver process is handed the device it is allowed to touch. Two bugs fell out of building it, neither in the new code: - `sockpair()` never set the socket's domain, so **every `socketpair()` socket claimed domain 0**. Invisible for as long as the data paths asked only whether a socket was `AF_INET` or `AF_NETLINK` — zero answers "neither" correctly. `SCM_RIGHTS` asks "is this `AF_UNIX`", and zero is wrong for that one. - `pipe_poll()` reports `POLLHUP` but not `POLLIN` for a pipe at end of file, so a **non-blocking read of a pipe whose writers have all gone returned `EAGAIN` forever** instead of 0. A program that polls and reads would spin waiting for an end that had already happened. `sockreadable()` always had the equivalent condition; the pipe path did not. ### 2. A shareable memory object — **done** `memfd_create`, plus `mmap(MAP_SHARED)` on it genuinely working between two processes. Described in [../architecture/kernel.md](../architecture/kernel.md#shared-memory-objects); asserted by `cmd/memfdtest`, seventeen checks including the one that matters — a descriptor sent over a socket, mapped by a receiver that has closed every inherited copy, with stores visible in both directions. Narrower than a general page cache, and sufficient. An anonymous shared object needs no file behind it, which is exactly why `memfd` exists on Linux at all — and it means this can be built on the shared-memory machinery already in `kernel/sys/sys_mem.c` (`shmget`/`shmat`/`shmdt` work, and `struct vma` already carries `shmid` and a `no_free` flag for pages that are not ours to release). The general answer — a page cache, and writable `MAP_SHARED` of a real file — stays owed. It is a larger project and nothing on this plan needs it. ### 3. `AF_UNIX` sockets in the filesystem namespace — **done** Clients find a Wayland compositor by `bind()`ing `$XDG_RUNTIME_DIR/wayland-0` as a real path, and libwayland `flock()`s the lock file beside it. A name table can express neither. `bind()` creates a `T_SOCK` node and `connect()` resolves the path and matches the socket bound to **that inode** — not to a string, which is what made two spellings of one path miss each other. `ls -l` shows `srwxr-xr-x`, `stat()` reports `S_IFSOCK`, and `unlink()` removes it. Asserted by `cmd/socktest`, section 15. `flock()` is still not implemented; see below. `kernel/net/unix.c`'s own header comment already predicted this: > *"What is lost is `ls -l /tmp/schcom` showing an `s`, and `unlink()` of a socket > path failing with ENOENT. Both are worth fixing when the VFS next gets opened > up; neither blocks anything."* They block something now. This is that moment, and it is a VFS change rather than a socket change: a socket inode type threaded through `iget`, `dirlookup` and `stat`, plus `flock`. ### 4. `setsockopt` that does not lie At minimum `SO_PASSCRED` and `SO_SNDBUF`. Returning 0 for an option you ignored is worse than `ENOPROTOOPT`, because the caller adjusts its behaviour on the strength of the lie. Small, and grouped here because it is in the same file as item 3 and will be found by the same testing. ### 5. Dynamic linking — **done** `PT_INTERP` and `ET_DYN` in `kernel/fs/exec.c`. A program that names an interpreter gets it loaded at the bottom of the mmap area and started instead, with `AT_PHDR`, `AT_PHENT`, `AT_PHNUM`, `AT_BASE` and `AT_ENTRY` to find the program by; a PIE program is placed where a fixed one would be. The interpreter is musl's own: `ports/musl` now builds `libc.so` as well, shipped as `/lib/ld-musl-x86_64.so.1`, which is libc and loader in one file. The kernel half that was missing besides `exec` was `mmap(MAP_FIXED)` of a file. It handed back zeroed pages whatever the descriptor said, and kept no record of them for `fork` — and a dynamic linker maps every library segment after the first exactly that way. Every port still links `-static`, on purpose: this makes the other kind possible, it does not convert anything. `cmd/dynhello` is the test — a program against `libc.so` and a library of its own, checking TLS, the heap and a forked child that still has its libraries. `mprotect` is accepted and not enforced, so RELRO is not read-only yet. ### 6. The event descriptors — **done** `epoll`, `eventfd`, `timerfd` and `signalfd`, in `kernel/sys/sys_event.c`. libwayland's event loop is all four in one epoll set, which is why they arrive together; not in the original list because they were stubs that looked like syscalls, returning `ENOSYS` from `table.inc` entries that read as done. They wait the way `poll()` does, a tick at a time, so the latency is 10 ms. `cmd/evtest` checks each one, then all four in one set, level- and edge-triggered. Building it found a signal bug: a default-fatal signal killed on arrival even when it was blocked. A blocked signal now stays pending, which is the contract `signalfd` is built on. --- ## Verification Each item gets a test before the thing that needs it is built, because a substrate that is subtly wrong fails inside the consumer and looks like the consumer's bug. - **Item 1:** two processes, one passes an open descriptor, the other reads through it. Plus the leak case: a sender that exits with descriptors in flight. - **Item 2:** two processes map the same `memfd`, one writes, the other sees it. - **Item 3:** `bind()` a socket, then `stat()` and `ls -l` the path and see an `s`, then `unlink()` it. - **Items 1–3 together:** a third test that does all of it at once, because that combination is what Wayland actually performs and the three passing separately does not prove it. These belong in `bootcheck` as a new switch, following the `-Sock` model. --- ## What this does not deliver - **No page cache**, so writable `MAP_SHARED` of a real file stays refused. - **No `dlopen`** until item 5 is done, which means no Mesa and no third-party shared libraries. - **`SO_*` coverage is the two options named**, not the full set. - **No credential passing** (`SCM_CREDENTIALS`) — only descriptors. Nothing on this plan needs it, and a compositor that wants to know who connected will. - **No `flock()`.** libwayland locks `wayland-0.lock` beside the socket to stop two compositors claiming one display. The socket itself is now a real path, so the lock file can be created and opened; what is missing is the advisory lock, which is a separate mechanism and has no other caller yet.