# Filesystem Every native session serves 9P2000 on a Unix socket. Pane shells receive `PARDES_PID` (the editor's process id), `PARDES_9P` (socket path) and `PARDES_PANE` (pane serial). The socket is `$XDG_RUNTIME_DIR/pardes-9p-.sock`, or lives under `~/.local/state/pardes` when XDG_RUNTIME_DIR is unset. Detached sessions use their session name; `--9p=` overrides it. A `pardes ` launched from a pane forwards Look to that pane over 9P. `PARDES_PID` alone says the shell is inside pardes; `PARDES_9P` and `PARDES_PANE` say how to reach it, and a launch that has the first without the other two refuses rather than opening a second editor. `--nested` opens a separate editor and withholds `PARDES_PID` from its pane shells, so a pardes started in one of them runs a session of its own; 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 | `/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`. At runtime, use `Mount peer dial` and `Unmount peer`. Mount dials the peer when it mounts it and fails its write if nothing answers, `Mount peer /tmp/s: dial failed: no answer` (or `timed out`, `hung up`), mounting nothing; a peer that goes away later is found out by the next use, as `look: /n/peer/f: dial failed: no answer`. 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; `/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`. Unix and TCP connections share sixteen slots served by cloud9's `std.Io` runner; QUIC has sixteen of its own on the editor's poll loop. A client that finds every Unix/TCP slot taken gets an Rerror `too many connections` to its Tversion, and the log an `err - 9p: too many connections` record. 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, and `9ns` mounts the tree in a private namespace: ```sh 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"' ``` (Under `9ns --unix`, `$NINE_MOUNT` is that session's root itself.) A `9ns --unix` mount lives in the private namespace of the command it runs, and nothing outside that command sees it. The mount everyone on the machine shares is the registry one, `9ns --mntgen` (default `/mnt/9p`): every running editor posts itself there, so `$NINE_MOUNT/pardes//` is that editor's tree for any process, and `$NINE_MOUNT/pardes/NAME/` a `--detach=NAME` session's. 9ns exports `$NINE_MOUNT` to everything it starts, so a script checks that variable to know the mount is there, and takes the name from `$PARDES_9P` (`pardes-9p-.sock`). A new pane made through `pane/new` is a scratch named `/+New` until it is given a name, where `` is the session's directory (the one pardes started in), whichever pane last had the keyboard: no pane asked for it, as acme's new window has acme's directory. So is a `New` written to a column's `exec` or to `/tagexec`, a word in a tag no pane owns. A column may hold no pane, as in acme: `Newcol` makes one empty, and closing a column's last pane leaves it empty with its tag holding the keyboard (`focus` reads empty) and logs only the `del`. `pane/new` places its pane as acme's makenewwindow(nil) does: in the active column, filling it when it is empty, else taking the bottom half of its last pane ([where new panes go](tags.md#where-new-panes-go)). `Delcol` closes the column. Closing the session's last pane quits pardes; see [tags](tags.md#empty-columns). 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 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 served tree ``` /README this guide, also src/fs-help.txt /index one line per pane: serial, kind (text|term|pdf|image), dirty flag, name, column serial (the name as the log shows it: one line of UTF-8, a newline in it `\n`) /status pid, version and pane count /look write a line: a right click on it at the active pane; read: the serials the last look, exec or ctl write touched (made, else acted at) /exec write a line: a middle click; read the same serials /log recent events, one a line: new|del|rename|save , msg , dump|restore , err : ; write follow to that open to wait for more /screen rendered screen JSON; frozen per open handle /listeners the session's dial addresses /focus the serial of the pane with the keyboard; write a serial to give it the keyboard, which makes its column the active one as a click there would /ctl the settings, one a line as a write takes them; write a setting or a session builtin; `size ` sets the screen of a session no frontend is attached to (`--detach`, 160x50 until then; refused while a frontend owns the size), from 20x6 to 4096x4096 (outside that, `invalid size`, EINVAL), and refused when a column has not the rows for its panes' minima, each its tag and 2 rows, the minimum placement keeps; so a size once taken is taken again, and growing is never refused. A pane the resize took under its minimum gets its rows back from its column's others; a terminal's pty follows its pane on every resize /commands every builtin: word, `arg` if it takes one, `root`, `pane` or `both` (the ctl that takes it: Edit is both, at the active pane from the root; a pane's word such as Undo or Msg is refused at the root), a setting's values, then ` -- ` and what it does /layout one line per column (16 at most; the board 6; Newcol past that fails, `no space for a column: 16 max`, ENOSPC), left to right: serial index x width current|notcurrent (the column with the keyboard now) empty|full pane-serials...; then active : acme's activecol, which the keyboard leaving for another column's tag does not move, so the two can differ -- the active column, where pane/new and a look place a pane next (- when there is none) /tag the workspace tag; > replaces it, >> appends, one line /tagexec write a word: a middle click on it in the workspace tag; read as /exec. A pane's word (Undo, Msg, Save) is refused there and at a column's exec, `not a session control message "Undo": write it to pane//ctl` (EINVAL), never done at the pane with the keyboard; a tag's own words (New, Tty, Find, Grep in a column's) run /col//tag the tag of the column with serial n, the same way /col//ctl write Delcol, Joincol, New or Tty: each acts on that column, as from its tag /col//exec write a word: a middle click on it in that column's tag; read as /exec; rmdir col/ closes an empty column (a column with panes is refused, ENOTEMPTY) /pane/new open it to make a pane (the bottom half of the active column's last pane, acme's coladd; with no room there, last all the same, taking half the tallest pane's rows); the read answers that pane's serial. A session holds 64 panes (16 on the board); at that, every route that would open one -- this open, look, exec, New, Tty -- fails with `no space for a pane: 64 max` (ENOSPC through 9ns, which has no word for ENFILE) and an err record, and look reads back empty; a column with no room for one (each pane keeps its tag and 2 rows) refuses it the same way, `no space for a pane in that column` (docs/tags.md) /pane// name body tag ctl addr dot limit data xdata sel dirty mark scroll errors event look exec tagexec (a word as a click in its tag), plus pty/{ctl,status,data} on terminals /os/ the host filesystem /src/ the editor's embedded sources, only when built with -Dembed-sources=true ``` `/layout`, `/tag` and `/col` go past acme, which serves no column files -- its columns are only where a window sits. They are here so a script can see where the panes are (`/index`'s last word is each one's column serial) and edit the tags a person clicks in: a column tag is one line, a newline written into it a space, and a truncating write (`echo Make > col/3/tag`) clears it and drops the newline that ends it, as a pane tag's does. A column is named by its serial, as a pane is: it stays while the column lives, whatever opens or closes beside it, and is never reused; /layout gives each column's serial and its index left to right, and the log says `newcol ` and `delcol ` as columns come and go, and after a Restore `restoredcol ` for each column as `restored` does for panes. Joincol folds a column into the one on its right, which keeps its own serial and tag; the joined column's panes go below that column's own, in their order, and its serial is gone (`delcol`). The root ctl takes no column word: its `Delcol` is refused, pointing at `col//ctl`. Control messages are split by what they act on, as acme keeps window verbs on a window's ctl and webfs and upas/fs keep session settings on a root ctl. Each builtin declares its scope in src/builtins.zig (`scope = .session`; every setting is one, the rest act on a pane). `/ctl` takes the session's builtins, one a line, at whichever pane has the keyboard as each runs -- `Newcol`, `Dump`, `Mount name dial`, `Theme ink`, `Verbose off`; `Exit`, which quits the editor as acme's does (it refuses once over the panes with unsaved text, a `+New` scratch of 100 bytes or more too, whether it came through a `ctl` write or a click: first one `/log` record per pane, `unsaved `, then the write fails with one line that is never a list cut short, `: Modified (Exit again to discard)` for one pane, `4 unsaved panes: Modified (Exit again to discard)` for more, and the `err` record says the same; each pane's message row names it, a `+New` scratch with its serial, as scratches share a name: `/dir/+New (pane 12): Modified (Exit again to discard)`. Restore, Del and Delcol refuse the same way, with their own word; an `Exit` after more editing refuses again naming only the panes edited since the last refusal, as acme's does, and an `Exit` with nothing edited since quits, throwing all of it away; a scratch or a command's output under 100 bytes is not asked about, as acme's winclean asks about no small unnamed window (so a scratch's `dirty` of 1 in `/index` blocks nothing until it holds 100 bytes: it has no file to be out of step with, and a few lines typed to try something are not work to lose); `Restore`, which replaces every pane, asks the same first -- `Dump` writes `pardes--