Commit dc6a4f3bf1

Andrew Kelley <andrew@ziglang.org>
2025-10-20 08:30:39
std.Io: add dirMakePath and dirMakeOpenPath
1 parent 71c86e1
Changed files (4)
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,