diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 10:55:10 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 2a71bff5f06ba3e785a5eb96a8377e2cab8e2cdd (patch) | |
| tree | 05ba873c6696b4e88261e8fe20482c498ed52eb6 /src/ninep/events.zig | |
| parent | 68a74ec034a24c9b016497880e375f291498ae5f (diff) | |
| download | pardes-2a71bff5f06ba3e785a5eb96a8377e2cab8e2cdd.tar.gz pardes-2a71bff5f06ba3e785a5eb96a8377e2cab8e2cdd.zip | |
A refused or failed write is an err record in the log, naming the file and the reason
Through a mount every refusal reads as Invalid argument, so a shell user
could not tell a missing regexp match from a bad ctl word. Every write or
truncation the tree refuses or that fails now adds err <serial|-> <file>:
<why> to /log. No per-pane readable error file: acme's errors only takes
text, and one stream is simpler to watch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/events.zig')
| -rw-r--r-- | src/ninep/events.zig | 61 |
1 files changed, 61 insertions, 0 deletions
diff --git a/src/ninep/events.zig b/src/ninep/events.zig index 172f5c00..8ee25b74 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -4,6 +4,7 @@ const std = @import("std"); const pardes = @import("../pardes.zig"); const exec = @import("../exec.zig"); const look = @import("../look.zig"); +const cloud9 = @import("cloud9"); const tree = @import("tree.zig"); const pane_files = @import("pane.zig"); @@ -149,6 +150,36 @@ pub fn noteMessage(p: *Pardes, serial: u32, text: []const u8) void { std.fmt.bufPrint(&buf, "msg {d} {s}\n", .{ serial, text })) catch return); } +/// Records `err <serial|-> <file>: <why>` for a write or truncation that was +/// refused or failed: through a mount a shell sees only the errno the +/// kernel mapped the reply to (`Invalid argument`), and here is the reason. +/// The log is the one place for it, as acme's `errors` file takes text and +/// answers nothing: a per-pane readable error file would be a second. +pub fn noteError(p: *Pardes, req: Req, reply: Reply) void { + const why = if (reply.ename.len > 0) reply.ename else cloud9.fs.errString(reply.errno); + var name: [16]u8 = undefined; + var serial: []const u8 = "-"; + var file: []const u8 = "os"; + 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(), + .pane => |pt| { + serial = std.fmt.bufPrint(&name, "{d}", .{pt.serial}) catch "-"; + file = switch (pt.file) { + .pty_ctl => "pty/ctl", + .pty_status => "pty/status", + .pty_data => "pty/data", + .pty_run => "pty/run", + else => pt.file.fileName(), + }; + }, + }; + } + var buf: [256]u8 = undefined; + pushLog(p, std.fmt.bufPrint(&buf, "err {s} {s}: {s}\n", .{ serial, file, why[0..@min(why.len, 200)] }) catch return); +} + /// The log is one ring that records whether or not anyone reads it. A record /// is one line: a newline in a message or a name would read as two records. fn pushLog(p: *Pardes, record: []u8) void { @@ -690,6 +721,8 @@ test "the log records whether or not anyone reads, and an open that follows wait return call(pp, .{ .tag = 9, .op = .read, .node = node, .handle = h, .off = off, .size = 8192 }); } }; + // The word it refused is on record, and then there is nothing new. + try testing.expect(std.mem.startsWith(u8, rdf.next(p, log, fh, frozen).bytes, "err - log: ")); try testing.expectEqual(Status.again, rdf.next(p, log, fh, frozen).reply.status); const serial = try th.newPane(p); @@ -761,6 +794,34 @@ test "the log records whether or not anyone reads, and an open that follows wait _ = call(p, .{ .tag = 13, .op = .release, .node = log, .handle = cut.reply.handle }); } +test "a refused or failed write is an err record in the log, saying which file and why" { + const p = try withFile(testing.allocator, "one\n"); + defer p.deinit(); + p.update(.tick); // the pane's `new` is logged before the log is opened + while (p.nextEffect()) |_| {} + const serial = serialOf(p); + const log = @intFromEnum(tree.TopFile.log); + const f = call(p, .{ .tag = 1, .op = .open, .node = log }).reply.handle; + const frozen = call(p, .{ .tag = 2, .op = .read, .node = log, .handle = f, .size = 1 << 16 }).bytes.len; + _ = call(p, .{ .tag = 3, .op = .write, .node = log, .handle = f, .data = "follow" }); + var want: [128]u8 = undefined; + const next = struct { + fn record(pp: *Pardes, h: u32, off: usize) []const u8 { + return call(pp, .{ .tag = 4, .op = .read, .node = @intFromEnum(tree.TopFile.log), .handle = h, .off = off, .size = 4096 }).bytes; + } + }; + _ = wr(p, Node.of(serial, .addr), "/nowhere/"); + try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "err {d} addr: no match for regexp\n", .{serial}), next.record(p, f, frozen)); + _ = wr(p, @intFromEnum(tree.TopFile.ctl), "Bogus"); + try testing.expectEqualStrings("err - ctl: unknown control message \"Bogus\"\n", next.record(p, f, frozen)); + _ = wr(p, @intFromEnum(tree.TopFile.focus), "99999"); + try testing.expectEqualStrings("err - focus: no such window\n", next.record(p, f, frozen)); + // A write that succeeds says nothing. + _ = wr(p, Node.of(serial, .addr), "#0"); + try testing.expectEqual(Status.again, call(p, .{ .tag = 5, .op = .read, .node = log, .handle = f, .off = frozen, .size = 4096 }).reply.status); + _ = call(p, .{ .tag = 6, .op = .release, .node = log, .handle = f }); +} + test "opens of the log share the open records, and a closed one frees its record" { const gpa = testing.allocator; const p = try withFile(gpa, "x\n"); |
