summaryrefslogtreecommitdiff
path: root/src/ninep/tree.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/ninep/tree.zig')
-rw-r--r--src/ninep/tree.zig41
1 files changed, 39 insertions, 2 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index d6f4211b..0960f00b 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -1073,7 +1073,18 @@ fn write(p: *Pardes, req: Req, target: Target) Reply {
// 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);
- const end = ctl.completeEnd(p, o.pending.items);
+ 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.
+ const cut = p.fs.write_room != 0 and req.data.len >= p.fs.write_room;
+ 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);
+ }
if (end == 0) {
if (o.pending.items.len <= pending_cap) return .{ .tag = req.tag, .written = @intCast(req.data.len) };
o.pending.clearRetainingCapacity();
@@ -1580,8 +1591,12 @@ test "a command line cut across writes runs once whole, and the last runs at rel
const exec = @intFromEnum(TopFile.exec);
const h = call(p, .{ .tag = 1, .op = .open, .node = exec, .omode = 1 }).reply.handle;
try testing.expect(h != 0);
- for ([_][]const u8{ "Msg he", "llo\nMsg wor", "ld\nMsg tail" }) |piece|
+ // Each piece fills its Twrite (a client's one write cut at msize).
+ for ([_][]const u8{ "Msg he", "llo\nMsg wor", "ld\nMsg tail" }) |piece| {
+ p.fs.write_room = @intCast(piece.len);
try testing.expectEqual(Status.ok, call(p, .{ .tag = 2, .op = .write, .node = exec, .handle = h, .data = piece }).reply.status);
+ }
+ p.fs.write_room = 0;
try testing.expect(th.logHas(p, " hello\n") and th.logHas(p, " world\n"));
try testing.expect(!th.logHas(p, " he\n") and !th.logHas(p, " tail\n"));
_ = call(p, .{ .tag = 3, .op = .release, .node = exec, .handle = h, .opened = true });
@@ -1596,6 +1611,28 @@ test "a command line cut across writes runs once whole, and the last runs at rel
try testing.expectEqualStrings("x\none\ntwo\n", p.panes[0].?.file.?.content);
}
+test "a write with no newline, whole in its Twrite, runs then and fails the write; one needing more waits" {
+ 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;
+ 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 = 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 });
+}
+
test "look and exec read back what their own open's write touched; a fresh open reads the session's last" {
const p = try th.withFile(testing.allocator, "x\n");
defer p.deinit();