diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/detached/server.zig | 2 | ||||
| -rw-r--r-- | src/gui/gui.zig | 2 | ||||
| -rw-r--r-- | src/host_io.zig | 51 | ||||
| -rw-r--r-- | src/macos.zig | 2 | ||||
| -rw-r--r-- | src/tty/tty.zig | 2 |
5 files changed, 49 insertions, 10 deletions
diff --git a/src/detached/server.zig b/src/detached/server.zig index 2fc165eb..857591d4 100644 --- a/src/detached/server.zig +++ b/src/detached/server.zig @@ -533,7 +533,7 @@ pub const Session = struct { const pt = s.ptys[pane]; // A command whose exit is recorded is reaped: its group may be another's. if (pt.fd < 0 or pt.cmd.exited) return; - host_io.signalTty(pt.pid, pt.fd, sig); + host_io.signalTty(pt.pid, pt.fd, sig, pt.cmd.watched); } fn ttyTaken(ctx: ?*anyopaque, pane: u8) bool { diff --git a/src/gui/gui.zig b/src/gui/gui.zig index cac311e2..02f7bd9e 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -4401,7 +4401,7 @@ fn ptyResize(ctx: ?*anyopaque, pane: u8, cols: u16, rows: u16) void { fn ptySignal(ctx: ?*anyopaque, pane: u8, sig: pardes.PtySignal) void { const s = shellOf(ctx); // A command whose exit is recorded is reaped: its group may be another's. - if (s.ptys[pane]) |pt| if (pt.fd >= 0 and !pt.cmd.exited) host_io.signalTty(pt.pid, pt.fd, sig); + if (s.ptys[pane]) |pt| if (pt.fd >= 0 and !pt.cmd.exited) host_io.signalTty(pt.pid, pt.fd, sig, pt.cmd.watched); } fn ttyTaken(ctx: ?*anyopaque, pane: u8) bool { diff --git a/src/host_io.zig b/src/host_io.zig index 53e1628d..df78f73c 100644 --- a/src/host_io.zig +++ b/src/host_io.zig @@ -1670,7 +1670,11 @@ fn reapShell(pid: libc.pid_t, grace_ms: u32) void { while (libc.waitpid(pid, null, 0) < 0 and libc.errno(-1) == .INTR) {} } -pub fn signalTty(shell_pid: libc.pid_t, master_fd: c_int, which: pardes.PtySignal) void { +/// The tty's foreground job is signalled. A command pane's shell (`command`) +/// is its command too: its line runs with job control on, so the job +/// running is in a group of its own, and the shell's group goes as well, or +/// the rest of the line would run on after the job it was on. +pub fn signalTty(shell_pid: libc.pid_t, master_fd: c_int, which: pardes.PtySignal, command: bool) void { const sig = switch (which) { .int => libc.SIG.INT, .term => libc.SIG.TERM, @@ -1679,11 +1683,9 @@ pub fn signalTty(shell_pid: libc.pid_t, master_fd: c_int, which: pardes.PtySigna .kill => libc.SIG.KILL, }; const fg = tcgetpgrp(master_fd); - if (fg > 0) { - _ = libc.kill(-fg, sig); - return; - } - if (shell_pid > 0) _ = libc.kill(shell_pid, sig); + if (fg > 0) _ = libc.kill(-fg, sig); + if (command and shell_pid > 0 and fg != shell_pid) _ = libc.kill(-shell_pid, sig); + if (fg <= 0 and !command and shell_pid > 0) _ = libc.kill(shell_pid, sig); } /// Kill: SIGTERM to the job in the tty's foreground, never to the shell. @@ -2074,6 +2076,43 @@ test "a command's exit is its process's, told by its watcher, whether or not its } } +test "a command's background job outlives it and its pty, and Kill stops its whole line" { + if (comptime !tty_probe_platform) return error.SkipZigTest; + if (!haveFile("/bin/sh")) return error.SkipZigTest; + const core = try pardes.Pardes.init(std.testing.allocator, .{ .tty_only = true }); + defer core.deinit(); + while (core.nextEffect()) |_| {} + const rcs: Shell.PromptFiles = .{}; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + var dir_buf: [4096]u8 = undefined; + const dir = dir_buf[0..try tmp.dir.realPath(std.testing.io, &dir_buf)]; + const Case = struct { line: []const u8, kill: bool, made: bool }; + for ([_]Case{ + // The shell leads the pty's session; its exit hangs up only the + // foreground group, which the job, in a group of its own, is not. + .{ .line = "(sleep 0.4; touch left) & exit 0", .kill = false, .made = true }, + // The job running is in its own group; the shell goes too. + .{ .line = "sleep 0.4; touch killed", .kill = true, .made = false }, + }) |c| { + const id = core.freeSlot().?; + _ = try core.newCommand(id, dir, c.line); + while (core.nextEffect()) |_| {} + const child = try forkShell(core, id, &rcs, "/bin/sh", dir, 24, 80, null); + if (c.kill) { + sleepMs(150); + signalTty(child.pid, child.file.handle, .term, true); + } + _ = exitStatus(child.pid, 2000); + _ = libc.close(child.file.handle); // the pane closes: the pty hangs up + sleepMs(800); + const name = if (c.kill) "killed" else "left"; + const made = if (tmp.dir.statFile(std.testing.io, name, .{})) |_| true else |_| false; + try std.testing.expectEqual(c.made, made); + try core.removePane(id, null); + } +} + test "a retired shell that ignores the hangup is killed and reaped, not left a zombie" { const pid = libc.fork(); if (pid == 0) { diff --git a/src/macos.zig b/src/macos.zig index a2175f3d..2b64784f 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -2469,7 +2469,7 @@ fn ptyResize(ctx: ?*anyopaque, pane: u8, cols: u16, rows: u16) void { fn ptySignal(ctx: ?*anyopaque, pane: u8, sig: pardes.PtySignal) void { const st = hostState(ctx); // A command whose exit is recorded is reaped: its group may be another's. - if (st.ptys[pane]) |pt| if (!pt.cmd.exited) host_io.signalTty(pt.pid, pt.file.handle, sig); + if (st.ptys[pane]) |pt| if (!pt.cmd.exited) host_io.signalTty(pt.pid, pt.file.handle, sig, pt.cmd.watched); } fn ttyTaken(ctx: ?*anyopaque, pane: u8) bool { diff --git a/src/tty/tty.zig b/src/tty/tty.zig index b18f4de5..96aec9a9 100644 --- a/src/tty/tty.zig +++ b/src/tty/tty.zig @@ -1273,7 +1273,7 @@ const Shell = struct { const s = of(ctx); // A command whose exit is recorded is reaped: its group may be // another's by now. - if (s.ptys[pane]) |pt| if (!pt.cmd.exited) host_io.signalTty(pt.pid, pt.file.handle, sig); + if (s.ptys[pane]) |pt| if (!pt.cmd.exited) host_io.signalTty(pt.pid, pt.file.handle, sig, pt.cmd.watched); } /// The pane is gone: hang its pty up, which the kernel passes on to the |
