summaryrefslogtreecommitdiff
path: root/src/ninep
diff options
context:
space:
mode:
Diffstat (limited to 'src/ninep')
-rw-r--r--src/ninep/ctl.zig42
1 files changed, 31 insertions, 11 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 945119ca..e47dec16 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -180,7 +180,8 @@ fn refuse(p: *Pardes, req: Req, why: []const u8, line: []const u8) Reply {
/// Checks one line written to a ctl as a builtin of `scope` before any line
/// of the write runs: a word the registry knows, of this ctl's scope, given
-/// an argument only if it takes one, and for a setting a value it takes.
+/// an argument if and only if it takes or requires one, and for a setting a
+/// value it takes.
/// Answers the refusal, or null. Words are the builtins' own, capitalised
/// as on a tag; acme's lowercase verbs are the pane ctl's and alias none.
fn checkBuiltin(p: *Pardes, req: Req, line: []const u8, scope: builtins.Scope) ?Reply {
@@ -191,6 +192,7 @@ fn checkBuiltin(p: *Pardes, req: Req, line: []const u8, scope: builtins.Scope) ?
if (builtins.registry.scope(b) != scope)
return refuse(p, req, if (scope == .pane) "not a window control message" else "not a session control message", line);
if (arg.len > 0 and !builtins.registry.takesArg(b)) return refuse(p, req, "wrong #args in control message", line);
+ if (arg.len == 0 and builtins.registry.requiresArg(b)) return refuse(p, req, "wrong #args in control message", line);
const setting = config.Runtime.find(word) orelse return null;
const takes = switch (setting.action) {
.theme => for (pardes.themes) |t| {
@@ -206,14 +208,19 @@ fn checkBuiltin(p: *Pardes, req: Req, line: []const u8, scope: builtins.Scope) ?
}
/// Runs a checked control message at pane `id`, as a click on its word
-/// would; false when it would have opened a prompt for an argument, which
-/// nobody writing to a file is there to type.
-fn runBuiltin(p: *Pardes, id: usize, line: []const u8) bool {
+/// would. Answers the refusal when it would have opened a prompt for an
+/// argument, which nobody writing to a file is there to type, or when it
+/// reported an error: `Mount: AlreadyMounted "Mount peer /tmp/s"`.
+fn runBuiltin(p: *Pardes, req: Req, id: usize, line: []const u8) ?Reply {
p.fs.no_prompt = true;
p.fs.refused = false;
+ p.fs.failure_len = 0;
defer p.fs.no_prompt = false;
_ = exec_line.executeBuiltinLine(p, id, line);
- return !p.fs.refused;
+ if (p.fs.refused) return refuse(p, req, e_prompt, line);
+ if (p.fs.failure_len == 0) return null;
+ const refusal = refuse(p, req, p.fs.failure[0..p.fs.failure_len], line);
+ return .{ .tag = req.tag, .status = .err, .errno = E.IO, .ename = refusal.ename };
}
const e_prompt = "control message needs its argument";
@@ -288,7 +295,7 @@ pub fn writeRoot(p: *Pardes, req: Req) Reply {
continue;
}
if (p.panes[p.active] == null) return Reply.fail(req.tag, E.NOENT);
- if (!runBuiltin(p, p.active, line)) return tree.failText(req.tag, E.INVAL, e_prompt);
+ if (runBuiltin(p, req, p.active, line)) |refusal| return refusal;
}
}
return .{ .tag = req.tag, .written = @intCast(req.data.len) };
@@ -404,8 +411,8 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply {
}
} else if (!apply) {
if (checkBuiltin(p, req, line, .pane)) |refusal| return refusal;
- } else if (!runBuiltin(p, p.paneBySerial(serial).?, line)) {
- return tree.failText(req.tag, E.INVAL, e_prompt);
+ } else if (runBuiltin(p, req, p.paneBySerial(serial).?, line)) |refusal| {
+ return refusal;
}
}
}
@@ -542,13 +549,15 @@ test "the pane ctl takes acme's verbs and the pane's builtins, and refuses the r
try testing.expectEqual(Status.ok, wr(p, ctl_node, "Msg from ctl\n").reply.status);
const pane = p.panes[p.paneBySerial(serial).?].?;
try testing.expectEqualStrings("from ctl", pane.msg[0..pane.msg_len]);
- // One that would ask at a prompt for its argument fails instead.
- try testing.expectEqualStrings(e_prompt, wr(p, ctl_node, "Find").reply.ename);
+ // One that means nothing bare is refused before anything runs.
+ try testing.expectEqualStrings("wrong #args in control message \"Find\"", wr(p, ctl_node, "Find").reply.ename);
+ try testing.expectEqual(E.INVAL, wr(p, ctl_node, "Msg first\nMsg").errno());
+ try testing.expect(!std.mem.eql(u8, pane.msg[0..pane.msg_len], "first"));
try testing.expect(pane.prompt == .none);
try testing.expect(!p.fs.no_prompt);
// Save with no name on a scratch would ask for one: refused, not asked.
const other = try th.newPane(p);
- try testing.expectEqualStrings(e_prompt, wr(p, Node.of(other, .ctl), "Save").reply.ename);
+ try testing.expectEqualStrings(e_prompt ++ " \"Save\"", wr(p, Node.of(other, .ctl), "Save").reply.ename);
try testing.expect(p.panes[p.paneBySerial(other).?].?.prompt == .none);
// A line after the one that closed the pane has nowhere to run.
try testing.expectEqual(E.NOENT, wr(p, Node.of(other, .ctl), "Del\nMsg after").errno());
@@ -587,7 +596,18 @@ test "the root ctl reads the settings as a write takes them, and takes the sessi
try testing.expectEqualStrings("bad value in control message \"Verbose maybe\"", wr(p, root_ctl, "Verbose maybe").reply.ename);
try testing.expectEqualStrings("bad value in control message \"Theme no-such-theme\"", wr(p, root_ctl, "Theme no-such-theme").reply.ename);
try testing.expectEqualStrings("wrong #args in control message \"Newcol 2\"", wr(p, root_ctl, "Newcol 2").reply.ename);
+ try testing.expectEqualStrings("wrong #args in control message \"Theme\"", wr(p, root_ctl, "Theme").reply.ename);
+ // A bare required word fails the check, so the line before never runs.
+ try testing.expectEqual(E.INVAL, wr(p, root_ctl, "Verbose on\nMount").errno());
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");
+ try testing.expectEqual(E.IO, failed.errno());
+ try testing.expectEqualStrings("Mount name dial: MissingArgument \"Mount x\"", 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);
}