std.Io.Threaded: fix compilation failures on Windows

This commit is contained in:
Andrew Kelley 2025-10-21 07:10:33 -07:00
parent aadd8d4a3e
commit d257b1337a
4 changed files with 186 additions and 144 deletions

View File

@ -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();
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 n;
}
fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
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;
}
}
return total;
}
const have_pread_but_not_preadv = switch (native_os) {
.windows, .haiku, .serenity => true,
else => false,
};
if (have_pread_but_not_preadv) {
@compileError("TODO");
}
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(),
};
}

View File

@ -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);

View File

@ -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)) {

View File

@ -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");