From 250db41743f77ce3d8421f729bdea0065aa6bc79 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 05:01:31 -0300 Subject: A name under a directory that may not be searched or written is refused, permission denied, and a Save there says so, not no such directory `pardes noperm/a/b/c` and `pardes /proc/1/root/x` took the missing directory for one Save would make, opened a pane and exited 0; its Save then said "no such directory". fs.deniedAbove finds the nearest directory there and asks whether it may be searched and written: forwarding refuses such a name, exit 1, "permission denied", and a failed Save says "permission denied", which the writer's 9P error carries as EPERM (9ns: EACCES). Co-Authored-By: Claude Opus 5.5 --- src/fs.zig | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'src/fs.zig') diff --git a/src/fs.zig b/src/fs.zig index 965eb47c..e5980fe9 100644 --- a/src/fs.zig +++ b/src/fs.zig @@ -685,6 +685,26 @@ pub fn makeDirs(dir: []const u8) void { _ = libc.mkdir(z[0..dir.len :0], 0o755); } +/// Whether `dir` is not there to make because one above it refuses: its +/// nearest ancestor that is there may not be searched or written (EACCES: +/// `noperm/a/b`, `/proc/1/root/x`). +pub fn deniedAbove(dir: []const u8) bool { + if (comptime !platform_has_fs) return false; + var at: ?[]const u8 = dir; + while (at) |d| : (at = std.fs.path.dirname(d)) { + var z: [4096]u8 = undefined; + const dz = std.fmt.bufPrintSentinel(&z, "{s}", .{d}, 0) catch return false; + if (libc.access(dz.ptr, 0) != 0) continue; // F_OK + return libc.access(dz.ptr, 2 | 1) != 0; // W_OK | X_OK + } + return false; +} + +test "a directory under one that may not be searched or written is denied, one merely missing is not" { + try std.testing.expect(deniedAbove("/proc/1/root/x/y")); + try std.testing.expect(!deniedAbove("/tmp/pardes-surely-missing-dir/a/b")); +} + pub fn localPath(path: []const u8) ?[]const u8 { if (std.mem.eql(u8, path, "/n/os")) return "/"; if (std.mem.startsWith(u8, path, "/n/os/")) return path[5..]; -- cgit v1.3