Commit aa6e8eff40

Andrew Kelley <andrew@ziglang.org>
2025-10-20 06:12:19
std.Io.Threaded: implement dirAccess for Windows
1 parent 482343f
lib/std/fs/path.zig
@@ -313,7 +313,7 @@ pub fn isAbsoluteWindowsW(path_w: [*:0]const u16) bool {
     return isAbsoluteWindowsImpl(u16, mem.sliceTo(path_w, 0));
 }
 
-pub fn isAbsoluteWindowsWTF16(path: []const u16) bool {
+pub fn isAbsoluteWindowsWtf16(path: []const u16) bool {
     return isAbsoluteWindowsImpl(u16, path);
 }
 
lib/std/Io/Threaded.zig
@@ -182,7 +182,7 @@ pub fn io(t: *Threaded) Io {
                 else => fileStatPosix,
             },
             .dirAccess = switch (builtin.os.tag) {
-                .windows => @panic("TODO"),
+                .windows => dirAccessWindows,
                 .wasi => dirAccessWasi,
                 else => dirAccessPosix,
             },
@@ -1315,6 +1315,51 @@ fn dirAccessWasi(
         return error.AccessDenied;
 }
 
+fn dirAccessWindows(
+    userdata: ?*anyopaque,
+    dir: Io.Dir,
+    sub_path: []const u8,
+    options: Io.Dir.AccessOptions,
+) Io.Dir.AccessError!void {
+    const t: *Threaded = @ptrCast(@alignCast(userdata));
+    try t.checkCancel();
+
+    _ = options; // TODO
+
+    const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
+    const sub_path_w = sub_path_w_array.span();
+
+    if (sub_path_w[0] == '.' and sub_path_w[1] == 0) return;
+    if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) return;
+
+    const path_len_bytes = std.math.cast(u16, std.mem.sliceTo(sub_path_w, 0).len * 2) orelse
+        return error.NameTooLong;
+    var nt_name: windows.UNICODE_STRING = .{
+        .Length = path_len_bytes,
+        .MaximumLength = path_len_bytes,
+        .Buffer = @constCast(sub_path_w.ptr),
+    };
+    var attr = windows.OBJECT_ATTRIBUTES{
+        .Length = @sizeOf(windows.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,
+    };
+    var basic_info: windows.FILE_BASIC_INFORMATION = undefined;
+    switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
+        .SUCCESS => return,
+        .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
+        .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
+        .OBJECT_NAME_INVALID => |err| return windows.statusBug(err),
+        .INVALID_PARAMETER => |err| return windows.statusBug(err),
+        .ACCESS_DENIED => return error.AccessDenied,
+        .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err),
+        else => |rc| return windows.unexpectedStatus(rc),
+    }
+}
+
 fn dirCreateFilePosix(
     userdata: ?*anyopaque,
     dir: Io.Dir,
@@ -1818,7 +1863,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File
                 var n: DWORD = undefined;
                 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) == 0) {
                     switch (windows.GetLastError()) {
-                        .IO_PENDING => unreachable,
+                        .IO_PENDING => |err| return windows.statusBug(err),
                         .OPERATION_ABORTED => continue,
                         .BROKEN_PIPE => return 0,
                         .HANDLE_EOF => return 0,
@@ -1935,7 +1980,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset
                 } else null;
                 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, overlapped) == 0) {
                     switch (windows.GetLastError()) {
-                        .IO_PENDING => unreachable,
+                        .IO_PENDING => |err| return windows.statusBug(err),
                         .OPERATION_ABORTED => continue,
                         .BROKEN_PIPE => return 0,
                         .HANDLE_EOF => return 0,
lib/std/os/windows.zig
@@ -89,7 +89,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
     };
     var attr = OBJECT_ATTRIBUTES{
         .Length = @sizeOf(OBJECT_ATTRIBUTES),
-        .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.dir,
+        .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
         .Attributes = if (options.sa) |ptr| blk: { // Note we do not use OBJ_CASE_INSENSITIVE here.
             const inherit: ULONG = if (ptr.bInheritHandle == TRUE) OBJ_INHERIT else 0;
             break :blk inherit;
@@ -847,7 +847,7 @@ pub fn CreateSymbolicLink(
                 //       the C:\ drive.
                 .rooted => break :target_path target_path,
                 // Keep relative paths relative, but anything else needs to get NT-prefixed.
-                else => if (!std.fs.path.isAbsoluteWindowsWTF16(target_path))
+                else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path))
                     break :target_path target_path,
             },
             // Already an NT path, no need to do anything to it
@@ -856,7 +856,7 @@ pub fn CreateSymbolicLink(
         }
         var prefixed_target_path = try wToPrefixedFileW(dir, target_path);
         // We do this after prefixing to ensure that drive-relative paths are treated as absolute
-        is_target_absolute = std.fs.path.isAbsoluteWindowsWTF16(prefixed_target_path.span());
+        is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span());
         break :target_path prefixed_target_path.span();
     };
 
