From 917e9a099f3a5048ac436aafcfabbf3d40d1c24f Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 13:26:10 -0300 Subject: docs: through a mount a malformed write is EINVAL and one that fails is EIO, and the error words keep it so The served README said every refused write reads as EINVAL, but a mount's errno is 9ns's reading of the error's words, so a no-match or out-of-range address and a refused Exit came back EIO, as the dogfood run saw. That split is the useful one, malformed against failed, so the docs now state it rather than the words being bent to EINVAL; a test pins each error's words to its side of it. It caught one: 'regular expression search took too long' read as ENAMETOOLONG, and now says it gave up past its step budget. Co-Authored-By: Claude Opus 5.5 --- src/ninep/ctl.zig | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'src/ninep/ctl.zig') diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 382cc0e7..386fd9a9 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -926,3 +926,28 @@ test "look and exec read back what the last command touched, a ctl write's too" try testing.expectEqual(tree.Status.ok, wr(p, @intFromEnum(tree.TopFile.ctl), "Verbose off\n").reply.status); try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{made}), rd(p, root_exec, 0, 64).bytes); } + +test "an error's words give the errno a mount reads: EINVAL for what is malformed, EIO for what failed" { + // 9ns (cloud9's 9ns/src/nine.zig) maps an error to an errno by its + // words, and EIO when none of them match: these words must not. + const addressing = @import("addr.zig"); + const einval = [_][]const u8{ "control message", "invalid", "bad " }; + const other = [_][]const u8{ "interrupt", "shut down", "not exist", "not found", "no such", "exists", "not empty", "not a dir", "is a dir", "permission", "denied", "read-only", "read only", "no space", "not allowed", "not permitted", "cannot", "fid", "busy", "in use", "too long", "not supported", "unsupported" }; + const Case = struct { text: []const u8, malformed: bool }; + for ([_]Case{ + .{ .text = addressing.e_syntax, .malformed = true }, + .{ .text = addressing.e_regexp, .malformed = true }, + .{ .text = e_prompt, .malformed = true }, + .{ .text = addressing.e_no_match, .malformed = false }, + .{ .text = addressing.e_range, .malformed = false }, + .{ .text = addressing.e_order, .malformed = false }, + .{ .text = addressing.e_slow, .malformed = false }, + .{ .text = "/tmp/x.txt: Modified (Exit again to discard)", .malformed = false }, + }) |c| { + const says_einval = for (einval) |w| { + if (std.ascii.findIgnoreCase(c.text, w) != null) break true; + } else false; + try testing.expectEqual(c.malformed, says_einval); + for (other) |w| try testing.expect(std.ascii.findIgnoreCase(c.text, w) == null); + } +} -- cgit v1.3