From f8710ab27b54bf0241ad2ed629ccc98aaa619709 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 21:40:57 -0300 Subject: Edit's w and the other left-out sam commands fail EIO in words, never EOPNOTSUPP, and /os refuses a rename or a chmod as not permitted, EACCES "w is not supported in pardes" read through a mount as EOPNOTSUPP, as if the file system lacked an operation, and a chmod under /os was answered "invalid truncate", EINVAL. An Edit command pardes leaves out now says so in words 9ns reads as EIO, and a rename or mode change of an /os file is "permission denied", EACCES. fs.md's Failure section and /os entry say both. Co-Authored-By: Claude Opus 5.5 --- src/fs.zig | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'src/fs.zig') diff --git a/src/fs.zig b/src/fs.zig index bc35e1cb..d13e36bd 100644 --- a/src/fs.zig +++ b/src/fs.zig @@ -213,6 +213,11 @@ pub fn osHandle(p: *pardes.Pardes, req: Req) Reply { return .{ .tag = req.tag, .payload = .{ .staged = @intCast(out.items.len) } }; }, .read, .write, .setattr => { + // A rename or a mode change (`mv`, `chmod`) is refused as not + // permitted, EACCES through a mount: /os reads and writes the + // files it shows and changes nothing about them. + if (req.op == .setattr and (req.set.name or req.set.mode)) + return tree.failText(req.tag, E.PERM, "permission denied: /os renames nothing and changes no mode"); if (stat.kind != .file) return Reply.fail(req.tag, E.PERM); var z: [4096]u8 = undefined; const path_z = std.fmt.bufPrintSentinel(&z, "{s}", .{path}, 0) catch return Reply.fail(req.tag, E.NOENT); @@ -255,6 +260,30 @@ pub fn validMountName(name: []const u8) bool { return true; } +test "/os refuses a rename or a mode change as not permitted, in words, and still truncates to zero" { + if (comptime !pardes.hosted) return error.SkipZigTest; + const p = try th.withFile(std.testing.allocator, "x\n"); + defer p.deinit(); + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "kept.txt", .data = "kept\n" }); + var dir_buf: [4096]u8 = undefined; + const dir = dir_buf[0..try tmp.dir.realPath(std.testing.io, &dir_buf)]; + var node: u64 = os_root; + var parts = std.mem.tokenizeScalar(u8, dir, '/'); + while (parts.next()) |part| node = th.look_up(p, node, part).reply.attr.node; + node = th.look_up(p, node, "kept.txt").reply.attr.node; + for ([_]Req{ + .{ .tag = 1, .op = .setattr, .node = node, .set = .{ .mode = true } }, + .{ .tag = 2, .op = .setattr, .node = node, .set = .{ .name = true }, .data = "moved.txt" }, + }) |req| { + const r = th.call(p, req); + try std.testing.expectEqual(E.PERM, r.errno()); + try std.testing.expectStringStartsWith(r.reply.ename, "permission denied"); + } + try std.testing.expectEqual(tree.Status.ok, th.call(p, .{ .tag = 3, .op = .setattr, .node = node, .truncate = true }).reply.status); +} + test "mounts own their names and dials and reject duplicate or reserved names" { const gpa = std.testing.allocator; var ns: Namespace = .{}; -- cgit v1.3