summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/detached/server.zig18
-rw-r--r--src/gui/gui.zig14
-rw-r--r--src/macos.zig8
-rw-r--r--src/ninep/ctl.zig16
-rw-r--r--src/ninep/pty.zig8
-rw-r--r--src/pardes.zig7
-rw-r--r--src/tty/tty.zig10
7 files changed, 72 insertions, 9 deletions
diff --git a/src/detached/server.zig b/src/detached/server.zig
index a08a0342..676b5b95 100644
--- a/src/detached/server.zig
+++ b/src/detached/server.zig
@@ -485,9 +485,11 @@ pub const Session = struct {
fn spawn(ctx: ?*anyopaque, pane: u8, cwd: []const u8) void {
const s = of(ctx);
if (pane >= s.ptys.len) return; // the core indexes its own panes
- s.closePty(pane);
s.harvest();
- if (s.ptys[pane].pid != 0) return s.core.reportError(pane, "shell", error.ShellClosing);
+ // The new shell starts before a running one goes: one that cannot
+ // start leaves the running one be (pty/ctl's exec).
+ const running = s.ptys[pane].fd >= 0;
+ if (!running and s.ptys[pane].pid != 0) return s.core.reportError(pane, "shell", error.ShellClosing);
for (s.retired_shells) |shell| {
if (shell.pid == 0) break;
} else return s.core.reportError(pane, "shell", error.ShellClosing);
@@ -500,7 +502,17 @@ pub const Session = struct {
s.core.screen_h,
s.core.screen_w,
s.ninep,
- ) catch |err| return s.core.shellFailed(pane, err);
+ ) catch |err| {
+ if (running) return s.core.restartFailed(pane, err);
+ return s.core.shellFailed(pane, err);
+ };
+ s.closePty(pane);
+ s.harvest();
+ if (s.ptys[pane].pid != 0) {
+ _ = libc.close(child.file.handle);
+ host_io.retireShell(child.pid);
+ return s.core.reportError(pane, "shell", error.ShellClosing);
+ }
const command = if (s.core.panes[pane]) |pn| pn.command != null else false;
s.ptys[pane] = .{ .fd = child.file.handle, .pid = child.pid, .cmd = .{ .watched = command and host_io.watchExit(child.pid), .fd = child.file.handle } };
setNonblock(child.file.handle);
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index cb3bdd97..851218bb 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -4553,10 +4553,20 @@ fn gridPostPresent(ctx: ?*anyopaque) void {
fn spawnPane(ctx: ?*anyopaque, pane: u8, cwd: []const u8) void {
const s = shellOf(ctx);
s.reap();
+ // The new shell starts before a running one goes: one that cannot
+ // start leaves the running one be (pty/ctl's exec).
+ const running = s.ptys[pane] != null;
+ const child = host_io.forkShell(s.core, pane, s.prompt_rcs, s.core.shellBin(), cwd, s.core.screen_h, s.core.screen_w, s.fs) catch |err| {
+ if (running) return s.core.restartFailed(pane, err);
+ return s.core.shellFailed(pane, err);
+ };
s.closePty(pane);
- if (s.ptys[pane] != null) return s.core.reportError(pane, "shell", error.WorkersBusy);
+ if (s.ptys[pane] != null) {
+ _ = libc.close(child.file.handle);
+ host_io.retireShell(child.pid);
+ return s.core.reportError(pane, "shell", error.WorkersBusy);
+ }
s.gens[pane] +%= 1;
- const child = host_io.forkShell(s.core, pane, s.prompt_rcs, s.core.shellBin(), cwd, s.core.screen_h, s.core.screen_w, s.fs) catch |err| return s.core.shellFailed(pane, err);
const command = s.core.panes[pane].?.command != null;
const pt: Pty = .{ .fd = child.file.handle, .pid = child.pid, .serial = s.core.panes[pane].?.serial, .cmd = .{ .watched = command and host_io.watchExit(child.pid), .fd = child.file.handle } };
s.ptys[pane] = pt;
diff --git a/src/macos.zig b/src/macos.zig
index 9b17803b..e839816c 100644
--- a/src/macos.zig
+++ b/src/macos.zig
@@ -2437,11 +2437,17 @@ fn hostState(ctx: ?*anyopaque) *State {
fn spawnShell(ctx: ?*anyopaque, pane: u8, cwd: []const u8) void {
const st = hostState(ctx);
const core = st.core;
+ // The new shell starts before a running one goes: one that cannot
+ // start leaves the running one be (pty/ctl's exec).
+ const running = st.ptys[pane] != null;
+ const child = host_io.forkShell(core, pane, &st.prompt_rcs, core.shellBin(), cwd, core.screen_h, core.screen_w, st.ninep) catch |err| {
+ if (running) return core.restartFailed(pane, err);
+ return core.shellFailed(pane, err);
+ };
reap(st, pane);
st.gens[pane] +%= 1;
const gen = st.gens[pane];
- const child = host_io.forkShell(core, pane, &st.prompt_rcs, core.shellBin(), cwd, core.screen_h, core.screen_w, st.ninep) catch |err| return core.shellFailed(pane, err);
st.ptys[pane] = .{
.file = child.file,
.pid = child.pid,
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 31382279..92342525 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -1996,7 +1996,7 @@ test "pty/ctl exec in a directory that is gone fails ENOENT; a shell that cannot
try testing.expectEqualStrings("shell: access denied", p.fs.late_failure[0..p.fs.late_failure_len]);
}
-test "a script whose interpreter is not there: Tty refuses it up front, only an err logged" {
+test "a script whose interpreter is not there: Tty refuses it up front, only an err logged, and pty/ctl exec keeps the running shell" {
const p = try th.withTerm(testing.allocator);
defer p.deinit();
const Starting = struct {
@@ -2023,6 +2023,20 @@ test "a script whose interpreter is not there: Tty refuses it up front, only an
try testing.expect(th.logHas(p, "Tty: interpreter /no/such/interp not found\n"));
try testing.expect(!th.logHas(p, "\nmsg "));
try testing.expect(!th.logHas(p, "\ndel "));
+ // The shell to start again is that script: refused before the running
+ // one goes.
+ const id = p.paneBySerial(serial).?;
+ p.panes[id].?.shell = try p.gpa.dupe(u8, try std.fmt.bufPrint(&line, "{s}/bad", .{dir}));
+ p.setCwd(id, dir);
+ const exec_refused = wr(p, Node.of(serial, .pty_ctl), "exec\n");
+ try testing.expectEqual(E.NOENT, exec_refused.errno());
+ try testing.expectEqualStrings("exec: interpreter /no/such/interp not found", exec_refused.reply.ename);
+ // One the host found could not start (the script changed after): said
+ // with the interpreter's name, and the running shell is not gone.
+ p.fs.late_failure_len = 0;
+ p.restartFailed(@intCast(id), error.InterpreterNotFound);
+ try testing.expectEqualStrings("shell: interpreter /no/such/interp not found", p.fs.late_failure[0..p.fs.late_failure_len]);
+ try testing.expect(!p.panes[id].?.shell_failed);
}
test "every EINVAL a write gets says why, in its err record too; DEL is a control character in a line" {
diff --git a/src/ninep/pty.zig b/src/ninep/pty.zig
index aa0f499a..bf5f432c 100644
--- a/src/ninep/pty.zig
+++ b/src/ninep/pty.zig
@@ -67,6 +67,14 @@ pub fn writeCtl(p: *Pardes, req: Req, id: usize) Reply {
const dir = pane.cwdSlice();
return tree.failText(req.tag, E.NOENT, std.fmt.bufPrint(&p.fs.ename, "exec: {s}: no such directory", .{dir[0..@min(dir.len, 256)]}) catch "exec: no such directory");
};
+ // Nor is a shell that is not there, or a script whose
+ // interpreter is not: refused, and the running shell kept.
+ if (comptime pardes.hosted) if (!apply and std.mem.eql(u8, line, "exec")) if (p.panes[id]) |pane| {
+ const bin = pane.shell orelse p.shellBin();
+ var why: [320]u8 = undefined;
+ if (@import("../host_io.zig").Shell.refusal(bin[0..@min(bin.len, 200)], &why)) |refused|
+ return tree.failText(req.tag, E.NOENT, std.fmt.bufPrint(&p.fs.ename, "exec: {s}", .{refused}) catch "exec: no such shell");
+ };
if (!verb(p, id, line, apply)) return tree.failText(req.tag, E.INVAL, if (outOfRange(line)) e_winsize_range else e_bad_pty_ctl);
}
}
diff --git a/src/pardes.zig b/src/pardes.zig
index d2fd795b..76ec2176 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -5353,6 +5353,13 @@ pub const Pardes = struct {
}
}
+ /// A shell started again in a terminal whose shell runs (pty/ctl's
+ /// exec) did not start: the host kept the running one, so only said.
+ pub fn restartFailed(p: *Pardes, id: u8, err: anyerror) void {
+ if (p.panes[id] == null) return;
+ p.sayShellFailure(id, err);
+ }
+
fn sayShellFailure(p: *Pardes, id: u8, err: anyerror) void {
const pane = p.panes[id] orelse return;
if (err == error.FileNotFound or err == error.NotDir) {
diff --git a/src/tty/tty.zig b/src/tty/tty.zig
index 3c8c90bc..a6f6bdfc 100644
--- a/src/tty/tty.zig
+++ b/src/tty/tty.zig
@@ -1253,9 +1253,15 @@ const Shell = struct {
fn spawn(ctx: ?*anyopaque, pane: u8, cwd: []const u8) void {
const s = of(ctx);
- closePty(ctx, pane); // a shell still in the slot goes first, reaped
+ // The new shell starts before a running one goes: one that cannot
+ // start leaves the running one be (pty/ctl's exec).
+ const running = s.ptys[pane] != null;
+ const child = host_io.forkShell(s.core, pane, s.prompt_rcs, s.core.shellBin(), cwd, s.core.screen_h, s.core.screen_w, s.fs) catch |err| {
+ if (running) return s.core.restartFailed(pane, err);
+ return s.core.shellFailed(pane, err);
+ };
+ closePty(ctx, pane); // a shell still in the slot goes, reaped
s.gens[pane] +%= 1;
- const child = host_io.forkShell(s.core, pane, s.prompt_rcs, s.core.shellBin(), cwd, s.core.screen_h, s.core.screen_w, s.fs) catch |err| return s.core.shellFailed(pane, err);
const command = if (s.core.panes[pane]) |pn| pn.command != null else false;
s.ptys[pane] = .{ .file = child.file, .pid = child.pid, .reader = .{ .any_future = null, .result = {} }, .cmd = .{ .watched = command and host_io.watchExit(child.pid), .fd = child.file.handle } };
var lbuf: [pardes.memory.limits.host_path_cap + 1]u8 = undefined;