From 594970f18dee92308fd1554663cc408cda4d102c Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 27 Sep 2026 18:20:06 -0300 Subject: Make pty/run and /log streams, and take refused lines back cleanly Review fixes to pty/run and /log: - Both are streams with a per-open cursor. A shell's exec 3<>file shares one offset between write and read, so cat <&3 after echo make >&3 asked for offset 5 and got "0" instead of "exit 0"; log after follow lost its first bytes the same way. - A run is accepted only in pardes's own tagged input phase, so a nested shell's prompt (ssh, a shell with its own integration) is never taken for this shell's. - A line the shell refused is taken back so the next finds the prompt clear: bash's continuation prompt (now tagged k=c) gets Ctrl-C and the answer waits for the fresh prompt; a bash syntax error (no C, but a D) is answered at once; fish's kept line gets Ctrl-U (a Ctrl-C sent while fish redraws is lost). fish's right prompt no longer reads as typed input. - bash marks a command's start from PS0 on bash 4.4+, not a DEBUG trap, so a user's own DEBUG trap (bash-preexec, atuin) survives. bash's own job notices now show as in plain bash (ttyfollow golden). - 32 run slots; a new line on an open starts a fresh answer. Checked live in bash, fish, and fish with a right prompt. Co-Authored-By: Claude Opus 5.5 --- src/ninep/events.zig | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/ninep/events.zig') diff --git a/src/ninep/events.zig b/src/ninep/events.zig index fa0a4975..20cdeda4 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -208,10 +208,11 @@ pub fn readLog(p: *Pardes, req: Req) Reply { if (slot.node != req.node) return Reply.fail(req.tag, E.INVAL); const frozen = slot.bytes orelse &.{}; const out = p.fs.stage(p.gpa); - if (!slot.follow or req.off < frozen.len) { - const off = @min(req.off, frozen.len); + if (!slot.follow or slot.pos < frozen.len) { + const off = @min(slot.pos, frozen.len); const len = @min(frozen.len - off, req.size); out.appendSlice(p.gpa, frozen[off..][0..len]) catch return Reply.fail(req.tag, E.NOMEM); + slot.pos += len; return .{ .tag = req.tag, .payload = .{ .staged = @intCast(len) } }; } const q = &p.fs.log; @@ -678,6 +679,8 @@ test "the log records whether or not anyone reads, and an open that follows wait const frozen = call(p, .{ .tag = 6, .op = .read, .node = log, .handle = fh, .size = 8192 }).bytes.len; try testing.expectEqual(Status.err, call(p, .{ .tag = 7, .op = .write, .node = log, .handle = fh, .data = "tail\n" }).reply.status); try testing.expectEqual(Status.ok, call(p, .{ .tag = 8, .op = .write, .node = log, .handle = fh, .data = "follow\n" }).reply.status); + // A stream: after `echo follow >&3` a shell reads at offset 7, and gets + // what comes next, not the frozen text from byte 7. const rdf = struct { fn next(pp: *Pardes, node: u64, h: u32, off: u64) th.Answer { return call(pp, .{ .tag = 9, .op = .read, .node = node, .handle = h, .off = off, .size = 8192 }); -- cgit v1.3