From cc8b72c1b1fd19cd3ef159fe4f56c7b7916380ce Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 21:52:42 -0300 Subject: Mount with no dial is wrong #args, and a dial that is no address says bad dial address, both EINVAL Mount name alone failed "Mount name dial: missing argument" and a malformed dial "Mount: bad dial", both EIO, though each is a malformed request. They now say wrong #args and bad dial address (with the forms a dial takes), and a builtin failure in those words answers EINVAL, as 9ns reads them. Co-Authored-By: Claude Opus 5.5 --- src/builtins.zig | 11 ++++++++--- src/ninep/ctl.zig | 21 +++++++++++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/builtins.zig b/src/builtins.zig index bc9bb075..89d89c3a 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -790,10 +790,15 @@ pub const Mount = struct { pub fn run(c: Ctx) void { if (comptime !enabled) unreachable; var args = std.mem.tokenizeAny(u8, c.arg orelse "", " \t"); - const name = args.next() orelse return c.p.reportError(c.id, "Mount name dial", error.MissingArgument); + const e_args = "wrong #args in control message \"Mount\": Mount name dial"; + const name = args.next() orelse return c.p.reportFailure(c.id, e_args); const dial = std.mem.trim(u8, args.rest(), " \t"); - if (dial.len == 0) return c.p.reportError(c.id, "Mount name dial", error.MissingArgument); - c.p.fs.mount(c.p.gpa, name, dial) catch |err| return c.p.reportError(c.id, "Mount", err); + if (dial.len == 0) return c.p.reportFailure(c.id, e_args); + c.p.fs.mount(c.p.gpa, name, dial) catch |err| { + var said: [200]u8 = undefined; + if (err == error.BadDial) return c.p.reportFailure(c.id, std.fmt.bufPrint(&said, "Mount: bad dial address: {s} (unix!/path, tcp!host!port, or a session name)", .{dial[0..@min(dial.len, 100)]}) catch "Mount: bad dial address"); + return c.p.reportError(c.id, "Mount", err); + }; // A mount that cannot be reached is said now, and taken back, not // found out by the first look through it. const reached = reached: { diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 6e09084b..5b63080e 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -302,6 +302,9 @@ fn failureErrno(failure: []const u8) u16 { // so is a word this build has not (`invalid: acme's Put ...`). if (std.mem.indexOf(u8, failure, "bad regular expression") != null or std.mem.startsWith(u8, failure, "invalid") or std.mem.startsWith(u8, failure, "address out of range")) return E.INVAL; + // A word's arguments wrong, or a dial that is no address: what 9ns + // reads as EINVAL from the same words (`control message`, `bad `). + if (std.mem.indexOf(u8, failure, "control message") != null or std.mem.indexOf(u8, failure, "bad dial address") != null) return E.INVAL; return E.IO; } @@ -1280,9 +1283,9 @@ test "the root ctl reads the settings as a write takes them, and takes the sessi try testing.expect(!p.settings.verbose); // A builtin that fails as it runs fails the write, quoting its error and // its line; the lines before it have taken effect, as in acme. - const failed = wr(p, root_ctl, "Verbose on\nMount x\nVerbose off"); + const failed = wr(p, root_ctl, "Verbose on\nMount x /tmp/pardes-no-such-sock-zz\nVerbose off"); try testing.expectEqual(E.IO, failed.errno()); - try testing.expectEqualStrings("Mount name dial: missing argument", failed.reply.ename); + try testing.expectStringStartsWith(failed.reply.ename, "Mount x /tmp/pardes-no-such-sock-zz: dial failed"); try testing.expect(p.settings.verbose); try testing.expect(!p.fs.no_prompt); try testing.expectEqual(Status.ok, wr(p, root_ctl, "Verbose off").reply.status); @@ -1674,6 +1677,20 @@ test "Save path of a file pane logs a save record naming that path" { try testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "save {d} /tmp/pardes-save-copy.txt\n", .{serial}))); } +test "Mount with no dial is wrong #args, and a dial that is no address says bad dial address" { + if (comptime !pardes.hosted) return; + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + const root_ctl = @intFromEnum(tree.TopFile.ctl); + const bare = wr(p, root_ctl, "Mount work\n"); + try testing.expectEqual(E.INVAL, bare.errno()); + try testing.expectStringStartsWith(bare.reply.ename, "wrong #args"); + const bad = wr(p, root_ctl, "Mount work tcp!nohost\n"); + try testing.expectEqual(E.INVAL, bad.errno()); + try testing.expectStringStartsWith(bad.reply.ename, "Mount: bad dial address: tcp!nohost"); + try testing.expectEqual(@as(usize, 0), p.fs.mounts.items.len); +} + test "an address looked at in an image pane names the image, never an empty subject" { const p = try withFile(testing.allocator, "x\n"); defer p.deinit(); -- cgit v1.3