diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ninep/ctl.zig | 29 | ||||
| -rw-r--r-- | src/ninep/tree.zig | 15 |
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 |
