Commit d257b1337a

Andrew Kelley <andrew@ziglang.org>
2025-10-21 16:10:33
std.Io.Threaded: fix compilation failures on Windows
1 parent aadd8d4
Changed files (4)
lib
std
crypto
Certificate
Io
os
lib/std/crypto/Certificate/Bundle.zig
@@ -144,10 +144,12 @@ fn rescanWithPath(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, cert_f
 
 const RescanWindowsError = Allocator.Error || ParseCertError || std.posix.UnexpectedError || error{FileNotFound};
 
-fn rescanWindows(cb: *Bundle, gpa: Allocator) RescanWindowsError!void {
+fn rescanWindows(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanWindowsError!void {
     cb.bytes.clearRetainingCapacity();
     cb.map.clearRetainingCapacity();
 
+    _ = io;
+
     const w = std.os.windows;
     const GetLastError = w.GetLastError;
     const root = [4:0]u16{ 'R', 'O', 'O', 'T' };
@@ -157,7 +159,7 @@ fn rescanWindows(cb: *Bundle, gpa: Allocator) RescanWindowsError!void {
     };
     defer _ = w.crypt32.CertCloseStore(store, 0);
 
-    const now_sec = std.time.timestamp();
+    const now_sec = now.toSeconds();
 
     var ctx = w.crypt32.CertEnumCertificatesInStore(store, null);
     while (ctx) |context| : (ctx = w.crypt32.CertEnumCertificatesInStore(store, ctx)) {
lib/std/Io/net/test.zig
@@ -186,15 +186,6 @@ test "listen on a port, send bytes, receive bytes" {
 
     const io = testing.io;
 
-    if (builtin.os.tag == .windows) {
-        _ = try std.os.windows.WSAStartup(2, 2);
-    }
-    defer {
-        if (builtin.os.tag == .windows) {
-            std.os.windows.WSACleanup() catch unreachable;
-        }
-    }
-
     // Try only the IPv4 variant as some CI builders have no IPv6 localhost
     // configured.
     const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
@@ -282,15 +273,6 @@ test "listen on a unix socket, send bytes, receive bytes" {
 
     const io = testing.io;
 
-    if (builtin.os.tag == .windows) {
-        _ = try std.os.windows.WSAStartup(2, 2);
-    }
-    defer {
-        if (builtin.os.tag == .windows) {
-            std.os.windows.WSACleanup() catch unreachable;
-        }
-    }
-
     const socket_path = try generateFileName("socket.unix");
     defer testing.allocator.free(socket_path);
 
lib/std/Io/Threaded.zig
@@ -220,8 +220,14 @@ pub fn io(t: *Threaded) Io {
             .fileClose = fileClose,
             .fileWriteStreaming = fileWriteStreaming,
             .fileWritePositional = fileWritePositional,
-            .fileReadStreaming = fileReadStreaming,
-            .fileReadPositional = fileReadPositional,
+            .fileReadStreaming = switch (builtin.os.tag) {
+                .windows => fileReadStreamingWindows,
+                else => fileReadStreamingPosix,
+            },
+            .fileReadPositional = switch (builtin.os.tag) {
+                .windows => fileReadPositionalWindows,
+                else => fileReadPositionalPosix,
+            },
             .fileSeekBy = fileSeekBy,
             .fileSeekTo = fileSeekTo,
             .openSelfExe = openSelfExe,
@@ -258,15 +264,21 @@ pub fn io(t: *Threaded) Io {
             .netConnectUnix = netConnectUnix,
             .netClose = netClose,
             .netRead = switch (builtin.os.tag) {
-                .windows => @panic("TODO"),
+                .windows => netReadWindows,
                 else => netReadPosix,
             },
             .netWrite = switch (builtin.os.tag) {
-                .windows => @panic("TODO"),
+                .windows => netWriteWindows,
                 else => netWritePosix,
             },
-            .netSend = netSend,
-            .netReceive = netReceive,
+            .netSend = switch (builtin.os.tag) {
+                .windows => netSendWindows,
+                else => netSendPosix,
+            },
+            .netReceive = switch (builtin.os.tag) {
+                .windows => netReceiveWindows,
+                else => netReceivePosix,
+            },
             .netInterfaceNameResolve = netInterfaceNameResolve,
             .netInterfaceName = netInterfaceName,
             .netLookup = netLookup,
@@ -284,6 +296,10 @@ const have_futex = switch (builtin.cpu.arch) {
     .wasm32, .wasm64 => builtin.cpu.has(.wasm, .atomics),
     else => true,
 };
+const have_preadv = switch (native_os) {
+    .windows, .haiku, .serenity => false, // ๐Ÿ’ฉ๐Ÿ’ฉ๐Ÿ’ฉ
+    else => true,
+};
 
 const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
 const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
@@ -1899,12 +1915,19 @@ fn dirOpenFileWindows(
     flags: Io.File.OpenFlags,
 ) Io.File.OpenError!Io.File {
     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_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
     const sub_path_w = sub_path_w_array.span();
+    return dirOpenFileWindowsInner(t, dir, sub_path_w, flags);
+}
 
+fn dirOpenFileWindowsInner(
+    t: *Threaded,
+    dir: Io.Dir,
+    sub_path_w: [:0]const u16,
+    flags: Io.File.OpenFlags,
+) Io.File.OpenError!Io.File {
+    try t.checkCancel();
+    const w = windows;
     const handle = try w.OpenFile(sub_path_w, .{
         .dir = dir.handle,
         .access_mask = w.SYNCHRONIZE |
@@ -2247,47 +2270,9 @@ fn fileClose(userdata: ?*anyopaque, file: Io.File) void {
     posix.close(file.handle);
 }
 
-fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.ReadStreamingError!usize {
+fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.ReadStreamingError!usize {
     const t: *Threaded = @ptrCast(@alignCast(userdata));
 
-    if (is_windows) {
-        const DWORD = windows.DWORD;
-        var index: usize = 0;
-        var truncate: usize = 0;
-        var total: usize = 0;
-        while (index < data.len) {
-            try t.checkCancel();
-            {
-                const untruncated = data[index];
-                data[index] = untruncated[truncate..];
-                defer data[index] = untruncated;
-                const buffer = data[index..];
-                const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
-                var n: DWORD = undefined;
-                if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) == 0) {
-                    switch (windows.GetLastError()) {
-                        .IO_PENDING => |err| return windows.statusBug(err),
-                        .OPERATION_ABORTED => continue,
-                        .BROKEN_PIPE => return 0,
-                        .HANDLE_EOF => return 0,
-                        .NETNAME_DELETED => return error.ConnectionResetByPeer,
-                        .LOCK_VIOLATION => return error.LockViolation,
-                        .ACCESS_DENIED => return error.AccessDenied,
-                        .INVALID_HANDLE => return error.NotOpenForReading,
-                        else => |err| return windows.unexpectedError(err),
-                    }
-                }
-                total += n;
-                truncate += n;
-            }
-            while (index < data.len and truncate >= data[index].len) {
-                truncate -= data[index].len;
-                index += 1;
-            }
-        }
-        return total;
-    }
-
     var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
     var i: usize = 0;
     for (data) |buf| {
@@ -2348,70 +2333,37 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File
     }
 }
 
-fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize {
+fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.ReadStreamingError!usize {
     const t: *Threaded = @ptrCast(@alignCast(userdata));
+    try t.checkCancel();
 
-    if (is_windows) {
-        const DWORD = windows.DWORD;
-        const OVERLAPPED = windows.OVERLAPPED;
-        var index: usize = 0;
-        var truncate: usize = 0;
-        var total: usize = 0;
-        while (true) {
-            try t.checkCancel();
-            {
-                const untruncated = data[index];
-                data[index] = untruncated[truncate..];
-                defer data[index] = untruncated;
-                const buffer = data[index..];
-                const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
-                var n: DWORD = undefined;
-                var overlapped_data: OVERLAPPED = undefined;
-                const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
-                    overlapped_data = .{
-                        .Internal = 0,
-                        .InternalHigh = 0,
-                        .DUMMYUNIONNAME = .{
-                            .DUMMYSTRUCTNAME = .{
-                                .Offset = @as(u32, @truncate(off)),
-                                .OffsetHigh = @as(u32, @truncate(off >> 32)),
-                            },
-                        },
-                        .hEvent = null,
-                    };
-                    break :blk &overlapped_data;
-                } else null;
-                if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, overlapped) == 0) {
-                    switch (windows.GetLastError()) {
-                        .IO_PENDING => |err| return windows.statusBug(err),
-                        .OPERATION_ABORTED => continue,
-                        .BROKEN_PIPE => return 0,
-                        .HANDLE_EOF => return 0,
-                        .NETNAME_DELETED => return error.ConnectionResetByPeer,
-                        .LOCK_VIOLATION => return error.LockViolation,
-                        .ACCESS_DENIED => return error.AccessDenied,
-                        .INVALID_HANDLE => return error.NotOpenForReading,
-                        else => |err| return windows.unexpectedError(err),
-                    }
-                }
-                total += n;
-                truncate += n;
-            }
-            while (index < data.len and truncate >= data[index].len) {
-                truncate -= data[index].len;
-                index += 1;
-            }
+    const DWORD = windows.DWORD;
+    var index: usize = 0;
+    while (data[index].len == 0) index += 1;
+
+    const buffer = data[index];
+    const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
+    var n: DWORD = undefined;
+    if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) == 0) {
+        switch (windows.GetLastError()) {
+            .IO_PENDING => |err| return windows.errorBug(err),
+            .OPERATION_ABORTED => return error.Canceled,
+            .BROKEN_PIPE => return 0,
+            .HANDLE_EOF => return 0,
+            .NETNAME_DELETED => return error.ConnectionResetByPeer,
+            .LOCK_VIOLATION => return error.LockViolation,
+            .ACCESS_DENIED => return error.AccessDenied,
+            .INVALID_HANDLE => return error.NotOpenForReading,
+            else => |err| return windows.unexpectedError(err),
         }
-        return total;
     }
+    return n;
+}
 
-    const have_pread_but_not_preadv = switch (native_os) {
-        .windows, .haiku, .serenity => true,
-        else => false,
-    };
-    if (have_pread_but_not_preadv) {
-        @compileError("TODO");
-    }
+fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize {
+    const t: *Threaded = @ptrCast(@alignCast(userdata));
+
+    if (!have_preadv) @compileError("TODO");
 
     var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
     var i: usize = 0;
@@ -2480,6 +2432,48 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset
     }
 }
 
+fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize {
+    const t: *Threaded = @ptrCast(@alignCast(userdata));
+    try t.checkCancel();
+
+    const DWORD = windows.DWORD;
+    const OVERLAPPED = windows.OVERLAPPED;
+
+    var index: usize = 0;
+    while (data[index].len == 0) index += 1;
+
+    const buffer = data[index];
+    const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
+    var n: DWORD = undefined;
+    var overlapped: OVERLAPPED = .{
+        .Internal = 0,
+        .InternalHigh = 0,
+        .DUMMYUNIONNAME = .{
+            .DUMMYSTRUCTNAME = .{
+                .Offset = @as(u32, @truncate(offset)),
+                .OffsetHigh = @as(u32, @truncate(offset >> 32)),
+            },
+        },
+        .hEvent = null,
+    };
+
+    if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) == 0) {
+        switch (windows.GetLastError()) {
+            .IO_PENDING => |err| return windows.errorBug(err),
+            .OPERATION_ABORTED => return error.Canceled,
+            .BROKEN_PIPE => return 0,
+            .HANDLE_EOF => return 0,
+            .NETNAME_DELETED => return error.ConnectionResetByPeer,
+            .LOCK_VIOLATION => return error.LockViolation,
+            .ACCESS_DENIED => return error.AccessDenied,
+            .INVALID_HANDLE => return error.NotOpenForReading,
+            else => |err| return windows.unexpectedError(err),
+        }
+    }
+
+    return n;
+}
+
 fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekError!void {
     const t: *Threaded = @ptrCast(@alignCast(userdata));
     try t.checkCancel();
@@ -2563,11 +2557,9 @@ fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelf
         // the file, we can let the openFileW call follow the symlink for us.
         const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
         const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
-        const prefixed_path_w_array = try windows.wToPrefixedFileW(null, image_path_name);
-        const prefixed_path_w = prefixed_path_w_array.span();
         const cwd_handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle;
 
-        return dirOpenFileWindows(t, .{ .handle = cwd_handle }, prefixed_path_w, flags);
+        return dirOpenFileWindowsInner(t, .{ .handle = cwd_handle }, image_path_name, flags);
     }
     @panic("TODO");
 }
@@ -3493,7 +3485,16 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
     }
 }
 
-fn netSend(
+fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {
+    if (!have_networking) return .{ error.NetworkDown, 0 };
+    const t: *Threaded = @ptrCast(@alignCast(userdata));
+    _ = t;
+    _ = handle;
+    _ = data;
+    @panic("TODO");
+}
+
+fn netSendPosix(
     userdata: ?*anyopaque,
     handle: net.Socket.Handle,
     messages: []net.OutgoingMessage,
@@ -3504,9 +3505,9 @@ fn netSend(
 
     const posix_flags: u32 =
         @as(u32, if (@hasDecl(posix.MSG, "CONFIRM") and flags.confirm) posix.MSG.CONFIRM else 0) |
-        @as(u32, if (flags.dont_route) posix.MSG.DONTROUTE else 0) |
-        @as(u32, if (flags.eor) posix.MSG.EOR else 0) |
-        @as(u32, if (flags.oob) posix.MSG.OOB else 0) |
+        @as(u32, if (@hasDecl(posix.MSG, "DONTROUTE") and flags.dont_route) posix.MSG.DONTROUTE else 0) |
+        @as(u32, if (@hasDecl(posix.MSG, "EOR") and flags.eor) posix.MSG.EOR else 0) |
+        @as(u32, if (@hasDecl(posix.MSG, "OOB") and flags.oob) posix.MSG.OOB else 0) |
         @as(u32, if (@hasDecl(posix.MSG, "FASTOPEN") and flags.fastopen) posix.MSG.FASTOPEN else 0) |
         posix.MSG.NOSIGNAL;
 
@@ -3522,6 +3523,21 @@ fn netSend(
     return .{ null, i };
 }
 
+fn netSendWindows(
+    userdata: ?*anyopaque,
+    handle: net.Socket.Handle,
+    messages: []net.OutgoingMessage,
+    flags: net.SendFlags,
+) struct { ?net.Socket.SendError, usize } {
+    if (!have_networking) return .{ error.NetworkDown, 0 };
+    const t: *Threaded = @ptrCast(@alignCast(userdata));
+    _ = t;
+    _ = handle;
+    _ = messages;
+    _ = flags;
+    @panic("TODO");
+}
+
 fn netSendOne(
     t: *Threaded,
     handle: net.Socket.Handle,
@@ -3676,7 +3692,7 @@ fn netSendMany(
     }
 }
 
-fn netReceive(
+fn netReceivePosix(
     userdata: ?*anyopaque,
     handle: net.Socket.Handle,
     message_buffer: []net.IncomingMessage,
@@ -3805,6 +3821,25 @@ fn netReceive(
     }
 }
 
+fn netReceiveWindows(
+    userdata: ?*anyopaque,
+    handle: net.Socket.Handle,
+    message_buffer: []net.IncomingMessage,
+    data_buffer: []u8,
+    flags: net.ReceiveFlags,
+    timeout: Io.Timeout,
+) struct { ?net.Socket.ReceiveTimeoutError, usize } {
+    if (!have_networking) return .{ error.NetworkDown, 0 };
+    const t: *Threaded = @ptrCast(@alignCast(userdata));
+    _ = t;
+    _ = handle;
+    _ = message_buffer;
+    _ = data_buffer;
+    _ = flags;
+    _ = timeout;
+    @panic("TODO");
+}
+
 fn netWritePosix(
     userdata: ?*anyopaque,
     fd: net.Socket.Handle,
@@ -3887,6 +3922,22 @@ fn netWritePosix(
     }
 }
 
+fn netWriteWindows(
+    userdata: ?*anyopaque,
+    handle: net.Socket.Handle,
+    header: []const u8,
+    data: []const []const u8,
+    splat: usize,
+) net.Stream.Writer.Error!usize {
+    const t: *Threaded = @ptrCast(@alignCast(userdata));
+    _ = t;
+    _ = handle;
+    _ = header;
+    _ = data;
+    _ = splat;
+    @panic("TODO");
+}
+
 fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void {
     // OS checks ptr addr before length so zero length vectors must be omitted.
     if (bytes.len == 0) return;
@@ -3899,7 +3950,7 @@ fn netClose(userdata: ?*anyopaque, handle: net.Socket.Handle) void {
     const t: *Threaded = @ptrCast(@alignCast(userdata));
     _ = t;
     switch (native_os) {
-        .windows => closeSocketWindows(handle) catch recoverableOsBugDetected(),
+        .windows => closeSocketWindows(handle),
         else => posix.close(handle),
     }
 }
@@ -4559,7 +4610,7 @@ fn lookupDns(
                     message_i += 1;
                 }
             }
-            _ = netSend(t, socket.handle, message_buffer[0..message_i], .{});
+            _ = netSendPosix(t, socket.handle, message_buffer[0..message_i], .{});
         }
 
         const timeout: Io.Timeout = .{ .deadline = .{
@@ -4607,7 +4658,7 @@ fn lookupDns(
                             .data_ptr = query.ptr,
                             .data_len = query.len,
                         };
-                        _ = netSend(t, socket.handle, (&retry_message)[0..1], .{});
+                        _ = netSendPosix(t, socket.handle, (&retry_message)[0..1], .{});
                         continue;
                     },
                     else => continue,
@@ -5157,9 +5208,9 @@ fn closeSocketWindows(s: ws2_32.SOCKET) void {
     if (builtin.mode == .Debug) switch (rc) {
         0 => {},
         ws2_32.SOCKET_ERROR => switch (ws2_32.WSAGetLastError()) {
-            else => unreachable,
+            else => recoverableOsBugDetected(),
         },
-        else => unreachable,
+        else => recoverableOsBugDetected(),
     };
 }
 
lib/std/os/windows.zig
@@ -2735,6 +2735,13 @@ pub fn statusBug(status: NTSTATUS) UnexpectedError {
     }
 }
 
+pub fn errorBug(err: Win32Error) UnexpectedError {
+    switch (builtin.mode) {
+        .Debug => std.debug.panic("programmer bug caused syscall status: {t}", .{err}),
+        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");