authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-05-06 19:46:29-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-22 07:01:14-07:00
log2563d32425e35c8500619e93375a8fa9fdeece11
tree5475c5c67334e038dc6c2d0db78295d69ff38172
parent33809a0c538b77770ef4f80e8208117d6bcb494e

fs: handle `OBJECT_NAME_COLLISION` in `makeOpenPath`

This fixes a race condition when two threads/processes try to `makeOpenPath` the same path simultaneously.

1 files changed, 37 insertions(+), 12 deletions(-)

lib/std/fs/Dir.zig+37-12
......@@ -1104,27 +1104,29 @@ pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags)
11041104 return file;
11051105}
11061106
1107pub const MakeError = posix.MakeDirError;
1108
11071109/// Creates a single directory with a relative or absolute path.
11081110/// To create multiple directories to make an entire path, see `makePath`.
11091111/// To operate on only absolute paths, see `makeDirAbsolute`.
11101112/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
11111113/// On WASI, `sub_path` should be encoded as valid UTF-8.
11121114/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1113pub fn makeDir(self: Dir, sub_path: []const u8) !void {
1115pub fn makeDir(self: Dir, sub_path: []const u8) MakeError!void {
11141116 try posix.mkdirat(self.fd, sub_path, default_mode);
11151117}
11161118
11171119/// Same as `makeDir`, but `sub_path` is null-terminated.
11181120/// To create multiple directories to make an entire path, see `makePath`.
11191121/// To operate on only absolute paths, see `makeDirAbsoluteZ`.
1120pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
1122pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) MakeError!void {
11211123 try posix.mkdiratZ(self.fd, sub_path, default_mode);
11221124}
11231125
11241126/// Creates a single directory with a relative or absolute null-terminated WTF-16 LE-encoded path.
11251127/// To create multiple directories to make an entire path, see `makePath`.
11261128/// To operate on only absolute paths, see `makeDirAbsoluteW`.
1127pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
1129pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) MakeError!void {
11281130 try posix.mkdiratW(self.fd, sub_path, default_mode);
11291131}
11301132
......@@ -1144,7 +1146,7 @@ pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
11441146/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,
11451147/// meaning a `sub_path` like "first/../second" will create both a `./first`
11461148/// and a `./second` directory.
1147pub fn makePath(self: Dir, sub_path: []const u8) !void {
1149pub fn makePath(self: Dir, sub_path: []const u8) (MakeError || StatFileError)!void {
11481150 var it = try fs.path.componentIterator(sub_path);
11491151 var component = it.last() orelse return;
11501152 while (true) {
......@@ -1178,7 +1180,7 @@ pub fn makePath(self: Dir, sub_path: []const u8) !void {
11781180/// This function is not atomic, and if it returns an error, the file system may
11791181/// have been modified regardless.
11801182/// `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1181fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {
1183fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) (MakeError || OpenError || StatFileError)!Dir {
11821184 const w = windows;
11831185 var it = try fs.path.componentIterator(sub_path);
11841186 // If there are no components in the path, then create a dummy component with the full path.
......@@ -1198,12 +1200,27 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no
11981200 component = it.previous() orelse return e;
11991201 continue;
12001202 },
1203 error.PathAlreadyExists => result: {
1204 assert(!is_last);
1205 // stat the file and return an error if it's not a directory
1206 // this is important because otherwise a dangling symlink
1207 // could cause an infinite loop
1208 check_dir: {
1209 // workaround for windows, see https://github.com/ziglang/zig/issues/16738
1210 const fstat = self.statFile(component.path) catch |stat_err| switch (stat_err) {
1211 error.IsDir => break :check_dir,
1212 else => |e| return e,
1213 };
1214 if (fstat.kind != .directory) return error.NotDir;
1215 }
1216 break :result null;
1217 },
12011218 else => |e| return e,
12021219 };
1203
1204 component = it.next() orelse return result;
12051220 // Don't leak the intermediate file handles
1206 result.close();
1221 errdefer if (result) |*dir| dir.close();
1222
1223 component = it.next() orelse return result.?;
12071224 }
12081225}
12091226
......@@ -1213,7 +1230,7 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no
12131230/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
12141231/// On WASI, `sub_path` should be encoded as valid UTF-8.
12151232/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1216pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
1233pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) (MakeError || OpenError || StatFileError)!Dir {
12171234 return switch (native_os) {
12181235 .windows => {
12191236 const w = windows;
......@@ -1516,10 +1533,17 @@ pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) Ope
15161533 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
15171534 w.SYNCHRONIZE | w.FILE_TRAVERSE;
15181535 const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1519 const dir = try self.makeOpenDirAccessMaskW(sub_path_w, flags, .{
1536 const dir = self.makeOpenDirAccessMaskW(sub_path_w, flags, .{
15201537 .no_follow = args.no_follow,
15211538 .create_disposition = w.FILE_OPEN,
1522 });
1539 }) catch |err| switch (err) {
1540 error.ReadOnlyFileSystem => unreachable,
1541 error.DiskQuota => unreachable,
1542 error.NoSpaceLeft => unreachable,
1543 error.PathAlreadyExists => unreachable,
1544 error.LinkQuotaExceeded => unreachable,
1545 else => |e| return e,
1546 };
15231547 return dir;
15241548}
15251549
......@@ -1544,7 +1568,7 @@ const MakeOpenDirAccessMaskWOptions = struct {
15441568 create_disposition: u32,
15451569};
15461570
1547fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) OpenError!Dir {
1571fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) (MakeError || OpenError)!Dir {
15481572 const w = windows;
15491573
15501574 var result = Dir{
......@@ -1585,6 +1609,7 @@ fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u3
15851609 .SUCCESS => return result,
15861610 .OBJECT_NAME_INVALID => return error.BadPathName,
15871611 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1612 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
15881613 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
15891614 .NOT_A_DIRECTORY => return error.NotDir,
15901615 // This can happen if the directory has 'List folder contents' permission set to 'Deny'