Commit 4cbf74bd9b

Amir Alawi <83512437+amiralawi@users.noreply.github.com>
2024-01-08 21:58:14
fix std.fs.Dir.makePath silent failure (#16878)
std.fs.dir.makePath silently failed if one of the items in the path already exists. For example: cwd.makePath("foo/bar/baz") Silently failing is OK if "bar" is already a directory - this is the intended use of makePath (like mkdir -p). But if bar is a file then the subdirectory baz cannot be created - the end result is that makePath doesn't do anything which should be a detectable error because baz is never created. The existing code had a TODO comment that did not specifically cover this error, but the solution for this silent failure also accomplishes the TODO task - the code now stats "foo" and returns an appropriate error. The new code also handles potential race condition if "bar" is deleted/permissions changed/etc in between the initial makeDir and statFile calls.
1 parent ed410b9
Changed files (2)
lib
lib/std/fs/Dir.zig
@@ -1125,9 +1125,17 @@ pub fn makePath(self: Dir, sub_path: []const u8) !void {
     while (true) {
         self.makeDir(component.path) catch |err| switch (err) {
             error.PathAlreadyExists => {
-                // TODO stat the file and return an error if it's not a directory
+                // stat the file and return an error if it's not a directory
                 // this is important because otherwise a dangling symlink
                 // could cause an infinite loop
+                check_dir: {
+                    // workaround for windows, see https://github.com/ziglang/zig/issues/16738
+                    const fstat = self.statFile(component.path) catch |stat_err| switch (stat_err) {
+                        error.IsDir => break :check_dir,
+                        else => |e| return e,
+                    };
+                    if (fstat.kind != .directory) return error.NotDir;
+                }
             },
             error.FileNotFound => |e| {
                 component = it.previous() orelse return e;
lib/std/fs/test.zig
@@ -1089,6 +1089,16 @@ test "makePath in a directory that no longer exists" {
     try testing.expectError(error.FileNotFound, tmp.dir.makePath("sub-path"));
 }
 
+test "makePath but sub_path contains pre-existing file" {
+    var tmp = tmpDir(.{});
+    defer tmp.cleanup();
+
+    try tmp.dir.makeDir("foo");
+    try tmp.dir.writeFile("foo/bar", "");
+
+    try testing.expectError(error.NotDir, tmp.dir.makePath("foo/bar/baz"));
+}
+
 fn expectDir(dir: Dir, path: []const u8) !void {
     var d = try dir.openDir(path, .{});
     d.close();