summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 17:38:06 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit13ef34f1c10a5880be411475cfabd7bb1344916b (patch)
tree8d83a2248790195774d082bf300c6979d1b0e7e7
parentdaf833bccf5aca958593738ecc59fd58dbb697b6 (diff)
downloadpardes-13ef34f1c10a5880be411475cfabd7bb1344916b.tar.gz
pardes-13ef34f1c10a5880be411475cfabd7bb1344916b.zip
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 <[email protected]>
-rw-r--r--src/ninep/tree.zig15
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();