diff options
Diffstat (limited to '9ns/README.md')
| -rw-r--r-- | 9ns/README.md | 61 |
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 |
