summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/fs.md5
-rw-r--r--src/ninep/ctl.zig20
2 files changed, 24 insertions, 1 deletions
diff --git a/docs/fs.md b/docs/fs.md
index 84b27b1f..4b0214c3 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -262,7 +262,10 @@ 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
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. The
+(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`),
+written bare to an `exec` or `/tagexec`, is refused the same way, `wrong
+#args in control message "Mount"` (EINVAL), before it runs. The
lines before a failing one have taken effect and those after it never run,
which is what acme's ctl loop does (editors/acme/xfid.c:600-790). An error
that only happens as the editor performs what a line asked for -- a `Save`
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 76cba9d5..1ef9a2f6 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -220,6 +220,16 @@ pub const e_control = "invalid command line: it holds a control character other
/// the write, with its words and an err record, and no msg, as a ctl line
/// does (runBuiltin). The refusal, or null.
pub fn captured(p: *Pardes, req: Req, id: usize, exec: bool, text: []const u8, in_tag: bool) ?Reply {
+ // A builtin that means nothing without its argument, written bare: the
+ // write is refused, EINVAL, as a ctl's is (checkBuiltin), not run to
+ // fail or to ask.
+ if (exec) {
+ const cmd = exec_line.commandText(text);
+ if (std.meta.stringToEnum(Builtin, cmd)) |b| if (builtins.registry.requiresArg(b)) {
+ p.fs.results_len = 0;
+ return refuse(p, req, "wrong #args in control message", cmd);
+ };
+ }
p.fs.capturing = true;
p.fs.failure_len = 0;
defer p.fs.capturing = false;
@@ -1799,6 +1809,16 @@ test "Tty in a directory that is not there fails and makes no pane; a shell that
try testing.expectEqualStrings("error shell gone\n", call(p, .{ .tag = 3, .op = .read, .node = run_node, .handle = h, .size = 64 }).bytes);
}
+test "a builtin that needs its argument, written bare to an exec, is EINVAL as through ctl" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ for ([_]u64{ root_exec, Node.of(serialOf(p), .exec), @intFromEnum(tree.TopFile.tagexec) }) |node| {
+ const refused = wr(p, node, "Mount\n");
+ try testing.expectEqual(E.INVAL, refused.errno());
+ try testing.expectEqualStrings("wrong #args in control message \"Mount\"", refused.reply.ename);
+ }
+}
+
test "size is monotonic: growing is never refused, and a size once taken is taken again" {
const p = try Pardes.init(testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 60 });
defer p.deinit();