diff options
| author | user <user@clank> | 2026-08-22 18:30:19 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-22 18:30:19 +0200 |
| commit | 063d43f7702fa74071e8555c6e9dabba1a914c74 (patch) | |
| tree | 08703e3200f9e4977726c09fc2f22a023dad52cd | |
| parent | slosh: paint the padding ring, so a float cannot be seen through (diff) | |
| download | slosh-063d43f7702fa74071e8555c6e9dabba1a914c74.tar.gz slosh-063d43f7702fa74071e8555c6e9dabba1a914c74.tar.xz slosh-063d43f7702fa74071e8555c6e9dabba1a914c74.zip | |
slosh: refuse to attach to the session this pane is inside
A new client displaces the current display, so `slosh` typed into a pane
of "main" displaced the very client showing that pane -- you watched your
own session detach, from inside it. SLOSH_SESSION names the pane's own
session; that one name is refused with the way out spelled in the message.
Any other name is deliberate nesting, and cmd/ls are untouched: they are
how a program in a pane is supposed to reach its own session.
| -rw-r--r-- | src/main.c | 20 | ||||
| -rw-r--r-- | tests/test_nested.py | 87 |
2 files changed, 107 insertions, 0 deletions
@@ -249,6 +249,26 @@ int main(int argc, char **argv) { if (isatty(STDOUT_FILENO)) term_size(&cols, &rows); if (server) return server_run(name, cmd_argv, cols, rows, layout, watch); + /* Not to the session this process is already inside. A new client + * displaces the current display (EXIT_REPLACED), so `slosh` typed into a + * pane of "main" attaches to main, displaces the very client showing that + * pane, and the user watches their own session detach from inside it. + * SLOSH_SESSION names the pane's own session; asking for any *other* name + * is deliberate nesting and none of our business. The one legitimate shape + * this refuses — a fresh terminal launched from a pane, inheriting the + * variables, attaching to take over as the display — gets its escape hatch + * named in the message rather than a flag: it is exactly what unsetting + * the variable means. */ + const char *inside = getenv("SLOSH") ? getenv("SLOSH_SESSION") : NULL; + if (inside && *inside && strcmp(inside, name) == 0) { + fprintf(stderr, + "slosh: this pane is already inside session %s\n" + " (C-a d detaches; `slosh -s NAME` nests another session;\n" + " unset SLOSH_SESSION to attach anyway, displacing this view)\n", + name); + return 1; + } + /* attach, creating the session if nobody is home */ int fd = server_connect(name); if (fd < 0) fd = server_spawn(name, cmd_argv, cols, rows, layout, watch); diff --git a/tests/test_nested.py b/tests/test_nested.py new file mode 100644 index 0000000..16c10ed --- /dev/null +++ b/tests/test_nested.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Running slosh from inside slosh. + +A new client displaces the current display, so `slosh` typed into a pane of +the very session it would attach used to displace the client showing that +pane — you watched your own session detach, from inside it. The pane's +environment (SLOSH, SLOSH_SESSION) says which session this is, so the attach +path refuses that one name and only that one name: any other is deliberate +nesting, and the control verbs (`cmd`, `ls`) are how a program in a pane is +*supposed* to reach its own session, so they are untouched. +""" + +import os +import shutil +import subprocess +import sys +import tempfile + +from harness import BIN, check, report + + +def run(args, session=None, timeout=10, **env_extra): + """Run the binary with a clean pane-environment: no inherited SLOSH_*, + sockets in a private runtime dir, stdin not a tty.""" + env = {k: v for k, v in os.environ.items() if not k.startswith("SLOSH")} + env.update(env_extra) + if session is not None: + env["SLOSH"] = "1" + env["SLOSH_SESSION"] = session + return subprocess.run( + [BIN] + args, + capture_output=True, + text=True, + env=env, + stdin=subprocess.DEVNULL, + timeout=timeout, + ) + + +def test_attaching_to_your_own_session_is_refused(): + rt = tempfile.mkdtemp() + try: + r = run(["-s", "mine"], session="mine", XDG_RUNTIME_DIR=rt) + check("it refuses", r.returncode == 1, str(r.returncode)) + check("and names the session", "inside session mine" in r.stderr, r.stderr) + check("and offers the detach key", "C-a d" in r.stderr, r.stderr) + socks = ( + os.listdir(os.path.join(rt, "slosh")) + if os.path.isdir(os.path.join(rt, "slosh")) + else [] + ) + check("no session was spawned", not socks, str(socks)) + finally: + shutil.rmtree(rt, ignore_errors=True) + + +def test_any_other_session_is_deliberate_nesting(): + rt = tempfile.mkdtemp() + try: + # Asked for a *different* session from inside a pane: the guard steps + # aside. Without a tty the attach then fails on its own terms, but the + # session was created — which is the proof the refusal did not fire. + r = run(["-s", "nest"], session="outer", XDG_RUNTIME_DIR=rt) + check("no refusal", "already inside" not in r.stderr, r.stderr) + + # The control verbs from inside the *same* session: the documented + # interface for a program in a pane, and exactly what must keep working. + r = run(["-s", "nest", "cmd", "alive"], session="nest", XDG_RUNTIME_DIR=rt) + check("cmd still answers", r.returncode == 0 and "true" in r.stdout, str(r)) + r = run(["ls"], session="nest", XDG_RUNTIME_DIR=rt) + check("ls still answers", r.returncode == 0 and "nest" in r.stdout, str(r)) + + # A --script pane leaves SLOSH_SESSION empty: there is nothing this + # pane is inside, so nothing to refuse. + r = run(["-s", "nest"], session="", XDG_RUNTIME_DIR=rt) + check( + "an empty name refuses nothing", "already inside" not in r.stderr, r.stderr + ) + finally: + run(["-s", "nest", "cmd", "quit"], XDG_RUNTIME_DIR=rt) + shutil.rmtree(rt, ignore_errors=True) + + +if __name__ == "__main__": + test_attaching_to_your_own_session_is_refused() + test_any_other_session_is_deliberate_nesting() + sys.exit(report()) |
