1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
# cloud9 integration
The published `cloud9` package owns the base 9P2000 wire format, client and server
connections, and TCP/Unix/QUIC transports. `build.zig.zon` pins a commit from
`[email protected]:~gbrls/cloud9`, so `zig build` fetches it into `zig-pkg/` like every
other dependency; no sibling checkout is required. Re-pin with
`zig fetch --save=cloud9 git+https://git.sr.ht/~gbrls/cloud9#<commit>`, and swap in
`.cloud9 = .{ .path = "../cloud9" }` while editing both packages at once.
The file-server engine (fids, jobs, parking, flush, hangup) is cloud9's
`fs.Server`; the control tree in `src/ninep/` is its backend, using cloud9's
`fs.Req`, `fs.Op`, `fs.Status`, `fs.E` and `fs.ReplyWith` (extended with the
editor's reply payload locator). `src/9p.zig` names the editor's and the
board's `fs.Options` and re-exports the wire names the transports use.
Mounting, Unix namespace discovery and permissions, the editor event loop,
connection limits, and exported tree policy remain here. The Unix and TCP
listeners run on cloud9's `serve.Runner` (`std.Io`: an accept task per
listener, a reader and a writer task per connection, four slots); its handler
answers every backend request on the connection's task, taking the editor's
turn with the core (`pardes.turn`) while the editor waits for input or is out
in a syscall. A request that would change a pane while the editor is mid-step
is parked with `Status.again` -- the engine parks reads, writes, opens,
truncations, clunks and removes -- and retried by `Runner.wakeAll` when the
editor next rests. A read with nothing yet parks the same way, but is never
retried for news: the core holds it (`ctlfs.hold`) and `answerHeld` answers
it on its connection through `Conn.reply`'s engine, under the connection's
lock and only while the engine still holds that tag parked, since cloud9 does
not tell the backend about a Tflush. `src/9p_quic.zig` selects the existing `pardes-9p` ALPN for
cloud9's optional OpenSSL transport; QUIC still runs on the poll loop in
`src/9p_io.zig` on the editor's thread, since cloud9's QUIC adapter is
nonblocking-descriptor based rather than `std.Io` based.
The standalone protocol/GPIO tests import the same module. The separate
`05-zig-p4` build also supplies cloud9 for the GPIO firmware entry.
Run `zig build 9p-test` for the engine configurations and
`zig build 9p-io-test -Dquic=true` for native transport/client integration. Run cloud9's `zig build test`,
`transport-test`, `quic-test -Dquic=true`, `fuzz`, and `differential` steps for the
shared implementation. See cloud9's `docs/validation.md` for recorded runs and
known test-environment limits.
Invalid framing now terminates a server connection. Cloud9 also checks reply
counts and reserves tags until flush completion. Client metadata is slightly
larger to track those reservations, and its bounds tests reflect that fixed cost.
## The posted-9P registry
`$XDG_RUNTIME_DIR/9p` is this machine's `/srv`: a server posts itself in it
under a name, and clients dial names rather than paths. cloud9 owns both
sides (`cloud9.post`), and `9ns --mntgen` mounts the whole registry at
`/mnt/9p` for programs that want it as a filesystem. pardes's only part in
it is to put itself there.
**Serving.** A listening editor advertises itself at
`$XDG_RUNTIME_DIR/9p/pardes/<name>`, a symlink to the socket it already
binds. One directory for the program, one entry per editor, so several
editors group instead of crowding the registry root — the layout zmx posts
its sessions under. The socket itself does not move: adopting the registry
only advertises. Only the runtime-directory socket posts; an instance that
fell back to `~/.local/state/pardes` stays out of the user's registry, the
way a private `ZMX_DIR` does for zmx. Stopping unposts, and only while the
entry is still ours, so a name another editor has since claimed is never
unlinked.
An exit that cannot run any code of its own — an aborted test, a kill, a
crash — leaves its entry and its socket behind, so posting first sweeps the
group: every entry that is a symlink and whose socket answers a connect with
a definite ECONNREFUSED is unlinked, along with the socket it points at when
a `stat` agrees that is a socket of ours. Anything that is not a symlink is
somebody else's, and any other answer — connected, busy, refused permission,
a surprise — counts as live, because uncertainty belongs to the server that
owns the socket rather than to the sweeper. That is `cloud9.post.Probe`'s
classification, repeated in `src/9p_io.zig` only because `post.probe` is raw
Linux syscalls and pardes also builds for darwin.
**Consuming.** Nothing. `9ns --mntgen` mounts the whole registry at
`/mnt/9p`, and an interactive fish already self-wraps in one, so a pardes
started from a terminal sees every posted service as ordinary files —
`/mnt/9p/harness/active/...` is read with the same code that reads any other
path. Teaching pardes to dial the registry itself would put discovery in a
second place for no gain: mounting is the client's job and 9ns is the
client. `--mount=<name>=<dial>` keeps meaning exactly what it always did,
and a dial keeps resolving exactly as it always did — a bare name is another
pardes session, and anything with a slash is a path, relative ones included.
The one case that is not free: a pardes started outside a mntgen mount has
no `/mnt/9p`. That is 9ns's problem to solve — by being in the namespace —
not a reason for pardes to carry its own registry client.
### What this diverges from, deliberately
* **pardes binds its own socket; it does not post through `cloud9.post`.**
`post` would give us its hardened claim protocol (temp-bind plus atomic
rename under a lock) instead of the stale-socket retry in `listen`, but it
claims *flat* names only: `legalName` rejects `/`, and `claimName` derives
its lock directory by stripping `/9p` from the registry path, so a name
inside a subdirectory cannot go through it. zmx hand-rolls the same
symlink for the same reason. Unifying them means teaching `post` a group —
passing the lock directory in rather than deriving it — and that is a
change to adversarially-hardened code, not a rename.
* **The registry entry is a symlink, not the socket.** A reader that expects
every registry entry to be a socket must `stat` following symlinks.
`9ns --mntgen` and `cloud9.post.dial` both do.
* **Dialing is untouched.** An earlier draft taught `resolve` to fall back
to the registry for a bare name and to read `<group>/<name>` as a
subdirectory entry. Both were reverted: the second reinterpreted relative
dials, which are a feature, and the first duplicated what 9ns already
does. `src/9p_io.zig`'s `resolve` is byte-identical to what it was.
|