From 36a9bfbaba649da3d988608f1cc7834e567df95d Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 11:09:59 -0300 Subject: Every pane pardes spawns gets $PARDES_MOUNT, the session's own directory under 9ns --mntgen ($NINE_MOUNT/pardes/), unset when it is not known 9ns says --mntgen with NINE_MNTGEN=1 (cloud9). Under `9ns --unix` the mount is the server it dialled, never this session (its socket does not exist until it starts), so it stays unset there; scripts fall back to `9p -a "unix!$PARDES_9P"`. Nothing is statted: the name comes from the session's socket and the environment. selfmount checks both modes. Co-Authored-By: Claude Opus 5.5 --- test/selfmount.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'test') diff --git a/test/selfmount.py b/test/selfmount.py index 8d78487d..0111e6ac 100644 --- a/test/selfmount.py +++ b/test/selfmount.py @@ -27,6 +27,46 @@ sys.path.insert(0, str(Path(__file__).resolve().parent)) from ninep import Client +def unix_mount(ns, binary, served, env, name, fixture): + """A second editor under `9ns --unix `, on the + fixture (a command runs from a file's pane, not a terminal's): what its + command pane sees in $PARDES_MOUNT.""" + second = name + 'u' + sock = f'{os.environ["XDG_RUNTIME_DIR"]}/pardes-9p-{second}.sock' + out = Path(f'/tmp/{second}.mount') + env = dict(env, PARDES_DUMP=f'/tmp/{second}.dump.zon') + pid, master = pty.fork() + if pid == 0: + os.execve(ns, ['9ns', '--unix', served, '--', binary, '--tty', '--9p=' + second, str(fixture)], env) + fcntl.ioctl(master, termios.TIOCSWINSZ, struct.pack('HHHH', 30, 100, 0, 0)) + os.set_blocking(master, False) + try: + deadline = time.monotonic() + 30 + while time.monotonic() < deadline and not out.exists(): + try: + os.read(master, 65536) + except OSError: + pass + if os.path.exists(sock) and not out.with_suffix('.sent').exists(): + time.sleep(.3) + with Client(sock) as client: + client.write('/ctl', b'Shell sh\n') + client.write('/exec', f'echo "${{PARDES_MOUNT-unset}}" > {out}.tmp; mv {out}.tmp {out}\n'.encode()) + out.with_suffix('.sent').touch() + time.sleep(.1) + return out.read_text().strip() if out.exists() else 'timeout' + finally: + os.kill(pid, signal.SIGTERM) + time.sleep(.2) + try: + os.kill(pid, signal.SIGKILL) + except ProcessLookupError: + pass + os.waitpid(pid, 0) + for path in (out, out.with_suffix('.sent'), Path(f'/tmp/{second}.dump.zon')): + path.unlink(missing_ok=True) + + def run(binary): ns = shutil.which('9ns') or str(Path.home() / '.local/bin/9ns') runtime = os.environ.get('XDG_RUNTIME_DIR') @@ -128,6 +168,14 @@ def run(binary): return exits[-1].split()[2] time.sleep(.1) return 'timeout' + # A pane's command finds the session's own directory in + # $PARDES_MOUNT (9ns --mntgen says NINE_MNTGEN=1). + check(run(f'test "$PARDES_MOUNT" = {mount} && grep -q . "$PARDES_MOUNT/index"\n'.encode()) == '0', + '$PARDES_MOUNT is the session under the mntgen mount, and it answers') + # Under `9ns --unix` the mount is another server, never this + # session: a second editor started there leaves it unset. + seen_mount = unix_mount(ns, binary, sock, env, name, fixture) + check(seen_mount == 'unset', f'under 9ns --unix $PARDES_MOUNT is unset ({seen_mount!r})') # Followed from before the burst: a fresh open of the log reads # only what its 64 KiB ring still holds, which a burst this size # outgrows, and a follower the ring outruns reads `lost N`, so -- cgit v1.3