summaryrefslogtreecommitdiff
path: root/src/ninep/pty.zig
diff options
context:
space:
mode:
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" {