summaryrefslogtreecommitdiff
path: root/src/ninep/ctl.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/ninep/ctl.zig')
-rw-r--r--src/ninep/ctl.zig26
1 files changed, 24 insertions, 2 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 2f94dd3c..ccba342e 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -179,7 +179,7 @@ pub fn command(p: *Pardes, req: Req, serial: ?u32, exec: bool, in_tag: bool) Rep
while (it.next()) |text| {
if (text.len == 0) continue;
// Only an Edit block holds newlines (Messages).
- for (text) |c| if (c < ' ' and c != '\t' and c != '\n') return tree.failText(req.tag, E.INVAL, e_control);
+ for (text) |c| if ((c < ' ' and c != '\t' and c != '\n') or c == 0x7f) return tree.failText(req.tag, E.INVAL, e_control);
if (exec) if (tooLong(req, text)) |refusal| return refusal;
if (!apply) continue;
const id = if (serial) |s| p.paneBySerial(s) orelse break else p.active;
@@ -214,7 +214,7 @@ pub fn tooLong(req: Req, line: []const u8) ?Reply {
return tree.failText(req.tag, E.INVAL, e_too_long);
}
-pub const e_control = "invalid command line: it holds a control character other than a tab";
+pub const e_control = "invalid command line: it holds a control character (or DEL) other than a tab";
/// Runs one click (`run`) as a 9P write's: a builtin that fails there fails
/// the write, with its words and an err record, and no msg, as a ctl line
@@ -1968,6 +1968,28 @@ test "pty/ctl exec in a directory that is gone fails ENOENT; a shell that cannot
try testing.expectEqualStrings("shell: access denied", p.fs.late_failure[0..p.fs.late_failure_len]);
}
+test "every EINVAL a write gets says why, in its err record too; DEL is a control character in a line" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ const log = @intFromEnum(tree.TopFile.log);
+ const h = call(p, .{ .tag = 1, .op = .open, .node = log, .omode = 2 }).reply.handle;
+ const Case = struct { node: u64, data: []const u8, words: []const u8, handle: u32 = 0 };
+ for ([_]Case{
+ .{ .node = root_exec, .data = "echo a\x7fb\n", .words = "control character" },
+ .{ .node = root_look, .data = "a\x7fb\n", .words = "control character" },
+ .{ .node = Node.of(serial, .name), .data = "\n", .words = "an empty name" },
+ .{ .node = log, .data = "bogus\n", .words = "takes `follow`", .handle = h },
+ }) |c| {
+ const refused = call(p, .{ .tag = 2, .op = .write, .node = c.node, .handle = c.handle, .data = c.data });
+ try testing.expectEqual(E.INVAL, refused.errno());
+ try testing.expect(std.mem.indexOf(u8, refused.reply.ename, c.words) != null);
+ }
+ _ = call(p, .{ .tag = 3, .op = .release, .node = log, .handle = h });
+ try testing.expect(!th.logHas(p, "Invalid argument"));
+ try testing.expect(th.logHas(p, "an empty name"));
+}
+
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();