From 0ec8d0dc6217486446f2fe2156dff136173a4ef5 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 08:02:47 -0300 Subject: 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: : 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 --- src/ninep/ctl.zig | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'src/ninep/ctl.zig') 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(); -- cgit v1.3