diff options
| -rw-r--r-- | src/tty/tty.zig | 36 | ||||
| -rw-r--r-- | test/fs.py | 42 |
2 files changed, 78 insertions, 0 deletions
diff --git a/src/tty/tty.zig b/src/tty/tty.zig index ef7f2761..1d7ff415 100644 --- a/src/tty/tty.zig +++ b/src/tty/tty.zig @@ -1083,6 +1083,8 @@ pub fn run(init: std.process.Init, opts: pardes.Options, attach: ?[]const u8) !v defer vx.exitAltScreen(tty.writer()) catch {}; try vx.setMouseMode(tty.writer(), true); try vx.setBracketedPaste(tty.writer(), true); + Killed.arm(tty.fd.handle, tty.termios); + defer Killed.disarm(); if (attach != null) { attach_end = attachSession(init, attach_name, &tty, &vx); @@ -1119,6 +1121,40 @@ pub fn run(init: std.process.Init, opts: pardes.Options, attach: ?[]const u8) !v } } +/// SIGTERM or SIGHUP: the terminal put back as a clean exit leaves it -- +/// the main screen, no mouse, paste or keyboard modes, the cursor shown, +/// cooked termios -- and then the signal's own death. A nested editor +/// killed in a pane must not leave that pane's terminal wedged. +/// Async-signal-safe: write, tcsetattr, sigaction, raise. +const Killed = struct { + var fd: posix.fd_t = -1; + var cooked: posix.termios = undefined; + const reset = "\x1b[<u\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1004l\x1b[?2004l\x1b[0m\x1b[?25h\x1b[?1049l"; + const signals = [_]posix.SIG{ .TERM, .HUP }; + + fn arm(tty_fd: posix.fd_t, termios: posix.termios) void { + cooked = termios; + fd = tty_fd; + const on: posix.Sigaction = .{ .handler = .{ .handler = handle }, .mask = posix.sigemptyset(), .flags = posix.SA.RESETHAND }; + for (signals) |sig| posix.sigaction(sig, &on, null); + } + + fn disarm() void { + fd = -1; + const default: posix.Sigaction = .{ .handler = .{ .handler = posix.SIG.DFL }, .mask = posix.sigemptyset(), .flags = 0 }; + for (signals) |sig| posix.sigaction(sig, &default, null); + } + + fn handle(sig: posix.SIG) callconv(.c) void { + if (fd >= 0) { + _ = std.c.write(fd, reset, reset.len); + _ = std.c.tcsetattr(fd, .FLUSH, &cooked); + } + // SA_RESETHAND put the default back: the same signal, now fatal. + _ = std.c.raise(sig); + } +}; + fn localSession( init: std.process.Init, opts: pardes.Options, @@ -4,6 +4,7 @@ import fcntl import json import os import shutil +import select import signal from pathlib import Path import subprocess @@ -1329,6 +1330,47 @@ def test(binary, quic=False): assert orphan.poll() is None assert orphan.wait(timeout=5) == 1 + # Killed with SIGTERM or SIGHUP, a tty editor puts its terminal + # back (main screen, cooked mode) before it dies: a nested one + # killed in a pane leaves that pane usable. + for sig in (signal.SIGTERM, signal.SIGHUP): + master, slave = os.openpty() + fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack('HHHH', 24, 80, 0, 0)) + cooked = termios.tcgetattr(slave) + env = {k: v for k, v in os.environ.items() if not k.startswith('PARDES_')} + env.update(HOME=str(root), XDG_RUNTIME_DIR=str(root), TERM='xterm-256color', PARDES_NOTIME='1') + killed = subprocess.Popen([binary, '--tty', '--9p=killed', 'x.txt'], cwd=root, env=env, + stdin=slave, stdout=slave, stderr=subprocess.DEVNULL, + start_new_session=True, + preexec_fn=lambda: fcntl.ioctl(0, termios.TIOCSCTTY, 0)) + seen = b'' + try: + deadline = time.monotonic() + 10 + while b'\x1b[?1049h' not in seen: + assert time.monotonic() < deadline, seen[-200:] + if select.select([master], [], [], .1)[0]: + seen += os.read(master, 65536) + time.sleep(.3) + killed.send_signal(sig) + assert killed.wait(timeout=5) == -sig + while select.select([master], [], [], .2)[0]: + try: + chunk = os.read(master, 65536) + except OSError: + break + if not chunk: + break + seen += chunk + tail = seen[seen.rindex(b'\x1b[?1049h'):] + assert b'\x1b[?1049l' in tail, tail[-200:] + assert termios.tcgetattr(slave)[3] & termios.ICANON == cooked[3] & termios.ICANON + finally: + if killed.poll() is None: + killed.kill() + killed.wait() + os.close(master) + os.close(slave) + # A launch with no terminal to draw on (a pty that is no one's # controlling terminal) says so and exits 1, no error trace. master, slave = os.openpty() |
