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
|
# Filesystem
Every native session serves 9P2000 on a Unix socket. Pane shells receive
`PARDES_9P` (socket path) and `PARDES_PANE` (pane serial). The socket is
`$XDG_RUNTIME_DIR/pardes-9p-<pid>.sock`, or lives under
`~/.local/state/pardes` when XDG_RUNTIME_DIR is unset. Detached sessions use
their session name; `--9p=<name>` overrides it.
A `pardes <file>` launched from a pane forwards Look to that pane over 9P.
`--nested` opens a separate editor and disables forwarding from its pane shells;
its 9P service stays available.
Look resolves the OS filesystem first, then the editor's virtual filesystem.
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` |
`--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`.
At runtime, use `Mount peer dial` and
`Unmount peer`. There are eight named mounts; `os` and `self` are reserved.
Unmount refuses mounts still used by a pane, its working directory, or a
pending Save. Mounts are saved in dumps. Save uses the file's original mount.
`pardes --9p-tcp='tcp!127.0.0.1!5640'` adds a TCP listener alongside the Unix
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.
All connections have session access, including `os`. TCP is unencrypted.
QUIC uses an ephemeral TLS identity without peer verification or login.
It carries 9P2000 on one bidirectional stream with ALPN `pardes-9p`.
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:
```sh
9p -n -a "unix!$PARDES_9P" read self/index
9p -n -a 'tcp!127.0.0.1!5640' read self/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
[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/<serial>` contains
`body`, `tag`, `ctl`, `addr`, `data`, `event`, and selection files. Terminal
panes additionally have `pty/{ctl,status,data}`.
`EffectCode <effect>` 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 <word>`, `get`, `put`, `del`, and `delete`. Look resolves from that pane
without editing its body or tag. Holding `event` open redirects the pane's Look
and Exec clicks to that client; writing a record back performs the action.
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.
Zero-length truncation accepts the accompanying `mtime` hint sent by Linux
v9fs; the hint is not stored. Standalone timestamp changes remain refused.
`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.
`zig build fs-test quic-test -Dquic=true` also exercises QUIC mounts and I/O.
|