@@ -864,7 +864,7 @@ pub fn CreateSymbolicLink(
     var buffer: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
     const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4;
     const header_len = @sizeOf(ULONG) + @sizeOf(USHORT) * 2;
-    const target_is_absolute = std.fs.path.isAbsoluteWindowsWTF16(final_target_path);
+    const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path);
     const symlink_data = SYMLINK_DATA{
         .ReparseTag = IO_REPARSE_TAG_SYMLINK,
         .ReparseDataLength = @intCast(buf_len - header_len),
@@ -905,7 +905,7 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
     };
     var attr = OBJECT_ATTRIBUTES{
         .Length = @sizeOf(OBJECT_ATTRIBUTES),
-        .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else dir,
+        .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir,
         .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
         .ObjectName = &nt_name,
         .SecurityDescriptor = null,
@@ -1035,7 +1035,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
 
     var attr = OBJECT_ATTRIBUTES{
         .Length = @sizeOf(OBJECT_ATTRIBUTES),
-        .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.dir,
+        .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
         .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
         .ObjectName = &nt_name,
         .SecurityDescriptor = null,
@@ -2885,6 +2885,13 @@ pub fn unexpectedStatus(status: NTSTATUS) UnexpectedError {
     return error.Unexpected;
 }
 
+pub fn statusBug(status: NTSTATUS) UnexpectedError {
+    switch (builtin.mode) {
+        .Debug => std.debug.panic("programmer bug caused syscall status: {t}", .{status}),
+        else => return error.Unexpected,
+    }
+}
+
 pub const Win32Error = @import("windows/win32error.zig").Win32Error;
 pub const NTSTATUS = @import("windows/ntstatus.zig").NTSTATUS;
 pub const LANG = @import("windows/lang.zig");
lib/std/fs.zig
@@ -263,7 +263,7 @@ pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.O
 
 /// Same as `openFileAbsolute` but the path parameter is WTF-16-encoded.
 pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
-    assert(path.isAbsoluteWindowsWTF16(absolute_path_w));
+    assert(path.isAbsoluteWindowsWtf16(absolute_path_w));
     return cwd().openFileW(absolute_path_w, flags);
 }
 
lib/std/posix.zig
@@ -2617,7 +2617,7 @@ pub fn renameatW(
         if (ReplaceIfExists == windows.TRUE) flags |= windows.FILE_RENAME_REPLACE_IF_EXISTS;
         rename_info.* = .{
             .Flags = flags,
-            .RootDirectory = if (fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd,
+            .RootDirectory = if (fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd,
             .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
             .FileName = undefined,
         };
@@ -2654,7 +2654,7 @@ pub fn renameatW(
 
         rename_info.* = .{
             .Flags = ReplaceIfExists,
-            .RootDirectory = if (fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd,
+            .RootDirectory = if (fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd,
             .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
             .FileName = undefined,
         };
lib/std/start.zig
@@ -708,7 +708,7 @@ pub inline fn callMain() u8 {
                 switch (native_os) {
                     .freestanding, .other => {},
                     else => if (@errorReturnTrace()) |trace| {
-                        std.debug.dumpStackTrace(trace);
+                        std.debug.dumpStackTrace(trace.*);
                     },
                 }
                 return 1;