diff --git a/lib/std/c.zig b/lib/std/c.zig index 555a4e4ce..2aeb30c87 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -194,6 +194,20 @@ pub usingnamespace switch (builtin.os.tag) { pub extern "c" fn socket(domain: c_uint, sock_type: c_uint, protocol: c_uint) c_int; pub extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: *Stat) c_int; }, + .windows => struct { + // TODO: copied the else case and removed the socket function (because its in ws2_32) + // need to verify which of these is actually supported on windows + pub extern "c" fn clock_getres(clk_id: c_int, tp: *timespec) c_int; + pub extern "c" fn clock_gettime(clk_id: c_int, tp: *timespec) c_int; + pub extern "c" fn fstat(fd: fd_t, buf: *Stat) c_int; + pub extern "c" fn getrusage(who: c_int, usage: *rusage) c_int; + pub extern "c" fn gettimeofday(noalias tv: ?*timeval, noalias tz: ?*timezone) c_int; + pub extern "c" fn nanosleep(rqtp: *const timespec, rmtp: ?*timespec) c_int; + pub extern "c" fn sched_yield() c_int; + pub extern "c" fn sigaction(sig: c_int, noalias act: *const Sigaction, noalias oact: ?*Sigaction) c_int; + pub extern "c" fn sigprocmask(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int; + pub extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: *Stat) c_int; + }, else => struct { pub extern "c" fn clock_getres(clk_id: c_int, tp: *timespec) c_int; pub extern "c" fn clock_gettime(clk_id: c_int, tp: *timespec) c_int; diff --git a/lib/std/net.zig b/lib/std/net.zig index 067c9026b..1c57f07c1 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -406,7 +406,8 @@ pub fn tcpConnectToHost(allocator: *mem.Allocator, name: []const u8, port: u16) pub fn tcpConnectToAddress(address: Address) !fs.File { const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0; - const sock_flags = os.SOCK_STREAM | os.SOCK_CLOEXEC | nonblock; + const sock_flags = os.SOCK_STREAM | nonblock | + (if (builtin.os.tag == .windows) 0 else os.SOCK_CLOEXEC); const sockfd = try os.socket(address.any.family, sock_flags, os.IPPROTO_TCP); errdefer os.close(sockfd); try os.connect(sockfd, &address.any, address.getOsSockLen()); @@ -431,16 +432,16 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !* const arena = &result.arena.allocator; errdefer result.arena.deinit(); - if (builtin.link_libc) { - const c = std.c; + if (builtin.os.tag == .windows or builtin.link_libc) { const name_c = try std.cstr.addNullByte(allocator, name); defer allocator.free(name_c); const port_c = try std.fmt.allocPrint(allocator, "{}\x00", .{port}); defer allocator.free(port_c); + const sys = if (builtin.os.tag == .windows) os.windows.ws2_32 else os.system; const hints = os.addrinfo{ - .flags = c.AI_NUMERICSERV, + .flags = sys.AI_NUMERICSERV, .family = os.AF_UNSPEC, .socktype = os.SOCK_STREAM, .protocol = os.IPPROTO_TCP, @@ -450,24 +451,42 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !* .next = null, }; var res: *os.addrinfo = undefined; - switch (os.system.getaddrinfo(name_c.ptr, @ptrCast([*:0]const u8, port_c.ptr), &hints, &res)) { - @intToEnum(os.system.EAI, 0) => {}, - .ADDRFAMILY => return error.HostLacksNetworkAddresses, - .AGAIN => return error.TemporaryNameServerFailure, - .BADFLAGS => unreachable, // Invalid hints - .FAIL => return error.NameServerFailure, - .FAMILY => return error.AddressFamilyNotSupported, - .MEMORY => return error.OutOfMemory, - .NODATA => return error.HostLacksNetworkAddresses, - .NONAME => return error.UnknownHostName, - .SERVICE => return error.ServiceUnavailable, - .SOCKTYPE => unreachable, // Invalid socket type requested in hints - .SYSTEM => switch (os.errno(-1)) { - else => |e| return os.unexpectedErrno(e), - }, - else => unreachable, + const rc = sys.getaddrinfo(name_c.ptr, @ptrCast([*:0]const u8, port_c.ptr), &hints, &res); + if (builtin.os.tag == .windows) { + const ws2_32 = os.windows.ws2_32; + switch (rc) { + 0 => {}, + @enumToInt(ws2_32.WinsockError.WSATRY_AGAIN) => return error.TemporaryNameServerFailure, + @enumToInt(ws2_32.WinsockError.WSANO_RECOVERY) => return error.NameServerFailure, + @enumToInt(ws2_32.WinsockError.WSAEAFNOSUPPORT) => return error.AddressFamilyNotSupported, + @enumToInt(ws2_32.WinsockError.WSA_NOT_ENOUGH_MEMORY) => return error.OutOfMemory, + @enumToInt(ws2_32.WinsockError.WSAHOST_NOT_FOUND) => return error.UnknownHostName, + @enumToInt(ws2_32.WinsockError.WSATYPE_NOT_FOUND) => return error.ServiceUnavailable, + @enumToInt(ws2_32.WinsockError.WSAEINVAL) => unreachable, + @enumToInt(ws2_32.WinsockError.WSAESOCKTNOSUPPORT) => unreachable, + else => |err| return os.windows.unexpectedWSAError( + std.meta.intToEnum(ws2_32.WinsockError, err) catch unreachable), + } + } else { + switch (rc) { + @intToEnum(sys.EAI, 0) => {}, + .ADDRFAMILY => return error.HostLacksNetworkAddresses, + .AGAIN => return error.TemporaryNameServerFailure, + .BADFLAGS => unreachable, // Invalid hints + .FAIL => return error.NameServerFailure, + .FAMILY => return error.AddressFamilyNotSupported, + .MEMORY => return error.OutOfMemory, + .NODATA => return error.HostLacksNetworkAddresses, + .NONAME => return error.UnknownHostName, + .SERVICE => return error.ServiceUnavailable, + .SOCKTYPE => unreachable, // Invalid socket type requested in hints + .SYSTEM => switch (os.errno(-1)) { + else => |e| return os.unexpectedErrno(e), + }, + else => unreachable, + } } - defer os.system.freeaddrinfo(res); + defer sys.freeaddrinfo(res); const addr_count = blk: { var count: usize = 0; diff --git a/lib/std/os.zig b/lib/std/os.zig index 22c884fce..79ceffb78 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -69,6 +69,8 @@ else switch (builtin.os.tag) { pub usingnamespace @import("os/bits.zig"); +pub const socket_t = if (builtin.os.tag == .windows) windows.ws2_32.SOCKET else fd_t; + /// See also `getenv`. Populated by startup code before main(). /// TODO this is a footgun because the value will be undefined when using `zig build-lib`. /// https://github.com/ziglang/zig/issues/4524 @@ -2443,7 +2445,21 @@ pub const SocketError = error{ SocketTypeNotSupported, } || UnexpectedError; -pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!fd_t { +pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t { + if (builtin.os.tag == .windows) { + // NOTE: cannot remove SOCK_NONBLOCK and SOCK_CLOEXEC from socket_type because + // windows does not define this flags yet + const rc = windows.ws2_32.socket(@intCast(c_int, domain), @intCast(c_int, socket_type), @intCast(c_int, protocol)); + if (rc != windows.ws2_32.INVALID_SOCKET) return rc; + switch (windows.ws2_32.WSAGetLastError()) { + .WSAEMFILE => return error.ProcessFdQuotaExceeded, + .WSAENOBUFS => return error.SystemResources, + .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, + .WSAEPROTONOSUPPORT => return error.ProtocolNotSupported, + else => |err| return windows.unexpectedWSAError(err), + } + } + const have_sock_flags = comptime !std.Target.current.isDarwin(); const filtered_sock_type = if (!have_sock_flags) socket_type & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC) @@ -2818,7 +2834,29 @@ pub const ConnectError = error{ } || UnexpectedError; /// Initiate a connection on a socket. -pub fn connect(sockfd: fd_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void { +pub fn connect(sockfd: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void { + if (builtin.os.tag == .windows) { + const rc = windows.ws2_32.connect(sockfd, sock_addr, len); + if (rc == 0) return; + switch (windows.ws2_32.WSAGetLastError()) { + .WSAEADDRINUSE => return error.AddressInUse, + .WSAEADDRNOTAVAIL => return error.AddressNotAvailable, + .WSAECONNREFUSED => return error.ConnectionRefused, + .WSAETIMEDOUT => return error.ConnectionTimedOut, + .WSAEHOSTUNREACH // TODO: should we return NetworkUnreachable in this case as well? + ,.WSAENETUNREACH => return error.NetworkUnreachable, + .WSAEFAULT => unreachable, + .WSAEINVAL => unreachable, + .WSAEISCONN => unreachable, + .WSAENOTSOCK => unreachable, + .WSAEWOULDBLOCK => unreachable, + .WSAEACCES => unreachable, + .WSAENOBUFS => return error.SystemResources, + .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, + else => |err| return windows.unexpectedWSAError(err), + } + } + while (true) { switch (errno(system.connect(sockfd, sock_addr, len))) { 0 => return, diff --git a/lib/std/os/bits/windows.zig b/lib/std/os/bits/windows.zig index 05486c8f7..505b34801 100644 --- a/lib/std/os/bits/windows.zig +++ b/lib/std/os/bits/windows.zig @@ -177,6 +177,8 @@ pub const sockaddr_un = ws2_32.sockaddr_un; pub const in6_addr = [16]u8; pub const in_addr = u32; +pub const addrinfo = ws2_32.addrinfo; + pub const AF_UNSPEC = ws2_32.AF_UNSPEC; pub const AF_UNIX = ws2_32.AF_UNIX; pub const AF_INET = ws2_32.AF_INET; diff --git a/lib/std/os/windows/ws2_32.zig b/lib/std/os/windows/ws2_32.zig index d467a60d1..6c1a71204 100644 --- a/lib/std/os/windows/ws2_32.zig +++ b/lib/std/os/windows/ws2_32.zig @@ -163,11 +163,33 @@ pub const IPPROTO_UDP = 17; pub const IPPROTO_ICMPV6 = 58; pub const IPPROTO_RM = 113; +pub const AI_PASSIVE = 0x00001; +pub const AI_CANONNAME = 0x00002; +pub const AI_NUMERICHOST = 0x00004; +pub const AI_NUMERICSERV = 0x00008; +pub const AI_ADDRCONFIG = 0x00400; +pub const AI_V4MAPPED = 0x00800; +pub const AI_NON_AUTHORITATIVE = 0x04000; +pub const AI_SECURE = 0x08000; +pub const AI_RETURN_PREFERRED_NAMES = 0x10000; +pub const AI_DISABLE_IDN_ENCODING = 0x80000; + pub const sockaddr = extern struct { family: ADDRESS_FAMILY, data: [14]u8, }; +pub const addrinfo = extern struct { + flags: i32, + family: i32, + socktype: i32, + protocol: i32, + addrlen: usize, + canonname: ?[*:0]u8, + addr: ?*sockaddr, + next: ?*addrinfo, +}; + /// IPv4 socket address pub const sockaddr_in = extern struct { family: ADDRESS_FAMILY = AF_INET, @@ -702,6 +724,11 @@ pub extern "ws2_32" fn WSAIoctl( lpOverlapped: ?*WSAOVERLAPPED, lpCompletionRoutine: ?WSAOVERLAPPED_COMPLETION_ROUTINE, ) callconv(.Stdcall) c_int; +pub extern "ws2_32" fn socket( + af: c_int, + type: c_int, + protocol: c_int, +) callconv(.Stdcall) SOCKET; pub extern "ws2_32" fn accept( s: SOCKET, addr: ?*sockaddr, @@ -752,3 +779,12 @@ pub extern "ws2_32" fn WSASendTo( lpOverlapped: ?*WSAOVERLAPPED, lpCompletionRoutine: ?WSAOVERLAPPED_COMPLETION_ROUTINE, ) callconv(.Stdcall) c_int; +pub extern "ws2_32" fn getaddrinfo( + pNodeName: [*:0]const u8, + pServiceName: [*:0]const u8, + pHints: *const addrinfo, + ppResult: **addrinfo, +) callconv(.Stdcall) i32; +pub extern "ws2_32" fn freeaddrinfo( + pAddrInfo: *addrinfo, +) callconv(.Stdcall) void;