summaryrefslogtreecommitdiff
path: root/src/ninep/pty.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 14:37:21 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commitd678a63e0abf2ba8994225a9f0fd0047fee4025a (patch)
tree9c1a13fe7895ec162e9ee24de2913f2375375241 /src/ninep/pty.zig
parent25c847e28ae77f5c648d423d64011736f55eda4a (diff)
downloadpardes-d678a63e0abf2ba8994225a9f0fd0047fee4025a.tar.gz
pardes-d678a63e0abf2ba8994225a9f0fd0047fee4025a.zip
Every catch unreachable, orelse unreachable and syscall assert outside tests is a real refusal or says why it cannot fire
A sweep for round 23's crash: a run's answer (pty/run) was bufPrint'd into 48 bytes with catch unreachable, so a foreground program's long name (macOS gives up to 32 bytes) panicked; it now cuts at the room, keeping its newline, in 96 bytes. The rest were numbers into buffers sized for them, a braille codepoint, pthread calls on the queue's own mutex, and pdf_view's resolved outline entries: each now carries a one-line comment saying why it cannot fire. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/pty.zig')
-rw-r--r--src/ninep/pty.zig21
1 files changed, 19 insertions, 2 deletions
diff --git a/src/ninep/pty.zig b/src/ninep/pty.zig
index bf5f432c..d32c35fd 100644
--- a/src/ninep/pty.zig
+++ b/src/ninep/pty.zig
@@ -183,14 +183,20 @@ pub const Run = struct {
/// Plan 9's kprint read the same way).
read: usize = 0,
/// The first line: how it ended.
- answer: [48]u8 = undefined,
+ answer: [96]u8 = undefined,
len: u8 = 0,
/// Then what the command printed, gpa-owned.
output: []u8 = &.{},
};
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);
+ // A program's name comes from the host, whatever its length: an answer
+ // longer than the room is cut, its newline kept, never a panic.
+ var w: std.Io.Writer = .fixed(&slot.answer);
+ w.print(fmt ++ "\n", args) catch {
+ slot.answer[slot.answer.len - 1] = '\n';
+ };
+ slot.len = @intCast(w.end);
slot.phase = .done;
p.fs.news = true; // the read held on it can be answered
}
@@ -362,6 +368,7 @@ fn finish(p: *Pardes, slot: *Run, pane: *Pane, status_code: ?i32) void {
}
// A D that carries no status says nothing of how the command went.
var code: [16]u8 = undefined;
+ // unreachable: an exit status fits 16
const status = if (status_code) |s| std.fmt.bufPrint(&code, "{d}", .{s}) catch unreachable else "?";
// The header is the whole first line, so a count there can never be
// mistaken for output; `cut` with no count: its start is not there to
@@ -387,6 +394,7 @@ pub fn shellExited(p: *Pardes, pane: *Pane, status: ?u8) void {
finish(p, slot, pane, code);
};
var buf: [4]u8 = undefined;
+ // unreachable: a u8 exit code is at most 3 digits
pardes.exec.noteRun(p, pane, "exit", std.fmt.bufPrint(&buf, "{d}", .{code}) catch unreachable);
}
@@ -1070,3 +1078,12 @@ test "the pty queue drops the oldest at its cap" {
}
try testing.expect(seen > 0 and seen <= events.queue_cap);
}
+
+test "a run's answer longer than its room is cut, its newline kept" {
+ var slot: Run = .{};
+ const p = try th.withTerm(testing.allocator);
+ defer p.deinit();
+ answer(p, &slot, "busy: {s} is running", .{"x" ** 200});
+ try testing.expectEqual(slot.answer.len, slot.len);
+ try testing.expectEqual(@as(u8, '\n'), slot.answer[slot.len - 1]);
+}