From 4cbf74bd9b839501a4fd4bc5f39a25e216237b3c Mon Sep 17 00:00:00 2001 From: Amir Alawi <83512437+amiralawi@users.noreply.github.com> Date: Mon, 8 Jan 2024 15:58:14 -0500 Subject: [PATCH] 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. --- lib/std/fs/Dir.zig | 10 +++++++++- lib/std/fs/test.zig | 10 ++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 8a1d38996e..0f996affcc 100644 --- a/lib/std/fs/Dir.zig +++ b/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; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 0cdcd0a631..4a8cc19517 100644 --- a/lib/std/fs/test.zig +++ b/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();