| author | |
| committer | |
| log | dc6a4f3bf1262b46a46e0e23f5c09a93c7c780c5 |
| tree | 41c5ff0fa28dfb58f02fc98040cabd1ce1f71ab8 |
| parent | 71c86e1d28b0205a3417f93a41b794f656625355 |
4 files changed, 259 insertions(+), 171 deletions(-)
lib/std/Io.zig+3-1| ... | ... | @@ -660,7 +660,9 @@ pub const VTable = struct { |
| 660 | 660 | conditionWaitUncancelable: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) void, |
| 661 | 661 | conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void, |
| 662 | 662 | |
| 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, | |
| 664 | 666 | dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat, |
| 665 | 667 | dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat, |
| 666 | 668 | 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 |
| 348 | 348 | } |
| 349 | 349 | } |
| 350 | 350 | |
| 351 | pub 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. | |
| 361 | pub 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 | ||
| 351 | 365 | pub const Stat = File.Stat; |
| 352 | 366 | pub const StatError = File.StatError; |
| 353 | 367 |
lib/std/Io/Threaded.zig+233-8| ... | ... | @@ -168,6 +168,15 @@ pub fn io(t: *Threaded) Io { |
| 168 | 168 | .wasi => dirMakeWasi, |
| 169 | 169 | else => dirMakePosix, |
| 170 | 170 | }, |
| 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 | }, | |
| 171 | 180 | .dirStat = dirStat, |
| 172 | 181 | .dirStatPath = switch (builtin.os.tag) { |
| 173 | 182 | .linux => dirStatPathLinux, |
| ... | ... | @@ -197,7 +206,7 @@ pub fn io(t: *Threaded) Io { |
| 197 | 206 | else => dirOpenFilePosix, |
| 198 | 207 | }, |
| 199 | 208 | .dirOpenDir = switch (builtin.os.tag) { |
| 200 | .windows => @panic("TODO"), | |
| 209 | .windows => dirOpenDirWindows, | |
| 201 | 210 | .wasi => dirOpenDirWasi, |
| 202 | 211 | else => dirOpenDirPosix, |
| 203 | 212 | }, |
| ... | ... | @@ -991,6 +1000,153 @@ fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode |
| 991 | 1000 | windows.CloseHandle(sub_dir_handle); |
| 992 | 1001 | } |
| 993 | 1002 | |
| 1003 | fn 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 | ||
| 1012 | fn 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 | ||
| 1021 | fn 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 | ||
| 1038 | fn 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 | ||
| 1141 | fn 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 | ||
| 994 | 1150 | fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat { |
| 995 | 1151 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 996 | 1152 | try t.checkCancel(); |
| ... | ... | @@ -1859,6 +2015,75 @@ fn dirOpenDirPosix( |
| 1859 | 2015 | @panic("TODO"); |
| 1860 | 2016 | } |
| 1861 | 2017 | |
| 2018 | fn 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 | ||
| 2082 | const MakeOpenDirAccessMaskWOptions = struct { | |
| 2083 | no_follow: bool, | |
| 2084 | create_disposition: u32, | |
| 2085 | }; | |
| 2086 | ||
| 1862 | 2087 | fn dirClose(userdata: ?*anyopaque, dir: Io.Dir) void { |
| 1863 | 2088 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1864 | 2089 | _ = t; |
| ... | ... | @@ -2304,17 +2529,17 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestam |
| 2304 | 2529 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2305 | 2530 | _ = t; |
| 2306 | 2531 | switch (clock) { |
| 2307 | .realtime => { | |
| 2532 | .real => { | |
| 2308 | 2533 | // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds |
| 2309 | 2534 | // and uses the NTFS/Windows epoch, which is 1601-01-01. |
| 2310 | 2535 | return .{ .nanoseconds = @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100 }; |
| 2311 | 2536 | }, |
| 2312 | .monotonic, .uptime => { | |
| 2537 | .awake, .boot => { | |
| 2313 | 2538 | // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. |
| 2314 | return .{ .timestamp = windows.QueryPerformanceCounter() }; | |
| 2539 | return .{ .nanoseconds = windows.QueryPerformanceCounter() }; | |
| 2315 | 2540 | }, |
| 2316 | .process_cputime_id, | |
| 2317 | .thread_cputime_id, | |
| 2541 | .cpu_process, | |
| 2542 | .cpu_thread, | |
| 2318 | 2543 | => return error.UnsupportedClock, |
| 2319 | 2544 | } |
| 2320 | 2545 | } |
| ... | ... | @@ -2360,9 +2585,9 @@ fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 2360 | 2585 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2361 | 2586 | try t.checkCancel(); |
| 2362 | 2587 | const ms = ms: { |
| 2363 | const duration_and_clock = (try timeout.toDurationFromNow(t.io())) orelse | |
| 2588 | const d = (try timeout.toDurationFromNow(t.io())) orelse | |
| 2364 | 2589 | 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()); | |
| 2366 | 2591 | }; |
| 2367 | 2592 | windows.kernel32.Sleep(ms); |
| 2368 | 2593 | } |
lib/std/fs/Dir.zig+9-162| ... | ... | @@ -898,85 +898,11 @@ pub fn makePathStatus(self: Dir, sub_path: []const u8) MakePathError!MakePathSta |
| 898 | 898 | return Io.Dir.makePathStatus(.{ .handle = self.fd }, io, sub_path); |
| 899 | 899 | } |
| 900 | 900 | |
| 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/). | |
| 907 | fn 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. | |
| 960 | pub 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`. | |
| 902 | pub 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)); | |
| 980 | 906 | } |
| 981 | 907 | |
| 982 | 908 | pub const RealPathError = posix.RealPathError || error{Canceled}; |
| ... | ... | @@ -1145,8 +1071,9 @@ pub const OpenOptions = Io.Dir.OpenOptions; |
| 1145 | 1071 | pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir { |
| 1146 | 1072 | switch (native_os) { |
| 1147 | 1073 | .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)); | |
| 1150 | 1077 | }, |
| 1151 | 1078 | .wasi => if (!builtin.link_libc) { |
| 1152 | 1079 | var threaded: Io.Threaded = .init_single_threaded; |
| ... | ... | @@ -1163,8 +1090,7 @@ pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir |
| 1163 | 1090 | pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenOptions) OpenError!Dir { |
| 1164 | 1091 | switch (native_os) { |
| 1165 | 1092 | .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"); | |
| 1168 | 1094 | }, |
| 1169 | 1095 | // Use the libc API when libc is linked because it implements things |
| 1170 | 1096 | // such as opening absolute directory paths. |
| ... | ... | @@ -1215,28 +1141,6 @@ pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenOptions) OpenErr |
| 1215 | 1141 | return self.openDirFlagsZ(sub_path_c, symlink_flags); |
| 1216 | 1142 | } |
| 1217 | 1143 | |
| 1218 | /// Same as `openDir` except the path parameter is WTF-16 LE encoded, NT-prefixed. | |
| 1219 | /// This function asserts the target OS is Windows. | |
| 1220 | pub 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 | ||
| 1240 | 1144 | /// Asserts `flags` has `DIRECTORY` set. |
| 1241 | 1145 | fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: posix.O) OpenError!Dir { |
| 1242 | 1146 | assert(flags.DIRECTORY); |
| ... | ... | @@ -1257,63 +1161,6 @@ fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: posix.O) OpenError |
| 1257 | 1161 | return Dir{ .fd = fd }; |
| 1258 | 1162 | } |
| 1259 | 1163 | |
| 1260 | const MakeOpenDirAccessMaskWOptions = struct { | |
| 1261 | no_follow: bool, | |
| 1262 | create_disposition: u32, | |
| 1263 | }; | |
| 1264 | ||
| 1265 | fn 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 | ||
| 1317 | 1164 | pub const DeleteFileError = posix.UnlinkError; |
| 1318 | 1165 | |
| 1319 | 1166 | /// Delete a file name and possibly the file it refers to, based on an open directory handle. |