Commit dc6a4f3bf1
Changed files (4)
lib
std
lib/std/fs/Dir.zig
@@ -898,85 +898,11 @@ pub fn makePathStatus(self: Dir, sub_path: []const u8) MakePathError!MakePathSta
return Io.Dir.makePathStatus(.{ .handle = self.fd }, io, sub_path);
}
-/// Windows only. Calls makeOpenDirAccessMaskW iteratively to make an entire path
-/// (i.e. creating any parent directories that do not exist).
-/// Opens the dir if the path already exists and is a directory.
-/// This function is not atomic, and if it returns an error, the file system may
-/// have been modified regardless.
-/// `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
-fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) (MakeError || OpenError || StatFileError)!Dir {
- const w = windows;
- var it = try fs.path.componentIterator(sub_path);
- // If there are no components in the path, then create a dummy component with the full path.
- var component = it.last() orelse fs.path.NativeComponentIterator.Component{
- .name = "",
- .path = sub_path,
- };
-
- while (true) {
- const sub_path_w = try w.sliceToPrefixedFileW(self.fd, component.path);
- const is_last = it.peekNext() == null;
- var result = self.makeOpenDirAccessMaskW(sub_path_w.span().ptr, access_mask, .{
- .no_follow = no_follow,
- .create_disposition = if (is_last) w.FILE_OPEN_IF else w.FILE_CREATE,
- }) catch |err| switch (err) {
- error.FileNotFound => |e| {
- component = it.previous() orelse return e;
- continue;
- },
- error.PathAlreadyExists => result: {
- assert(!is_last);
- // 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;
- }
- break :result null;
- },
- else => |e| return e,
- };
-
- component = it.next() orelse return result.?;
-
- // Don't leak the intermediate file handles
- if (result) |*dir| {
- dir.close();
- }
- }
-}
-
-/// This function performs `makePath`, followed by `openDir`.
-/// If supported by the OS, this operation is atomic. It is not atomic on
-/// all operating systems.
-/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
-/// On WASI, `sub_path` should be encoded as valid UTF-8.
-/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
-pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenOptions) (MakeError || OpenError || StatFileError)!Dir {
- return switch (native_os) {
- .windows => {
- const w = windows;
- const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
- w.SYNCHRONIZE | w.FILE_TRAVERSE |
- (if (open_dir_options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0));
-
- return self.makeOpenPathAccessMaskW(sub_path, base_flags, !open_dir_options.follow_symlinks);
- },
- else => {
- return self.openDir(sub_path, open_dir_options) catch |err| switch (err) {
- error.FileNotFound => {
- try self.makePath(sub_path);
- return self.openDir(sub_path, open_dir_options);
- },
- else => |e| return e,
- };
- },
- };
+/// Deprecated in favor of `Io.Dir.makeOpenPath`.
+pub fn makeOpenPath(dir: Dir, sub_path: []const u8, options: OpenOptions) Io.Dir.MakeOpenPathError!Dir {
+ var threaded: Io.Threaded = .init_single_threaded;
+ const io = threaded.io();
+ return .adaptFromNewApi(try Io.Dir.makeOpenPath(dir.adaptToNewApi(), io, sub_path, options));
}
pub const RealPathError = posix.RealPathError || error{Canceled};
@@ -1145,8 +1071,9 @@ pub const OpenOptions = Io.Dir.OpenOptions;
pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir {
switch (native_os) {
.windows => {
- const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
- return self.openDirW(sub_path_w.span().ptr, args);
+ var threaded: Io.Threaded = .init_single_threaded;
+ const io = threaded.io();
+ return .adaptFromNewApi(try Io.Dir.openDir(.{ .handle = self.fd }, io, sub_path, args));
},
.wasi => if (!builtin.link_libc) {
var threaded: Io.Threaded = .init_single_threaded;
@@ -1163,8 +1090,7 @@ pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir
pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenOptions) OpenError!Dir {
switch (native_os) {
.windows => {
- const sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
- return self.openDirW(sub_path_w.span().ptr, args);
+ @compileError("use std.Io instead");
},
// Use the libc API when libc is linked because it implements things
// such as opening absolute directory paths.
@@ -1215,28 +1141,6 @@ pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenOptions) OpenErr
return self.openDirFlagsZ(sub_path_c, symlink_flags);
}
-/// Same as `openDir` except the path parameter is WTF-16 LE encoded, NT-prefixed.
-/// This function asserts the target OS is Windows.
-pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenOptions) OpenError!Dir {
- const w = windows;
- // TODO remove some of these flags if args.access_sub_paths is false
- const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
- w.SYNCHRONIZE | w.FILE_TRAVERSE;
- const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
- const dir = self.makeOpenDirAccessMaskW(sub_path_w, flags, .{
- .no_follow = !args.follow_symlinks,
- .create_disposition = w.FILE_OPEN,
- }) catch |err| switch (err) {
- error.ReadOnlyFileSystem => unreachable,
- error.DiskQuota => unreachable,
- error.NoSpaceLeft => unreachable,
- error.PathAlreadyExists => unreachable,
- error.LinkQuotaExceeded => unreachable,
- else => |e| return e,
- };
- return dir;
-}
-
/// Asserts `flags` has `DIRECTORY` set.
fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: posix.O) OpenError!Dir {
assert(flags.DIRECTORY);
@@ -1257,63 +1161,6 @@ fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: posix.O) OpenError
return Dir{ .fd = fd };
}
-const MakeOpenDirAccessMaskWOptions = struct {
- no_follow: bool,
- create_disposition: u32,
-};
-
-fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) (MakeError || OpenError)!Dir {
- const w = windows;
-
- var result = Dir{
- .fd = undefined,
- };
-
- const path_len_bytes = @as(u16, @intCast(mem.sliceTo(sub_path_w, 0).len * 2));
- var nt_name = w.UNICODE_STRING{
- .Length = path_len_bytes,
- .MaximumLength = path_len_bytes,
- .Buffer = @constCast(sub_path_w),
- };
- var attr = w.OBJECT_ATTRIBUTES{
- .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
- .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
- .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
- .ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- };
- const open_reparse_point: w.DWORD = if (flags.no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
- var io: w.IO_STATUS_BLOCK = undefined;
- const rc = w.ntdll.NtCreateFile(
- &result.fd,
- access_mask,
- &attr,
- &io,
- null,
- w.FILE_ATTRIBUTE_NORMAL,
- w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
- flags.create_disposition,
- w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
- null,
- 0,
- );
-
- switch (rc) {
- .SUCCESS => return result,
- .OBJECT_NAME_INVALID => return error.BadPathName,
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .NOT_A_DIRECTORY => return error.NotDir,
- // This can happen if the directory has 'List folder contents' permission set to 'Deny'
- // and the directory is trying to be opened for iteration.
- .ACCESS_DENIED => return error.AccessDenied,
- .INVALID_PARAMETER => unreachable,
- else => return w.unexpectedStatus(rc),
- }
-}
-
pub const DeleteFileError = posix.UnlinkError;
/// Delete a file name and possibly the file it refers to, based on an open directory handle.
lib/std/Io/Dir.zig
@@ -348,6 +348,20 @@ pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!Make
}
}
+pub const MakeOpenPathError = MakeError || OpenError || StatPathError;
+
+/// Performs the equivalent of `makePath` followed by `openDir`, atomically if possible.
+///
+/// When this operation is canceled, it may leave the file system in a
+/// partially modified state.
+///
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
+pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) MakeOpenPathError!Dir {
+ return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, options);
+}
+
pub const Stat = File.Stat;
pub const StatError = File.StatError;
lib/std/Io/Threaded.zig
@@ -168,6 +168,15 @@ pub fn io(t: *Threaded) Io {
.wasi => dirMakeWasi,
else => dirMakePosix,
},
+ .dirMakePath = switch (builtin.os.tag) {
+ .windows => dirMakePathWindows,
+ else => dirMakePathPosix,
+ },
+ .dirMakeOpenPath = switch (builtin.os.tag) {
+ .windows => dirMakeOpenPathWindows,
+ .wasi => dirMakeOpenPathWasi,
+ else => dirMakeOpenPathPosix,
+ },
.dirStat = dirStat,
.dirStatPath = switch (builtin.os.tag) {
.linux => dirStatPathLinux,
@@ -197,7 +206,7 @@ pub fn io(t: *Threaded) Io {
else => dirOpenFilePosix,
},
.dirOpenDir = switch (builtin.os.tag) {
- .windows => @panic("TODO"),
+ .windows => dirOpenDirWindows,
.wasi => dirOpenDirWasi,
else => dirOpenDirPosix,
},
@@ -991,6 +1000,153 @@ fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode
windows.CloseHandle(sub_dir_handle);
}
+fn dirMakePathPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ _ = t;
+ _ = dir;
+ _ = sub_path;
+ _ = mode;
+ @panic("TODO");
+}
+
+fn dirMakePathWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ _ = t;
+ _ = dir;
+ _ = sub_path;
+ _ = mode;
+ @panic("TODO");
+}
+
+fn dirMakeOpenPathPosix(
+ userdata: ?*anyopaque,
+ dir: Io.Dir,
+ sub_path: []const u8,
+ options: Io.Dir.OpenOptions,
+) Io.Dir.MakeOpenPathError!Io.Dir {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ const t_io = t.io();
+ return dir.openDir(t_io, sub_path, options) catch |err| switch (err) {
+ error.FileNotFound => {
+ try dir.makePath(t_io, sub_path);
+ return dir.openDir(t_io, sub_path, options);
+ },
+ else => |e| return e,
+ };
+}
+
+fn dirMakeOpenPathWindows(
+ userdata: ?*anyopaque,
+ dir: Io.Dir,
+ sub_path: []const u8,
+ options: Io.Dir.OpenOptions,
+) Io.Dir.MakeOpenPathError!Io.Dir {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ const w = windows;
+ const access_mask = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
+ w.SYNCHRONIZE | w.FILE_TRAVERSE |
+ (if (options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0));
+
+ var it = try std.fs.path.componentIterator(sub_path);
+ // If there are no components in the path, then create a dummy component with the full path.
+ var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{
+ .name = "",
+ .path = sub_path,
+ };
+
+ while (true) {
+ try t.checkCancel();
+
+ const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path);
+ const sub_path_w = sub_path_w_array.span();
+ const is_last = it.peekNext() == null;
+ const create_disposition: u32 = if (is_last) w.FILE_OPEN_IF else w.FILE_CREATE;
+
+ var result: Io.Dir = .{ .handle = undefined };
+
+ const path_len_bytes: u16 = @intCast(sub_path_w.len * 2);
+ var nt_name: w.UNICODE_STRING = .{
+ .Length = path_len_bytes,
+ .MaximumLength = path_len_bytes,
+ .Buffer = @constCast(sub_path_w.ptr),
+ };
+ var attr: w.OBJECT_ATTRIBUTES = .{
+ .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
+ .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
+ .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
+ .ObjectName = &nt_name,
+ .SecurityDescriptor = null,
+ .SecurityQualityOfService = null,
+ };
+ const open_reparse_point: w.DWORD = if (!options.follow_symlinks) w.FILE_OPEN_REPARSE_POINT else 0x0;
+ var io_status_block: w.IO_STATUS_BLOCK = undefined;
+ const rc = w.ntdll.NtCreateFile(
+ &result.handle,
+ access_mask,
+ &attr,
+ &io_status_block,
+ null,
+ w.FILE_ATTRIBUTE_NORMAL,
+ w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
+ create_disposition,
+ w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
+ null,
+ 0,
+ );
+
+ switch (rc) {
+ .SUCCESS => {
+ component = it.next() orelse return result;
+ w.CloseHandle(result.handle);
+ continue;
+ },
+ .OBJECT_NAME_INVALID => return error.BadPathName,
+ .OBJECT_NAME_COLLISION => {
+ assert(!is_last);
+ // 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 = dir.statPath(t.io(), component.path, .{
+ .follow_symlinks = options.follow_symlinks,
+ }) catch |stat_err| switch (stat_err) {
+ error.IsDir => break :check_dir,
+ else => |e| return e,
+ };
+ if (fstat.kind != .directory) return error.NotDir;
+ }
+
+ component = it.next().?;
+ continue;
+ },
+
+ .OBJECT_NAME_NOT_FOUND,
+ .OBJECT_PATH_NOT_FOUND,
+ => {
+ component = it.previous() orelse return error.FileNotFound;
+ continue;
+ },
+
+ .NOT_A_DIRECTORY => return error.NotDir,
+ // This can happen if the directory has 'List folder contents' permission set to 'Deny'
+ // and the directory is trying to be opened for iteration.
+ .ACCESS_DENIED => return error.AccessDenied,
+ .INVALID_PARAMETER => |err| return w.statusBug(err),
+ else => return w.unexpectedStatus(rc),
+ }
+ }
+}
+
+fn dirMakeOpenPathWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeOpenPathError!Io.Dir {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ _ = t;
+ _ = dir;
+ _ = sub_path;
+ _ = mode;
+ @panic("TODO");
+}
+
fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {
const t: *Threaded = @ptrCast(@alignCast(userdata));
try t.checkCancel();
@@ -1859,6 +2015,75 @@ fn dirOpenDirPosix(
@panic("TODO");
}
+fn dirOpenDirWindows(
+ userdata: ?*anyopaque,
+ dir: Io.Dir,
+ sub_path: []const u8,
+ options: Io.Dir.OpenOptions,
+) Io.Dir.OpenError!Io.Dir {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ try t.checkCancel();
+
+ const w = windows;
+ const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path);
+ const sub_path_w = sub_path_w_array.span();
+
+ // TODO remove some of these flags if options.access_sub_paths is false
+ const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
+ w.SYNCHRONIZE | w.FILE_TRAVERSE;
+ const access_mask: u32 = if (options.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
+
+ const path_len_bytes: u16 = @intCast(sub_path_w.len * 2);
+ var nt_name: w.UNICODE_STRING = .{
+ .Length = path_len_bytes,
+ .MaximumLength = path_len_bytes,
+ .Buffer = @constCast(sub_path_w.ptr),
+ };
+ var attr: w.OBJECT_ATTRIBUTES = .{
+ .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
+ .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
+ .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
+ .ObjectName = &nt_name,
+ .SecurityDescriptor = null,
+ .SecurityQualityOfService = null,
+ };
+ const open_reparse_point: w.DWORD = if (!options.follow_symlinks) w.FILE_OPEN_REPARSE_POINT else 0x0;
+ var io_status_block: w.IO_STATUS_BLOCK = undefined;
+ var result: Io.Dir = .{ .handle = undefined };
+ const rc = w.ntdll.NtCreateFile(
+ &result.handle,
+ access_mask,
+ &attr,
+ &io_status_block,
+ null,
+ w.FILE_ATTRIBUTE_NORMAL,
+ w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
+ w.FILE_OPEN,
+ w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
+ null,
+ 0,
+ );
+
+ switch (rc) {
+ .SUCCESS => return result,
+ .OBJECT_NAME_INVALID => return error.BadPathName,
+ .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
+ .OBJECT_NAME_COLLISION => |err| return w.statusBug(err),
+ .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
+ .NOT_A_DIRECTORY => return error.NotDir,
+ // This can happen if the directory has 'List folder contents' permission set to 'Deny'
+ // and the directory is trying to be opened for iteration.
+ .ACCESS_DENIED => return error.AccessDenied,
+ .INVALID_PARAMETER => |err| return w.statusBug(err),
+ else => return w.unexpectedStatus(rc),
+ }
+}
+
+const MakeOpenDirAccessMaskWOptions = struct {
+ no_follow: bool,
+ create_disposition: u32,
+};
+
fn dirClose(userdata: ?*anyopaque, dir: Io.Dir) void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
@@ -2304,17 +2529,17 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestam
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
switch (clock) {
- .realtime => {
+ .real => {
// RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds
// and uses the NTFS/Windows epoch, which is 1601-01-01.
return .{ .nanoseconds = @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100 };
},
- .monotonic, .uptime => {
+ .awake, .boot => {
// QPC on windows doesn't fail on >= XP/2000 and includes time suspended.
- return .{ .timestamp = windows.QueryPerformanceCounter() };
+ return .{ .nanoseconds = windows.QueryPerformanceCounter() };
},
- .process_cputime_id,
- .thread_cputime_id,
+ .cpu_process,
+ .cpu_thread,
=> return error.UnsupportedClock,
}
}
@@ -2360,9 +2585,9 @@ fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
try t.checkCancel();
const ms = ms: {
- const duration_and_clock = (try timeout.toDurationFromNow(t.io())) orelse
+ const d = (try timeout.toDurationFromNow(t.io())) orelse
break :ms std.math.maxInt(windows.DWORD);
- break :ms std.math.lossyCast(windows.DWORD, duration_and_clock.duration.toMilliseconds());
+ break :ms std.math.lossyCast(windows.DWORD, d.raw.toMilliseconds());
};
windows.kernel32.Sleep(ms);
}
lib/std/Io.zig
@@ -660,7 +660,9 @@ pub const VTable = struct {
conditionWaitUncancelable: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) void,
conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
- dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,
+ dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void,
+ dirMakePath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void,
+ dirMakeOpenPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir,
dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
dirAccess: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.AccessOptions) Dir.AccessError!void,