SchOS/.claude/memory/schunix-sockets-and-evdev.md
Nexoniarz 3182da3965 Carry project memory in the repo, with tools/memory-sync.sh
Claude Code keeps per-project memory at <home>/.claude/projects/<key>/,
where <key> is the project's absolute path with every separator and the
drive colon turned into a dash. That makes it both rename-fragile and
machine-specific, and this project has hit each of those once:

  - D:\DEV\Schunix -> D:\DEV\SchOS changed the key, and 43 memories were
    invisible until moved across by hand.
  - Development happens from Windows and from Linux, and each looks in a
    different place for the same checkout.

Committing .claude/memory/ fixes both -- git carries it across renames
and between hosts -- and `import` puts it wherever the local machine
expects. .claude/settings.local.json stays ignored: those are per-machine
permissions.

The colon maps to a dash rather than being dropped. Deleting it gives
D-DEV-SchOS, which matches nothing and silently finds zero memories; that
was the first attempt. On Git Bash the key comes from `cygpath -w`,
because Claude Code is a Windows program and sees D:\DEV\SchOS, not
/d/DEV/SchOS.
2026-08-09 14:27:22 +02:00

2.7 KiB

name description metadata
schunix-sockets-and-evdev Unix domain sockets and /dev/input evdev both work; the one lock rule, the names-not-in-the-VFS tradeoff, and where the syscall numbers went
node_type type originSessionId modified
memory project 9d8a2aef-bcf0-47a0-96c6-f706efab4a95 2026-08-05T07:12:20.950Z

Unix domain sockets (kernel/net/unix.c, include/socket.h, kernel/sys/sys_net.c) and raw input events (kernel/dev/input.c, include/input.h) are both done and verified — tools\bootcheck.ps1 -Sock and -Evdev.

One lock for the whole socket table, deliberately. A pipe gets a lock per pipe because every operation touches one object; a socket operation touches two, and per-socket locks need an ordering rule every path obeys or they deadlock. Worse, a reader testing peer_closed under its own lock while the closer sets it under the peer's has no interlock and sleeps through its own wakeup. Don't "optimise" this into per-socket locks.

Socket names live in a table, not the filesystem. ls -l /tmp/schcom will not show an s and unlink() of a socket path returns ENOENT. Fixing it properly means a new inode type through both filesystems, iget, dirlookup and stat — a VFS change, not a socket change. Everything that matters works.

connect() deliberately does not wait for accept(). A queued connection is already established on Unix; blocking would deadlock any server that connects to itself.

The syscalls had real Linux numbers assigned (41/49/50/43/42 and friends). They were NO_LINUX stubs before, which meant a musl program calling socket() got "bad syscall" from the ABI translation and never reached the stub at all — same trap as schunix-shm-and-mmap. shutdown is registered as sockshutdown because Schunix already has a native shutdown that powers the machine off.

evdev: /dev/input/event0 (keyboard) and event1 (mouse), major 7, Linux struct input_event layout. Fed by the PS/2 keyboard, the PS/2 mouse and USB HID. The console keeps its own path — both consumers see every key, so keys pressed during a test also land on the shell's input line.

Two things that are not obvious:

  • Below 0x54 a Linux keycode is the AT set-1 scancode, so the PS/2 path needs no table. USB HID does need one (hid_keycode[]), because HID numbers keys by position and the AT set numbers them by matrix wiring order.
  • A full event ring drops the oldest event. Dropping the newest is the obvious choice and it is wrong: the event most likely lost is a key release, and a lost release leaves the key held down forever.

See also schunix-console-tty, schunix-framebuffer-device, schunix-verify-by-driving-shell.