diff options
| -rw-r--r-- | src/ninep/tree.zig | 15 |
1 files changed, 15 insertions, 0 deletions
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(); |
