summaryrefslogtreecommitdiff
path: root/src/ninep
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 08:02:47 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit0ec8d0dc6217486446f2fe2156dff136173a4ef5 (patch)
tree8e20f9f0fec47f614492d31c7f03a03e3ac1d0c5 /src/ninep
parentba0a0f82be43dde325012b3018b186bc808bdafc (diff)
downloadpardes-0ec8d0dc6217486446f2fe2156dff136173a4ef5.tar.gz
pardes-0ec8d0dc6217486446f2fe2156dff136173a4ef5.zip
Tty in a missing directory fails and makes no pane; a shell that never started answers runs
The host could not start the shell (its chdir failed), and the pane stayed with no pty: a run written to it waited for a prompt for ever. Tty now refuses a directory that is not there, `Tty: <dir>: no such directory`, before making a pane; and a terminal whose shell the host could not start (Pardes.shellFailed, from each host's spawn) answers any run with `error shell gone`. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep')
-rw-r--r--src/ninep/ctl.zig31
-rw-r--r--src/ninep/pty.zig5
2 files changed, 35 insertions, 1 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 6a8a37be..9bb9c044 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -1746,6 +1746,37 @@ test "a root exec that fails logs its err against the pane it ran at" {
try testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "err {d} exec: ", .{serialOf(p)})));
}
+test "Tty in a directory that is not there fails and makes no pane; a shell that never started answers a run" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ _ = wr(p, Node.of(serial, .name), "/tmp/pardes-no-such-dir-zz/f.txt\n");
+ // A host that starts shells (the core alone starts none, and asks
+ // nothing of a directory).
+ const Starting = struct {
+ fn spawn(_: ?*anyopaque, _: u8, _: []const u8) void {}
+ };
+ p.host = .{ .vtable = &.{ .spawn = Starting.spawn } };
+ defer p.host = .{};
+ var count: usize = 0;
+ for (p.panes) |slot| count += @intFromBool(slot != null);
+ const refused = wr(p, Node.of(serial, .ctl), "Tty\n");
+ try testing.expectEqual(E.IO, refused.errno());
+ try testing.expectStringStartsWith(refused.reply.ename, "Tty: /tmp/pardes-no-such-dir-zz: no such directory");
+ var after: usize = 0;
+ for (p.panes) |slot| after += @intFromBool(slot != null);
+ try testing.expectEqual(count, after);
+ // A terminal whose shell the host could not start.
+ _ = wr(p, Node.of(serial, .name), "/tmp/f.txt\n");
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .ctl), "Tty\n").reply.status);
+ const term = p.active;
+ p.shellFailed(@intCast(term), error.FileNotFound);
+ const run_node = Node.of(p.panes[term].?.serial, .pty_run);
+ const h = call(p, .{ .tag = 1, .op = .open, .node = run_node, .omode = 2 }).reply.handle;
+ _ = call(p, .{ .tag = 2, .op = .write, .node = run_node, .handle = h, .data = "true\n" });
+ try testing.expectEqualStrings("error shell gone\n", call(p, .{ .tag = 3, .op = .read, .node = run_node, .handle = h, .size = 64 }).bytes);
+}
+
test "size is monotonic: growing is never refused, and a size once taken is taken again" {
const p = try Pardes.init(testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 60 });
defer p.deinit();
diff --git a/src/ninep/pty.zig b/src/ninep/pty.zig
index 4009af49..68f98554 100644
--- a/src/ninep/pty.zig
+++ b/src/ninep/pty.zig
@@ -196,7 +196,10 @@ pub fn writeRun(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply {
const pf = &pane.fs;
const state = pane.terminal orelse return tree.failText(req.tag, E.INVAL, e_bad_line);
const marks = &state.stream.handler;
- if (pane.command != null) {
+ if (pane.shell_failed) {
+ // Its shell never started: no prompt is coming to wait for.
+ answer(p, slot, "error shell gone", .{});
+ } else if (pane.command != null) {
// Its child is the command, and no shell is there to take a line.
answer(p, slot, "error {s}", .{if (pane.command_done) "command done; not a shell" else "a command runs here, not a shell"});
} else if (pf.unmarked) {