summaryrefslogtreecommitdiff
path: root/9ns/README.md
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-19 23:55:47 -0300
committerGabriel Schneider <[email protected]>2026-09-19 23:55:47 -0300
commit3e9f8805f293f622bb885cf849b5ce47dc062ad1 (patch)
tree0f31107fd20e7a9aa065826c891d2d326efc2309 /9ns/README.md
parentba996acfcad1698adbf4a1834fe50e73b1c6cab9 (diff)
downloadcloud9-3e9f8805f293f622bb885cf849b5ce47dc062ad1.tar.gz
cloud9-3e9f8805f293f622bb885cf849b5ce47dc062ad1.zip
9ns: --name and /mnt/9p/<name> mounts, qid.path as inode number, interrupts as Tflush
- --name NAME (default derived from the transport: socket basename, tcp-IP-PORT, spawned command, fdN) mounts at /mnt/9p/<name>; --mount still overrides. ensureMountpoint walks down and creates missing components, shadowing the deepest unwritable ancestor. NINE_MOUNT is the only exported variable. - The inode number reported to the kernel is the 9P qid.path for every node, root included; a server handing qid.path 1 to a file (Pardes /self) no longer collides with the root. - FUSE_INTERRUPT for the request in flight becomes Tflush; a blocked read returns EINTR when the server answers the flush, chunked transfers return short counts, other requests arriving meanwhile are stashed and served next. Servers ignoring Tflush still block until they answer. - 9ns-test now covers nine/bridge/fuse; new adv_bridge_interrupt suite (28); 9ns-itest grows to 88 checks. Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to '9ns/README.md')
-rw-r--r--9ns/README.md61
1 files changed, 44 insertions, 17 deletions
diff --git a/9ns/README.md b/9ns/README.md
index 04edffb..7d6babe 100644
--- a/9ns/README.md
+++ b/9ns/README.md
@@ -4,15 +4,20 @@ Mount a 9P2000 file tree into a fresh mount namespace and run a program in it,
as a plain user, without touching the host's mount table.
```sh
-9ns --unix /run/user/1000/acme -- fish # a shell that sees the tree at /mnt/9p
-9ns --tcp 127.0.0.1:564 -- claude # an agent that sees it too
-9ns --spawn '9proc-demo --stdio' -- bash # start the server yourself, talk over a socketpair
+9ns --unix /run/user/1000/acme -- fish # a shell that sees the tree at /mnt/9p/acme
+9ns --tcp 127.0.0.1:564 -- claude # an agent that sees it at /mnt/9p/tcp-127.0.0.1-564
+9ns --spawn '9proc-demo --stdio' -- bash # start the server yourself; /mnt/9p/9proc-demo
+9ns --unix /tmp/9debug.sock --name dbg -- bash # pick the name: /mnt/9p/dbg
```
Inside, the tree is ordinary files: `ls`, `cat`, `echo x > ctl`, editors,
-`find`, `rsync`, whatever. `$NINE_MOUNT` tells programs where it is
-(default `/mnt/9p`). When the program exits, 9ns exits with its status
-and the namespace, mount and connection disappear.
+`find`, `rsync`, whatever. Every mount lives under `/mnt/9p/<name>`, where
+the name comes from `--name` or, by default, from the transport (the socket's
+basename, `tcp-IP-PORT`, the spawned command's basename, `fdN`); `--mount
+PATH` puts it anywhere else. `$NINE_MOUNT` tells programs where it is. When
+the program exits, 9ns exits with its status and the namespace, mount and
+connection disappear. Nesting works: `9ns --name a -- 9ns --name b -- fish`
+gives a shell that sees both `/mnt/9p/a` and `/mnt/9p/b`.
## How it works
@@ -24,7 +29,7 @@ of the kernel FUSE protocol needed, straight from `linux/fuse.h`.
```
program (fish/bash/claude) 9ns (parent) 9P server
in a new user+mount namespace │
- /mnt/9p ── FUSE ──▶ kernel ────▶│ fuse.zig ─▶ bridge.zig ─▶ nine.zig ──▶ unix / tcp / socketpair
+ /mnt/9p/<name> ─FUSE─▶ kernel ─▶│ fuse.zig ─▶ bridge.zig ─▶ nine.zig ──▶ unix / tcp / socketpair
│ (framing) (translation) (cloud9 Client)
```
@@ -43,10 +48,15 @@ Files are opened with `FOPEN_DIRECT_IO`, so synthetic files that report length
travels inside the 9P open mode (`OTRUNC`) rather than as a separate
truncate. Repeated lookups of the same qid map to the same inode.
-If `/mnt/9p` does not exist and cannot be created (the normal case), 9ns
-mounts a tmpfs over `/mnt` *inside the namespace only* and bind-mounts every
-existing entry of `/mnt` back into it, so nothing is hidden. Pass `--mount DIR`
-to use any other directory.
+The mountpoint `/mnt/9p/<name>` normally does not exist and cannot be
+created by a plain user. 9ns then walks down the path to the deepest existing
+directory (`/mnt` on a host without `/mnt/9p`, `/mnt/9p` if the host has one),
+mounts a tmpfs over it *inside the namespace only*, bind-mounts every existing
+entry of that directory back into the tmpfs so nothing is hidden, and creates
+the missing components inside. Inside a 9ns namespace `/mnt/9p` is already
+that writable tmpfs directory, so a nested 9ns just adds its own name next to
+the outer mount (the same name mounts over it). Pass `--mount DIR` to use any
+other directory.
## Building and testing
@@ -87,7 +97,9 @@ Transport (exactly one):
--spawn CMD run CMD via /bin/sh -c with a socketpair on its stdin/stdout
Options:
- --mount PATH mountpoint inside the new namespace (default /mnt/9p)
+ --name NAME mount name: the tree appears at /mnt/9p/NAME (one path
+ component; default derived from the transport, see below)
+ --mount PATH mountpoint inside the new namespace (overrides --name)
--uname NAME 9P user name (default $USER)
--aname NAME 9P tree to attach (default "")
--msize BYTES maximum 9P message size to request (default 131072, max 16 MiB)
@@ -101,6 +113,17 @@ PROGRAM defaults to `$SHELL`. Exit status is the program's (`128+signal` if it
was killed); 125 means 9ns itself failed (usage, connect, namespace,
mount); 126/127 are exec failures as usual.
+The default name comes from the transport: `--unix PATH` → the basename of
+PATH without a trailing `.sock`, `.9p` or `.socket` (`/tmp/9debug.sock` →
+`9debug`); `--tcp IP:PORT` → `tcp-IP-PORT` with every `:` turned into `-`
+(`[::1]:564` → `tcp---1-564`); `--spawn CMD` → the basename of CMD's first
+word (`/x/9proc-demo --stdio` → `9proc-demo`); `--fd N` → `fdN`. A name must
+be a single path component (not empty, no `/`, not `.` or `..`); an invalid
+`--name` is a usage error, an unusable derived name falls back to `9p`. With
+`--mount PATH` the name is simply PATH's last component. The program gets
+`NINE_MOUNT=<mountpoint>` (replacing any inherited value, so a nested 9ns
+overwrites it).
+
## 9proc-demo: a demo 9P server
`9proc-demo` is a single-binary 9P2000 server whose file tree is the binary
@@ -131,14 +154,18 @@ zig-out/bin/9ns --unix /tmp/intro.sock -- sh -c '
echo "fib 20" > $NINE_MOUNT/runtime/ctl; cat $NINE_MOUNT/runtime/ctl'
```
-The same command with `claude -p "explore /mnt/9p ..."` as the program gives an
-agent a live, file-shaped view into a running process; that is the intended
-use.
+The same command with `claude -p "explore /mnt/9p/intro ..."` as the program
+gives an agent a live, file-shaped view into a running process; that is the
+intended use.
## Limitations
-* One 9P request is in flight at a time; a server read that blocks (event
- files) stalls the mount until it returns.
+* One 9P request is in flight at a time, so a server read that blocks (event
+ files, consoles) stalls the mount while it is outstanding. The blocked
+ request can be interrupted, though: killing or Ctrl-C-ing the reader makes
+ the kernel send `FUSE_INTERRUPT`, which 9ns forwards as 9P `Tflush`;
+ servers that honour it release the reader with `EINTR` at once, servers
+ that ignore it keep the mount stalled until they answer.
* Base 9P2000 only: no symlinks, ownership, xattrs or locks. Every file is
reported as owned by the invoking user. Cross-directory rename is `EXDEV`.
* No PID namespace and no `/proc` remount. `--tcp` takes IP literals only