summaryrefslogtreecommitdiff
path: root/src/ninep/pty.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-27 20:20:27 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit7dc3891ed574d6e96548f592b09b0f8abc87ae2a (patch)
tree70cddb9ce4a7e9039f8167e806b7e996f94eefb0 /src/ninep/pty.zig
parentacd9e032a147f4aa7ad49110dc2f4fb945e7a9bb (diff)
downloadpardes-7dc3891ed574d6e96548f592b09b0f8abc87ae2a.tar.gz
pardes-7dc3891ed574d6e96548f592b09b0f8abc87ae2a.zip
Keep what each open holds in one table of open records
Snapshots (and /log's cursor), runs, and the reader_handle constants for event and pty/data each reused the open handle and each validated handle and node on its own. Now p.fs.opens is one table of 64 records, each the node it was opened on and a tagged union of what it holds, like lib9p's per-fid aux and acme's Fid (editors/acme/dat.h:373-385): one lookup (openOf), one release, ENFILE when full. A held read lives in its open's record, so it goes with the release. Opens that hold nothing answer handle 0 and take no record. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/pty.zig')
-rw-r--r--src/ninep/pty.zig63
1 files changed, 15 insertions, 48 deletions
diff --git a/src/ninep/pty.zig b/src/ninep/pty.zig
index ddb7bcd1..706163e5 100644
--- a/src/ninep/pty.zig
+++ b/src/ninep/pty.zig
@@ -109,15 +109,12 @@ pub fn readStatus(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply {
// command. The shell's own OSC 133 marks say when the command started (C)
// and ended (D, with its status); pardes injects them into bash and fish.
-pub const run_slots = 32;
-
/// The most of a command's output an answer carries: its last this-many
/// bytes, since that is where a failure says what went wrong.
pub const output_cap = 64 * 1024;
+/// What an open of run holds (tree.zig's open records).
pub const Run = struct {
- /// 0 is a free slot.
- node: u64 = 0,
phase: enum { idle, sent, done } = .idle,
/// The shell's command count its C must reach for the end to be ours.
want: u32 = 0,
@@ -139,29 +136,8 @@ pub const Run = struct {
len: u8 = 0,
/// Then what the command printed, gpa-owned.
output: []u8 = &.{},
-
- pub fn reset(slot: *Run, gpa: std.mem.Allocator, node: u64) void {
- gpa.free(slot.output);
- slot.* = .{ .node = node };
- }
};
-fn runOf(p: *Pardes, req: Req) ?*Run {
- if (req.handle == 0 or req.handle > run_slots) return null;
- const slot = &p.fs.runs[req.handle - 1];
- return if (slot.node == req.node) slot else null;
-}
-
-pub fn openRun(p: *Pardes, req: Req, serial: u32) Reply {
- _ = serial;
- for (&p.fs.runs, 0..) |*slot, i| {
- if (slot.node != 0) continue;
- slot.* = .{ .node = req.node };
- return .{ .tag = req.tag, .handle = @intCast(i + 1) };
- }
- return Reply.fail(req.tag, E.NFILE);
-}
-
fn answer(p: *Pardes, slot: *Run, comptime fmt: []const u8, args: anytype) void {
slot.len = @intCast((std.fmt.bufPrint(&slot.answer, fmt ++ "\n", args) catch unreachable).len);
slot.phase = .done;
@@ -169,9 +145,11 @@ fn answer(p: *Pardes, slot: *Run, comptime fmt: []const u8, args: anytype) void
}
pub fn writeRun(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply {
- const slot = runOf(p, req) orelse return Reply.fail(req.tag, E.INVAL);
+ const slot = &(tree.openOf(p, req) orelse return Reply.fail(req.tag, E.INVAL)).what.run;
if (slot.phase == .sent) return tree.failText(req.tag, E.BUSY, tree.e_in_use);
- slot.reset(p.gpa, slot.node); // a new line on this open: a new answer
+ // A new line on this open: a new answer.
+ p.gpa.free(slot.output);
+ slot.* = .{};
const line = std.mem.trimEnd(u8, req.data, "\r\n");
if (std.mem.trim(u8, line, " \t").len == 0) return tree.failText(req.tag, E.INVAL, e_bad_line);
if (line.len == 0) return tree.failText(req.tag, E.INVAL, e_bad_line);
@@ -197,13 +175,13 @@ pub fn writeRun(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply {
p.emitWrite(id, line);
p.emitWrite(id, "\r");
slot.phase = .sent;
- pf.run = @intCast(slot - &p.fs.runs[0]);
+ pf.run = @intCast(req.handle - 1);
}
return .{ .tag = req.tag, .written = @intCast(req.data.len) };
}
pub fn readRun(p: *Pardes, req: Req) Reply {
- const slot = runOf(p, req) orelse return Reply.fail(req.tag, E.INVAL);
+ const slot = &(tree.openOf(p, req) orelse return Reply.fail(req.tag, E.INVAL)).what.run;
switch (slot.phase) {
.idle => return .{ .tag = req.tag, .payload = .{ .staged = 0 } },
.sent => return .{ .tag = req.tag, .status = .again },
@@ -226,17 +204,6 @@ pub fn readRun(p: *Pardes, req: Req) Reply {
return .{ .tag = req.tag, .payload = .{ .staged = @intCast(out.items.len) } };
}
-pub fn releaseRun(p: *Pardes, req: Req) Reply {
- const slot = runOf(p, req) orelse return .{ .tag = req.tag };
- const idx: u8 = @intCast(slot - &p.fs.runs[0]);
- // The command runs on in the shell; nobody is waiting for it any more.
- for (p.panes) |live| if (live) |pane| if (pane.fs.run == idx) {
- pane.fs.run = null;
- };
- slot.reset(p.gpa, 0);
- return .{ .tag = req.tag };
-}
-
/// After each chunk of a terminal's output: once the command the run sent
/// has ended and the shell is back at a prompt, the run answers how. Waiting
/// for the prompt means whoever reads the answer can send the next command
@@ -245,7 +212,7 @@ pub fn noteMarks(p: *Pardes, id: usize, pane: *Pane) void {
if (comptime !pardes.panes.Terminal.enabled) return;
const state = pane.terminal orelse return;
const pf = &pane.fs;
- const slot = &p.fs.runs[pf.run orelse return];
+ const slot = &p.fs.opens[pf.run orelse return].what.run;
const marks = &state.stream.handler;
if (marks.phase != .input) return;
// ponytail: a prompt redrawn before the command starts (a resize in that
@@ -325,7 +292,7 @@ pub fn noteMarks(p: *Pardes, id: usize, pane: *Pane) void {
/// be reported, so the run says so instead of waiting forever.
pub fn shellGone(p: *Pardes, pane: *Pane) void {
const idx = pane.fs.run orelse return;
- answer(p, &p.fs.runs[idx], "error shell gone", .{});
+ answer(p, &p.fs.opens[idx].what.run, "error shell gone", .{});
pane.fs.run = null;
}
@@ -387,7 +354,7 @@ test "a pane that is not a terminal has no pty/ at all" {
.op = .open,
.node = Node.of(serial, .pty_data),
}).errno());
- try testing.expectEqual(@as(u16, 0), p.panes[0].?.fs.pty_readers);
+ try testing.expect(!p.panes[0].?.fs.pty_reader);
var buf: [32]th.Dirent = undefined;
const files = th.dirents(rdir(p, dir, 0).bytes, &buf);
@@ -578,11 +545,11 @@ test "pty/data writes at the shell and reads the raw stream" {
// An open that only writes is not a reader: nothing queues for it.
const writer = call(p, .{ .tag = 4, .op = .open, .node = data, .omode = 1 });
- try testing.expectEqual(@as(u16, 0), p.panes[0].?.fs.pty_readers);
+ try testing.expect(!p.panes[0].?.fs.pty_reader);
_ = call(p, .{ .tag = 4, .op = .release, .node = data, .handle = writer.reply.handle });
const reader = call(p, .{ .tag = 5, .op = .open, .node = data });
- try testing.expectEqual(@as(u16, 1), p.panes[0].?.fs.pty_readers);
+ try testing.expect(p.panes[0].?.fs.pty_reader);
try testing.expectEqual(@as(u16, 0), p.fs.listeners);
try testing.expect(p.panes[0].?.fs.readers == 0);
@@ -602,7 +569,7 @@ test "pty/data writes at the shell and reads the raw stream" {
p.update(.{ .output = .{ .pane = 0, .bytes = "orphan" } });
while (p.nextEffect()) |_| {}
_ = call(p, .{ .tag = 6, .op = .release, .node = data, .handle = reader.reply.handle });
- try testing.expectEqual(@as(u16, 0), p.panes[0].?.fs.pty_readers);
+ try testing.expect(!p.panes[0].?.fs.pty_reader);
try testing.expectEqual(@as(usize, 0), p.panes[0].?.fs.pty_out.buf.capacity);
try testing.expectEqual(Status.again, rd(p, data, 0, 64).reply.status);
@@ -611,7 +578,7 @@ test "pty/data writes at the shell and reads the raw stream" {
const second = call(p, .{ .tag = 8, .op = .open, .node = data, .omode = 2 });
try testing.expectEqual(Status.err, second.reply.status);
try testing.expectEqualStrings(tree.e_in_use, second.reply.ename);
- try testing.expectEqual(@as(u16, 1), p.panes[0].?.fs.pty_readers);
+ try testing.expect(p.panes[0].?.fs.pty_reader);
p.update(.{ .output = .{ .pane = 0, .bytes = "still" } });
while (p.nextEffect()) |_| {}
try testing.expectEqualStrings("still", rd(p, data, 0, 64).bytes);
@@ -717,7 +684,7 @@ test "run answers how its command ended once the shell is back at a prompt" {
try p.removePane(0, null);
try testing.expectEqualStrings("error shell gone\n", call(p, .{ .tag = 19, .op = .read, .node = node, .handle = gone.reply.handle, .size = 64 }).bytes);
_ = call(p, .{ .tag = 20, .op = .release, .node = node, .handle = gone.reply.handle });
- for (p.fs.runs) |slot| try testing.expect(slot.node == 0);
+ for (p.fs.opens) |o| try testing.expect(o.node == 0);
}
test "the pty queue drops the oldest at its cap" {