From 13ef34f1c10a5880be411475cfabd7bb1344916b Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 17:38:06 -0300 Subject: A rename (mv) of a pane's directory or any file of the tree is refused as not permitted, EPERM, not an EIO cloud9's engine passes a wstat that changes a name to the backend, and the tree's setattr ignored the name: the rename answered as done, the name did not change, and a mount then gave `mv` EIO. The tree names its own files, so a setattr with a name is now refused, "rename not permitted", which 9ns reads as EPERM. A pane's buffer is still renamed through its name file. Co-Authored-By: Claude Opus 5.5 --- src/ninep/tree.zig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src') diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index 53d33731..4300d2b5 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -987,6 +987,10 @@ fn releaseHandle(p: *Pardes, req: Req) void { } fn setattr(p: *Pardes, req: Req, target: Target) Reply { + // A rename (`mv`) of a pane's directory, a column's or any file here: + // the tree names them, not the client. Refused as not permitted + // (EPERM), not taken and then lost, which a mount read as EIO. + if (req.set.name) return failText(req.tag, E.PERM, "rename not permitted: the tree names its files (a pane's name file renames its buffer)"); if (req.truncate) switch (target) { .pane => |t| { const id = p.paneBySerial(t.serial) orelse return Reply.fail(req.tag, E.NOENT); @@ -1850,6 +1854,17 @@ test "a write cut where a writer's 4096-byte buffer filled holds its partial lin try testing.expect(!th.logHas(p, "wrong #args")); } +test "a rename of a pane's directory or file is refused as not permitted" { + const p = try th.withFile(testing.allocator, "x\n"); + defer p.deinit(); + const serial = serialOf(p); + for ([_]u64{ Node.of(serial, .dir), Node.of(serial, .body) }) |node| { + const r = call(p, .{ .tag = 1, .op = .setattr, .node = node, .set = .{ .name = true } }); + try testing.expectEqual(E.PERM, r.errno()); + try testing.expect(std.mem.indexOf(u8, r.reply.ename, "not permitted") != null); + } +} + 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(); -- cgit v1.3