summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 11:55:25 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit8c86eb1bccf5846d5f5b627845f640e3b6b12134 (patch)
treeaba9d20470ce7504d6f75a5e2226f2f4702abe65
parentff161c1200e57f10bfc5d5b7dc6fc573967b9303 (diff)
downloadpardes-8c86eb1bccf5846d5f5b627845f640e3b6b12134.tar.gz
pardes-8c86eb1bccf5846d5f5b627845f640e3b6b12134.zip
A builtin that takes no argument, written with one to an exec, is refused wrong #args as at a ctl
At a ctl, `Config extra` and `DumpConfig extra` were refused with `wrong #args`. Written to an exec, the builtin match missed, since the word takes no argument, and the line ran as a shell command named Config, which did nothing and said nothing. An exec now refuses it with the ctl's words. A setting word keeps its value (`Verbose off`). Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--docs/fs.md3
-rw-r--r--src/ninep/ctl.zig19
2 files changed, 21 insertions, 1 deletions
diff --git a/docs/fs.md b/docs/fs.md
index 33f70644..d837e478 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -211,7 +211,8 @@ A line written to `exec` is a middle click:
- a builtin word runs (`/commands` lists them): `Save`, `Del`, `New`, `Tty
[shell]`, `Msg text`, `Find`, `Grep`, `Edit ...`, `Mount`, ...
A builtin that needs its argument (`Msg`, `Mount`, `Find`) written bare is
- `wrong #args in control message "Msg"`.
+ `wrong #args in control message "Msg"`, and so is one that takes none
+ written with one (`Config extra`).
- acme's words run as pardes's where it has one (`Put` is `Save`, `Delete`
a `Del` that does not ask); the rest (`Get`, `Putall`, `Snarf`, `Cut`,
`Paste`, `Zerox`, `Sort`, `Load`, `ID`, `Send`, `Tab`, `Indent`, `Local`,
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 98980f2c..e44368ea 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -239,6 +239,14 @@ pub fn captured(p: *Pardes, req: Req, id: usize, exec: bool, text: []const u8, i
p.fs.results_len = 0;
return refuse(p, req, "wrong #args in control message", cmd);
};
+ // ...and one that takes none, written with one (`Config extra`):
+ // refused as a ctl's is, not run as a shell command of that name.
+ if (std.mem.indexOfAny(u8, cmd, " \t")) |sp| if (std.meta.stringToEnum(Builtin, cmd[0..sp])) |b| {
+ if (!builtins.registry.takesArg(b) and config.Runtime.find(cmd[0..sp]) == null) {
+ p.fs.results_len = 0;
+ return refuse(p, req, "wrong #args in control message", cmd);
+ }
+ };
}
p.fs.capturing = true;
p.fs.failure_len = 0;
@@ -2435,6 +2443,17 @@ test "a look that misses file:/re/ names the file, and is logged as its pane's"
try testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "err {d} look: {s}/f.txt: no match for regexp", .{ f_serial, dir })));
}
+test "a builtin that takes no argument, written with one to an exec, is refused wrong #args, as at a ctl" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ for ([_]u64{ @intFromEnum(tree.TopFile.exec), Node.of(serial, .exec) }) |node| for ([_][]const u8{ "Config extra\n", "DumpConfig extra\n" }) |line| {
+ const r = wr(p, node, line);
+ try testing.expectEqual(E.INVAL, r.errno());
+ try testing.expectStringStartsWith(r.reply.ename, "wrong #args in control message");
+ };
+}
+
test "acme's words run as pardes's (Put is Save, Delete a Del that does not ask) or are refused, never run as commands" {
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();