diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-21 22:37:06 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 5dcfade5f102256de787b2157b01293160780411 (patch) | |
| tree | 256416f7a82eacc06233d6a543a1fdfec390daab /docs | |
| parent | 297e14cfc36e4613a8c1cb3b995597d0a9c2873b (diff) | |
| download | pardes-5dcfade5f102256de787b2157b01293160780411.tar.gz pardes-5dcfade5f102256de787b2157b01293160780411.zip | |
Make a pane by opening /pane/new, and a Plan 9 idiom pass
The Tcreate that replaced acme's /new was a step away from the idiom dressed
up as a step toward it. A pane is named by a server-assigned serial, so the
create ignored the client's name: `mkdir /pane/foo` succeeded and left you
/pane/12. A mkdir that does not make the directory you named is worse than the
read-with-side-effect it replaced, and it broke in the shell workflow that
motivated the change. `create` is out of the declared features, so Tcreate is
EPERM again; Tremove stays, since `rm` to close a pane is unambiguously right.
/pane/new is now opened, not created: the open makes the pane, the read of
that fid answers its serial, two reads agree, and closing it leaves the pane.
That is /net/tcp/clone's mechanism (kernel/network/ip/devip.c, in ipopen),
not acme's, and the difference is deliberate. acme allocates during the walk
and lands inside the new window, so /dev/new/body works in one step, and it
can afford to list `new` because a Plan 9 directory read carries every entry's
stat and nothing walks. A kernel or FUSE mount walks and stats each name a
listing gave it, so allocate-on-walk would make a pane per `ls -l`. Allocating
on open keeps `new` listed -- a stat is not an open -- at the cost of the
one-step new/body. `new` stays unreachable from an editor path, because that
resolution serves Look hover previews.
The idiom pass behind it, read out of the Plan 9 tree at
~/05-genizah/principia-softwarica rather than recalled:
Rerror carries a string, not an errno (man 5 error: `ename[s]`), and acme
names every refusal. The five refusals pardes shares with acme now say what
they mean; the generic sites keep their bare errno rather than invent strings
acme does not have. body and tag declare DMAPPEND, which they had always
behaved as (acme(4): "always appended; the file offset is ignored"), checked
first against Linux's fs/9p, which never maps the bit. excl stays unset
everywhere, because acme sets DMEXCL on nothing. Blocking reads, per-object
addr scope and the readable pane ctl were already right. Real stat sizes and
qid versions stay: acme reports length 0 and version 0 for everything, and
Linux clients need better.
One bug fell out of it. open reset the addr range, so `echo '#0,#5' >addr;
cat addr` answered `0 0` and `cp addr dot` copied zeros. acme(4) makes the
contract explicit -- "a regular expression may be evaluated by writing it to
addr and reading it back" -- and acme gets away with resetting on the 0-to-1
open only because its clients hold the fid across both. A shell cannot: that
is two opens. The register is cleared by truncating it now.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/fs.md | 40 | ||||
| -rw-r--r-- | docs/v9fs.md | 9 |
2 files changed, 37 insertions, 12 deletions
@@ -79,19 +79,32 @@ Existing Plan9port/v9fs clients need a userspace bridge for QUIC. /log one line per editor event: new|del|rename|save <serial> <name>; reads park /screen rendered screen JSON; frozen per open handle /listeners the session's dial addresses -/pane/ create a directory here to open a pane; remove one to close it +/pane/new open it to make a pane; the read answers that pane's serial /pane/<n>/ name body tag ctl addr dot limit data xdata sel dirty mark scroll errors event look exec, plus pty/{ctl,status,data} on terminals /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. A pane is opened -by Tcreate in `/pane` (`mkdir`) and closed by Tremove on `/pane/<n>` (`rmdir`), -which is the only create and the only remove the tree serves. The name a -create asks for is ignored, since a pane is named by the serial the editor -gives it: the Rcreate qid names the new directory, and because `/index` is -ordered by serial its last line is the pane just made. +A pane is made by **opening** `/pane/new`, and closed by Tremove on +`/pane/<n>` (`rmdir`), which is the only remove the tree serves; Tcreate is +refused everywhere, as it is in acme. Reading the open fid answers the serial +of the pane that open made, so `n=$(cat /pane/new)` makes one and names it in +a line. Each open makes another pane, and two reads of one fid answer the same +serial: the open acted, the read only observes. Closing the fid leaves the +pane. + +This is `/net/tcp/clone`'s mechanism, not acme's `new`, and the difference is +deliberate. acme allocates during the *walk* and lets the walk land inside the +new window, so `/dev/new/body` works in one step (acme(4): "accessing any file +in `new` creates a new window"). acme can also afford to list `new`, because a +Plan 9 directory read carries the stat of every entry and nothing walks. A +kernel or FUSE mount is not so lucky: it walks and stats each name a listing +gave it, so an allocate-on-walk name would make a pane per `ls -l`. Allocating +on open instead keeps `new` listed and `ls` honest — a stat is not an open — +at the cost of acme's one-step `new/body`. Nothing in the tree is created by +list, stat, walk or read; only that one open. Every other name in `/pane` is a +serial. `/look` and `/exec` are the editor's two clicks, one per line of a write: @@ -130,6 +143,9 @@ address expression (`#0,#5`, `/pattern/`, `2+1`); `addr` selects what `data` and `xdata` read or replace, `dot` is the editor's own selection and moving it scrolls the pane into view, and `limit` bounds a search and reads empty until it is set. Truncating a range file empties it; truncating `limit` lifts it. +`addr` belongs to the pane rather than to a client and keeps what was written +until someone writes or truncates it, so writing an address and reading it +back evaluates it, which is what acme(4) promises of its own `addr`. The three flag files `dirty`, `mark` and `scroll` read `0` or `1` and take `0` or `1`: whether the buffer differs from its file, whether a write pushes @@ -164,8 +180,8 @@ 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; under `/os` protocol create, remove, rename and other -metadata changes are refused, as is every create and remove in the control -tree but the pane directories. Ownership and permissions under `/os` are +metadata changes are refused, as is every create in the control tree and every +remove in it but a pane directory's. 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. @@ -180,9 +196,9 @@ for Unix and TCP, a poll loop for QUIC, and the 9P client for mounts); `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 fs-discovery-test` checks that browsing creates -nothing, that a pane create and remove work, and that `look`, `exec`, `name`, -`sel` and `log` behave. `zig build +in `test/ninep.py`; `zig build fs-discovery-test` checks that browsing +creates nothing, that the walk to `/pane/new` and a remove work, and that +`look`, `exec`, `name`, `sel` and `log` behave. `zig build 9p-test` checks the two engine configurations' budgets (the engine's own tests are cloud9's `zig build test`); `zig build fs-bench` measures filesystem transactions in the core. diff --git a/docs/v9fs.md b/docs/v9fs.md index 8cc97ff9..1b14c21f 100644 --- a/docs/v9fs.md +++ b/docs/v9fs.md @@ -13,8 +13,17 @@ cat "$PARDES_MOUNT/index" cat "$PARDES_MOUNT/README" cat "$PARDES_MOUNT/pane/$PARDES_PANE/body" echo 'Msg hello' > "$PARDES_MOUNT/exec" +awk '{print $1}' "$PARDES_MOUNT/pane/new/ctl" ``` +Walking to `pane/new` opens a pane, and the walk lands on that pane's own +directory, so its `ctl` answers the serial to use afterwards. The kernel keeps +the name it walked rather than the one the server answers back, so +`pane/new` stays in the dentry cache as a name of its own; address the pane as +`pane/<serial>` once you have it, and expect a fresh path resolution of +`pane/new` to open another pane. It is not listed in `pane/`, so `ls -l` and +`find` over the mount create nothing. + The mount belongs to that pane's subprocess tree. Other panes and the editor core keep their original mount namespace. It works in native Linux TTY and SDL sessions, including detached sessions. A frontend attaching from elsewhere does |
