authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-19 23:30:39-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:50-07:00
logdc6a4f3bf1262b46a46e0e23f5c09a93c7c780c5
tree41c5ff0fa28dfb58f02fc98040cabd1ce1f71ab8
parent71c86e1d28b0205a3417f93a41b794f656625355

std.Io: add dirMakePath and dirMakeOpenPath


4 files changed, 259 insertions(+), 171 deletions(-)

lib/std/Io.zig+3-1
......@@ -660,7 +660,9 @@ pub const VTable = struct {
660660 conditionWaitUncancelable: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) void,
661661 conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
662662
663 dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,
663 dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void,
664 dirMakePath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void,
665 dirMakeOpenPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir,
664666 dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
665667 dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
666668 dirAccess: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.AccessOptions) Dir.AccessError!void,
lib/std/Io/Dir.zig+14
......@@ -348,6 +348,20 @@ pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!Make
348348 }
349349}
350350
351pub const MakeOpenPathError = MakeError || OpenError || StatPathError;
352
353/// Performs the equivalent of `makePath` followed by `openDir`, atomically if possible.
354///
355/// When this operation is canceled, it may leave the file system in a
356/// partially modified state.
357///
358/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
359/// On WASI, `sub_path` should be encoded as valid UTF-8.
360/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
361pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) MakeOpenPathError!Dir {
362 return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, options);
363}
364
351365pub const Stat = File.Stat;
352366pub const StatError = File.StatError;
353367
lib/std/Io/Threaded.zig+233-8
......@@ -168,6 +168,15 @@ pub fn io(t: *Threaded) Io {
168168 .wasi => dirMakeWasi,
169169 else => dirMakePosix,
170170 },
171 .dirMakePath = switch (builtin.os.tag) {
172 .windows => dirMakePathWindows,
173 else => dirMakePathPosix,
174 },
175 .dirMakeOpenPath = switch (builtin.os.tag) {
176 .windows => dirMakeOpenPathWindows,
177 .wasi => dirMakeOpenPathWasi,
178 else => dirMakeOpenPathPosix,
179 },
171180 .dirStat = dirStat,
172181 .dirStatPath = switch (builtin.os.tag) {
173182 .linux => dirStatPathLinux,
......@@ -197,7 +206,7 @@ pub fn io(t: *Threaded) Io {
197206 else => dirOpenFilePosix,
198207 },
199208 .dirOpenDir = switch (builtin.os.tag) {
200 .windows => @panic("TODO"),
209 .windows => dirOpenDirWindows,
201210 .wasi => dirOpenDirWasi,
202211 else => dirOpenDirPosix,
203212 },
......@@ -991,6 +1000,153 @@ fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode
9911000 windows.CloseHandle(sub_dir_handle);
9921001}
9931002
1003fn dirMakePathPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
1004 const t: *Threaded = @ptrCast(@alignCast(userdata));
1005 _ = t;
1006 _ = dir;
1007 _ = sub_path;
1008 _ = mode;
1009 @panic("TODO");
1010}
1011
1012fn dirMakePathWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
1013 const t: *Threaded = @ptrCast(@alignCast(userdata));
1014 _ = t;
1015 _ = dir;
1016 _ = sub_path;
1017 _ = mode;
1018 @panic("TODO");
1019}
1020
1021fn dirMakeOpenPathPosix(
1022 userdata: ?*anyopaque,
1023 dir: Io.Dir,
1024 sub_path: []const u8,
1025 options: Io.Dir.OpenOptions,
1026) Io.Dir.MakeOpenPathError!Io.Dir {
1027 const t: *Threaded = @ptrCast(@alignCast(userdata));
1028 const t_io = t.io();
1029 return dir.openDir(t_io, sub_path, options) catch |err| switch (err) {
1030 error.FileNotFound => {
1031 try dir.makePath(t_io, sub_path);
1032 return dir.openDir(t_io, sub_path, options);
1033 },
1034 else => |e| return e,
1035 };
1036}
1037
1038fn dirMakeOpenPathWindows(
1039 userdata: ?*anyopaque,
1040 dir: Io.Dir,
1041 sub_path: []const u8,
1042 options: Io.Dir.OpenOptions,
1043) Io.Dir.MakeOpenPathError!Io.Dir {
1044 const t: *Threaded = @ptrCast(@alignCast(userdata));
1045 const w = windows;
1046 const access_mask = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1047 w.SYNCHRONIZE | w.FILE_TRAVERSE |
1048 (if (options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0));
1049
1050 var it = try std.fs.path.componentIterator(sub_path);
1051 // If there are no components in the path, then create a dummy component with the full path.
1052 var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{
1053 .name = "",
1054 .path = sub_path,
1055 };
1056
1057 while (true) {
1058 try t.checkCancel();
1059
1060 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path);
1061 const sub_path_w = sub_path_w_array.span();
1062 const is_last = it.peekNext() == null;
1063 const create_disposition: u32 = if (is_last) w.FILE_OPEN_IF else w.FILE_CREATE;
1064
1065 var result: Io.Dir = .{ .handle = undefined };
1066
1067 const path_len_bytes: u16 = @intCast(sub_path_w.len * 2);
1068 var nt_name: w.UNICODE_STRING = .{
1069 .Length = path_len_bytes,
1070 .MaximumLength = path_len_bytes,
1071 .Buffer = @constCast(sub_path_w.ptr),
1072 };
1073 var attr: w.OBJECT_ATTRIBUTES = .{
1074 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
1075 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
1076 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1077 .ObjectName = &nt_name,
1078 .SecurityDescriptor = null,
1079 .SecurityQualityOfService = null,
1080 };
1081 const open_reparse_point: w.DWORD = if (!options.follow_symlinks) w.FILE_OPEN_REPARSE_POINT else 0x0;
1082 var io_status_block: w.IO_STATUS_BLOCK = undefined;
1083 const rc = w.ntdll.NtCreateFile(
1084 &result.handle,
1085 access_mask,
1086 &attr,
1087 &io_status_block,
1088 null,
1089 w.FILE_ATTRIBUTE_NORMAL,
1090 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
1091 create_disposition,
1092 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
1093 null,
1094 0,
1095 );
1096
1097 switch (rc) {
1098 .SUCCESS => {
1099 component = it.next() orelse return result;
1100 w.CloseHandle(result.handle);
1101 continue;
1102 },
1103 .OBJECT_NAME_INVALID => return error.BadPathName,
1104 .OBJECT_NAME_COLLISION => {
1105 assert(!is_last);
1106 // stat the file and return an error if it's not a directory
1107 // this is important because otherwise a dangling symlink
1108 // could cause an infinite loop
1109 check_dir: {
1110 // workaround for windows, see https://github.com/ziglang/zig/issues/16738
1111 const fstat = dir.statPath(t.io(), component.path, .{
1112 .follow_symlinks = options.follow_symlinks,
1113 }) catch |stat_err| switch (stat_err) {
1114 error.IsDir => break :check_dir,
1115 else => |e| return e,
1116 };
1117 if (fstat.kind != .directory) return error.NotDir;
1118 }
1119
1120 component = it.next().?;
1121 continue;
1122 },
1123
1124 .OBJECT_NAME_NOT_FOUND,
1125 .OBJECT_PATH_NOT_FOUND,
1126 => {
1127 component = it.previous() orelse return error.FileNotFound;
1128 continue;
1129 },
1130
1131 .NOT_A_DIRECTORY => return error.NotDir,
1132 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
1133 // and the directory is trying to be opened for iteration.
1134 .ACCESS_DENIED => return error.AccessDenied,
1135 .INVALID_PARAMETER => |err| return w.statusBug(err),
1136 else => return w.unexpectedStatus(rc),
1137 }
1138 }
1139}
1140
1141fn dirMakeOpenPathWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeOpenPathError!Io.Dir {
1142 const t: *Threaded = @ptrCast(@alignCast(userdata));
1143 _ = t;
1144 _ = dir;
1145 _ = sub_path;
1146 _ = mode;
1147 @panic("TODO");
1148}
1149
9941150fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {
9951151 const t: *Threaded = @ptrCast(@alignCast(userdata));
9961152 try t.checkCancel();
......@@ -1859,6 +2015,75 @@ fn dirOpenDirPosix(
18592015 @panic("TODO");
18602016}
18612017
2018fn dirOpenDirWindows(
2019 userdata: ?*anyopaque,
2020 dir: Io.Dir,
2021 sub_path: []const u8,
2022 options: Io.Dir.OpenOptions,
2023) Io.Dir.OpenError!Io.Dir {
2024 const t: *Threaded = @ptrCast(@alignCast(userdata));
2025 try t.checkCancel();
2026
2027 const w = windows;
2028 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path);
2029 const sub_path_w = sub_path_w_array.span();
2030
2031 // TODO remove some of these flags if options.access_sub_paths is false
2032 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
2033 w.SYNCHRONIZE | w.FILE_TRAVERSE;
2034 const access_mask: u32 = if (options.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
2035
2036 const path_len_bytes: u16 = @intCast(sub_path_w.len * 2);
2037 var nt_name: w.UNICODE_STRING = .{
2038 .Length = path_len_bytes,
2039 .MaximumLength = path_len_bytes,
2040 .Buffer = @constCast(sub_path_w.ptr),
2041 };
2042 var attr: w.OBJECT_ATTRIBUTES = .{
2043 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
2044 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
2045 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
2046 .ObjectName = &nt_name,
2047 .SecurityDescriptor = null,
2048 .SecurityQualityOfService = null,
2049 };
2050 const open_reparse_point: w.DWORD = if (!options.follow_symlinks) w.FILE_OPEN_REPARSE_POINT else 0x0;
2051 var io_status_block: w.IO_STATUS_BLOCK = undefined;
2052 var result: Io.Dir = .{ .handle = undefined };
2053 const rc = w.ntdll.NtCreateFile(
2054 &result.handle,
2055 access_mask,
2056 &attr,
2057 &io_status_block,
2058 null,
2059 w.FILE_ATTRIBUTE_NORMAL,
2060 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
2061 w.FILE_OPEN,
2062 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
2063 null,
2064 0,
2065 );
2066
2067 switch (rc) {
2068 .SUCCESS => return result,
2069 .OBJECT_NAME_INVALID => return error.BadPathName,
2070 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
2071 .OBJECT_NAME_COLLISION => |err| return w.statusBug(err),
2072 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
2073 .NOT_A_DIRECTORY => return error.NotDir,
2074 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
2075 // and the directory is trying to be opened for iteration.
2076 .ACCESS_DENIED => return error.AccessDenied,
2077 .INVALID_PARAMETER => |err| return w.statusBug(err),
2078 else => return w.unexpectedStatus(rc),
2079 }
2080}
2081
2082const MakeOpenDirAccessMaskWOptions = struct {
2083 no_follow: bool,
2084 create_disposition: u32,
2085};
2086
18622087fn dirClose(userdata: ?*anyopaque, dir: Io.Dir) void {
18632088 const t: *Threaded = @ptrCast(@alignCast(userdata));
18642089 _ = t;
......@@ -2304,17 +2529,17 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestam
23042529 const t: *Threaded = @ptrCast(@alignCast(userdata));
23052530 _ = t;
23062531 switch (clock) {
2307 .realtime => {
2532 .real => {
23082533 // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds
23092534 // and uses the NTFS/Windows epoch, which is 1601-01-01.
23102535 return .{ .nanoseconds = @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100 };
23112536 },
2312 .monotonic, .uptime => {
2537 .awake, .boot => {
23132538 // QPC on windows doesn't fail on >= XP/2000 and includes time suspended.
2314 return .{ .timestamp = windows.QueryPerformanceCounter() };
2539 return .{ .nanoseconds = windows.QueryPerformanceCounter() };
23152540 },
2316 .process_cputime_id,
2317 .thread_cputime_id,
2541 .cpu_process,
2542 .cpu_thread,
23182543 => return error.UnsupportedClock,
23192544 }
23202545}
......@@ -2360,9 +2585,9 @@ fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
23602585 const t: *Threaded = @ptrCast(@alignCast(userdata));
23612586 try t.checkCancel();
23622587 const ms = ms: {
2363 const duration_and_clock = (try timeout.toDurationFromNow(t.io())) orelse
2588 const d = (try timeout.toDurationFromNow(t.io())) orelse
23642589 break :ms std.math.maxInt(windows.DWORD);
2365 break :ms std.math.lossyCast(windows.DWORD, duration_and_clock.duration.toMilliseconds());
2590 break :ms std.math.lossyCast(windows.DWORD, d.raw.toMilliseconds());
23662591 };
23672592 windows.kernel32.Sleep(ms);
23682593}
lib/std/fs/Dir.zig+9-162
......@@ -898,85 +898,11 @@ pub fn makePathStatus(self: Dir, sub_path: []const u8) MakePathError!MakePathSta
898898 return Io.Dir.makePathStatus(.{ .handle = self.fd }, io, sub_path);
899899}
900900
901/// Windows only. Calls makeOpenDirAccessMaskW iteratively to make an entire path
902/// (i.e. creating any parent directories that do not exist).
903/// Opens the dir if the path already exists and is a directory.
904/// This function is not atomic, and if it returns an error, the file system may
905/// have been modified regardless.
906/// `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
907fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) (MakeError || OpenError || StatFileError)!Dir {
908 const w = windows;
909 var it = try fs.path.componentIterator(sub_path);
910 // If there are no components in the path, then create a dummy component with the full path.
911 var component = it.last() orelse fs.path.NativeComponentIterator.Component{
912 .name = "",
913 .path = sub_path,
914 };
915
916 while (true) {
917 const sub_path_w = try w.sliceToPrefixedFileW(self.fd, component.path);
918 const is_last = it.peekNext() == null;
919 var result = self.makeOpenDirAccessMaskW(sub_path_w.span().ptr, access_mask, .{
920 .no_follow = no_follow,
921 .create_disposition = if (is_last) w.FILE_OPEN_IF else w.FILE_CREATE,
922 }) catch |err| switch (err) {
923 error.FileNotFound => |e| {
924 component = it.previous() orelse return e;
925 continue;
926 },
927 error.PathAlreadyExists => result: {
928 assert(!is_last);
929 // stat the file and return an error if it's not a directory
930 // this is important because otherwise a dangling symlink
931 // could cause an infinite loop
932 check_dir: {
933 // workaround for windows, see https://github.com/ziglang/zig/issues/16738
934 const fstat = self.statFile(component.path) catch |stat_err| switch (stat_err) {
935 error.IsDir => break :check_dir,
936 else => |e| return e,
937 };
938 if (fstat.kind != .directory) return error.NotDir;
939 }
940 break :result null;
941 },
942 else => |e| return e,
943 };
944
945 component = it.next() orelse return result.?;
946
947 // Don't leak the intermediate file handles
948 if (result) |*dir| {
949 dir.close();
950 }
951 }
952}
953
954/// This function performs `makePath`, followed by `openDir`.
955/// If supported by the OS, this operation is atomic. It is not atomic on
956/// all operating systems.
957/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
958/// On WASI, `sub_path` should be encoded as valid UTF-8.
959/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
960pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenOptions) (MakeError || OpenError || StatFileError)!Dir {
961 return switch (native_os) {
962 .windows => {
963 const w = windows;
964 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
965 w.SYNCHRONIZE | w.FILE_TRAVERSE |
966 (if (open_dir_options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0));
967
968 return self.makeOpenPathAccessMaskW(sub_path, base_flags, !open_dir_options.follow_symlinks);
969 },
970 else => {
971 return self.openDir(sub_path, open_dir_options) catch |err| switch (err) {
972 error.FileNotFound => {
973 try self.makePath(sub_path);
974 return self.openDir(sub_path, open_dir_options);
975 },
976 else => |e| return e,
977 };
978 },
979 };
901/// Deprecated in favor of `Io.Dir.makeOpenPath`.
902pub fn makeOpenPath(dir: Dir, sub_path: []const u8, options: OpenOptions) Io.Dir.MakeOpenPathError!Dir {
903 var threaded: Io.Threaded = .init_single_threaded;
904 const io = threaded.io();
905 return .adaptFromNewApi(try Io.Dir.makeOpenPath(dir.adaptToNewApi(), io, sub_path, options));
980906}
981907
982908pub const RealPathError = posix.RealPathError || error{Canceled};
......@@ -1145,8 +1071,9 @@ pub const OpenOptions = Io.Dir.OpenOptions;
11451071pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir {
11461072 switch (native_os) {
11471073 .windows => {
1148 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1149 return self.openDirW(sub_path_w.span().ptr, args);
1074 var threaded: Io.Threaded = .init_single_threaded;
1075 const io = threaded.io();
1076 return .adaptFromNewApi(try Io.Dir.openDir(.{ .handle = self.fd }, io, sub_path, args));
11501077 },
11511078 .wasi => if (!builtin.link_libc) {
11521079 var threaded: Io.Threaded = .init_single_threaded;
......@@ -1163,8 +1090,7 @@ pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir
11631090pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenOptions) OpenError!Dir {
11641091 switch (native_os) {
11651092 .windows => {
1166 const sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1167 return self.openDirW(sub_path_w.span().ptr, args);
1093 @compileError("use std.Io instead");
11681094 },
11691095 // Use the libc API when libc is linked because it implements things
11701096 // such as opening absolute directory paths.
......@@ -1215,28 +1141,6 @@ pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenOptions) OpenErr
12151141 return self.openDirFlagsZ(sub_path_c, symlink_flags);
12161142}
12171143
1218/// Same as `openDir` except the path parameter is WTF-16 LE encoded, NT-prefixed.
1219/// This function asserts the target OS is Windows.
1220pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenOptions) OpenError!Dir {
1221 const w = windows;
1222 // TODO remove some of these flags if args.access_sub_paths is false
1223 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1224 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1225 const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1226 const dir = self.makeOpenDirAccessMaskW(sub_path_w, flags, .{
1227 .no_follow = !args.follow_symlinks,
1228 .create_disposition = w.FILE_OPEN,
1229 }) catch |err| switch (err) {
1230 error.ReadOnlyFileSystem => unreachable,
1231 error.DiskQuota => unreachable,
1232 error.NoSpaceLeft => unreachable,
1233 error.PathAlreadyExists => unreachable,
1234 error.LinkQuotaExceeded => unreachable,
1235 else => |e| return e,
1236 };
1237 return dir;
1238}
1239
12401144/// Asserts `flags` has `DIRECTORY` set.
12411145fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: posix.O) OpenError!Dir {
12421146 assert(flags.DIRECTORY);
......@@ -1257,63 +1161,6 @@ fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: posix.O) OpenError
12571161 return Dir{ .fd = fd };
12581162}
12591163
1260const MakeOpenDirAccessMaskWOptions = struct {
1261 no_follow: bool,
1262 create_disposition: u32,
1263};
1264
1265fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) (MakeError || OpenError)!Dir {
1266 const w = windows;
1267
1268 var result = Dir{
1269 .fd = undefined,
1270 };
1271
1272 const path_len_bytes = @as(u16, @intCast(mem.sliceTo(sub_path_w, 0).len * 2));
1273 var nt_name = w.UNICODE_STRING{
1274 .Length = path_len_bytes,
1275 .MaximumLength = path_len_bytes,
1276 .Buffer = @constCast(sub_path_w),
1277 };
1278 var attr = w.OBJECT_ATTRIBUTES{
1279 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
1280 .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
1281 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1282 .ObjectName = &nt_name,
1283 .SecurityDescriptor = null,
1284 .SecurityQualityOfService = null,
1285 };
1286 const open_reparse_point: w.DWORD = if (flags.no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
1287 var io: w.IO_STATUS_BLOCK = undefined;
1288 const rc = w.ntdll.NtCreateFile(
1289 &result.fd,
1290 access_mask,
1291 &attr,
1292 &io,
1293 null,
1294 w.FILE_ATTRIBUTE_NORMAL,
1295 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
1296 flags.create_disposition,
1297 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
1298 null,
1299 0,
1300 );
1301
1302 switch (rc) {
1303 .SUCCESS => return result,
1304 .OBJECT_NAME_INVALID => return error.BadPathName,
1305 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1306 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
1307 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
1308 .NOT_A_DIRECTORY => return error.NotDir,
1309 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
1310 // and the directory is trying to be opened for iteration.
1311 .ACCESS_DENIED => return error.AccessDenied,
1312 .INVALID_PARAMETER => unreachable,
1313 else => return w.unexpectedStatus(rc),
1314 }
1315}
1316
13171164pub const DeleteFileError = posix.UnlinkError;
13181165
13191166/// Delete a file name and possibly the file it refers to, based on an open directory handle.