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)...@@ -1104,27 +1104,29 @@ pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags)
1104 return file;1104 return file;
1105}1105}
11061106
1107pub const MakeError = posix.MakeDirError;
1108
1107/// Creates a single directory with a relative or absolute path.1109/// Creates a single directory with a relative or absolute path.
1108/// To create multiple directories to make an entire path, see `makePath`.1110/// To create multiple directories to make an entire path, see `makePath`.
1109/// To operate on only absolute paths, see `makeDirAbsolute`.1111/// To operate on only absolute paths, see `makeDirAbsolute`.
1110/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).1112/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1111/// On WASI, `sub_path` should be encoded as valid UTF-8.1113/// On WASI, `sub_path` should be encoded as valid UTF-8.
1112/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.1114/// 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 {
1114 try posix.mkdirat(self.fd, sub_path, default_mode);1116 try posix.mkdirat(self.fd, sub_path, default_mode);
1115}1117}
11161118
1117/// Same as `makeDir`, but `sub_path` is null-terminated.1119/// Same as `makeDir`, but `sub_path` is null-terminated.
1118/// To create multiple directories to make an entire path, see `makePath`.1120/// To create multiple directories to make an entire path, see `makePath`.
1119/// To operate on only absolute paths, see `makeDirAbsoluteZ`.1121/// 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 {
1121 try posix.mkdiratZ(self.fd, sub_path, default_mode);1123 try posix.mkdiratZ(self.fd, sub_path, default_mode);
1122}1124}
11231125
1124/// Creates a single directory with a relative or absolute null-terminated WTF-16 LE-encoded path.1126/// Creates a single directory with a relative or absolute null-terminated WTF-16 LE-encoded path.
1125/// To create multiple directories to make an entire path, see `makePath`.1127/// To create multiple directories to make an entire path, see `makePath`.
1126/// To operate on only absolute paths, see `makeDirAbsoluteW`.1128/// 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 {
1128 try posix.mkdiratW(self.fd, sub_path, default_mode);1130 try posix.mkdiratW(self.fd, sub_path, default_mode);
1129}1131}
11301132
...@@ -1144,7 +1146,7 @@ pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {...@@ -1144,7 +1146,7 @@ pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
1144/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,1146/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,
1145/// meaning a `sub_path` like "first/../second" will create both a `./first`1147/// meaning a `sub_path` like "first/../second" will create both a `./first`
1146/// and a `./second` directory.1148/// 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 {
1148 var it = try fs.path.componentIterator(sub_path);1150 var it = try fs.path.componentIterator(sub_path);
1149 var component = it.last() orelse return;1151 var component = it.last() orelse return;
1150 while (true) {1152 while (true) {
...@@ -1178,7 +1180,7 @@ pub fn makePath(self: Dir, sub_path: []const u8) !void {...@@ -1178,7 +1180,7 @@ pub fn makePath(self: Dir, sub_path: []const u8) !void {
1178/// This function is not atomic, and if it returns an error, the file system may1180/// This function is not atomic, and if it returns an error, the file system may
1179/// have been modified regardless.1181/// have been modified regardless.
1180/// `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).1182/// `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 {
1182 const w = windows;1184 const w = windows;
1183 var it = try fs.path.componentIterator(sub_path);1185 var it = try fs.path.componentIterator(sub_path);
1184 // If there are no components in the path, then create a dummy component with the full path.1186 // 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...@@ -1198,12 +1200,27 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no
1198 component = it.previous() orelse return e;1200 component = it.previous() orelse return e;
1199 continue;1201 continue;
1200 },1202 },
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 },
1201 else => |e| return e,1218 else => |e| return e,
1202 };1219 };
1203
1204 component = it.next() orelse return result;
1205 // Don't leak the intermediate file handles1220 // 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.?;
1207 }1224 }
1208}1225}
12091226
...@@ -1213,7 +1230,7 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no...@@ -1213,7 +1230,7 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no
1213/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).1230/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1214/// On WASI, `sub_path` should be encoded as valid UTF-8.1231/// On WASI, `sub_path` should be encoded as valid UTF-8.
1215/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.1232/// 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 {
1217 return switch (native_os) {1234 return switch (native_os) {
1218 .windows => {1235 .windows => {
1219 const w = windows;1236 const w = windows;
...@@ -1516,10 +1533,17 @@ pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) Ope...@@ -1516,10 +1533,17 @@ pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) Ope
1516 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |1533 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1517 w.SYNCHRONIZE | w.FILE_TRAVERSE;1534 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1518 const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;1535 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, .{
1520 .no_follow = args.no_follow,1537 .no_follow = args.no_follow,
1521 .create_disposition = w.FILE_OPEN,1538 .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 };
1523 return dir;1547 return dir;
1524}1548}
15251549
...@@ -1544,7 +1568,7 @@ const MakeOpenDirAccessMaskWOptions = struct {...@@ -1544,7 +1568,7 @@ const MakeOpenDirAccessMaskWOptions = struct {
1544 create_disposition: u32,1568 create_disposition: u32,
1545};1569};
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 {
1548 const w = windows;1572 const w = windows;
15491573
1550 var result = Dir{1574 var result = Dir{
...@@ -1585,6 +1609,7 @@ fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u3...@@ -1585,6 +1609,7 @@ fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u3
1585 .SUCCESS => return result,1609 .SUCCESS => return result,
1586 .OBJECT_NAME_INVALID => return error.BadPathName,1610 .OBJECT_NAME_INVALID => return error.BadPathName,
1587 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,1611 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1612 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
1588 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,1613 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
1589 .NOT_A_DIRECTORY => return error.NotDir,1614 .NOT_A_DIRECTORY => return error.NotDir,
1590 // This can happen if the directory has 'List folder contents' permission set to 'Deny'1615 // This can happen if the directory has 'List folder contents' permission set to 'Deny'