summaryrefslogtreecommitdiff
path: root/src/acmefs.zig
Commit message (Collapse)AuthorAge
* Refactor panes and filesystem; replace FUSE with 9PGabriel Schneider2026-09-07
| | | | | | Consolidate pane, layout, memory and host code. Serve 9P by default over Unix sockets, with runtime mounts and optional TCP/QUIC transports. Remove FUSE and obsolete proof-of-concept examples. Fix highlighting and terminal-history performance, expand differential and stress-test infrastructure, sort navigation results while preserving the next occurrence, add syntax-colored Braille minimaps, remove SPC-k, and document 9P interaction as a repository skill.
* 9p: the client half, and a board that serves its own tree over the UARTGabriel Schneider2026-08-27
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Step 5 of the 9P chain (docs/9p.typ 12.5, docs/registry.typ 9P-22, 9P-11, BOARD-1). THE CLIENT. `Client` in src/9p.zig is the mirror of `Server` and the same shape: sans-io, no allocator, no threads, no descriptor, caller-owned buffers, and it builds freestanding. 152 bytes of struct against the server's 9,488, because a client owns neither a fid table nor a park table -- the far end does. The API is submit / push+output+wrote / take. Completion is a PULL: a callback would fire inside push, inside the transport's read, inside the host's poll dispatch, which is exactly where fs9_service says filesystem work must not happen. `take()` returns the next completed operation or null, which is `Server.next()`'s loop-until-null contract read from the other side. Tags are a fixed 16-entry table indexed BY the tag, so an out-of-order reply -- which 9P allows and both reference clients rely on -- costs one bounds check. The reply's TYPE is checked against the request's op, because a tag is only as good as the table behind it. A `Done` borrows the input buffer and is valid until the next call; `take()` releases the previous frame on entry, so the rule is mechanical rather than remembered, and read data and error strings are zero-copy. And one real caller, so this is not a library with no user: the `9p` word takes a dial and a path, walks another instance's tree, and opens the bytes in a pane like any other `Look`. THE BOARD. A SECOND image, not a second role: the console runtime keeps UART0 bidirectionally and is behaviourally untouched. On the new one the UART carries 9P AND NOTHING ELSE -- no ANSI, no vaxis, no allocator, no heap module. The loop is uart.read -> push / retry+next -> handle -> reply / output -> writeSome -> wrote. `writeSome` is new and additive: `write`'s bounded spin DROPS bytes on a stalled transmitter, which on a protocol stream truncates a reply mid-message and desynchronises for good, where a short count cannot. BOARD-1's one divider write raises the line to 921600. 88,000 B text, 49,424 B bss, an 88,080-byte image -- 5.7% of the 1,536,000 B partition, against the console image's 809,536 B. THE COMPTIME BRIDGE, which is the part worth reading. `board9p.caps` is the ONLY place the GPIO tree is described; node ids, parents, names, permissions, handlers, buffer size and the per-pin directories are all derived from it, and `fan.dirs` makes `gpio/<n>/value` one table entry serving eleven pins. Modes are derived from which handlers a file has rather than declared. A second capability is a table entry, not new tree code. JP1 became a real table in the new leaf `src/board_pins.zig`, with the ASCII drawing RENDERED from it at comptime and the pin list COLLECTED from it -- the 9P image links no core and so cannot import board_memory.zig, and copying the table was not acceptable. A golden test pins the drawing byte for byte, the console's own shape test still passes, and the identical bytes are present in all three artifacts. PROVED. Two daemons: B read A's `/1/body` through the `9p` word into a pane, byte-identical to plan9port's `9p read` of the same path. Both board images build. No hardware was attached, so nothing about the board is claimed beyond what builds and what the host tests cover. zig build unit-test 585/585. fs-bench unchanged and still zero allocations on every read row. --- REVIEW FIXES FOLDED IN. Steps 3, 4 and 5 were verified on the happy path and then adversarially reviewed by three agents; eight defects, six fixed here, five of them reproduced with measurements before and after. Full writeup in docs/registry.typ `9P-27`. In brief: * a remote crash of the WHOLE daemon: one `size[4]` of zero plus one byte hit `unreachable` in `fs9_service.fill`. Also 99.7% of a core when the stuck buffer made `room == 0` return without reading. Now `srv.dead` is a hangup, checked before the room guard. * the editor froze 177 s on a dial: `connect(2)` ran on a still-BLOCKING socket before the deadline existed, and a full accept backlog waits forever. Now non-blocking with the wait spent against the budget. After: 2.03 s. * a 64 KiB pty read is exactly `queue_cap` and wiped every unread byte AND dropped itself. `notePtyOutput` splits at half the cap. Deterministic. * four silent sockets denied `--fs9` forever; connections now expire on the same five-second rule the frontend transport already had. * EMFILE spun a core; the listener pauses and leaves the poll set, as the frontend listener does. * `max_fids = 32` made `find` over `9pfuse` fail with 57 consecutive `Rerror`s -- refuting this step's own acceptance clause. 256 for a host, `board_fids` 32 for the microcontroller. Found clean and worth recording: `sig` reaches the foreground process group; the two-namespace pty lookup is right over both transports; `PaneFile`'s u4 wall is guarded; reader counts release on every abrupt-death path; `fs_origin` routing and the reply arithmetic hold under probing.
* acmefs: a pane's terminal gets pty/data, pty/ctl and pty/statusGabriel Schneider2026-08-27
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Step 3 of the 9P chain (docs/9p.typ 12.3, docs/registry.typ 9P-8). Nothing here is about 9P: it lands in the FUSE-served tree and any later transport inherits it. A script could write into a terminal that already existed and read its rendered scrollback. It could not START one, RESIZE one or SIGNAL one. Two of those were already effects the core emits, so `exec` and `winsize` are existing capabilities acquiring a name; only `sig` is new, and it brings the one new host method, `push_pty_signal`. pty/ctl winsize <cols> <rows> | sig INT|TERM|HUP|QUIT|KILL | exec one verb per line, validate-all then apply-all, EINVAL applies nothing -- `writeCtl`'s shape and `writeCtl`'s reason pty/status cols, rows, tty-taken as three %11d fields pty/data write is input to the process; read is the RAW output stream, gated on a reader count so a pane nobody reads costs one branch A pane that is not a terminal has no pty/ at all: the lookup is ENOENT and readdir does not list it. `PaneFile` is an enum(u4) and this takes it from 11 values to 15. ONE REMAINS. That is also why pty/ is a DIRECTORY and not three more flat names -- a subdirectory costs one value and buys its own namespace, so `ctl` and `data` did not have to be renamed. Two things the core does not know, and which are therefore not invented: a child's EXIT STATUS (a shell's death is `Event.eof`, which removes the pane, so there is no directory left to read it in) and RAW/COOKED (the core never sets a termios; the mode belongs to the program on the far side). Verified live against a daemon: pty/ appears only on the terminal pane; a `winsize 0 24` and a `sig SIGINT` are refused; a bad verb beside a good one applies neither; `echo pty-works` written to pty/data runs in the shell and its output reaches the body; and a blocking read of pty/data returns the raw stream, OSC 133 marks and all. fs-bench unchanged and still zero allocations.
* A fourth platform: pardes as ESP32-P4 firmware, bytes in and bytes outGabriel Schneider2026-08-25
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | `-Dplatform=p4 -Dtarget=riscv32-freestanding` emits a single freestanding OBJECT exporting a seven-function C ABI, not an executable. The board's toolchain (../05-zig-p4) owns `_start`, the linker script and the UART driver and links this in. The seam is bytes rather than types, so neither side can accidentally depend on the other's internals, and a signature that drifts fails at link time. The serial line is the whole of the I/O. `src/p4.zig` drives vaxis unchanged over it: the renderer is a byte writer and `queryTerminalSend` is a byte writer, so the terminal emulator on the host answers the capability handshake and the firmware sees a real terminal. Measured going out over the wire on attach: alt screen, in-band resize, cursor report, kitty keyboard, kitty graphics, DA1. THREE WORDS EXIST ONLY HERE. `src/board_memory.zig` implements `Peek`, `Poke` and `Hexdump`, gated on `builtin.os.tag == .freestanding and !isWasm()` - derived from the TARGET, because they are a property of running with no OS under you rather than a product option, and because wasm is freestanding too and is exactly what must be excluded: in a browser an address is an offset into the linear memory this editor's own heap lives in. Every access goes through `*allowzero volatile`: a peripheral register is not memory, and address 0 is an ordinary unmapped address on this bus. One 4 KiB cap per command, set by the console rather than the memory - an unbounded dump would wedge the only console the board has for eleven hours. Measured on ESP32-P4 rev v1.3 silicon, driven from a host terminal: Peek 0x501101a4 0x0e63ce71, then 0xaeaa6919 on a second read - the RNG register, so the volatile loads are not folded Poke 0x5011002c 0xdeadbeef LP_STORE0; a later Peek returned 0xdeadbeef Hexdump 0x5011002c 32 16 bytes a row, hex columns and an ASCII gutter Peek 0x50110001 `peek: MisalignedAddress` on the message row That last line is the one that matters. A misaligned 32-bit access traps, and a trap in firmware is a watchdog reset that takes the session with it, so the check that turns it into a message is the reason the file is hand-written rather than a generic reader. BARE METAL BOOTS AN EMPTY OUTPUT BUFFER. Every other boot layout in `init` makes a shell, and on this platform that is not a preference but an impossibility: nothing to fork, no pty to give a terminal pane. Booting one anyway produced precisely what that describes - a pane whose tag ends in `Filter`, no gutter, no buffer, and every keystroke vanishing into the Fallback's silent pty. An output buffer is also what the platform's own words want, since Peek, Poke and Hexdump each fill one. Sized for the board rather than for a desktop: * `allocators.zig` gains a p4 tier that is ALL fallback - every capacity is zero, so each arena spills immediately to the 384 KiB heap the firmware hands over, and no megabyte-shaped static reservation lands in `.bss`. * `source_manifest.zig`'s allowlist is EMPTY on p4. The table is ~0.95 MiB of rodata against a 1.5 MiB flash partition; the firmware's filesystem is the serial host's, through the Host vtable. * The grid is clamped and the clamp is measured, not guessed: every cell is paid for four times (vaxis Screen + InternalScreen, pardes Surface + previous_cells), so 40x12 fits and 80x24 exhausts the heap during `Pardes.init`. * `Vaxis.resize` deinits both screens before allocating replacements, so a failed resize leaves vaxis rendering nothing. The p4 shell keeps the previous geometry on failure instead of leaving a half-applied one. Also here: `output_pane_integration_test.zig` had an exhaustive switch over `Platform` that adding `.p4` left unhandled, which broke `zig build unit-test` outright - the native test binary is the one consumer no platform build compiles. 346 tests pass again.
* acmefs: pardes --fs serves acme's control filesystem over raw Linux FUSEGabriel Schneider2026-08-25