summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ninep/pane.zig6
-rw-r--r--src/ninep/tree.zig52
2 files changed, 26 insertions, 32 deletions
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig
index 48c2888e..e2ec31d1 100644
--- a/src/ninep/pane.zig
+++ b/src/ninep/pane.zig
@@ -914,7 +914,7 @@ fn writeFlag(p: *Pardes, req: Req, pane: *Pane, file: PaneFile) Reply {
/// (editors/acme/xfid.c:650-652). A blank inside a name is taken here, as
/// pardes names files with spaces; one at either end is refused, not
/// quietly cut off, so the name a script wrote is the name it gets.
-const e_name_char = "bad character in file name";
+pub const e_name_char = "bad character in file name";
/// Why `name` is not one file name, with the reason, or null: a newline, a
/// control byte, DEL or a C1 control (U+0080-U+009F), a blank at either
@@ -1566,10 +1566,10 @@ test "a name cut across writes is one name, applied once at its newline or its c
_ = call(p, .{ .tag = 2, .op = .write, .node = node, .handle = h, .data = "me.txt\n" });
try testing.expectEqualStrings("/tmp/pardes-name.txt", nameOf(p, p.panes[p.paneBySerial(serial).?].?));
_ = call(p, .{ .tag = 3, .op = .release, .node = node, .handle = h, .opened = true });
- // No newline, the whole write: applied at once, on its own open.
+ // No newline: held, whatever the write's size, and applied at the close.
const h3 = call(p, .{ .tag = 1, .op = .open, .node = node, .omode = 1 }).reply.handle;
_ = call(p, .{ .tag = 2, .op = .write, .node = node, .handle = h3, .data = "/tmp/pardes-closed.txt" });
- try testing.expectEqualStrings("/tmp/pardes-closed.txt", nameOf(p, p.panes[p.paneBySerial(serial).?].?));
+ try testing.expectEqualStrings("/tmp/pardes-name.txt", nameOf(p, p.panes[p.paneBySerial(serial).?].?));
_ = call(p, .{ .tag = 3, .op = .release, .node = node, .handle = h3, .opened = true });
try testing.expectEqualStrings("/tmp/pardes-closed.txt", nameOf(p, p.panes[p.paneBySerial(serial).?].?));
}
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index 95e46ab3..e6c139c1 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -1177,6 +1177,9 @@ 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);
+ // A NUL no name may hold fails its write at once, not the close.
+ if (target == .pane and target.pane.file == .name and std.mem.indexOfScalar(u8, req.data, 0) != null)
+ return failText(req.tag, E.INVAL, pane.e_name_char ++ ": a NUL");
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);
// The rest of a line refused for its length goes through its newline
@@ -1189,23 +1192,12 @@ fn write(p: *Pardes, req: Req, target: Target) Reply {
if (data.len == 0) return .{ .tag = req.tag, .written = @intCast(req.data.len) };
}
o.pending.appendSlice(p.gpa, 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
- // what its client wrote (shorter than a Twrite can carry): it runs now,
- // and its failure is this write's, as acme takes each write whole. Held
- // only when it is not whole yet -- an open `{` block, an `a`/`c`/`i`
- // text awaiting its `.` -- or the write filled its Twrite and may go on.
- // So may one cut where a writer's buffer filled: stdio flushes, and a
- // mount's page cache writes back, in multiples of 4096 bytes. `seq ...
- // > exec` through a mount arrived in 4096-byte writes, and the line cut
- // at each end ran as two commands (the half `Msg` refused, failing the
- // write and the rest of the burst).
- const cut = (p.fs.write_room != 0 and req.data.len >= p.fs.write_room) or req.data.len % 4096 == 0;
- if (end < o.pending.items.len and !cut) {
- o.pending.append(p.gpa, '\n') catch return Reply.fail(req.tag, E.NOMEM);
- const whole = ctl.completeEnd(p, o.pending.items);
- if (whole == o.pending.items.len) end = whole else o.pending.shrinkRetainingCapacity(o.pending.items.len - 1);
- }
+ // A line runs when its newline comes, or, the last one, when its open
+ // is let go (release). Nothing is inferred from a write's size: a mount
+ // cuts a long write anywhere (9ns sends 512 KiB as 65512-byte Twrites,
+ // stdio and the page cache at 4096 multiples), and a piece shorter than
+ // its message is no whole line. An Edit block is whole when it closes.
+ const end = ctl.completeEnd(p, o.pending.items);
if (end == 0) {
if (o.pending.items.len <= pending_cap) return .{ .tag = req.tag, .written = @intCast(req.data.len) };
o.pending.clearRetainingCapacity();
@@ -1819,27 +1811,29 @@ test "a refused open, create or remove says why in words and logs no err; a refu
try testing.expectEqual(@as(usize, 2), th.logCount(p, "\nerr "));
}
-test "a write with no newline, whole in its Twrite, runs then and fails the write; one needing more waits" {
+test "a write with no newline waits for its newline or the close, whatever its size, and fails there in the log" {
const p = try th.withFile(testing.allocator, "abc\n");
defer p.deinit();
const serial = serialOf(p);
const ctl_node = Node.of(serial, .ctl);
const c = call(p, .{ .tag = 1, .op = .open, .node = ctl_node, .omode = 1 }).reply.handle;
+ // Short of its message or not, a piece with no newline is held: a mount
+ // cuts a long line anywhere.
p.fs.write_room = 8192;
defer p.fs.write_room = 0;
- try testing.expectEqual(Status.err, call(p, .{ .tag = 2, .op = .write, .node = ctl_node, .handle = c, .data = "bogus" }).reply.status);
- try testing.expectEqual(Status.err, call(p, .{ .tag = 3, .op = .write, .node = ctl_node, .handle = c, .data = "Edit ,s/zzz/y/" }).reply.status);
- // An `a` needs its text and `.`: held, not run, not failed.
+ try testing.expectEqual(Status.ok, call(p, .{ .tag = 2, .op = .write, .node = ctl_node, .handle = c, .data = "Edit ,s/a/" }).reply.status);
+ try testing.expectEqual(Status.ok, call(p, .{ .tag = 3, .op = .write, .node = ctl_node, .handle = c, .data = "A/\n" }).reply.status);
+ try testing.expectEqualStrings("Abc\n", p.panes[0].?.file.?.content);
+ // An `a` needs its text and `.`: held across writes.
try testing.expectEqual(Status.ok, call(p, .{ .tag = 4, .op = .write, .node = ctl_node, .handle = c, .data = "Edit $a" }).reply.status);
try testing.expectEqual(Status.ok, call(p, .{ .tag = 5, .op = .write, .node = ctl_node, .handle = c, .data = "\nmore\n.\n" }).reply.status);
- try testing.expectEqualStrings("abc\nmore\n", p.panes[0].?.file.?.content);
- _ = call(p, .{ .tag = 6, .op = .release, .node = ctl_node, .handle = c, .opened = true });
- // The root's ctl too.
- const root_ctl = @intFromEnum(TopFile.ctl);
- const r = call(p, .{ .tag = 7, .op = .open, .node = root_ctl, .omode = 1 }).reply.handle;
- try testing.expectEqual(Status.err, call(p, .{ .tag = 8, .op = .write, .node = root_ctl, .handle = r, .data = "bogus" }).reply.status);
- _ = call(p, .{ .tag = 9, .op = .release, .node = root_ctl, .handle = r, .opened = true });
- // And a name: a bad one fails its write, not the close after it.
+ try testing.expectEqualStrings("Abc\nmore\n", p.panes[0].?.file.?.content);
+ // The last line, never ended, runs at the close: its failure is its err.
+ try testing.expectEqual(Status.ok, call(p, .{ .tag = 6, .op = .write, .node = ctl_node, .handle = c, .data = "bogus" }).reply.status);
+ try testing.expect(!th.logHas(p, "bogus"));
+ _ = call(p, .{ .tag = 7, .op = .release, .node = ctl_node, .handle = c, .opened = true });
+ try testing.expect(th.logHas(p, "unknown control message \"bogus\""));
+ // A name: a NUL fails its write, not the close after it.
const name = Node.of(serial, .name);
const n = call(p, .{ .tag = 10, .op = .open, .node = name, .omode = 1 }).reply.handle;
const bad = call(p, .{ .tag = 11, .op = .write, .node = name, .handle = n, .data = "del\x00ete" });