summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ninep/ctl.zig29
-rw-r--r--src/ninep/tree.zig15
2 files changed, 39 insertions, 5 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 05f93ac5..d6230308 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -372,17 +372,27 @@ const Builtin = builtins.registry.Builtin();
/// (kernel/misc/parse.c:82): `unknown control message "Bogus 3"`.
fn refuse(p: *Pardes, req: Req, why: []const u8, line: []const u8) Reply {
const room = p.fs.ename.len -| (why.len + 3);
- const text = std.fmt.bufPrint(&p.fs.ename, "{s} \"{s}\"", .{ why, line[0..@min(line.len, room)] }) catch why;
+ var shown: [320]u8 = undefined;
+ const text = std.fmt.bufPrint(&p.fs.ename, "{s} \"{s}\"", .{ why, blanked(line[0..@min(line.len, room)], &shown) }) catch why;
return tree.failText(req.tag, E.INVAL, text);
}
/// `refuse`, saying which ctl takes the message instead.
pub fn refuseTo(p: *Pardes, req: Req, why: []const u8, line: []const u8, ctl: []const u8) Reply {
const room = p.fs.ename.len -| (why.len + ctl.len + 18);
- const text = std.fmt.bufPrint(&p.fs.ename, "{s} \"{s}\": write it to {s}", .{ why, line[0..@min(line.len, room)], ctl }) catch why;
+ var shown: [320]u8 = undefined;
+ const text = std.fmt.bufPrint(&p.fs.ename, "{s} \"{s}\": write it to {s}", .{ why, blanked(line[0..@min(line.len, room)], &shown), ctl }) catch why;
return tree.failText(req.tag, E.INVAL, text);
}
+/// A quoted line as a refusal shows it: a control byte (a tab, the one a
+/// line may hold) is a blank, so the reason reads as one line of words.
+fn blanked(line: []const u8, buf: *[320]u8) []const u8 {
+ const n = @min(line.len, buf.len);
+ for (line[0..n], buf[0..n]) |c, *o| o.* = if (c < ' ' or c == 0x7f) ' ' else c;
+ return buf[0..n];
+}
+
/// 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 if and only if it takes or requires one, and for a setting a
@@ -1008,6 +1018,21 @@ const root_status = @intFromEnum(tree.TopFile.status);
const root_look = @intFromEnum(tree.TopFile.look);
const root_exec = @intFromEnum(tree.TopFile.exec);
+test "a control character in a write to any ctl refuses the whole write, and a quoted line shows a tab as a blank" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const col = layout.columnSerial(p, 0);
+ for ([_]u64{ @intFromEnum(tree.TopFile.ctl), Node.of(serialOf(p), .ctl), Node.ofCol(col, .ctl) }) |node| {
+ const r = wr(p, node, "Wrap off\nbo\x01gus\n");
+ try testing.expectEqual(E.INVAL, r.errno());
+ try testing.expectEqualStrings(e_control, r.reply.ename);
+ }
+ const quoted = wr(p, Node.of(serialOf(p), .ctl), "bogus\tword\n");
+ try testing.expectEqual(E.INVAL, quoted.errno());
+ try testing.expect(std.mem.indexOf(u8, quoted.reply.ename, "bogus word") != null);
+ try testing.expect(std.mem.indexOfScalar(u8, quoted.reply.ename, '\t') == null);
+}
+
test "pane ctl read is acme's fields -- index's five, width in cells, font, tab width, undo, redo -- then whether it is current" {
const gpa = testing.allocator;
const p = try withFile(gpa, "x\n");
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index d39854e1..61ac7e87 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -1147,12 +1147,21 @@ fn holdsLines(p: *Pardes, req: Req) bool {
/// A line held back longer than this is refused rather than kept growing.
const pending_cap = 1 << 20;
+/// The ctl files, whose lines are commands as look's and exec's are.
+fn ctlFile(target: Target) bool {
+ return switch (target) {
+ .top => |f| f == .ctl,
+ .col => |c| c.file == .ctl,
+ .pane => |t| t.file == .ctl,
+ };
+}
+
fn write(p: *Pardes, req: Req, target: Target) Reply {
+ // A command line holds no control character but a tab: the whole write
+ // is refused at once, not held to fail unseen at the close.
+ if (resultsFile(target) or ctlFile(target)) for (req.data) |c| if ((c < ' ' and c != '\t' and c != '\n' and c != '\r') or c == 0x7f) return failText(req.tag, E.INVAL, ctl.e_control);
const o = (if (linesFile(target)) openOf(p, req) else null) orelse return writeNow(p, req, target);
if (o.what != .lines and o.what != .ctl) return writeNow(p, req, target);
- // A clicked line holds no control character: refused at once, not held
- // to fail unseen at the close.
- if (resultsFile(target)) for (req.data) |c| if ((c < ' ' and c != '\t' and c != '\n' and c != '\r') or c == 0x7f) return failText(req.tag, E.INVAL, ctl.e_control);
o.pending.appendSlice(p.gpa, req.data) catch return Reply.fail(req.tag, E.NOMEM);
var end = ctl.completeEnd(p, o.pending.items);
// A tail with no newline is a whole line when the write is the whole of