From a628330543d1c2a0b29062036f23b1c74e4ccc6e Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 23:03:35 -0300 Subject: A builtin refused when its record is written back through event fails that write, EIO and its err, as fs.md says A Del refused over unsaved text, written back by an event reader, said so on the message row and answered the write ok, so the script that wrote it could not tell. The write-back now captures failures as an exec write does: the first one fails the write with its words and the errno they read as. Co-Authored-By: Claude Opus 5.5 --- src/ninep/ctl.zig | 21 ++++++++++++++++++++- src/ninep/events.zig | 10 ++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 31265a94..aa309ad1 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -296,7 +296,7 @@ pub fn captured(p: *Pardes, req: Req, id: usize, exec: bool, text: []const u8, i /// The errno a builtin's failure answers with: ENOENT for what is not /// there (`no such`, `not found`, the words 9ns reads as ENOENT too), else /// EIO. -fn failureErrno(failure: []const u8) u16 { +pub fn failureErrno(failure: []const u8) u16 { if (std.mem.indexOf(u8, failure, "no such") != null or std.mem.indexOf(u8, failure, "not found") != null) return E.NOENT; // A pattern refused (Edit's too) is malformed input, as 9ns reads it; // so is a word this build has not (`invalid: acme's Put ...`). @@ -1760,6 +1760,25 @@ test "exec Save writes the named file through the host" { try testing.expectEqualStrings(pane.file.?.content, p.fallback.get("/tmp/renamed.txt").?); } +test "a refused Del written back through event fails that write, EIO, with its err" { + const gpa = testing.allocator; + const p = try withFile(gpa, "text\n"); + defer p.deinit(); + const serial = serialOf(p); + try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .body), "more\n").reply.status); // unsaved + const event = Node.of(serial, .event); + const h = call(p, .{ .tag = 1, .op = .open, .node = event }).reply.handle; + defer _ = call(p, .{ .tag = 9, .op = .release, .node = event, .handle = h }); + try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .exec), "Del\n").reply.status); + const record = call(p, .{ .tag = 2, .op = .read, .node = event, .handle = h, .size = 256 }).bytes; + var copy: [256]u8 = undefined; + @memcpy(copy[0..record.len], record); + const back = wr(p, event, copy[0..record.len]); + try testing.expectEqual(E.IO, back.errno()); + try testing.expect(std.mem.indexOf(u8, back.reply.ename, "Modified") != null); + try testing.expect(p.paneBySerial(serial) != null); +} + test "a pane's own look and exec go to the event reader holding it, which writes them back to act" { const gpa = testing.allocator; const p = try withFile(gpa, "Msg routed\n"); diff --git a/src/ninep/events.zig b/src/ninep/events.zig index 4d1bbe64..1ffbaab9 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -6,6 +6,7 @@ const exec = @import("../exec.zig"); const look = @import("../look.zig"); const cloud9 = @import("cloud9"); const tree = @import("tree.zig"); +const ctl = @import("ctl.zig"); const pane_files = @import("pane.zig"); const dump = @import("../dump.zig"); @@ -736,6 +737,11 @@ pub fn writeEvent(p: *Pardes, asked: Req, id: usize) Reply { } if (check.i != req.data.len) return tree.failText(req.tag, E.INVAL, tree.e_bad_event ++ ": a record is , a line each"); } + // What a record written back runs fails the write as a click written + // to exec would (a refused Del: EIO and its err), not said and lost. + p.fs.capturing = true; + p.fs.failure_len = 0; + defer p.fs.capturing = false; var run: EventReader = .{ .data = req.data }; while (run.next()) |r| { const live = p.paneBySerial(serial) orelse break; @@ -815,6 +821,10 @@ pub fn writeEvent(p: *Pardes, asked: Req, id: usize) Reply { else => unreachable, } } + if (p.fs.failure_len > 0) { + const failure = p.fs.failure[0..p.fs.failure_len]; + return tree.failText(req.tag, ctl.failureErrno(failure), std.fmt.bufPrint(&p.fs.ename, "{s}", .{failure}) catch failure); + } return .{ .tag = req.tag, .written = @intCast(asked.data.len) }; } -- cgit v1.3