diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ninep/tree.zig | 26 |
1 files changed, 25 insertions, 1 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index 17941e10..322728da 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -1169,7 +1169,12 @@ fn write(p: *Pardes, req: Req, target: Target) Reply { // 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; + // 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); @@ -1826,6 +1831,25 @@ test "look and exec read back what their own open's write touched; a fresh open for ([_]u32{ mine, theirs, fresh }) |hh| _ = call(p, .{ .tag = 10, .op = .release, .node = exec, .handle = hh, .opened = true }); } +test "a write cut where a writer's 4096-byte buffer filled holds its partial line for the next write" { + const p = try th.withFile(testing.allocator, "x\n"); + defer p.deinit(); + const exec_node = Node.of(serialOf(p), .exec); + const h = call(p, .{ .tag = 1, .op = .open, .node = exec_node, .omode = 1 }).reply.handle; + // 4096 bytes: whole `Msg first` lines, then a line cut after `Msg`. + var buf: [4096]u8 = undefined; + const line = "Msg first\n"; + var n: usize = 0; + while (n + line.len <= buf.len - 3) : (n += line.len) @memcpy(buf[n..][0..line.len], line); + @memset(buf[n..], ' '); + @memcpy(buf[buf.len - 3 ..], "Msg"); + try testing.expectEqual(Status.ok, call(p, .{ .tag = 2, .op = .write, .node = exec_node, .handle = h, .data = &buf }).reply.status); + try testing.expectEqual(Status.ok, call(p, .{ .tag = 3, .op = .write, .node = exec_node, .handle = h, .data = " last\n" }).reply.status); + _ = call(p, .{ .tag = 4, .op = .release, .node = exec_node, .handle = h, .opened = true }); + try testing.expect(th.logHas(p, "last")); + try testing.expect(!th.logHas(p, "wrong #args")); +} + test "no placement leaves a pane shorter than its tag and two rows; a full column refuses the next" { const p = try th.withFile(testing.allocator, "x\n"); defer p.deinit(); |
