diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/detached/server.zig | 18 | ||||
| -rw-r--r-- | src/gui/gui.zig | 14 | ||||
| -rw-r--r-- | src/macos.zig | 8 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 16 | ||||
| -rw-r--r-- | src/ninep/pty.zig | 8 | ||||
| -rw-r--r-- | src/pardes.zig | 7 | ||||
| -rw-r--r-- | src/tty/tty.zig | 10 |
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; |
