summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ninep/cols.zig26
-rw-r--r--src/ninep/pane.zig6
2 files changed, 30 insertions, 2 deletions
diff --git a/src/ninep/cols.zig b/src/ninep/cols.zig
index c4ee83eb..4e497545 100644
--- a/src/ninep/cols.zig
+++ b/src/ninep/cols.zig
@@ -88,7 +88,11 @@ pub fn write(p: *Pardes, req: Req, serial: ?u32) Reply {
return tree.failText(req.tag, E.INVAL, why);
}
const had = if (pending) 0 else headerText(p, serial).?.len;
- if (req.data.len > limits.max_tag_tail -| had) {
+ // A rewrite drops its one trailing newline: `echo <4096 bytes> > tag`
+ // is 4096 bytes of tag, and fits.
+ const rewrite = pending or p.fs.header_rewrite == rewriteKey(serial);
+ const counted = req.data.len - @intFromBool(rewrite and req.data[req.data.len - 1] == '\n');
+ if (counted > limits.max_tag_tail -| had) {
if (pending) p.fs.header_trunc_pending = null;
return tree.failText(req.tag, E.NOSPC, pardes.ctlfs.pane.e_tag_over);
}
@@ -249,6 +253,26 @@ test "pane/new takes rows from a pane in another column before refusing ENOSPC"
try testing.expect(moved);
}
+test "exactly 4096 bytes written with echo's newline after a truncation fit every kind of tag" {
+ const p = try th.withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const serial = th.serialOf(p);
+ const text = "w" ** 4096 ++ "\n";
+ for ([_]u64{ tree.Node.of(serial, .tag), tree.Node.ofCol(layout.columnSerial(p, 0), .tag), @intFromEnum(tree.TopFile.tag) }) |node| {
+ _ = th.call(p, .{ .tag = 1, .op = .setattr, .node = node, .truncate = true });
+ const h = th.call(p, .{ .tag = 2, .op = .open, .node = node, .omode = 1 }).reply.handle;
+ const r = th.call(p, .{ .tag = 3, .op = .write, .node = node, .handle = h, .data = text });
+ try testing.expectEqual(tree.Status.ok, r.reply.status);
+ _ = th.call(p, .{ .tag = 4, .op = .release, .node = node, .handle = h, .opened = true });
+ try testing.expect(std.mem.endsWith(u8, th.rd(p, node, 0, 1 << 16).bytes, "w" ** 4096));
+ // One more byte is over.
+ _ = th.call(p, .{ .tag = 5, .op = .setattr, .node = node, .truncate = true });
+ const h2 = th.call(p, .{ .tag = 6, .op = .open, .node = node, .omode = 1 }).reply.handle;
+ try testing.expectEqual(E.NOSPC, th.call(p, .{ .tag = 7, .op = .write, .node = node, .handle = h2, .data = "w" ** 4097 ++ "\n" }).errno());
+ _ = th.call(p, .{ .tag = 8, .op = .release, .node = node, .handle = h2, .opened = true });
+ }
+}
+
test "a Newcol refused because the tags would not fit logs its err alone and spends no column serial" {
const p = try pardes.Pardes.init(testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 14 });
defer p.deinit();
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig
index fef0f85f..d5f2f20a 100644
--- a/src/ninep/pane.zig
+++ b/src/ninep/pane.zig
@@ -662,7 +662,11 @@ fn writeTag(p: *Pardes, req: Req, pane: *Pane) Reply {
// is a text like any other (acme's tag file appends the same way).
const had = if (pf.tag_trunc_pending) 0 else tagline.curTail(pane).len;
const room = limits.max_tag_tail -| had -| @intFromBool(pf.tag_held_newline and !pf.tag_trunc_pending);
- if (req.data.len > room) {
+ // A rewrite drops its one trailing newline: `echo <4096 bytes> > tag`
+ // is 4096 bytes of tag, and fits.
+ const rewrite = pf.tag_rewrite or pf.tag_trunc_pending;
+ const counted = req.data.len - @intFromBool(rewrite and req.data[req.data.len - 1] == '\n');
+ if (counted > room) {
pf.tag_trunc_pending = false;
return tree.failText(req.tag, E.NOSPC, e_tag_over);
}