summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/fs.md11
-rw-r--r--src/host_io.zig37
-rw-r--r--test/snapshots/cmdexit.golden6
-rw-r--r--test/snapshots/cmdexit.snap6
4 files changed, 50 insertions, 10 deletions
diff --git a/docs/fs.md b/docs/fs.md
index 3800e8cb..a883e185 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -249,12 +249,19 @@ same tree without leaving the process.
is typed into that shell. From anywhere else -- a file, a scratch, a tag,
a terminal whose tty a program holds -- it runs as a command pane: a
terminal whose child is the root ctl's `Shell` (fish unless set) run
- with `-c` and the line, in the pane's directory,
+ with `-c` and the line, in the pane's directory, with job control on
+ (bash, sh, dash, zsh, ksh `-m`; fish `status job-control full`),
which shows its output and then `exit N` (its tag reads `<dir> (<line>)
running`, then `exit N`), and stays. The command is over when its process
exits, as in acme, not when its terminal closes: a job it left in the
background prints on below `exit N` until it lets go of the pty, and a
- command that lets go of its terminal early runs on to its own exit. The directory's next command runs in
+ command that lets go of its terminal early runs on to its own exit. A
+ background job outlives the command: job control gives it a process
+ group of its own, so the hangup the kernel sends the terminal's
+ foreground group when the shell exits misses it. It survives the pane
+ closing or taking the next command too, but its writes to the terminal
+ then fail, so start one that must keep writing with `nohup` or its
+ output redirected. The directory's next command runs in
that pane once it is done, below what it showed, after a `% <line>` line;
one still running gets a second pane. Before the next command the pane
leaves any alternate screen and turns off the modes a program left on
diff --git a/src/host_io.zig b/src/host_io.zig
index 25bd61e1..53e1628d 100644
--- a/src/host_io.zig
+++ b/src/host_io.zig
@@ -792,7 +792,7 @@ pub const Shell = struct {
pub const Spawn = struct {
path: [*:0]const u8,
- argv: [4:null]?[*:0]const u8,
+ argv: [6:null]?[*:0]const u8,
};
// Resolve in the parent. The path buffer and prompt files must survive through exec.
@@ -803,7 +803,24 @@ pub const Shell = struct {
.fish => if (prompt_rcs.fishCommand()) |command| .{ "-C", command.ptr } else .{ null, null },
.none => .{ null, null },
};
- return .{ .path = path, .argv = .{ path, marks[0], marks[1], null } };
+ return .{ .path = path, .argv = .{ path, marks[0], marks[1], null, null, null } };
+ }
+
+ /// A command pane's shell runs its one line with job control on, so a
+ /// job the line puts in the background has a process group of its own.
+ /// The shell leads the terminal's session, and when it exits the kernel
+ /// hangs up the terminal's foreground group: with job control off that
+ /// is the shell's own, the job's too, and the job died with the command
+ /// (nohup'd or not; only setsid got out, and not always in time). A
+ /// shell pardes does not know runs the line as it is.
+ pub fn commandArgv(path: [*:0]const u8, line: [*:0]const u8) [6:null]?[*:0]const u8 {
+ const bin = std.mem.span(path);
+ const base = if (std.mem.lastIndexOfScalar(u8, bin, '/')) |s| bin[s + 1 ..] else bin;
+ const posix_shells = [_][]const u8{ "bash", "sh", "dash", "zsh", "ksh", "mksh", "yash" };
+ for (posix_shells) |name| if (std.mem.eql(u8, base, name))
+ return .{ path, "-m", "-c", line, null, null };
+ if (family(bin) == .fish) return .{ path, "-C", "status job-control full", "-c", line, null };
+ return .{ path, "-c", line, null, null, null };
}
/// A shell by its path or its name in the usual directories, or null.
@@ -840,6 +857,20 @@ pub const Shell = struct {
return @ptrCast(buf);
}
+ test "a command pane's line runs with job control on in the shells that have it" {
+ const want = struct {
+ fn check(path: [*:0]const u8, words: []const []const u8) !void {
+ const argv = commandArgv(path, "sleep 9 &");
+ for (words, 0..) |w, i| try std.testing.expectEqualStrings(w, std.mem.span(argv[i].?));
+ try std.testing.expect(argv[words.len] == null);
+ }
+ };
+ try want.check("/usr/bin/bash", &.{ "/usr/bin/bash", "-m", "-c", "sleep 9 &" });
+ try want.check("/bin/sh", &.{ "/bin/sh", "-m", "-c", "sleep 9 &" });
+ try want.check("/usr/bin/fish", &.{ "/usr/bin/fish", "-C", "status job-control full", "-c", "sleep 9 &" });
+ try want.check("/usr/bin/nu", &.{ "/usr/bin/nu", "-c", "sleep 9 &" });
+ }
+
test "a path is taken at its word, a name is looked up, and both pick their own marks" {
if (builtin.os.tag == .windows) return;
var buf: [std.fs.max_path_bytes]u8 = undefined;
@@ -1070,7 +1101,7 @@ pub fn forkShell(
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 };
+ spawn.argv = Shell.commandArgv(spawn.path, command_buf[0..line.len :0].ptr);
}
var helper_buf: [4096]u8 = undefined;
const mounted = one_line == null and if (core) |c| if (c.panes[pane]) |pn| pn.v9fs_on_spawn else false else false;
diff --git a/test/snapshots/cmdexit.golden b/test/snapshots/cmdexit.golden
index de95c383..4e9dc5fc 100644
--- a/test/snapshots/cmdexit.golden
+++ b/test/snapshots/cmdexit.golden
@@ -10,6 +10,6 @@
|19: BGSTARTED
|20: exit 0
== snap letgo grid=120x31 cursor=7,3
-|17: /tmp/pardes-snap/cmdexit/cwd (exec </dev/null >/dev/null 2>&1; sleep 1; false) exit 1 Kill Save Collapse Del
-|21: % exec </dev/null >/dev/null 2>&1; sleep 1; false
-|22: exit 1
+|22: /tmp/pardes-snap/cmdexit/cwd (exec </dev/null >/dev/null 2>&1; sleep 1; false) exit 1 Kill Save Collapse Del
+|23: % exec </dev/null >/dev/null 2>&1; sleep 1; false
+|24: exit 1
diff --git a/test/snapshots/cmdexit.snap b/test/snapshots/cmdexit.snap
index ff60a881..e9f39bfd 100644
--- a/test/snapshots/cmdexit.snap
+++ b/test/snapshots/cmdexit.snap
@@ -16,8 +16,10 @@ release middle 34 4
wait 10000 exit 0
stable 700 15000
snap background
-# line 2, in the same pane now its command is done: its end of file comes at
-# once, and its exit a second later, from `false`
+# line 2, in a second pane: the first's command is done but its background
+# `sleep 30` lives on (job control gives it its own group) and still holds
+# that pane's pty; here the end of file comes at once, and the exit a second
+# later, from `false`
press middle 7 5
drag middle 54 5
release middle 54 5