From c1990b3e6e196ad41379aa432bf3ccca8a65d9f0 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 20 Sep 2026 00:36:50 -0300 Subject: Flatten the 9P control tree and move it out of fs.zig The served tree loses the self/ level: /index /ctl /new /log /screen /listeners /pane//... /os, with /src only in -Dembed-sources=true builds (default off, on for esp32p4). ctl speaks the editor's own language with two lowercase verbs, look TEXT and exec TEXT, plus acme's addr verbs; the new/ factory directory becomes one clone file; cons is gone (exec Msg); name and sel are files; stats report real lengths, modes and mtimes; /log streams pane new/del/rename/save events. The tree code lives in src/ninep/ (tree, pane, ctl, addr, pty, events, screen, sources); fs.zig keeps host access, mounts, resolution and find/grep. Same engine and transports. README (fs-help.txt) and docs rewritten; tests updated and extended. Co-Authored-By: Claude Fable 5.1 --- docs/fs.md | 126 ++++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 91 insertions(+), 35 deletions(-) (limited to 'docs/fs.md') diff --git a/docs/fs.md b/docs/fs.md index 942a26a3..c16f810b 100644 --- a/docs/fs.md +++ b/docs/fs.md @@ -16,9 +16,13 @@ Explicit paths bypass that search: | Editor path | Meaning | 9P server path | |---|---|---| | `/n/os/proc/self` | OS filesystem | `/os/proc/self` | -| `/n/self/pane/2/body` | pane 2's text | `/self/pane/2/body` | -| `/virtual/src/pardes.zig` | source embedded in this build | `/self/src/pardes.zig` | -| `/n/peer/self/pane/2/body` | another session's text | peer's `/self/pane/2/body` | +| `/n/self/pane/2/body` | pane 2's text | `/pane/2/body` | +| `/virtual/pane/2/body` | the same, in the editor's own spelling | `/pane/2/body` | +| `/virtual/src/pardes.zig` | source embedded in this build | `/src/pardes.zig` | +| `/n/peer/pane/2/body` | another session's text | peer's `/pane/2/body` | + +The mount name `self` is reserved and maps to the server root, so `/n/self/X` +and `/virtual/X` both name the served `/X`. `--mount=peer=work` mounts the named session `work`; the dial can also be an absolute socket path, `unix!/path`, `tcp!IP!port`, or `quic!IP!port`. @@ -31,7 +35,7 @@ pending Save. Mounts are saved in dumps. Save uses the file's original mount. socket. Build with `-Dquic=true` and system OpenSSL 3.6+ to enable QUIC; `--9p-quic='quic!127.0.0.1!5641'` adds its listener. Both accept numeric IPv4/IPv6 addresses, not DNS names. Listener port zero chooses a free port; -`/self/listeners` reports all active dial addresses. +`/listeners` reports all active dial addresses. All connections have session access, including `os`. TCP is unencrypted. QUIC uses an ephemeral TLS identity without peer verification or login. @@ -40,56 +44,108 @@ The four application connection slots are shared across transports; OpenSSL's internal buffers are separate, dynamically allocated memory. [Plan9port's client](https://9fans.github.io/plan9port/man/man1/9p.html) can -drive Unix or TCP without a kernel mount: +drive Unix or TCP without a kernel mount, and `9ns` mounts the tree in a +private namespace: ```sh -9p -n -a "unix!$PARDES_9P" read self/index -9p -n -a 'tcp!127.0.0.1!5640' read self/index +9p -n -a "unix!$PARDES_9P" read index +9p -n -a 'tcp!127.0.0.1!5640' ls pane/1 +9ns --unix "$PARDES_9P" -- sh -c 'cat "$NINE_MOUNT/index"' ``` For [Linux v9fs](https://www.kernel.org/doc/html/latest/filesystems/9p.html), use `version=9p2000,cache=none,access=any` and `trans=unix`, or `trans=tcp` with `port=5640`. Set `uname`, `dfltuid`, and `dfltgid` for the local user. -Leave `aname` empty: the mount root contains `os` and `self`. The opt-in +Leave `aname` empty. The opt-in [Linux v9fs experiment](v9fs.md) tests a kernel mount in a separate subprocess namespace (`zig build v9fs-test`, requiring explicit mount authorization). Neither 9P2000.u nor 9P2000.L is implemented. Existing Plan9port/v9fs clients need a userspace bridge for QUIC. -The server root contains `os` and `self`. Under `self`, `index` lists panes, -`README` explains the interface. `new/` lists its creation endpoints and another -`README`; listing, walking and statting entries do not create panes. Opening -`new/ctl` creates a pane and returns its serial, and `pane/` contains -`body`, `tag`, `ctl`, `addr`, `data`, `event`, and selection files. Terminal -panes additionally have `pty/{ctl,status,data}`. - -`EffectCode ` lists the current backend's embedded implementation -files under `/virtual`; Look opens their full source. - -`self/screen` returns JSON with `cols`, `rows`, `cursor`, a `styles` table, -and row-major `cells` of `[grapheme, style_index]`. Each open freezes one -frame until close, including across multiple reads. The independent client -can inspect it directly with `Client(socket_path).screen()`. - -A terminal `body` freezes its history on the first read of each open handle; -later reads use the same bytes while output continues. Close and reopen for -newer history, or read `pty/data` for the live stream. Screen and terminal-body -snapshots share 32 handle slots, released on close or disconnect. - -Writing `body` appends; opening it with truncation replaces its contents. -`addr` selects a range and `data` replaces that range. `ctl` accepts `name`, -`look `, `get`, `put`, `del`, and `delete`. Look resolves from that pane -without editing its body or tag. Holding `event` open redirects the pane's Look +## The served tree + +``` +/README this guide, also src/fs-help.txt +/index one line per pane: serial, kind (text|term|pdf|image), dirty flag, name +/ctl write: one command per line; read: the serials the last command made or touched +/new reading it creates one empty pane and answers "\n" (the /net/tcp/clone idiom) +/log one line per editor event: new|del|rename|save ; reads park +/screen rendered screen JSON; frozen per open handle +/listeners the session's dial addresses +/pane// name body tag ctl addr data xdata sel errors event, plus pty/{ctl,status,data} +/os/ the host filesystem +/src/ the editor's embedded sources, only when built with -Dembed-sources=true +``` + +Nothing in the tree is created by list, stat, walk or read, except that +reading `/new` makes a pane; that is its whole purpose, so a recursive read +of the tree makes one pane per open of it. + +`/ctl` and `/pane//ctl` take the editor's own command language, two verbs: + +- `look TEXT` is a right click on `TEXT`: a path opens a file, `file:12` jumps + to a line, a directory opens a shell there, a URL opens in the browser. From + `/ctl` the look happens at the active pane; from a pane's `ctl` at that pane. +- `exec TEXT` is a middle click: a command word from `src/builtins.zig` + (`Save`, `Del`, `New`, `Newcol`, `Mount NAME DIAL`, `Unmount NAME`, `Dump`, + `Restore`, `Msg TEXT`, `Find`, `Grep`, `Tty`, ...), or anything else, which + runs in the pane's terminal. + +Both verbs are lowercase; the words after `exec` are the editor's capitalized +commands. A pane's `ctl` also keeps acme's addr verbs (`addr=dot`, `dot=addr`, +`limit=addr`, `clean`, `dirty`, `cleartag`, `get`, `mark`, `nomark`, +`noscroll`, `scroll`, `show`). Every line of a write is checked before any +line runs; a malformed line fails the write with EINVAL. A command that fails +inside the editor is reported on the message row, not as a write error. + +After a write to either `ctl`, reading `/ctl` answers the serials of the panes +the command created, or, when it created none, the pane a look focused or the +pane an exec acted on (even one it closed), one per line. Before any command it answers `pid`, `version` and `panes` lines. + +`/pane//name` reads the pane's file name (a terminal's directory) and +writing it renames the buffer; a relative name resolves against the pane's +directory. `sel` reads the editor selection and writing it replaces the +selection. `body` appends on write and replaces on truncating open. `addr` +selects a range and `data` or `xdata` read or replace it. `errors` appends to +the directory's `+Errors` pane. Holding `event` open redirects the pane's Look and Exec clicks to that client; writing a record back performs the action. +Stats report real lengths for `index`, `ctl`, `name`, `body`, `tag` and `sel`, +modes 0644/0666 (0444 for read-only files, 0222 for write-only), the pane's +last edit time or the process start as mtime, and stable qids. Directory +entries carry no sizes; stat the entry. + +`/log` records `new`, `del`, `rename` and `save` while at least one client +holds it open; a read parks until a record arrives. `/screen` returns JSON with +`cols`, `rows`, `cursor`, a `styles` table, and row-major `cells` of +`[grapheme, style_index]`. Each open freezes one frame until close. A +terminal `body` freezes its history on the first read of each open handle; +`pty/data` streams live output. Screen and terminal-body snapshots share 32 +handle slots, released on close or disconnect. + +`-Dembed-sources=true` embeds the editor's sources and serves them under +`/src` (and `/shaders` on GUI builds). `EffectCode ` lists the current +backend's implementation files under `/virtual`, which Look opens; without the +option the command reports the sources as unavailable. The esp32p4 build +enables the option by default, so the device can serve its own source. + This is a control filesystem, not a complete POSIX export. Native filenames may contain up to 255 bytes. Existing regular OS files support read, write, and truncation to zero; protocol create, remove, rename, and other metadata -changes are refused. Ownership, permissions, and timestamps are synthetic. +changes are refused. Ownership and permissions under `/os` are synthetic. Zero-length truncation accepts the accompanying `mtime` hint sent by Linux v9fs; the hint is not stored. Standalone timestamp changes remain refused. +The tree lives in `src/ninep/`: `tree.zig` (nodes, lookup, readdir, dispatch, +the backend contract), `pane.zig` (pane files), `ctl.zig`, `addr.zig`, +`pty.zig`, `events.zig` (event and log streams), `screen.zig` and +`sources.zig`. The protocol engine is `src/9p.zig`, the transports +`src/9p_io.zig`; `src/fs.zig` keeps host access, mounts, resolution, find and +grep. + `zig build fs-test` drives real sessions using the independent Python client -in `test/ninep.py`. `zig build 9p-test` checks the freestanding wire protocol; -`zig build fs-bench` measures filesystem transactions in the core. +in `test/ninep.py`; `zig build fs-discovery-test` checks that browsing creates +nothing and that `new`, `ctl`, `name`, `sel` and `log` behave. `zig build +9p-test` checks the freestanding wire protocol; `zig build fs-bench` measures +filesystem transactions in the core. `zig build fs-test quic-test -Dquic=true` also exercises QUIC mounts and I/O. -- cgit v1.3