summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 04:20:34 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commitb85f9e56fa04291fc1791bdceae7b0f2c284114c (patch)
treeeea0672eeba12acfe24d85eb49327c3aa7e8c96a
parentf4510215f01c599aaf35b333d308587d423d74bb (diff)
downloadpardes-b85f9e56fa04291fc1791bdceae7b0f2c284114c.tar.gz
pardes-b85f9e56fa04291fc1791bdceae7b0f2c284114c.zip
A column's ctl refuses as the other ctls do, and a bare answer says what it takes
A column's ctl refused an unknown word in words 9ns maps to EIO, and took New extra as New. It now refuses the word as unknown control message (EINVAL, as the root and pane ctls) and any word but Tty with an argument as wrong #args. A bare answer on a pane's ctl says answer takes a choice or -, not unknown control message. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--src/ninep/cols.zig24
-rw-r--r--src/ninep/ctl.zig2
2 files changed, 24 insertions, 2 deletions
diff --git a/src/ninep/cols.zig b/src/ninep/cols.zig
index ef2e6cbd..ebe3279a 100644
--- a/src/ninep/cols.zig
+++ b/src/ninep/cols.zig
@@ -136,7 +136,12 @@ pub fn writeCtl(p: *Pardes, req: Req, serial: u32) Reply {
const takes = for ([_][]const u8{ "Delcol", "Joincol", "New", "Tty" }) |w| {
if (std.mem.eql(u8, w, word)) break true;
} else false;
- if (!takes) return tree.failText(req.tag, E.INVAL, "a column's ctl takes Delcol, Joincol, New or Tty");
+ // Plan 9's words for it, so a mount's errno is EINVAL as the root's
+ // and a pane's ctl give (9ns reads "control message").
+ if (!takes) return tree.failText(req.tag, E.INVAL, std.fmt.bufPrint(&p.fs.ename, "unknown control message \"{s}\": a column's ctl takes Delcol, Joincol, New or Tty", .{line[0..@min(line.len, 64)]}) catch "unknown control message");
+ // Only Tty takes a word (its shell); `New extra` is no New.
+ if (!std.mem.eql(u8, word, "Tty") and word.len != line.len)
+ return tree.failText(req.tag, E.INVAL, std.fmt.bufPrint(&p.fs.ename, "wrong #args in control message \"{s}\"", .{line[0..@min(line.len, 64)]}) catch "wrong #args in control message");
const col = layout.columnBySerial(p, serial) orelse return Reply.fail(req.tag, E.NOENT);
p.exec_column = col;
defer p.exec_column = null;
@@ -272,7 +277,7 @@ test "a column's ctl and exec act on it as its tag would, and rmdir closes it on
try testing.expectEqual(@as(usize, 1), p.col_n[1]);
try testing.expectEqual(E.INVAL, th.wr(p, Node.ofCol(empty, .ctl), "Exit\n").errno());
var rec: [64]u8 = undefined;
- try testing.expect(th.logHas(p, try std.fmt.bufPrint(&rec, "err - col/{d}/ctl: a column's ctl takes", .{empty})));
+ try testing.expect(th.logHas(p, try std.fmt.bufPrint(&rec, "err - col/{d}/ctl: unknown control message \"Exit\"", .{empty})));
// rmdir refuses a column with a pane, and says why.
const full = th.call(p, .{ .tag = 1, .op = .release, .node = Node.ofCol(empty, .dir), .remove = true });
try testing.expectEqual(E.NOTEMPTY, full.errno());
@@ -356,3 +361,18 @@ test "a command run from a column's tag gets its pane in that column, not the la
try testing.expect(other != made);
try testing.expectEqual(@as(usize, 1), layout.findPane(p, other).?.col);
}
+
+test "a column's ctl refuses an unknown word and an argument as the other ctls do" {
+ const p = try th.withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const ctl_node = Node.ofCol(layout.columnSerial(p, 0), .ctl);
+ const unknown = th.wr(p, ctl_node, "Bogus\n");
+ try testing.expectEqual(E.INVAL, unknown.errno());
+ try testing.expect(std.mem.startsWith(u8, unknown.reply.ename, "unknown control message \"Bogus\""));
+ const extra = th.wr(p, ctl_node, "New extra\n");
+ try testing.expectEqual(E.INVAL, extra.errno());
+ try testing.expect(std.mem.startsWith(u8, extra.reply.ename, "wrong #args"));
+ try testing.expectEqual(@as(usize, 1), p.col_n[0]);
+ // A pane's bare answer and a winsize of no size say what they take.
+ try testing.expectEqualStrings("answer takes a choice or -", th.wr(p, Node.of(p.panes[0].?.serial, .ctl), "answer\n").reply.ename);
+}
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 1b1288a1..424245eb 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -660,6 +660,8 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply {
if (!held) return tree.failText(req.tag, E.INVAL, e_not_locked);
held = false;
if (apply) pane.fs.lock = null;
+ } else if (std.mem.eql(u8, line, "answer")) {
+ return tree.failText(req.tag, E.INVAL, "answer takes a choice or -");
} else if (std.mem.startsWith(u8, line, "answer ")) {
// The pane's question (its `ask` in the log), answered.
const choice = std.mem.trim(u8, line["answer ".len..], " ");