diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 07:29:46 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | 740effb5c9e18e7638fde196000030c899e8798e (patch) | |
| tree | f78a92e9d25b4691796e7e3a286af736b94c5ce1 | |
| parent | 07706163ab3a5dfbb2c6940ac6e5966b19e0074e (diff) | |
| download | pardes-740effb5c9e18e7638fde196000030c899e8798e.tar.gz pardes-740effb5c9e18e7638fde196000030c899e8798e.zip | |
A root exec or look that fails logs its err against the pane it ran at
`err - exec: ...` said nothing of where the line ran; the root's exec and
look run at the keyboard's pane, and their err now names it, as a click
there would.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | docs/fs.md | 4 | ||||
| -rw-r--r-- | src/fs.zig | 3 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 18 | ||||
| -rw-r--r-- | src/ninep/events.zig | 8 |
4 files changed, 30 insertions, 3 deletions
@@ -748,7 +748,9 @@ between words, and ends in an ellipsis, `…`, as an `err`'s reason is past 200; its serial is the pane it ran at, `-` when the keyboard was on a column or workspace tag, or the line came to the root's ctl, `/tagexec` or a column's ctl or exec), and `err <serial|-> -<file>: <why>` for every write or truncation the tree refused or that +<file>: <why>` (the serial the pane's whose file it is, and for the root's +`exec` and `look` the pane the line ran at, `err 3 exec: ...`; `-` for +another root file) for every write or truncation the tree refused or that failed -- through a mount a shell sees only the errno its kernel mapped the reply to, usually `Invalid argument`, and this is the reason (`err 3 addr: no match for regexp`). A record said again word for word, straight after @@ -1303,6 +1303,9 @@ pub const Namespace = struct { /// host work fails (a Save, a Dump), takes it back out of the log, so /// an announcement stands only for a builtin that did what it said. announced_seq: ?u64 = null, + /// The serial of the pane the root's exec or look last ran a line at + /// (the keyboard's), 0 before one: its err record names that pane. + ran_at: u32 = 0, /// Every failure said (Messages.reportFailure), counted: a builtin /// that said one while it ran failed. failures: u32 = 0, diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 7c68210a..4f52d661 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -170,6 +170,7 @@ pub fn completeEnd(p: *Pardes, data: []const u8) usize { /// A null `serial` clicks at whichever pane is active as each line runs, /// which is what the root's own look and exec mean; a serial stays put. pub fn command(p: *Pardes, req: Req, serial: ?u32, exec: bool, in_tag: bool) Reply { + p.fs.ran_at = 0; for ([2]bool{ false, true }) |apply| { if (apply) p.fs.results_len = 0; var it = Messages.init(p, req.data); @@ -181,6 +182,7 @@ pub fn command(p: *Pardes, req: Req, serial: ?u32, exec: bool, in_tag: bool) Rep if (!apply) continue; const id = if (serial) |s| p.paneBySerial(s) orelse break else p.active; if (p.panes[id] == null) return Reply.fail(req.tag, E.NOENT); + p.fs.ran_at = p.panes[id].?.serial; p.fs.no_pane_slot = false; if (captured(p, req, id, exec, text, in_tag)) |refusal| return refusal; // A pane it would have opened had no slot: the write fails, and @@ -1728,6 +1730,17 @@ test "Mount dials its peer and fails, mounting nothing, when none answers; a loo try testing.expect(th.logHas(p, "look: /n/gone/f.txt: dial failed: no answer")); } +test "a root exec that fails logs its err against the pane it ran at" { + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + const f = &p.panes[0].?.file.?; + f.saved_revision = f.revision -% 1; + f.saved_hash = 0; + try testing.expect(wr(p, root_exec, "Exit\n").errno() != 0); + var want: [32]u8 = undefined; + try testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "err {d} exec: ", .{serialOf(p)}))); +} + 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(); @@ -1983,7 +1996,10 @@ test "a builtin that fails through exec, tagexec or a column's exec fails the wr const h = call(p, .{ .tag = 1, .op = .open, .node = log }).reply.handle; const text = call(p, .{ .tag = 2, .op = .read, .node = log, .handle = h, .size = 1 << 16 }).bytes; try testing.expect(std.mem.indexOf(u8, text, "msg ") == null); - try testing.expectEqual(@as(usize, 4), std.mem.count(u8, text, "Kill: no running command has that first word")); + // The root's exec ran at the pane, so its err and the pane's exec's are + // the same record, counted. + try testing.expectEqual(@as(usize, 3), std.mem.count(u8, text, "Kill: no running command has that first word")); + try testing.expect(std.mem.indexOf(u8, text, "first word (x2)") != null); _ = call(p, .{ .tag = 3, .op = .release, .node = log, .handle = h }); // A control character is refused with its reason. try testing.expectEqualStrings(e_control, wr(p, root_exec, "Msg a\x01b\n").reply.ename); diff --git a/src/ninep/events.zig b/src/ninep/events.zig index fb0141f4..98799774 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -187,7 +187,13 @@ pub fn noteError(p: *Pardes, req: Req, reply: Reply) void { if (req.node & tree.sources.archive_node != 0) file = "src"; if (req.node != pardes.filesystem.os_root and req.node & pardes.filesystem.os_node == 0 and req.node & tree.sources.archive_node == 0) { if (tree.Node.target(req.node)) |t| switch (t) { - .top => |f| file = f.fileName(), + .top => |f| { + file = f.fileName(); + // The root's exec or look ran at the keyboard's pane: its + // failure is that pane's, as a click there would be. + if ((f == .exec or f == .look) and p.fs.ran_at != 0) + serial = std.fmt.bufPrint(&name, "{d}", .{p.fs.ran_at}) catch "-"; + }, .col => |c| { serial = "-"; file = (if (c.file == .dir) |
