diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 09:56:14 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | 742d986c1b773baba11b897598e870131b9b874b (patch) | |
| tree | 5ab65cf14e9bc4fbf4ec30da24c8e289c08f88e4 | |
| parent | a72d0c6650242a9b02858de7020e9a4ae387ebba (diff) | |
| download | pardes-742d986c1b773baba11b897598e870131b9b874b.tar.gz pardes-742d986c1b773baba11b897598e870131b9b874b.zip | |
A ctl line that ran and failed is not quoted after its reason; only one refused before running is
Failures of a builtin through a ctl quoted the line after the builtin's
own words, which already named it (`Mount dead /x: dial failed: no answer
"Mount dead /x"`), while the same failure through exec did not. A line
that ran says why in its own words everywhere now; only a line refused
as no message at all quotes it, as Plan 9's cmderror does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | docs/fs.md | 6 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 24 |
2 files changed, 17 insertions, 13 deletions
@@ -270,8 +270,10 @@ all of them, `ThemeSel`'s list, are too many for an error); and `not a session control message "X": write it to pane/<n>/ctl` or `not a window control message "X": write it to /ctl` for a word of the other ctl. 9ns maps them all to EINVAL, and a write refused here has done nothing. A line that then fails as it runs fails the -write with the error the editor reports for it and the line, e.g. `Mount: -already mounted "Mount peer /tmp/s"` (EIO), and `control message needs its +write with the error the editor reports for it, in its own words, which +name what failed, the line not quoted after them (only a line refused +before it runs, as no message at all, is quoted), e.g. `Mount: already +mounted` (EIO), and `control message needs its argument "Save"` (EINVAL), for a builtin that would have asked at a prompt (a `Save` on a scratch) rather than open one nobody is there to answer. A builtin that means nothing without its argument (`Mount`, `Msg`, `Find`), diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 2b256b1e..cf2068bc 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -447,11 +447,13 @@ pub fn runBuiltin(p: *Pardes, req: Req, id: usize, line: []const u8) ?Reply { if (!created) noteResult(p, context); if (p.fs.refused) return refuse(p, req, e_prompt, line); if (p.fs.failure_len == 0) return null; - // An Edit's failure names what failed (the pattern that missed): its - // line, a program, is not quoted after it. - const edit = std.mem.startsWith(u8, line, "Edit ") or std.mem.eql(u8, line, "Edit"); - const refusal = if (edit) tree.failText(req.tag, E.IO, p.fs.failure[0..p.fs.failure_len]) else refuse(p, req, p.fs.failure[0..p.fs.failure_len], line); - return .{ .tag = req.tag, .status = .err, .errno = failureErrno(p.fs.failure[0..p.fs.failure_len]), .ename = refusal.ename }; + // A line that ran and failed says why in the builtin's own words, which + // name what failed (`Mount peer /tmp/s: dial failed: ...`): the line is + // not quoted after them, as an exec's is not. Only a line refused before + // it runs, as no message at all, quotes it (`unknown control message + // "X"`). + const failure = p.fs.failure[0..p.fs.failure_len]; + return tree.failText(req.tag, failureErrno(failure), std.fmt.bufPrint(&p.fs.ename, "{s}", .{failure}) catch failure); } const e_prompt = "control message needs its argument"; @@ -774,8 +776,8 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply { var said_buf: [320]u8 = undefined; const said = said_buf[0..said_in_ename.len]; @memcpy(said, said_in_ename); - const r = refuse(p, req, said, line); - return .{ .tag = req.tag, .status = .err, .errno = E.IO, .ename = r.ename }; + // It ran and was refused: its words, not the line quoted. + return tree.failText(req.tag, E.IO, std.fmt.bufPrint(&p.fs.ename, "{s}", .{said}) catch said); } var failed: anyerror = error.Unexpected; const errno = get(p, pane, &failed); @@ -1095,7 +1097,7 @@ test "the root ctl reads the settings as a write takes them, and takes the sessi // its line; the lines before it have taken effect, as in acme. const failed = wr(p, root_ctl, "Verbose on\nMount x\nVerbose off"); try testing.expectEqual(E.IO, failed.errno()); - try testing.expectEqualStrings("Mount name dial: missing argument \"Mount x\"", failed.reply.ename); + try testing.expectEqualStrings("Mount name dial: missing argument", failed.reply.ename); 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); @@ -1453,7 +1455,7 @@ test "get reloads the pane from disk and reports a missing file without losing d // Unsaved text is asked about once, as acme's get asks winclean. const asked = wr(p, ctl_node, "get\n"); try testing.expectEqual(E.IO, asked.errno()); - try testing.expect(std.mem.endsWith(u8, asked.reply.ename, ": Modified (get again to discard) \"get\"")); + try testing.expect(std.mem.endsWith(u8, asked.reply.ename, ": Modified (get again to discard)")); try testing.expectEqual(E.NOENT, wr(p, ctl_node, "get\n").errno()); try testing.expect(p.paneBySerial(serial) != null); try testing.expectEqualStrings("from disk\nunsaved\n", pane.file.?.content); @@ -1701,7 +1703,7 @@ test "a builtin that fails a ctl write logs only its err, so the same failure ag const log = @intFromEnum(tree.TopFile.log); 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.endsWith(u8, text, "err - ctl: Kill: no running command has that first word \"Kill zzz\" (x3)\n")); + try testing.expect(std.mem.endsWith(u8, text, "err - ctl: Kill: no running command has that first word (x3)\n")); try testing.expect(std.mem.indexOf(u8, text, "msg - Kill") == null); _ = call(p, .{ .tag = 3, .op = .release, .node = log, .handle = h }); } @@ -1760,7 +1762,7 @@ test "Mount dials its peer and fails, mounting nothing, when none answers; a loo const dead = "/tmp/pardes-test-no-such-peer.sock"; const refused = wr(p, root_ctl, "Mount dead " ++ dead ++ "\n"); try testing.expectEqual(E.IO, refused.errno()); - try testing.expectEqualStrings("Mount dead " ++ dead ++ ": dial failed: no answer \"Mount dead " ++ dead ++ "\"", refused.reply.ename); + try testing.expectEqualStrings("Mount dead " ++ dead ++ ": dial failed: no answer", refused.reply.ename); try testing.expectEqual(@as(usize, 0), p.fs.mounts.items.len); // A pane of a peer gone since: get says so too. try p.fs.mounts.append(p.gpa, .{ .name = try p.gpa.dupe(u8, "away"), .dial = try p.gpa.dupe(u8, dead) }); |
