summaryrefslogtreecommitdiff
path: root/src/fs.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 05:01:31 -0300
committerGabriel Schneider <[email protected]>2026-10-01 05:18:09 -0300
commit250db41743f77ce3d8421f729bdea0065aa6bc79 (patch)
tree906bbefca6d448ba04c81c93cf552e23b5e6c3a4 /src/fs.zig
parent36fe45e3a4a7ce9b61ab7bf2df07f0132a9170c1 (diff)
downloadpardes-250db41743f77ce3d8421f729bdea0065aa6bc79.tar.gz
pardes-250db41743f77ce3d8421f729bdea0065aa6bc79.zip
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 <[email protected]>
Diffstat (limited to 'src/fs.zig')
-rw-r--r--src/fs.zig20
1 files changed, 20 insertions, 0 deletions
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..];