diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-30 11:03:10 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | 67be3b6594a60d36723000a1a33a09016b29ff97 (patch) | |
| tree | 3260b872643bf98f001e59038e5c55e06268b229 /src/ninep/tree.zig | |
| parent | a96c7e873d26686f0e49cf2882c1336046396d37 (diff) | |
| download | pardes-67be3b6594a60d36723000a1a33a09016b29ff97.tar.gz pardes-67be3b6594a60d36723000a1a33a09016b29ff97.zip | |
A control character in a write to any ctl file refuses the whole write, and a refused line is quoted with its control bytes shown as blanks
fs.md already promised that the whole of a write to /ctl, a pane's ctl and
a column's ctl is checked first, as it is for exec and look. But a ctl write
ran its lines one by one, so a line holding a control byte gave that line's
own, misleading reason ("unknown command"). Worse, the reason quoted the raw
byte back into the err record. The check now happens in tree.write before
anything runs, with the same EINVAL and reason exec gives. A refusal's quoted
line shows control bytes as blanks, so an err record never carries a raw
escape.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/tree.zig')
| -rw-r--r-- | src/ninep/tree.zig | 15 |
1 files changed, 12 insertions, 3 deletions
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 |
