diff options
Diffstat (limited to 'src/host_io.zig')
| -rw-r--r-- | src/host_io.zig | 64 |
1 files changed, 61 insertions, 3 deletions
diff --git a/src/host_io.zig b/src/host_io.zig index 6009c00f..820cb2b6 100644 --- a/src/host_io.zig +++ b/src/host_io.zig @@ -949,6 +949,9 @@ test "the child environment replaces the launcher's terminal identity exactly on extern "c" fn unsetenv(name: [*:0]const u8) c_int; +/// The longest line a command pane runs (exec.zig refuses a longer one). +pub const exec_command_max = pardes.exec.command_max; + pub fn forkShell( core: ?*pardes.Pardes, pane: usize, @@ -973,9 +976,19 @@ pub fn forkShell( }; var master: c_int = -1; var path_buf: [std.fs.max_path_bytes]u8 = undefined; - const spawn = Shell.resolve(bin, &path_buf, prompt_rcs); + var spawn = Shell.resolve(bin, &path_buf, prompt_rcs); + // A command pane's child is the shell running its one line, which has + // no prompt to mark (exec.zig runCommand). + const one_line = if (core) |c| if (c.panes[pane]) |pn| pn.command else null else null; + var command_buf: [exec_command_max + 1]u8 = undefined; + if (one_line) |line| { + if (line.len > exec_command_max or std.mem.indexOfScalar(u8, line, 0) != null) return error.InvalidCommand; + @memcpy(command_buf[0..line.len], line); + command_buf[line.len] = 0; + spawn.argv = .{ spawn.path, "-c", command_buf[0..line.len :0].ptr, null }; + } var helper_buf: [4096]u8 = undefined; - const mounted = if (core) |c| if (c.panes[pane]) |pn| pn.v9fs_on_spawn else false else false; + const mounted = one_line == null and if (core) |c| if (c.panes[pane]) |pn| pn.v9fs_on_spawn else false else false; const helper: ?[:0]u8 = if (mounted) blk: { if (comptime builtin.os.tag != .linux) return error.LinuxRequired; if (fs == null) return error.SessionSocketRequired; @@ -1016,7 +1029,7 @@ pub fn forkShell( _exit(127); } ninep_io.setCloexec(master); - if (core) |c| c.acknowledgeShell(pane, std.mem.span(spawn.path), spawn.argv[1] != null); + if (core) |c| c.acknowledgeShell(pane, std.mem.span(spawn.path), one_line == null and spawn.argv[1] != null); return .{ .file = .{ .handle = master, .flags = .{ .nonblocking = false } }, .pid = pid }; } @@ -1349,6 +1362,29 @@ pub fn retireShell(pid: libc.pid_t) void { thread.detach(); } +/// A pane's child at its pty's end of file: reaped, and how it exited +/// (its code, or 128 and the signal that ended it), if it is gone within +/// `wait_ms`; null if it is still running (a job it left holds nothing +/// open, but the child itself may be a moment behind its pty), which the +/// caller then retires as it would any shell. +pub fn exitStatus(pid: libc.pid_t, wait_ms: u32) ?u8 { + if (pid <= 0) return null; + var waited: u32 = 0; + while (true) : (waited += 2) { + var status: c_int = 0; + const got = libc.waitpid(pid, &status, libc.W.NOHANG); + if (got == pid) { + const s: u32 = @bitCast(status); + if (libc.W.IFEXITED(s)) return @truncate(libc.W.EXITSTATUS(s)); + if (libc.W.IFSIGNALED(s)) return @truncate(128 + @as(u32, @intFromEnum(libc.W.TERMSIG(s)))); + return null; + } + if (got < 0 or waited >= wait_ms) return null; + const ts: libc.timespec = .{ .sec = 0, .nsec = 2 * std.time.ns_per_ms }; + _ = libc.nanosleep(&ts, null); + } +} + fn reapShell(pid: libc.pid_t, grace_ms: u32) void { var waited: u32 = 0; while (waited < grace_ms) : (waited += 5) { @@ -1711,6 +1747,28 @@ test "Kill stops the foreground job, never the shell, and finds none with job co try std.testing.expectEqual(@as(libc.pid_t, 0), libc.waitpid(sh.pid, null, libc.W.NOHANG)); } +test "a command pane's child is its shell running the one line, and its exit status is read at the end" { + if (comptime !tty_probe_platform) return error.SkipZigTest; + const core = try pardes.Pardes.init(std.testing.allocator, .{ .tty_only = true }); + defer core.deinit(); + while (core.nextEffect()) |_| {} + const id = core.freeSlot().?; + _ = try core.newCommand(id, "", "printf 'said '; exit 7"); + while (core.nextEffect()) |_| {} + const rcs: Shell.PromptFiles = .{}; + const child = try forkShell(core, id, &rcs, "/bin/sh", "", 24, 80, null); + defer _ = libc.close(child.file.handle); + var got: [256]u8 = undefined; + var n: usize = 0; + while (n < got.len) { + const r = libc.read(child.file.handle, got[n..].ptr, got.len - n); + if (r <= 0) break; // EIO once the child is gone: the pty's end + n += @intCast(r); + } + try std.testing.expect(std.mem.indexOf(u8, got[0..n], "said") != null); + try std.testing.expectEqual(@as(?u8, 7), exitStatus(child.pid, 2000)); +} + test "a retired shell that ignores the hangup is killed and reaped, not left a zombie" { const pid = libc.fork(); if (pid == 0) { |
