diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 88e791b9a..ad03d9e74 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -15,6 +15,11 @@ const renderer = @import("../renderer.zig"); const log = std.log.scoped(.io_exec); +const c = @cImport({ + @cInclude("signal.h"); + @cInclude("unistd.h"); +}); + /// Allocator alloc: Allocator, @@ -78,8 +83,8 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec { .env = &env, .cwd = opts.config.@"working-directory", .pre_exec = (struct { - fn callback(c: *Command) void { - const p = c.getData(Pty) orelse unreachable; + fn callback(cmd: *Command) void { + const p = cmd.getData(Pty) orelse unreachable; p.childPreExec() catch |err| log.err("error initializing child: {}", .{err}); } @@ -113,9 +118,8 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec { } pub fn deinit(self: *Exec) void { - // Deinitialize the pty. This closes the pty handles. This should - // cause a close in the our subprocess so just wait for that. - self.pty.deinit(); + // Kill our command + self.killCommand(); _ = self.command.wait() catch |err| log.err("error waiting for command to exit: {}", .{err}); @@ -123,6 +127,22 @@ pub fn deinit(self: *Exec) void { self.terminal.deinit(self.alloc); } +/// Kill the underlying subprocess. This closes the pty file handle and +/// sends a SIGHUP to the child process. This doesn't wait for the child +/// process to be exited. +fn killCommand(self: *Exec) void { + // Close our PTY + self.pty.deinit(); + + // We need to get our process group ID and send a SIGHUP to it. + if (self.command.pid) |pid| { + const pgid = c.getpgid(pid); + if (pgid > 0) { + _ = c.killpg(pgid, c.SIGHUP); + } + } +} + pub fn threadEnter(self: *Exec, loop: libuv.Loop) !ThreadData { assert(self.data == null); @@ -367,15 +387,15 @@ fn ttyRead(t: *libuv.Tty, n: isize, buf: []const u8) void { var i: usize = 0; const end = @intCast(usize, n); if (ev.terminal_stream.parser.state == .ground) { - for (buf[i..end]) |c| { - switch (terminal.parse_table.table[c][@enumToInt(terminal.Parser.State.ground)].action) { + for (buf[i..end]) |ch| { + switch (terminal.parse_table.table[ch][@enumToInt(terminal.Parser.State.ground)].action) { // Print, call directly. - .print => ev.terminal_stream.handler.print(@intCast(u21, c)) catch |err| + .print => ev.terminal_stream.handler.print(@intCast(u21, ch)) catch |err| log.err("error processing terminal data: {}", .{err}), // C0 execute, let our stream handle this one but otherwise // continue since we're guaranteed to be back in ground. - .execute => ev.terminal_stream.execute(c) catch |err| + .execute => ev.terminal_stream.execute(ch) catch |err| log.err("error processing terminal data: {}", .{err}), // Otherwise, break out and go the slow path until we're @@ -413,8 +433,8 @@ const StreamHandler = struct { try self.ev.queueWrite(data); } - pub fn print(self: *StreamHandler, c: u21) !void { - try self.terminal.print(c); + pub fn print(self: *StreamHandler, ch: u21) !void { + try self.terminal.print(ch); } pub fn bell(self: StreamHandler) !void {