summaryrefslogtreecommitdiff
path: root/docs/v9fs.md
blob: a105fe063f9e6a37afee824185213fae3733f913 (plain) (blame)
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
108
109
110
111
112
113
114
# Linux terminals with a kernel 9P mount

Execute `Tty9p`, or press `SPC n 9` in editor mode, to open a terminal below
this pane with the current Pardes session mounted through Linux v9fs.

The new pane asks for your sudo password when needed. After mounting, it starts
your configured shell as your normal user, with your account's supplementary
groups. The shell receives `PARDES_MOUNT`, the absolute mountpoint:

```sh
ls "$PARDES_MOUNT/pane"
cat "$PARDES_MOUNT/index"
cat "$PARDES_MOUNT/README"
cat "$PARDES_MOUNT/pane/$PARDES_PANE/body"
echo 'Msg hello' > "$PARDES_MOUNT/exec"
n=$(cat "$PARDES_MOUNT/pane/new")
```

**Opening** `pane/new` makes a pane, and reading that open file answers its
serial; address the pane as `pane/<serial>` from then on. Each open makes
another one, so read it once and keep the number. A *stat* makes nothing,
which is why `new` can be listed at all: `ls`, `ls -l` and `find` over the
whole mount create nothing, because none of them open it.

The mount belongs to that pane's subprocess tree. Other panes and the editor
core keep their original mount namespace. It works in native Linux TTY and SDL
sessions, including detached sessions. A frontend attaching from elsewhere does
not perform the mount; the session host starts the new terminal.

## Build and setup

The normal Linux build installs the ordinary `pardes-v9fs` executable beside
`pardes` and `pardes-gui`:

```sh
zig build
```

Start an updated editor to get the builtin. An already running core retains
its old code. The host resolves the helper beside its own executable;
`PARDES_V9FS_HELPER=/absolute/path/to/pardes-v9fs` overrides this for development
builds whose editor and helper live in different build-cache directories.

Linux must support `9p` and its Unix transport (`9pnet_fd`). v9fs mounting needs
`CAP_SYS_ADMIN` in the initial user namespace, which sudo supplies. The launcher
uses `sudo -E` to retain the shell environment; local sudo policy must allow
that. It restores the caller's PATH after dropping privileges because sudo's
`secure_path` can replace it even with `-E`.

No setuid installation, passwordless sudo policy, system group, or FUSE is
installed. The helper currently accepts explicit mount paths and a command;
it is not a restricted privilege broker. Do not grant it blanket passwordless
access. A group-authorized helper would require a separate restricted design.

## Runtime organization

`src/linux/v9fs.zig` builds `pardes-v9fs` and provides helper discovery to the
native host. `Tty9p` marks the new pane for a mounted shell; `host_io.forkShell`
starts the normal interactive shell and queues a quoted helper command. For
bash and fish, the command waits for the shell's prompt-ready mark. The helper
runs as a foreground shell job, so sudo uses the terminal like a manually run
command. Authentication failure or cancellation returns to the original shell;
exiting the mounted shell also returns there.

The unprivileged launcher creates a private temporary mountpoint and invokes
sudo inside the new PTY. The elevated helper creates a private mount namespace,
makes propagation recursively private, and mounts the session's Unix socket
with `version=9p2000,cache=none,access=any,nosuid,nodev,noexec`. It restores the
calling user's account groups, drops all real/effective/saved root IDs and
mount capabilities, and executes the shell. Ordinary commands such as sudo
remain available for subsequent explicit authentication. The launcher waits for sudo,
forwards termination signals, and removes its empty temporary directory on exit.
Namespace destruction releases the mount when its last process exits.

The core stays outside the mount namespace, which is the helper's private
one: a path under `$PARDES_MOUNT` names nothing in the editor's own namespace,
so a Look on one from that shell opens nothing.

Each `Tty9p` currently creates its own mount and consumes a server connection;
the session has four application connection slots across all transports. This
is the explicit per-pane version. A shared launcher for all pane shells remains
future work. Detached sessions retain the running mounted pane when frontends
leave; Dump/Restore does not reconstruct mounted-shell namespaces. Descendants
that deliberately outlive the terminal may retain their namespace until exit.

## Tests

```sh
zig build v9fs-terminal-test -Dplatform=tty
zig build v9fs-driver-test -Dplatform=tty
zig build 9p-test -Dplatform=tty
sudo -v
zig build v9fs-test -Dplatform=tty
```

`v9fs-terminal-test` exercises the builtin, real launcher, PTY input, session
environment, quoted helper paths, hidden password input, and core responsiveness
using an unprivileged sudo stand-in. It checks that authentication failure and
interruption clean up the temporary mountpoint and leave the original shell usable. It does not claim kernel-mount coverage.

`v9fs-test` mounts through the same runtime helper. Its driver keeps the editor
unprivileged and uses `sudo -n`, retaining the calling terminal's authorization.
Missing authorization or kernel support fails the test instead of skipping it.
It checks mount isolation, privilege dropping, inherited access, directory
refresh, body reads and truncation, independent wire updates, addressed edits,
shell redirection to name, Exec dispatch, rendered screen JSON, and OS-file reads.

Linux follows `O_TRUNC` with a `Twstat` carrying zero length and an `mtime` hint.
Pardes accepts this truncation without storing caller-selected timestamps;
standalone timestamp, permission, and ownership changes remain unsupported.

The initial kernel probe passed on this host on 2026-09-14. The broader
`fs-test` has an existing syntax-bold assertion failure (now `test/fs.py:615`),
also reproduced on the cached editor binary preceding the truncation fix.