diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 3d6e700a5a..a67b96da03 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -2,12 +2,12 @@ gpa: Allocator, thread_pool: *std.Thread.Pool, graph: *const Build.Graph, all_steps: []const *Build.Step, -listen_address: std.net.Address, +listen_address: net.IpAddress, ttyconf: std.Io.tty.Config, root_prog_node: std.Progress.Node, watch: bool, -tcp_server: ?std.net.Server, +tcp_server: ?net.Server, serve_thread: ?std.Thread, base_timestamp: i128, @@ -56,7 +56,7 @@ pub const Options = struct { ttyconf: std.Io.tty.Config, root_prog_node: std.Progress.Node, watch: bool, - listen_address: std.net.Address, + listen_address: net.IpAddress, }; pub fn init(opts: Options) WebServer { // The upcoming `std.Io` interface should allow us to use `Io.async` and `Io.concurrent` @@ -244,7 +244,7 @@ pub fn now(s: *const WebServer) i64 { return @intCast(std.time.nanoTimestamp() - s.base_timestamp); } -fn accept(ws: *WebServer, connection: std.net.Server.Connection) void { +fn accept(ws: *WebServer, connection: net.Server.Connection) void { defer connection.stream.close(); var send_buffer: [4096]u8 = undefined; @@ -851,5 +851,6 @@ const Cache = Build.Cache; const Fuzz = Build.Fuzz; const abi = Build.abi; const http = std.http; +const net = std.Io.net; const WebServer = @This(); diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index abd74bdd98..a3be8f2c11 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -1,6 +1,8 @@ const File = @This(); const builtin = @import("builtin"); +const native_os = builtin.os.tag; +const is_windows = native_os == .windows; const std = @import("../std.zig"); const Io = std.Io; @@ -131,6 +133,18 @@ pub const Stat = struct { } }; +pub fn stdout() File { + return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdOutput else std.posix.STDOUT_FILENO }; +} + +pub fn stderr() File { + return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdError else std.posix.STDERR_FILENO }; +} + +pub fn stdin() File { + return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdInput else std.posix.STDIN_FILENO }; +} + pub const StatError = std.posix.FStatError || Io.Cancelable; /// Returns `Stat` containing basic information about the `File`. @@ -183,6 +197,11 @@ pub fn write(file: File, io: Io, buffer: []const u8) WriteError!usize { return @errorCast(file.pwrite(io, buffer, -1)); } +pub fn writeAll(file: File, io: Io, bytes: []const u8) WriteError!void { + var index: usize = 0; + while (index < bytes.len) index += try file.write(io, bytes[index..]); +} + pub const PWriteError = std.fs.File.PWriteError || Io.Cancelable; pub fn pwrite(file: File, io: Io, buffer: []const u8, offset: std.posix.off_t) PWriteError!usize { @@ -350,7 +369,7 @@ pub const Reader = struct { const io = r.io; switch (r.mode) { .positional, .positional_reading => { - setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset)); + setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); }, .streaming, .streaming_reading => { if (std.posix.SEEK == void) { @@ -359,7 +378,7 @@ pub const Reader = struct { } const seek_err = r.seek_err orelse e: { if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| { - setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset)); + setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); return; } else |err| { r.seek_err = err; @@ -384,16 +403,17 @@ pub const Reader = struct { const io = r.io; switch (r.mode) { .positional, .positional_reading => { - setPosAdjustingBuffer(r, offset); + setLogicalPos(r, offset); }, .streaming, .streaming_reading => { - if (offset >= r.pos) return Reader.seekBy(r, @intCast(offset - r.pos)); + const logical_pos = logicalPos(r); + if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos)); if (r.seek_err) |err| return err; io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| { r.seek_err = err; return err; }; - setPosAdjustingBuffer(r, offset); + setLogicalPos(r, offset); }, .failure => return r.seek_err.?, } @@ -403,7 +423,7 @@ pub const Reader = struct { return r.pos - r.interface.bufferedLen(); } - fn setPosAdjustingBuffer(r: *Reader, offset: u64) void { + fn setLogicalPos(r: *Reader, offset: u64) void { const logical_pos = logicalPos(r); if (offset < logical_pos or offset >= r.pos) { r.interface.seek = 0; @@ -544,9 +564,10 @@ pub const Reader = struct { } } + /// Returns whether the stream is at the logical end. pub fn atEnd(r: *Reader) bool { // Even if stat fails, size is set when end is encountered. const size = r.size orelse return false; - return size - r.pos == 0; + return size - logicalPos(r) == 0; } }; diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index 5672abd071..178aa75ca4 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -43,6 +43,14 @@ pub const Protocol = enum(u32) { mptcp = 262, }; +/// Windows 10 added support for unix sockets in build 17063, redstone 4 is the +/// first release to support them. +pub const has_unix_sockets = switch (native_os) { + .windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false, + .wasi => false, + else => true, +}; + pub const IpAddress = union(enum) { ip4: Ip4Address, ip6: Ip6Address, @@ -980,7 +988,13 @@ pub const Stream = struct { stream: Stream, err: ?Error, - pub const Error = std.net.Stream.ReadError || Io.Cancelable || Io.Writer.Error || error{EndOfStream}; + pub const Error = std.posix.ReadError || error{ + SocketNotBound, + MessageTooBig, + NetworkSubsystemFailed, + ConnectionResetByPeer, + SocketUnconnected, + } || Io.Cancelable || Io.Writer.Error || error{EndOfStream}; pub fn init(stream: Stream, buffer: []u8) Reader { return .{ @@ -1019,7 +1033,15 @@ pub const Stream = struct { stream: Stream, err: ?Error = null, - pub const Error = std.net.Stream.WriteError || Io.Cancelable; + pub const Error = std.posix.SendMsgError || error{ + ConnectionResetByPeer, + SocketNotBound, + MessageTooBig, + NetworkSubsystemFailed, + SystemResources, + SocketUnconnected, + Unexpected, + } || Io.Cancelable; pub fn init(stream: Stream, buffer: []u8) Writer { return .{ @@ -1096,4 +1118,5 @@ fn testIp6ParseTransform(expected: []const u8, input: []const u8) !void { test { _ = HostName; + _ = @import("net/test.zig"); } diff --git a/lib/std/net/test.zig b/lib/std/Io/net/test.zig similarity index 77% rename from lib/std/net/test.zig rename to lib/std/Io/net/test.zig index cf736591fc..3e561d2db3 100644 --- a/lib/std/net/test.zig +++ b/lib/std/Io/net/test.zig @@ -1,38 +1,38 @@ const std = @import("std"); const builtin = @import("builtin"); -const net = std.net; +const net = std.Io.net; const mem = std.mem; const testing = std.testing; test "parse and render IP addresses at comptime" { comptime { - const ipv6addr = net.Address.parseIp("::1", 0) catch unreachable; + const ipv6addr = net.IpAddress.parseIp("::1", 0) catch unreachable; try std.testing.expectFmt("[::1]:0", "{f}", .{ipv6addr}); - const ipv4addr = net.Address.parseIp("127.0.0.1", 0) catch unreachable; + const ipv4addr = net.IpAddress.parseIp("127.0.0.1", 0) catch unreachable; try std.testing.expectFmt("127.0.0.1:0", "{f}", .{ipv4addr}); - try testing.expectError(error.InvalidIpAddressFormat, net.Address.parseIp("::123.123.123.123", 0)); - try testing.expectError(error.InvalidIpAddressFormat, net.Address.parseIp("127.01.0.1", 0)); - try testing.expectError(error.InvalidIpAddressFormat, net.Address.resolveIp("::123.123.123.123", 0)); - try testing.expectError(error.InvalidIpAddressFormat, net.Address.resolveIp("127.01.0.1", 0)); + try testing.expectError(error.InvalidIpAddressFormat, net.IpAddress.parseIp("::123.123.123.123", 0)); + try testing.expectError(error.InvalidIpAddressFormat, net.IpAddress.parseIp("127.01.0.1", 0)); + try testing.expectError(error.InvalidIpAddressFormat, net.IpAddress.resolveIp("::123.123.123.123", 0)); + try testing.expectError(error.InvalidIpAddressFormat, net.IpAddress.resolveIp("127.01.0.1", 0)); } } test "format IPv6 address with no zero runs" { - const addr = try std.net.Address.parseIp6("2001:db8:1:2:3:4:5:6", 0); + const addr = try std.net.IpAddress.parseIp6("2001:db8:1:2:3:4:5:6", 0); try std.testing.expectFmt("[2001:db8:1:2:3:4:5:6]:0", "{f}", .{addr}); } test "parse IPv6 addresses and check compressed form" { try std.testing.expectFmt("[2001:db8::1:0:0:2]:0", "{f}", .{ - try std.net.Address.parseIp6("2001:0db8:0000:0000:0001:0000:0000:0002", 0), + try std.net.IpAddress.parseIp6("2001:0db8:0000:0000:0001:0000:0000:0002", 0), }); try std.testing.expectFmt("[2001:db8::1:2]:0", "{f}", .{ - try std.net.Address.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0), + try std.net.IpAddress.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0), }); try std.testing.expectFmt("[2001:db8:1:0:1::2]:0", "{f}", .{ - try std.net.Address.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0), + try std.net.IpAddress.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0), }); } @@ -44,7 +44,7 @@ test "parse IPv6 address, check raw bytes" { 0x00, 0x00, 0x00, 0x02, // :0000:0002 }; - const addr = try std.net.Address.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0); + const addr = try std.net.IpAddress.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0); const actual_raw = addr.in6.sa.addr[0..]; try std.testing.expectEqualSlices(u8, expected_raw[0..], actual_raw); @@ -77,30 +77,30 @@ test "parse and render IPv6 addresses" { "::ffff:123.5.123.5", }; for (ips, 0..) |ip, i| { - const addr = net.Address.parseIp6(ip, 0) catch unreachable; + const addr = net.IpAddress.parseIp6(ip, 0) catch unreachable; var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable; try std.testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3])); if (builtin.os.tag == .linux) { - const addr_via_resolve = net.Address.resolveIp6(ip, 0) catch unreachable; + const addr_via_resolve = net.IpAddress.resolveIp6(ip, 0) catch unreachable; var newResolvedIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr_via_resolve}) catch unreachable; try std.testing.expect(std.mem.eql(u8, printed[i], newResolvedIp[1 .. newResolvedIp.len - 3])); } } - try testing.expectError(error.InvalidCharacter, net.Address.parseIp6(":::", 0)); - try testing.expectError(error.Overflow, net.Address.parseIp6("FF001::FB", 0)); - try testing.expectError(error.InvalidCharacter, net.Address.parseIp6("FF01::Fb:zig", 0)); - try testing.expectError(error.InvalidEnd, net.Address.parseIp6("FF01:0:0:0:0:0:0:FB:", 0)); - try testing.expectError(error.Incomplete, net.Address.parseIp6("FF01:", 0)); - try testing.expectError(error.InvalidIpv4Mapping, net.Address.parseIp6("::123.123.123.123", 0)); - try testing.expectError(error.Incomplete, net.Address.parseIp6("1", 0)); + try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp6(":::", 0)); + try testing.expectError(error.Overflow, net.IpAddress.parseIp6("FF001::FB", 0)); + try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp6("FF01::Fb:zig", 0)); + try testing.expectError(error.InvalidEnd, net.IpAddress.parseIp6("FF01:0:0:0:0:0:0:FB:", 0)); + try testing.expectError(error.Incomplete, net.IpAddress.parseIp6("FF01:", 0)); + try testing.expectError(error.InvalidIpv4Mapping, net.IpAddress.parseIp6("::123.123.123.123", 0)); + try testing.expectError(error.Incomplete, net.IpAddress.parseIp6("1", 0)); // TODO Make this test pass on other operating systems. if (builtin.os.tag == .linux or comptime builtin.os.tag.isDarwin() or builtin.os.tag == .windows) { - try testing.expectError(error.Incomplete, net.Address.resolveIp6("ff01::fb%", 0)); + try testing.expectError(error.Incomplete, net.IpAddress.resolveIp6("ff01::fb%", 0)); // Assumes IFNAMESIZE will always be a multiple of 2 - try testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%wlp3" ++ "s0" ** @divExact(std.posix.IFNAMESIZE - 4, 2), 0)); - try testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%12345678901234", 0)); + try testing.expectError(error.Overflow, net.IpAddress.resolveIp6("ff01::fb%wlp3" ++ "s0" ** @divExact(std.posix.IFNAMESIZE - 4, 2), 0)); + try testing.expectError(error.Overflow, net.IpAddress.resolveIp6("ff01::fb%12345678901234", 0)); } } @@ -111,7 +111,7 @@ test "invalid but parseable IPv6 scope ids" { return error.SkipZigTest; } - try testing.expectError(error.InterfaceNotFound, net.Address.resolveIp6("ff01::fb%123s45678901234", 0)); + try testing.expectError(error.InterfaceNotFound, net.IpAddress.resolveIp6("ff01::fb%123s45678901234", 0)); } test "parse and render IPv4 addresses" { @@ -123,17 +123,17 @@ test "parse and render IPv4 addresses" { "123.255.0.91", "127.0.0.1", }) |ip| { - const addr = net.Address.parseIp4(ip, 0) catch unreachable; + const addr = net.IpAddress.parseIp4(ip, 0) catch unreachable; var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable; try std.testing.expect(std.mem.eql(u8, ip, newIp[0 .. newIp.len - 2])); } - try testing.expectError(error.Overflow, net.Address.parseIp4("256.0.0.1", 0)); - try testing.expectError(error.InvalidCharacter, net.Address.parseIp4("x.0.0.1", 0)); - try testing.expectError(error.InvalidEnd, net.Address.parseIp4("127.0.0.1.1", 0)); - try testing.expectError(error.Incomplete, net.Address.parseIp4("127.0.0.", 0)); - try testing.expectError(error.InvalidCharacter, net.Address.parseIp4("100..0.1", 0)); - try testing.expectError(error.NonCanonical, net.Address.parseIp4("127.01.0.1", 0)); + try testing.expectError(error.Overflow, net.IpAddress.parseIp4("256.0.0.1", 0)); + try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp4("x.0.0.1", 0)); + try testing.expectError(error.InvalidEnd, net.IpAddress.parseIp4("127.0.0.1.1", 0)); + try testing.expectError(error.Incomplete, net.IpAddress.parseIp4("127.0.0.", 0)); + try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp4("100..0.1", 0)); + try testing.expectError(error.NonCanonical, net.IpAddress.parseIp4("127.01.0.1", 0)); } test "parse and render UNIX addresses" { @@ -161,8 +161,8 @@ test "resolve DNS" { // Resolve localhost, this should not fail. { - const localhost_v4 = try net.Address.parseIp("127.0.0.1", 80); - const localhost_v6 = try net.Address.parseIp("::2", 80); + const localhost_v4 = try net.IpAddress.parseIp("127.0.0.1", 80); + const localhost_v6 = try net.IpAddress.parseIp("::2", 80); const result = try net.getAddressList(testing.allocator, "localhost", 80); defer result.deinit(); @@ -198,13 +198,13 @@ test "listen on a port, send bytes, receive bytes" { // Try only the IPv4 variant as some CI builders have no IPv6 localhost // configured. - const localhost = try net.Address.parseIp("127.0.0.1", 0); + const localhost = try net.IpAddress.parseIp("127.0.0.1", 0); var server = try localhost.listen(.{}); defer server.deinit(); const S = struct { - fn clientFn(server_address: net.Address) !void { + fn clientFn(server_address: net.IpAddress) !void { const socket = try net.tcpConnectToAddress(server_address); defer socket.close(); @@ -232,7 +232,7 @@ test "listen on an in use port" { return error.SkipZigTest; } - const localhost = try net.Address.parseIp("127.0.0.1", 0); + const localhost = try net.IpAddress.parseIp("127.0.0.1", 0); var server1 = try localhost.listen(.{ .reuse_address = true }); defer server1.deinit(); @@ -253,7 +253,7 @@ fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyer try testing.expect(mem.eql(u8, msg, "hello from server\n")); } -fn testClient(addr: net.Address) anyerror!void { +fn testClient(addr: net.IpAddress) anyerror!void { if (builtin.os.tag == .wasi) return error.SkipZigTest; const socket_file = try net.tcpConnectToAddress(addr); @@ -290,7 +290,7 @@ test "listen on a unix socket, send bytes, receive bytes" { const socket_path = try generateFileName("socket.unix"); defer testing.allocator.free(socket_path); - const socket_addr = try net.Address.initUnix(socket_path); + const socket_addr = try net.IpAddress.initUnix(socket_path); defer std.fs.cwd().deleteFile(socket_path) catch {}; var server = try socket_addr.listen(.{}); @@ -351,7 +351,7 @@ test "non-blocking tcp server" { return error.SkipZigTest; } - const localhost = try net.Address.parseIp("127.0.0.1", 0); + const localhost = try net.IpAddress.parseIp("127.0.0.1", 0); var server = localhost.listen(.{ .force_nonblocking = true }); defer server.deinit(); diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 102b4d8404..d9ec89b893 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -523,9 +523,7 @@ pub fn setStatus(new_status: Status) void { /// Returns whether a resize is needed to learn the terminal size. fn wait(timeout_ns: u64) bool { - const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_| - true - else |err| switch (err) { + const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_| true else |err| switch (err) { error.Timeout => false, }; global_progress.redraw_event.reset(); diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 46698cbe99..6da58e17bc 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -71,7 +71,7 @@ pub const ResetEvent = enum(u32) { /// /// The memory accesses before the set() can be said to happen before /// timedWait() returns without error. - pub fn timedWait(re: *ResetEvent, timeout_ns: u64) void { + pub fn timedWait(re: *ResetEvent, timeout_ns: u64) error{Timeout}!void { if (builtin.single_threaded) switch (re.*) { .unset => { sleep(timeout_ns); @@ -1774,9 +1774,9 @@ test "setName, getName" { if (builtin.single_threaded) return error.SkipZigTest; const Context = struct { - start_wait_event: ResetEvent = .{}, - test_done_event: ResetEvent = .{}, - thread_done_event: ResetEvent = .{}, + start_wait_event: ResetEvent = .unset, + test_done_event: ResetEvent = .unset, + thread_done_event: ResetEvent = .unset, done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), thread: Thread = undefined, @@ -1843,7 +1843,7 @@ test join { if (builtin.single_threaded) return error.SkipZigTest; var value: usize = 0; - var event = ResetEvent{}; + var event: ResetEvent = .unset; const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event }); thread.join(); @@ -1855,7 +1855,7 @@ test detach { if (builtin.single_threaded) return error.SkipZigTest; var value: usize = 0; - var event = ResetEvent{}; + var event: ResetEvent = .unset; const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event }); thread.detach(); @@ -1902,8 +1902,7 @@ fn testTls() !void { } test "ResetEvent smoke test" { - // make sure the event is unset - var event = ResetEvent{}; + var event: ResetEvent = .unset; try testing.expectEqual(false, event.isSet()); // make sure the event gets set @@ -1932,8 +1931,8 @@ test "ResetEvent signaling" { } const Context = struct { - in: ResetEvent = .{}, - out: ResetEvent = .{}, + in: ResetEvent = .unset, + out: ResetEvent = .unset, value: usize = 0, fn input(self: *@This()) !void { @@ -1994,7 +1993,7 @@ test "ResetEvent broadcast" { const num_threads = 10; const Barrier = struct { - event: ResetEvent = .{}, + event: ResetEvent = .unset, counter: std.atomic.Value(usize) = std.atomic.Value(usize).init(num_threads), fn wait(self: *@This()) void { diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 0470ee4e2a..7d5ac75276 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -97,25 +97,6 @@ pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null); /// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem. pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null); -/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path` -/// are absolute. See `Dir.updateFile` for a function that operates on both -/// absolute and relative paths. -/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn updateFileAbsolute( - source_path: []const u8, - dest_path: []const u8, - args: Dir.CopyFileOptions, -) !std.Io.Dir.PrevStatus { - assert(path.isAbsolute(source_path)); - assert(path.isAbsolute(dest_path)); - const my_cwd = cwd(); - return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, args); -} - -test updateFileAbsolute {} - /// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path` /// are absolute. See `Dir.copyFile` for a function that operates on both /// absolute and relative paths. diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index b79ef44d3b..7c94fc1df6 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -698,17 +698,6 @@ pub fn read(self: File, buffer: []u8) ReadError!usize { return posix.read(self.handle, buffer); } -/// Deprecated in favor of `Reader`. -pub fn readAll(self: File, buffer: []u8) ReadError!usize { - var index: usize = 0; - while (index != buffer.len) { - const amt = try self.read(buffer[index..]); - if (amt == 0) break; - index += amt; - } - return index; -} - /// On Windows, this function currently does alter the file pointer. /// https://github.com/ziglang/zig/issues/12783 pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize { @@ -719,17 +708,6 @@ pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize { return posix.pread(self.handle, buffer, offset); } -/// Deprecated in favor of `Reader`. -pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize { - var index: usize = 0; - while (index != buffer.len) { - const amt = try self.pread(buffer[index..], offset + index); - if (amt == 0) break; - index += amt; - } - return index; -} - /// See https://github.com/ziglang/zig/issues/7699 pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize { if (is_windows) { @@ -741,36 +719,6 @@ pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize { return posix.readv(self.handle, iovecs); } -/// Deprecated in favor of `Reader`. -pub fn readvAll(self: File, iovecs: []posix.iovec) ReadError!usize { - if (iovecs.len == 0) return 0; - - // We use the address of this local variable for all zero-length - // vectors so that the OS does not complain that we are giving it - // addresses outside the application's address space. - var garbage: [1]u8 = undefined; - for (iovecs) |*v| { - if (v.len == 0) v.base = &garbage; - } - - var i: usize = 0; - var off: usize = 0; - while (true) { - var amt = try self.readv(iovecs[i..]); - var eof = amt == 0; - off += amt; - while (amt >= iovecs[i].len) { - amt -= iovecs[i].len; - i += 1; - if (i >= iovecs.len) return off; - eof = false; - } - if (eof) return off; - iovecs[i].base += amt; - iovecs[i].len -= amt; - } -} - /// See https://github.com/ziglang/zig/issues/7699 /// On Windows, this function currently does alter the file pointer. /// https://github.com/ziglang/zig/issues/12783 @@ -784,28 +732,6 @@ pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!u return posix.preadv(self.handle, iovecs, offset); } -/// Deprecated in favor of `Reader`. -pub fn preadvAll(self: File, iovecs: []posix.iovec, offset: u64) PReadError!usize { - if (iovecs.len == 0) return 0; - - var i: usize = 0; - var off: usize = 0; - while (true) { - var amt = try self.preadv(iovecs[i..], offset + off); - var eof = amt == 0; - off += amt; - while (amt >= iovecs[i].len) { - amt -= iovecs[i].len; - i += 1; - if (i >= iovecs.len) return off; - eof = false; - } - if (eof) return off; - iovecs[i].base += amt; - iovecs[i].len -= amt; - } -} - pub const WriteError = posix.WriteError; pub const PWriteError = posix.PWriteError; @@ -817,7 +743,6 @@ pub fn write(self: File, bytes: []const u8) WriteError!usize { return posix.write(self.handle, bytes); } -/// Deprecated in favor of `Writer`. pub fn writeAll(self: File, bytes: []const u8) WriteError!void { var index: usize = 0; while (index < bytes.len) { @@ -835,14 +760,6 @@ pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize { return posix.pwrite(self.handle, bytes, offset); } -/// Deprecated in favor of `Writer`. -pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void { - var index: usize = 0; - while (index < bytes.len) { - index += try self.pwrite(bytes[index..], offset + index); - } -} - /// See https://github.com/ziglang/zig/issues/7699 pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize { if (is_windows) { @@ -855,31 +772,6 @@ pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize { return posix.writev(self.handle, iovecs); } -/// Deprecated in favor of `Writer`. -pub fn writevAll(self: File, iovecs: []posix.iovec_const) WriteError!void { - if (iovecs.len == 0) return; - - // We use the address of this local variable for all zero-length - // vectors so that the OS does not complain that we are giving it - // addresses outside the application's address space. - var garbage: [1]u8 = undefined; - for (iovecs) |*v| { - if (v.len == 0) v.base = &garbage; - } - - var i: usize = 0; - while (true) { - var amt = try self.writev(iovecs[i..]); - while (amt >= iovecs[i].len) { - amt -= iovecs[i].len; - i += 1; - if (i >= iovecs.len) return; - } - iovecs[i].base += amt; - iovecs[i].len -= amt; - } -} - /// See https://github.com/ziglang/zig/issues/7699 /// On Windows, this function currently does alter the file pointer. /// https://github.com/ziglang/zig/issues/12783 @@ -893,485 +785,8 @@ pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError return posix.pwritev(self.handle, iovecs, offset); } -/// Deprecated in favor of `Writer`. -pub fn pwritevAll(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!void { - if (iovecs.len == 0) return; - var i: usize = 0; - var off: u64 = 0; - while (true) { - var amt = try self.pwritev(iovecs[i..], offset + off); - off += amt; - while (amt >= iovecs[i].len) { - amt -= iovecs[i].len; - i += 1; - if (i >= iovecs.len) return; - } - iovecs[i].base += amt; - iovecs[i].len -= amt; - } -} - -pub const CopyRangeError = posix.CopyFileRangeError; - -/// Deprecated in favor of `Writer`. -pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 { - const adjusted_len = math.cast(usize, len) orelse maxInt(usize); - const result = try posix.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0); - return result; -} - -/// Deprecated in favor of `Writer`. -pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 { - var total_bytes_copied: u64 = 0; - var in_off = in_offset; - var out_off = out_offset; - while (total_bytes_copied < len) { - const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied); - if (amt_copied == 0) return total_bytes_copied; - total_bytes_copied += amt_copied; - in_off += amt_copied; - out_off += amt_copied; - } - return total_bytes_copied; -} - -/// Memoizes key information about a file handle such as: -/// * The size from calling stat, or the error that occurred therein. -/// * The current seek position. -/// * The error that occurred when trying to seek. -/// * Whether reading should be done positionally or streaming. -/// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`) -/// versus plain variants (e.g. `read`). -/// -/// Fulfills the `std.Io.Reader` interface. -pub const Reader = struct { - file: File, - err: ?ReadError = null, - mode: Reader.Mode = .positional, - /// Tracks the true seek position in the file. To obtain the logical - /// position, use `logicalPos`. - pos: u64 = 0, - size: ?u64 = null, - size_err: ?SizeError = null, - seek_err: ?Reader.SeekError = null, - interface: std.Io.Reader, - - pub const SizeError = std.os.windows.GetFileSizeError || StatError || error{ - /// Occurs if, for example, the file handle is a network socket and therefore does not have a size. - Streaming, - }; - - pub const SeekError = File.SeekError || error{ - /// Seeking fell back to reading, and reached the end before the requested seek position. - /// `pos` remains at the end of the file. - EndOfStream, - /// Seeking fell back to reading, which failed. - ReadFailed, - }; - - pub const Mode = enum { - streaming, - positional, - /// Avoid syscalls other than `read` and `readv`. - streaming_reading, - /// Avoid syscalls other than `pread` and `preadv`. - positional_reading, - /// Indicates reading cannot continue because of a seek failure. - failure, - - pub fn toStreaming(m: @This()) @This() { - return switch (m) { - .positional, .streaming => .streaming, - .positional_reading, .streaming_reading => .streaming_reading, - .failure => .failure, - }; - } - - pub fn toReading(m: @This()) @This() { - return switch (m) { - .positional, .positional_reading => .positional_reading, - .streaming, .streaming_reading => .streaming_reading, - .failure => .failure, - }; - } - }; - - pub fn initInterface(buffer: []u8) std.Io.Reader { - return .{ - .vtable = &.{ - .stream = Reader.stream, - .discard = Reader.discard, - .readVec = Reader.readVec, - }, - .buffer = buffer, - .seek = 0, - .end = 0, - }; - } - - pub fn init(file: File, buffer: []u8) Reader { - return .{ - .file = file, - .interface = initInterface(buffer), - }; - } - - pub fn initSize(file: File, buffer: []u8, size: ?u64) Reader { - return .{ - .file = file, - .interface = initInterface(buffer), - .size = size, - }; - } - - /// Positional is more threadsafe, since the global seek position is not - /// affected, but when such syscalls are not available, preemptively - /// initializing in streaming mode skips a failed syscall. - pub fn initStreaming(file: File, buffer: []u8) Reader { - return .{ - .file = file, - .interface = Reader.initInterface(buffer), - .mode = .streaming, - .seek_err = error.Unseekable, - .size_err = error.Streaming, - }; - } - - pub fn getSize(r: *Reader) SizeError!u64 { - return r.size orelse { - if (r.size_err) |err| return err; - if (is_windows) { - if (windows.GetFileSizeEx(r.file.handle)) |size| { - r.size = size; - return size; - } else |err| { - r.size_err = err; - return err; - } - } - if (posix.Stat == void) { - r.size_err = error.Streaming; - return error.Streaming; - } - if (stat(r.file)) |st| { - if (st.kind == .file) { - r.size = st.size; - return st.size; - } else { - r.mode = r.mode.toStreaming(); - r.size_err = error.Streaming; - return error.Streaming; - } - } else |err| { - r.size_err = err; - return err; - } - }; - } - - pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void { - switch (r.mode) { - .positional, .positional_reading => { - setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); - }, - .streaming, .streaming_reading => { - if (posix.SEEK == void) { - r.seek_err = error.Unseekable; - return error.Unseekable; - } - const seek_err = r.seek_err orelse e: { - if (posix.lseek_CUR(r.file.handle, offset)) |_| { - setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); - return; - } else |err| { - r.seek_err = err; - break :e err; - } - }; - var remaining = std.math.cast(u64, offset) orelse return seek_err; - while (remaining > 0) { - remaining -= discard(&r.interface, .limited64(remaining)) catch |err| { - r.seek_err = err; - return err; - }; - } - r.interface.seek = 0; - r.interface.end = 0; - }, - .failure => return r.seek_err.?, - } - } - - pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void { - switch (r.mode) { - .positional, .positional_reading => { - setLogicalPos(r, offset); - }, - .streaming, .streaming_reading => { - const logical_pos = logicalPos(r); - if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos)); - if (r.seek_err) |err| return err; - posix.lseek_SET(r.file.handle, offset) catch |err| { - r.seek_err = err; - return err; - }; - setLogicalPos(r, offset); - }, - .failure => return r.seek_err.?, - } - } - - pub fn logicalPos(r: *const Reader) u64 { - return r.pos - r.interface.bufferedLen(); - } - - fn setLogicalPos(r: *Reader, offset: u64) void { - const logical_pos = logicalPos(r); - if (offset < logical_pos or offset >= r.pos) { - r.interface.seek = 0; - r.interface.end = 0; - r.pos = offset; - } else { - const logical_delta: usize = @intCast(offset - logical_pos); - r.interface.seek += logical_delta; - } - } - - /// Number of slices to store on the stack, when trying to send as many byte - /// vectors through the underlying read calls as possible. - const max_buffers_len = 16; - - fn stream(io_reader: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize { - const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); - switch (r.mode) { - .positional, .streaming => @panic("TODO"), - .positional_reading => { - const dest = limit.slice(try w.writableSliceGreedy(1)); - var data: [1][]u8 = .{dest}; - const n = try readVecPositional(r, &data); - w.advance(n); - return n; - }, - .streaming_reading => { - const dest = limit.slice(try w.writableSliceGreedy(1)); - var data: [1][]u8 = .{dest}; - const n = try readVecStreaming(r, &data); - w.advance(n); - return n; - }, - .failure => return error.ReadFailed, - } - } - - fn readVec(io_reader: *std.Io.Reader, data: [][]u8) std.Io.Reader.Error!usize { - const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); - switch (r.mode) { - .positional, .positional_reading => return readVecPositional(r, data), - .streaming, .streaming_reading => return readVecStreaming(r, data), - .failure => return error.ReadFailed, - } - } - - fn readVecPositional(r: *Reader, data: [][]u8) std.Io.Reader.Error!usize { - const io_reader = &r.interface; - if (is_windows) { - // Unfortunately, `ReadFileScatter` cannot be used since it - // requires page alignment. - if (io_reader.seek == io_reader.end) { - io_reader.seek = 0; - io_reader.end = 0; - } - const first = data[0]; - if (first.len >= io_reader.buffer.len - io_reader.end) { - return readPositional(r, first); - } else { - io_reader.end += try readPositional(r, io_reader.buffer[io_reader.end..]); - return 0; - } - } - var iovecs_buffer: [max_buffers_len]posix.iovec = undefined; - const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data); - const dest = iovecs_buffer[0..dest_n]; - assert(dest[0].len > 0); - const n = posix.preadv(r.file.handle, dest, r.pos) catch |err| switch (err) { - error.Unseekable => { - r.mode = r.mode.toStreaming(); - const pos = r.pos; - if (pos != 0) { - r.pos = 0; - r.seekBy(@intCast(pos)) catch { - r.mode = .failure; - return error.ReadFailed; - }; - } - return 0; - }, - else => |e| { - r.err = e; - return error.ReadFailed; - }, - }; - if (n == 0) { - r.size = r.pos; - return error.EndOfStream; - } - r.pos += n; - if (n > data_size) { - io_reader.end += n - data_size; - return data_size; - } - return n; - } - - fn readVecStreaming(r: *Reader, data: [][]u8) std.Io.Reader.Error!usize { - const io_reader = &r.interface; - if (is_windows) { - // Unfortunately, `ReadFileScatter` cannot be used since it - // requires page alignment. - if (io_reader.seek == io_reader.end) { - io_reader.seek = 0; - io_reader.end = 0; - } - const first = data[0]; - if (first.len >= io_reader.buffer.len - io_reader.end) { - return readStreaming(r, first); - } else { - io_reader.end += try readStreaming(r, io_reader.buffer[io_reader.end..]); - return 0; - } - } - var iovecs_buffer: [max_buffers_len]posix.iovec = undefined; - const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data); - const dest = iovecs_buffer[0..dest_n]; - assert(dest[0].len > 0); - const n = posix.readv(r.file.handle, dest) catch |err| { - r.err = err; - return error.ReadFailed; - }; - if (n == 0) { - r.size = r.pos; - return error.EndOfStream; - } - r.pos += n; - if (n > data_size) { - io_reader.end += n - data_size; - return data_size; - } - return n; - } - - fn discard(io_reader: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize { - const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); - const file = r.file; - const pos = r.pos; - switch (r.mode) { - .positional, .positional_reading => { - const size = r.getSize() catch { - r.mode = r.mode.toStreaming(); - return 0; - }; - const delta = @min(@intFromEnum(limit), size - pos); - r.pos = pos + delta; - return delta; - }, - .streaming, .streaming_reading => { - // Unfortunately we can't seek forward without knowing the - // size because the seek syscalls provided to us will not - // return the true end position if a seek would exceed the - // end. - fallback: { - if (r.size_err == null and r.seek_err == null) break :fallback; - var trash_buffer: [128]u8 = undefined; - if (is_windows) { - const n = windows.ReadFile(file.handle, limit.slice(&trash_buffer), null) catch |err| { - r.err = err; - return error.ReadFailed; - }; - if (n == 0) { - r.size = pos; - return error.EndOfStream; - } - r.pos = pos + n; - return n; - } - var iovecs: [max_buffers_len]std.posix.iovec = undefined; - var iovecs_i: usize = 0; - var remaining = @intFromEnum(limit); - while (remaining > 0 and iovecs_i < iovecs.len) { - iovecs[iovecs_i] = .{ .base = &trash_buffer, .len = @min(trash_buffer.len, remaining) }; - remaining -= iovecs[iovecs_i].len; - iovecs_i += 1; - } - const n = posix.readv(file.handle, iovecs[0..iovecs_i]) catch |err| { - r.err = err; - return error.ReadFailed; - }; - if (n == 0) { - r.size = pos; - return error.EndOfStream; - } - r.pos = pos + n; - return n; - } - const size = r.getSize() catch return 0; - const n = @min(size - pos, maxInt(i64), @intFromEnum(limit)); - file.seekBy(n) catch |err| { - r.seek_err = err; - return 0; - }; - r.pos = pos + n; - return n; - }, - .failure => return error.ReadFailed, - } - } - - fn readPositional(r: *Reader, dest: []u8) std.Io.Reader.Error!usize { - const n = r.file.pread(dest, r.pos) catch |err| switch (err) { - error.Unseekable => { - r.mode = r.mode.toStreaming(); - const pos = r.pos; - if (pos != 0) { - r.pos = 0; - r.seekBy(@intCast(pos)) catch { - r.mode = .failure; - return error.ReadFailed; - }; - } - return 0; - }, - else => |e| { - r.err = e; - return error.ReadFailed; - }, - }; - if (n == 0) { - r.size = r.pos; - return error.EndOfStream; - } - r.pos += n; - return n; - } - - fn readStreaming(r: *Reader, dest: []u8) std.Io.Reader.Error!usize { - const n = r.file.read(dest) catch |err| { - r.err = err; - return error.ReadFailed; - }; - if (n == 0) { - r.size = r.pos; - return error.EndOfStream; - } - r.pos += n; - return n; - } - - pub fn atEnd(r: *Reader) bool { - // Even if stat fails, size is set when end is encountered. - const size = r.size orelse return false; - return size - r.pos == 0; - } -}; +/// Deprecated in favor of `std.Io.File.Reader`. +pub const Reader = std.Io.File.Reader; pub const Writer = struct { file: File, diff --git a/lib/std/net.zig b/lib/std/net.zig deleted file mode 100644 index 7863967e63..0000000000 --- a/lib/std/net.zig +++ /dev/null @@ -1,2424 +0,0 @@ -//! Cross-platform networking abstractions. - -const std = @import("std.zig"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const net = @This(); -const mem = std.mem; -const posix = std.posix; -const fs = std.fs; -const Io = std.Io; -const native_endian = builtin.target.cpu.arch.endian(); -const native_os = builtin.os.tag; -const windows = std.os.windows; -const Allocator = std.mem.Allocator; -const ArrayList = std.ArrayListUnmanaged; -const File = std.fs.File; - -// Windows 10 added support for unix sockets in build 17063, redstone 4 is the -// first release to support them. -pub const has_unix_sockets = switch (native_os) { - .windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false, - .wasi => false, - else => true, -}; - -pub const IPParseError = error{ - Overflow, - InvalidEnd, - InvalidCharacter, - Incomplete, -}; - -pub const IPv4ParseError = IPParseError || error{NonCanonical}; - -pub const IPv6ParseError = IPParseError || error{InvalidIpv4Mapping}; -pub const IPv6InterfaceError = posix.SocketError || posix.IoCtl_SIOCGIFINDEX_Error || error{NameTooLong}; -pub const IPv6ResolveError = IPv6ParseError || IPv6InterfaceError; - -pub const Address = extern union { - any: posix.sockaddr, - in: Ip4Address, - in6: Ip6Address, - un: if (has_unix_sockets) posix.sockaddr.un else void, - - /// Parse an IP address which may include a port. For IPv4, this is just written `address:port`. - /// For IPv6, RFC 3986 defines this as an "IP literal", and the port is differentiated from the - /// address by surrounding the address part in brackets '[addr]:port'. Even if the port is not - /// given, the brackets are mandatory. - pub fn parseIpAndPort(str: []const u8) error{ InvalidAddress, InvalidPort }!Address { - if (str.len == 0) return error.InvalidAddress; - if (str[0] == '[') { - const addr_end = std.mem.indexOfScalar(u8, str, ']') orelse - return error.InvalidAddress; - const addr_str = str[1..addr_end]; - const port: u16 = p: { - if (addr_end == str.len - 1) break :p 0; - if (str[addr_end + 1] != ':') return error.InvalidAddress; - break :p parsePort(str[addr_end + 2 ..]) orelse return error.InvalidPort; - }; - return parseIp6(addr_str, port) catch error.InvalidAddress; - } else { - if (std.mem.indexOfScalar(u8, str, ':')) |idx| { - // hold off on `error.InvalidPort` since `error.InvalidAddress` might make more sense - const port: ?u16 = parsePort(str[idx + 1 ..]); - const addr = parseIp4(str[0..idx], port orelse 0) catch return error.InvalidAddress; - if (port == null) return error.InvalidPort; - return addr; - } else { - return parseIp4(str, 0) catch error.InvalidAddress; - } - } - } - fn parsePort(str: []const u8) ?u16 { - var p: u16 = 0; - for (str) |c| switch (c) { - '0'...'9' => { - const shifted = std.math.mul(u16, p, 10) catch return null; - p = std.math.add(u16, shifted, c - '0') catch return null; - }, - else => return null, - }; - if (p == 0) return null; - return p; - } - - /// Parse the given IP address string into an Address value. - /// It is recommended to use `resolveIp` instead, to handle - /// IPv6 link-local unix addresses. - pub fn parseIp(name: []const u8, port: u16) !Address { - if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) { - error.Overflow, - error.InvalidEnd, - error.InvalidCharacter, - error.Incomplete, - error.NonCanonical, - => {}, - } - - if (parseIp6(name, port)) |ip6| return ip6 else |err| switch (err) { - error.Overflow, - error.InvalidEnd, - error.InvalidCharacter, - error.Incomplete, - error.InvalidIpv4Mapping, - => {}, - } - - return error.InvalidIpAddressFormat; - } - - pub fn resolveIp(name: []const u8, port: u16) !Address { - if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) { - error.Overflow, - error.InvalidEnd, - error.InvalidCharacter, - error.Incomplete, - error.NonCanonical, - => {}, - } - - if (resolveIp6(name, port)) |ip6| return ip6 else |err| switch (err) { - error.Overflow, - error.InvalidEnd, - error.InvalidCharacter, - error.Incomplete, - error.InvalidIpv4Mapping, - => {}, - else => return err, - } - - return error.InvalidIpAddressFormat; - } - - pub fn parseExpectingFamily(name: []const u8, family: posix.sa_family_t, port: u16) !Address { - switch (family) { - posix.AF.INET => return parseIp4(name, port), - posix.AF.INET6 => return parseIp6(name, port), - posix.AF.UNSPEC => return parseIp(name, port), - else => unreachable, - } - } - - pub fn parseIp6(buf: []const u8, port: u16) IPv6ParseError!Address { - return .{ .in6 = try Ip6Address.parse(buf, port) }; - } - - pub fn resolveIp6(buf: []const u8, port: u16) IPv6ResolveError!Address { - return .{ .in6 = try Ip6Address.resolve(buf, port) }; - } - - pub fn parseIp4(buf: []const u8, port: u16) IPv4ParseError!Address { - return .{ .in = try Ip4Address.parse(buf, port) }; - } - - pub fn initIp4(addr: [4]u8, port: u16) Address { - return .{ .in = Ip4Address.init(addr, port) }; - } - - pub fn initIp6(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Address { - return .{ .in6 = Ip6Address.init(addr, port, flowinfo, scope_id) }; - } - - pub fn initUnix(path: []const u8) !Address { - var sock_addr = posix.sockaddr.un{ - .family = posix.AF.UNIX, - .path = undefined, - }; - - // Add 1 to ensure a terminating 0 is present in the path array for maximum portability. - if (path.len + 1 > sock_addr.path.len) return error.NameTooLong; - - @memset(&sock_addr.path, 0); - @memcpy(sock_addr.path[0..path.len], path); - - return .{ .un = sock_addr }; - } - - /// Returns the port in native endian. - /// Asserts that the address is ip4 or ip6. - pub fn getPort(self: Address) u16 { - return switch (self.any.family) { - posix.AF.INET => self.in.getPort(), - posix.AF.INET6 => self.in6.getPort(), - else => unreachable, - }; - } - - /// `port` is native-endian. - /// Asserts that the address is ip4 or ip6. - pub fn setPort(self: *Address, port: u16) void { - switch (self.any.family) { - posix.AF.INET => self.in.setPort(port), - posix.AF.INET6 => self.in6.setPort(port), - else => unreachable, - } - } - - /// Asserts that `addr` is an IP address. - /// This function will read past the end of the pointer, with a size depending - /// on the address family. - pub fn initPosix(addr: *align(4) const posix.sockaddr) Address { - switch (addr.family) { - posix.AF.INET => return Address{ .in = Ip4Address{ .sa = @as(*const posix.sockaddr.in, @ptrCast(addr)).* } }, - posix.AF.INET6 => return Address{ .in6 = Ip6Address{ .sa = @as(*const posix.sockaddr.in6, @ptrCast(addr)).* } }, - else => unreachable, - } - } - - pub fn format(self: Address, w: *Io.Writer) Io.Writer.Error!void { - switch (self.any.family) { - posix.AF.INET => try self.in.format(w), - posix.AF.INET6 => try self.in6.format(w), - posix.AF.UNIX => { - if (!has_unix_sockets) unreachable; - try w.writeAll(std.mem.sliceTo(&self.un.path, 0)); - }, - else => unreachable, - } - } - - pub fn eql(a: Address, b: Address) bool { - const a_bytes = @as([*]const u8, @ptrCast(&a.any))[0..a.getOsSockLen()]; - const b_bytes = @as([*]const u8, @ptrCast(&b.any))[0..b.getOsSockLen()]; - return mem.eql(u8, a_bytes, b_bytes); - } - - pub fn getOsSockLen(self: Address) posix.socklen_t { - switch (self.any.family) { - posix.AF.INET => return self.in.getOsSockLen(), - posix.AF.INET6 => return self.in6.getOsSockLen(), - posix.AF.UNIX => { - if (!has_unix_sockets) { - unreachable; - } - - // Using the full length of the structure here is more portable than returning - // the number of bytes actually used by the currently stored path. - // This also is correct regardless if we are passing a socket address to the kernel - // (e.g. in bind, connect, sendto) since we ensure the path is 0 terminated in - // initUnix() or if we are receiving a socket address from the kernel and must - // provide the full buffer size (e.g. getsockname, getpeername, recvfrom, accept). - // - // To access the path, std.mem.sliceTo(&address.un.path, 0) should be used. - return @as(posix.socklen_t, @intCast(@sizeOf(posix.sockaddr.un))); - }, - - else => unreachable, - } - } - - pub const ListenError = posix.SocketError || posix.BindError || posix.ListenError || - posix.SetSockOptError || posix.GetSockNameError; - - pub const ListenOptions = struct { - /// How many connections the kernel will accept on the application's behalf. - /// If more than this many connections pool in the kernel, clients will start - /// seeing "Connection refused". - kernel_backlog: u31 = 128, - /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX. - /// Sets SO_REUSEADDR on Windows, which is roughly equivalent. - reuse_address: bool = false, - /// Sets O_NONBLOCK. - force_nonblocking: bool = false, - }; - - /// The returned `Server` has an open `stream`. - pub fn listen(address: Address, options: ListenOptions) ListenError!Server { - const nonblock: u32 = if (options.force_nonblocking) posix.SOCK.NONBLOCK else 0; - const sock_flags = posix.SOCK.STREAM | posix.SOCK.CLOEXEC | nonblock; - const proto: u32 = if (address.any.family == posix.AF.UNIX) 0 else posix.IPPROTO.TCP; - - const sockfd = try posix.socket(address.any.family, sock_flags, proto); - var s: Server = .{ - .listen_address = undefined, - .stream = .{ .handle = sockfd }, - }; - errdefer s.stream.close(); - - if (options.reuse_address) { - try posix.setsockopt( - sockfd, - posix.SOL.SOCKET, - posix.SO.REUSEADDR, - &mem.toBytes(@as(c_int, 1)), - ); - if (@hasDecl(posix.SO, "REUSEPORT") and address.any.family != posix.AF.UNIX) { - try posix.setsockopt( - sockfd, - posix.SOL.SOCKET, - posix.SO.REUSEPORT, - &mem.toBytes(@as(c_int, 1)), - ); - } - } - - var socklen = address.getOsSockLen(); - try posix.bind(sockfd, &address.any, socklen); - try posix.listen(sockfd, options.kernel_backlog); - try posix.getsockname(sockfd, &s.listen_address.any, &socklen); - return s; - } -}; - -pub const Ip4Address = extern struct { - sa: posix.sockaddr.in, - - pub fn parse(buf: []const u8, port: u16) IPv4ParseError!Ip4Address { - var result: Ip4Address = .{ - .sa = .{ - .port = mem.nativeToBig(u16, port), - .addr = undefined, - }, - }; - const out_ptr = mem.asBytes(&result.sa.addr); - - var x: u8 = 0; - var index: u8 = 0; - var saw_any_digits = false; - var has_zero_prefix = false; - for (buf) |c| { - if (c == '.') { - if (!saw_any_digits) { - return error.InvalidCharacter; - } - if (index == 3) { - return error.InvalidEnd; - } - out_ptr[index] = x; - index += 1; - x = 0; - saw_any_digits = false; - has_zero_prefix = false; - } else if (c >= '0' and c <= '9') { - if (c == '0' and !saw_any_digits) { - has_zero_prefix = true; - } else if (has_zero_prefix) { - return error.NonCanonical; - } - saw_any_digits = true; - x = try std.math.mul(u8, x, 10); - x = try std.math.add(u8, x, c - '0'); - } else { - return error.InvalidCharacter; - } - } - if (index == 3 and saw_any_digits) { - out_ptr[index] = x; - return result; - } - - return error.Incomplete; - } - - pub fn resolveIp(name: []const u8, port: u16) !Ip4Address { - if (parse(name, port)) |ip4| return ip4 else |err| switch (err) { - error.Overflow, - error.InvalidEnd, - error.InvalidCharacter, - error.Incomplete, - error.NonCanonical, - => {}, - } - return error.InvalidIpAddressFormat; - } - - pub fn init(addr: [4]u8, port: u16) Ip4Address { - return Ip4Address{ - .sa = posix.sockaddr.in{ - .port = mem.nativeToBig(u16, port), - .addr = @as(*align(1) const u32, @ptrCast(&addr)).*, - }, - }; - } - - /// Returns the port in native endian. - /// Asserts that the address is ip4 or ip6. - pub fn getPort(self: Ip4Address) u16 { - return mem.bigToNative(u16, self.sa.port); - } - - /// `port` is native-endian. - /// Asserts that the address is ip4 or ip6. - pub fn setPort(self: *Ip4Address, port: u16) void { - self.sa.port = mem.nativeToBig(u16, port); - } - - pub fn format(self: Ip4Address, w: *Io.Writer) Io.Writer.Error!void { - const bytes: *const [4]u8 = @ptrCast(&self.sa.addr); - try w.print("{d}.{d}.{d}.{d}:{d}", .{ bytes[0], bytes[1], bytes[2], bytes[3], self.getPort() }); - } - - pub fn getOsSockLen(self: Ip4Address) posix.socklen_t { - _ = self; - return @sizeOf(posix.sockaddr.in); - } -}; - -pub const Ip6Address = extern struct { - sa: posix.sockaddr.in6, - - /// Parse a given IPv6 address string into an Address. - /// Assumes the Scope ID of the address is fully numeric. - /// For non-numeric addresses, see `resolveIp6`. - pub fn parse(buf: []const u8, port: u16) IPv6ParseError!Ip6Address { - var result = Ip6Address{ - .sa = posix.sockaddr.in6{ - .scope_id = 0, - .port = mem.nativeToBig(u16, port), - .flowinfo = 0, - .addr = undefined, - }, - }; - var ip_slice: *[16]u8 = result.sa.addr[0..]; - - var tail: [16]u8 = undefined; - - var x: u16 = 0; - var saw_any_digits = false; - var index: u8 = 0; - var scope_id = false; - var abbrv = false; - for (buf, 0..) |c, i| { - if (scope_id) { - if (c >= '0' and c <= '9') { - const digit = c - '0'; - { - const ov = @mulWithOverflow(result.sa.scope_id, 10); - if (ov[1] != 0) return error.Overflow; - result.sa.scope_id = ov[0]; - } - { - const ov = @addWithOverflow(result.sa.scope_id, digit); - if (ov[1] != 0) return error.Overflow; - result.sa.scope_id = ov[0]; - } - } else { - return error.InvalidCharacter; - } - } else if (c == ':') { - if (!saw_any_digits) { - if (abbrv) return error.InvalidCharacter; // ':::' - if (i != 0) abbrv = true; - @memset(ip_slice[index..], 0); - ip_slice = tail[0..]; - index = 0; - continue; - } - if (index == 14) { - return error.InvalidEnd; - } - ip_slice[index] = @as(u8, @truncate(x >> 8)); - index += 1; - ip_slice[index] = @as(u8, @truncate(x)); - index += 1; - - x = 0; - saw_any_digits = false; - } else if (c == '%') { - if (!saw_any_digits) { - return error.InvalidCharacter; - } - scope_id = true; - saw_any_digits = false; - } else if (c == '.') { - if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) { - // must start with '::ffff:' - return error.InvalidIpv4Mapping; - } - const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1; - const addr = (Ip4Address.parse(buf[start_index..], 0) catch { - return error.InvalidIpv4Mapping; - }).sa.addr; - ip_slice = result.sa.addr[0..]; - ip_slice[10] = 0xff; - ip_slice[11] = 0xff; - - const ptr = mem.sliceAsBytes(@as(*const [1]u32, &addr)[0..]); - - ip_slice[12] = ptr[0]; - ip_slice[13] = ptr[1]; - ip_slice[14] = ptr[2]; - ip_slice[15] = ptr[3]; - return result; - } else { - const digit = try std.fmt.charToDigit(c, 16); - { - const ov = @mulWithOverflow(x, 16); - if (ov[1] != 0) return error.Overflow; - x = ov[0]; - } - { - const ov = @addWithOverflow(x, digit); - if (ov[1] != 0) return error.Overflow; - x = ov[0]; - } - saw_any_digits = true; - } - } - - if (!saw_any_digits and !abbrv) { - return error.Incomplete; - } - if (!abbrv and index < 14) { - return error.Incomplete; - } - - if (index == 14) { - ip_slice[14] = @as(u8, @truncate(x >> 8)); - ip_slice[15] = @as(u8, @truncate(x)); - return result; - } else { - ip_slice[index] = @as(u8, @truncate(x >> 8)); - index += 1; - ip_slice[index] = @as(u8, @truncate(x)); - index += 1; - @memcpy(result.sa.addr[16 - index ..][0..index], ip_slice[0..index]); - return result; - } - } - - pub fn resolve(buf: []const u8, port: u16) IPv6ResolveError!Ip6Address { - // TODO: Unify the implementations of resolveIp6 and parseIp6. - var result = Ip6Address{ - .sa = posix.sockaddr.in6{ - .scope_id = 0, - .port = mem.nativeToBig(u16, port), - .flowinfo = 0, - .addr = undefined, - }, - }; - var ip_slice: *[16]u8 = result.sa.addr[0..]; - - var tail: [16]u8 = undefined; - - var x: u16 = 0; - var saw_any_digits = false; - var index: u8 = 0; - var abbrv = false; - - var scope_id = false; - var scope_id_value: [posix.IFNAMESIZE - 1]u8 = undefined; - var scope_id_index: usize = 0; - - for (buf, 0..) |c, i| { - if (scope_id) { - // Handling of percent-encoding should be for an URI library. - if ((c >= '0' and c <= '9') or - (c >= 'A' and c <= 'Z') or - (c >= 'a' and c <= 'z') or - (c == '-') or (c == '.') or (c == '_') or (c == '~')) - { - if (scope_id_index >= scope_id_value.len) { - return error.Overflow; - } - - scope_id_value[scope_id_index] = c; - scope_id_index += 1; - } else { - return error.InvalidCharacter; - } - } else if (c == ':') { - if (!saw_any_digits) { - if (abbrv) return error.InvalidCharacter; // ':::' - if (i != 0) abbrv = true; - @memset(ip_slice[index..], 0); - ip_slice = tail[0..]; - index = 0; - continue; - } - if (index == 14) { - return error.InvalidEnd; - } - ip_slice[index] = @as(u8, @truncate(x >> 8)); - index += 1; - ip_slice[index] = @as(u8, @truncate(x)); - index += 1; - - x = 0; - saw_any_digits = false; - } else if (c == '%') { - if (!saw_any_digits) { - return error.InvalidCharacter; - } - scope_id = true; - saw_any_digits = false; - } else if (c == '.') { - if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) { - // must start with '::ffff:' - return error.InvalidIpv4Mapping; - } - const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1; - const addr = (Ip4Address.parse(buf[start_index..], 0) catch { - return error.InvalidIpv4Mapping; - }).sa.addr; - ip_slice = result.sa.addr[0..]; - ip_slice[10] = 0xff; - ip_slice[11] = 0xff; - - const ptr = mem.sliceAsBytes(@as(*const [1]u32, &addr)[0..]); - - ip_slice[12] = ptr[0]; - ip_slice[13] = ptr[1]; - ip_slice[14] = ptr[2]; - ip_slice[15] = ptr[3]; - return result; - } else { - const digit = try std.fmt.charToDigit(c, 16); - { - const ov = @mulWithOverflow(x, 16); - if (ov[1] != 0) return error.Overflow; - x = ov[0]; - } - { - const ov = @addWithOverflow(x, digit); - if (ov[1] != 0) return error.Overflow; - x = ov[0]; - } - saw_any_digits = true; - } - } - - if (!saw_any_digits and !abbrv) { - return error.Incomplete; - } - - if (scope_id and scope_id_index == 0) { - return error.Incomplete; - } - - var resolved_scope_id: u32 = 0; - if (scope_id_index > 0) { - const scope_id_str = scope_id_value[0..scope_id_index]; - resolved_scope_id = std.fmt.parseInt(u32, scope_id_str, 10) catch |err| blk: { - if (err != error.InvalidCharacter) return err; - break :blk try if_nametoindex(scope_id_str); - }; - } - - result.sa.scope_id = resolved_scope_id; - - if (index == 14) { - ip_slice[14] = @as(u8, @truncate(x >> 8)); - ip_slice[15] = @as(u8, @truncate(x)); - return result; - } else { - ip_slice[index] = @as(u8, @truncate(x >> 8)); - index += 1; - ip_slice[index] = @as(u8, @truncate(x)); - index += 1; - @memcpy(result.sa.addr[16 - index ..][0..index], ip_slice[0..index]); - return result; - } - } - - pub fn init(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Ip6Address { - return Ip6Address{ - .sa = posix.sockaddr.in6{ - .addr = addr, - .port = mem.nativeToBig(u16, port), - .flowinfo = flowinfo, - .scope_id = scope_id, - }, - }; - } - - /// Returns the port in native endian. - /// Asserts that the address is ip4 or ip6. - pub fn getPort(self: Ip6Address) u16 { - return mem.bigToNative(u16, self.sa.port); - } - - /// `port` is native-endian. - /// Asserts that the address is ip4 or ip6. - pub fn setPort(self: *Ip6Address, port: u16) void { - self.sa.port = mem.nativeToBig(u16, port); - } - - pub fn format(self: Ip6Address, w: *Io.Writer) Io.Writer.Error!void { - const port = mem.bigToNative(u16, self.sa.port); - if (mem.eql(u8, self.sa.addr[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) { - try w.print("[::ffff:{d}.{d}.{d}.{d}]:{d}", .{ - self.sa.addr[12], - self.sa.addr[13], - self.sa.addr[14], - self.sa.addr[15], - port, - }); - return; - } - const big_endian_parts = @as(*align(1) const [8]u16, @ptrCast(&self.sa.addr)); - const native_endian_parts = switch (native_endian) { - .big => big_endian_parts.*, - .little => blk: { - var buf: [8]u16 = undefined; - for (big_endian_parts, 0..) |part, i| { - buf[i] = mem.bigToNative(u16, part); - } - break :blk buf; - }, - }; - - // Find the longest zero run - var longest_start: usize = 8; - var longest_len: usize = 0; - var current_start: usize = 0; - var current_len: usize = 0; - - for (native_endian_parts, 0..) |part, i| { - if (part == 0) { - if (current_len == 0) { - current_start = i; - } - current_len += 1; - if (current_len > longest_len) { - longest_start = current_start; - longest_len = current_len; - } - } else { - current_len = 0; - } - } - - // Only compress if the longest zero run is 2 or more - if (longest_len < 2) { - longest_start = 8; - longest_len = 0; - } - - try w.writeAll("["); - var i: usize = 0; - var abbrv = false; - while (i < native_endian_parts.len) : (i += 1) { - if (i == longest_start) { - // Emit "::" for the longest zero run - if (!abbrv) { - try w.writeAll(if (i == 0) "::" else ":"); - abbrv = true; - } - i += longest_len - 1; // Skip the compressed range - continue; - } - if (abbrv) { - abbrv = false; - } - try w.print("{x}", .{native_endian_parts[i]}); - if (i != native_endian_parts.len - 1) { - try w.writeAll(":"); - } - } - if (self.sa.scope_id != 0) { - try w.print("%{}", .{self.sa.scope_id}); - } - try w.print("]:{}", .{port}); - } - - pub fn getOsSockLen(self: Ip6Address) posix.socklen_t { - _ = self; - return @sizeOf(posix.sockaddr.in6); - } -}; - -pub fn connectUnixSocket(path: []const u8) !Stream { - const opt_non_block = 0; - const sockfd = try posix.socket( - posix.AF.UNIX, - posix.SOCK.STREAM | posix.SOCK.CLOEXEC | opt_non_block, - 0, - ); - errdefer Stream.close(.{ .handle = sockfd }); - - var addr = try Address.initUnix(path); - try posix.connect(sockfd, &addr.any, addr.getOsSockLen()); - - return .{ .handle = sockfd }; -} - -fn if_nametoindex(name: []const u8) IPv6InterfaceError!u32 { - if (native_os == .linux) { - var ifr: posix.ifreq = undefined; - const sockfd = try posix.socket(posix.AF.UNIX, posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, 0); - defer Stream.close(.{ .handle = sockfd }); - - @memcpy(ifr.ifrn.name[0..name.len], name); - ifr.ifrn.name[name.len] = 0; - - // TODO investigate if this needs to be integrated with evented I/O. - try posix.ioctl_SIOCGIFINDEX(sockfd, &ifr); - - return @bitCast(ifr.ifru.ivalue); - } - - if (native_os.isDarwin()) { - if (name.len >= posix.IFNAMESIZE) - return error.NameTooLong; - - var if_name: [posix.IFNAMESIZE:0]u8 = undefined; - @memcpy(if_name[0..name.len], name); - if_name[name.len] = 0; - const if_slice = if_name[0..name.len :0]; - const index = std.c.if_nametoindex(if_slice); - if (index == 0) - return error.InterfaceNotFound; - return @as(u32, @bitCast(index)); - } - - if (native_os == .windows) { - if (name.len >= posix.IFNAMESIZE) - return error.NameTooLong; - - var interface_name: [posix.IFNAMESIZE:0]u8 = undefined; - @memcpy(interface_name[0..name.len], name); - interface_name[name.len] = 0; - const index = std.os.windows.ws2_32.if_nametoindex(@as([*:0]const u8, &interface_name)); - if (index == 0) - return error.InterfaceNotFound; - return index; - } - - @compileError("std.net.if_nametoindex unimplemented for this OS"); -} - -pub const AddressList = struct { - arena: std.heap.ArenaAllocator, - addrs: []Address, - canon_name: ?[]u8, - - pub fn deinit(self: *AddressList) void { - // Here we copy the arena allocator into stack memory, because - // otherwise it would destroy itself while it was still working. - var arena = self.arena; - arena.deinit(); - // self is destroyed - } -}; - -pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError; - -/// All memory allocated with `allocator` will be freed before this function returns. -pub fn tcpConnectToHost(allocator: Allocator, name: []const u8, port: u16) TcpConnectToHostError!Stream { - const list = try getAddressList(allocator, name, port); - defer list.deinit(); - - if (list.addrs.len == 0) return error.UnknownHostName; - - for (list.addrs) |addr| { - return tcpConnectToAddress(addr) catch |err| switch (err) { - error.ConnectionRefused => { - continue; - }, - else => return err, - }; - } - return posix.ConnectError.ConnectionRefused; -} - -pub const TcpConnectToAddressError = posix.SocketError || posix.ConnectError; - -pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream { - const nonblock = 0; - const sock_flags = posix.SOCK.STREAM | nonblock | - (if (native_os == .windows) 0 else posix.SOCK.CLOEXEC); - const sockfd = try posix.socket(address.any.family, sock_flags, posix.IPPROTO.TCP); - errdefer Stream.close(.{ .handle = sockfd }); - - try posix.connect(sockfd, &address.any, address.getOsSockLen()); - - return Stream{ .handle = sockfd }; -} - -// TODO: Instead of having a massive error set, make the error set have categories, and then -// store the sub-error as a diagnostic value. -const GetAddressListError = Allocator.Error || File.OpenError || File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{ - TemporaryNameServerFailure, - NameServerFailure, - AddressFamilyNotSupported, - UnknownHostName, - ServiceUnavailable, - Unexpected, - - HostLacksNetworkAddresses, - - InvalidCharacter, - InvalidEnd, - NonCanonical, - Overflow, - Incomplete, - InvalidIpv4Mapping, - InvalidIpAddressFormat, - - InterfaceNotFound, - FileSystem, - ResolveConfParseFailed, -}; - -/// Call `AddressList.deinit` on the result. -pub fn getAddressList(gpa: Allocator, name: []const u8, port: u16) GetAddressListError!*AddressList { - const result = blk: { - var arena = std.heap.ArenaAllocator.init(gpa); - errdefer arena.deinit(); - - const result = try arena.allocator().create(AddressList); - result.* = AddressList{ - .arena = arena, - .addrs = undefined, - .canon_name = null, - }; - break :blk result; - }; - const arena = result.arena.allocator(); - errdefer result.deinit(); - - if (native_os == .windows) { - const name_c = try gpa.dupeZ(u8, name); - defer gpa.free(name_c); - - const port_c = try std.fmt.allocPrintSentinel(gpa, "{d}", .{port}, 0); - defer gpa.free(port_c); - - const ws2_32 = windows.ws2_32; - const hints: posix.addrinfo = .{ - .flags = .{ .NUMERICSERV = true }, - .family = posix.AF.UNSPEC, - .socktype = posix.SOCK.STREAM, - .protocol = posix.IPPROTO.TCP, - .canonname = null, - .addr = null, - .addrlen = 0, - .next = null, - }; - var res: ?*posix.addrinfo = null; - var first = true; - while (true) { - const rc = ws2_32.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res); - switch (@as(windows.ws2_32.WinsockError, @enumFromInt(@as(u16, @intCast(rc))))) { - @as(windows.ws2_32.WinsockError, @enumFromInt(0)) => break, - .WSATRY_AGAIN => return error.TemporaryNameServerFailure, - .WSANO_RECOVERY => return error.NameServerFailure, - .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, - .WSA_NOT_ENOUGH_MEMORY => return error.OutOfMemory, - .WSAHOST_NOT_FOUND => return error.UnknownHostName, - .WSATYPE_NOT_FOUND => return error.ServiceUnavailable, - .WSAEINVAL => unreachable, - .WSAESOCKTNOSUPPORT => unreachable, - .WSANOTINITIALISED => { - if (!first) return error.Unexpected; - first = false; - try windows.callWSAStartup(); - continue; - }, - else => |err| return windows.unexpectedWSAError(err), - } - } - defer ws2_32.freeaddrinfo(res); - - const addr_count = blk: { - var count: usize = 0; - var it = res; - while (it) |info| : (it = info.next) { - if (info.addr != null) { - count += 1; - } - } - break :blk count; - }; - result.addrs = try arena.alloc(Address, addr_count); - - var it = res; - var i: usize = 0; - while (it) |info| : (it = info.next) { - const addr = info.addr orelse continue; - result.addrs[i] = Address.initPosix(@alignCast(addr)); - - if (info.canonname) |n| { - if (result.canon_name == null) { - result.canon_name = try arena.dupe(u8, mem.sliceTo(n, 0)); - } - } - i += 1; - } - - return result; - } - - if (builtin.link_libc) { - const name_c = try gpa.dupeZ(u8, name); - defer gpa.free(name_c); - - const port_c = try std.fmt.allocPrintSentinel(gpa, "{d}", .{port}, 0); - defer gpa.free(port_c); - - const hints: posix.addrinfo = .{ - .flags = .{ .NUMERICSERV = true }, - .family = posix.AF.UNSPEC, - .socktype = posix.SOCK.STREAM, - .protocol = posix.IPPROTO.TCP, - .canonname = null, - .addr = null, - .addrlen = 0, - .next = null, - }; - var res: ?*posix.addrinfo = null; - switch (posix.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) { - @as(posix.system.EAI, @enumFromInt(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 (posix.errno(-1)) { - else => |e| return posix.unexpectedErrno(e), - }, - else => unreachable, - } - defer if (res) |some| posix.system.freeaddrinfo(some); - - const addr_count = blk: { - var count: usize = 0; - var it = res; - while (it) |info| : (it = info.next) { - if (info.addr != null) { - count += 1; - } - } - break :blk count; - }; - result.addrs = try arena.alloc(Address, addr_count); - - var it = res; - var i: usize = 0; - while (it) |info| : (it = info.next) { - const addr = info.addr orelse continue; - result.addrs[i] = Address.initPosix(@alignCast(addr)); - - if (info.canonname) |n| { - if (result.canon_name == null) { - result.canon_name = try arena.dupe(u8, mem.sliceTo(n, 0)); - } - } - i += 1; - } - - return result; - } - - if (native_os == .linux) { - const family = posix.AF.UNSPEC; - var lookup_addrs: ArrayList(LookupAddr) = .empty; - defer lookup_addrs.deinit(gpa); - - var canon: ArrayList(u8) = .empty; - defer canon.deinit(gpa); - - try linuxLookupName(gpa, &lookup_addrs, &canon, name, family, .{ .NUMERICSERV = true }, port); - - result.addrs = try arena.alloc(Address, lookup_addrs.items.len); - if (canon.items.len != 0) { - result.canon_name = try arena.dupe(u8, canon.items); - } - - for (lookup_addrs.items, 0..) |lookup_addr, i| { - result.addrs[i] = lookup_addr.addr; - assert(result.addrs[i].getPort() == port); - } - - return result; - } - @compileError("std.net.getAddressList unimplemented for this OS"); -} - -const LookupAddr = struct { - addr: Address, - sortkey: i32 = 0, -}; - -const DAS_USABLE = 0x40000000; -const DAS_MATCHINGSCOPE = 0x20000000; -const DAS_MATCHINGLABEL = 0x10000000; -const DAS_PREC_SHIFT = 20; -const DAS_SCOPE_SHIFT = 16; -const DAS_PREFIX_SHIFT = 8; -const DAS_ORDER_SHIFT = 0; - -fn linuxLookupName( - gpa: Allocator, - addrs: *ArrayList(LookupAddr), - canon: *ArrayList(u8), - opt_name: ?[]const u8, - family: posix.sa_family_t, - flags: posix.AI, - port: u16, -) !void { - if (opt_name) |name| { - // reject empty name and check len so it fits into temp bufs - canon.items.len = 0; - try canon.appendSlice(gpa, name); - if (Address.parseExpectingFamily(name, family, port)) |addr| { - try addrs.append(gpa, .{ .addr = addr }); - } else |name_err| if (flags.NUMERICHOST) { - return name_err; - } else { - try linuxLookupNameFromHosts(gpa, addrs, canon, name, family, port); - if (addrs.items.len == 0) { - // RFC 6761 Section 6.3.3 - // Name resolution APIs and libraries SHOULD recognize localhost - // names as special and SHOULD always return the IP loopback address - // for address queries and negative responses for all other query - // types. - - // Check for equal to "localhost(.)" or ends in ".localhost(.)" - const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost"; - if (mem.endsWith(u8, name, localhost) and (name.len == localhost.len or name[name.len - localhost.len] == '.')) { - try addrs.append(gpa, .{ .addr = .{ .in = Ip4Address.parse("127.0.0.1", port) catch unreachable } }); - try addrs.append(gpa, .{ .addr = .{ .in6 = Ip6Address.parse("::1", port) catch unreachable } }); - return; - } - - try linuxLookupNameFromDnsSearch(gpa, addrs, canon, name, family, port); - } - } - } else { - try canon.resize(gpa, 0); - try addrs.ensureUnusedCapacity(gpa, 2); - linuxLookupNameFromNull(addrs, family, flags, port); - } - if (addrs.items.len == 0) return error.UnknownHostName; - - // No further processing is needed if there are fewer than 2 - // results or if there are only IPv4 results. - if (addrs.items.len == 1 or family == posix.AF.INET) return; - const all_ip4 = for (addrs.items) |addr| { - if (addr.addr.any.family != posix.AF.INET) break false; - } else true; - if (all_ip4) return; - - // The following implements a subset of RFC 3484/6724 destination - // address selection by generating a single 31-bit sort key for - // each address. Rules 3, 4, and 7 are omitted for having - // excessive runtime and code size cost and dubious benefit. - // So far the label/precedence table cannot be customized. - // This implementation is ported from musl libc. - // A more idiomatic "ziggy" implementation would be welcome. - for (addrs.items, 0..) |*addr, i| { - var key: i32 = 0; - var sa6: posix.sockaddr.in6 = undefined; - @memset(@as([*]u8, @ptrCast(&sa6))[0..@sizeOf(posix.sockaddr.in6)], 0); - var da6 = posix.sockaddr.in6{ - .family = posix.AF.INET6, - .scope_id = addr.addr.in6.sa.scope_id, - .port = 65535, - .flowinfo = 0, - .addr = [1]u8{0} ** 16, - }; - var sa4: posix.sockaddr.in = undefined; - @memset(@as([*]u8, @ptrCast(&sa4))[0..@sizeOf(posix.sockaddr.in)], 0); - var da4 = posix.sockaddr.in{ - .family = posix.AF.INET, - .port = 65535, - .addr = 0, - .zero = [1]u8{0} ** 8, - }; - var sa: *align(4) posix.sockaddr = undefined; - var da: *align(4) posix.sockaddr = undefined; - var salen: posix.socklen_t = undefined; - var dalen: posix.socklen_t = undefined; - if (addr.addr.any.family == posix.AF.INET6) { - da6.addr = addr.addr.in6.sa.addr; - da = @ptrCast(&da6); - dalen = @sizeOf(posix.sockaddr.in6); - sa = @ptrCast(&sa6); - salen = @sizeOf(posix.sockaddr.in6); - } else { - sa6.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*; - da6.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*; - mem.writeInt(u32, da6.addr[12..], addr.addr.in.sa.addr, native_endian); - da4.addr = addr.addr.in.sa.addr; - da = @ptrCast(&da4); - dalen = @sizeOf(posix.sockaddr.in); - sa = @ptrCast(&sa4); - salen = @sizeOf(posix.sockaddr.in); - } - const dpolicy = policyOf(da6.addr); - const dscope: i32 = scopeOf(da6.addr); - const dlabel = dpolicy.label; - const dprec: i32 = dpolicy.prec; - const MAXADDRS = 3; - var prefixlen: i32 = 0; - const sock_flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC; - if (posix.socket(addr.addr.any.family, sock_flags, posix.IPPROTO.UDP)) |fd| syscalls: { - defer Stream.close(.{ .handle = fd }); - posix.connect(fd, da, dalen) catch break :syscalls; - key |= DAS_USABLE; - posix.getsockname(fd, sa, &salen) catch break :syscalls; - if (addr.addr.any.family == posix.AF.INET) { - mem.writeInt(u32, sa6.addr[12..16], sa4.addr, native_endian); - } - if (dscope == @as(i32, scopeOf(sa6.addr))) key |= DAS_MATCHINGSCOPE; - if (dlabel == labelOf(sa6.addr)) key |= DAS_MATCHINGLABEL; - prefixlen = prefixMatch(sa6.addr, da6.addr); - } else |_| {} - key |= dprec << DAS_PREC_SHIFT; - key |= (15 - dscope) << DAS_SCOPE_SHIFT; - key |= prefixlen << DAS_PREFIX_SHIFT; - key |= (MAXADDRS - @as(i32, @intCast(i))) << DAS_ORDER_SHIFT; - addr.sortkey = key; - } - mem.sort(LookupAddr, addrs.items, {}, addrCmpLessThan); -} - -const Policy = struct { - addr: [16]u8, - len: u8, - mask: u8, - prec: u8, - label: u8, -}; - -const defined_policies = [_]Policy{ - Policy{ - .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01".*, - .len = 15, - .mask = 0xff, - .prec = 50, - .label = 0, - }, - Policy{ - .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00".*, - .len = 11, - .mask = 0xff, - .prec = 35, - .label = 4, - }, - Policy{ - .addr = "\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*, - .len = 1, - .mask = 0xff, - .prec = 30, - .label = 2, - }, - Policy{ - .addr = "\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*, - .len = 3, - .mask = 0xff, - .prec = 5, - .label = 5, - }, - Policy{ - .addr = "\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*, - .len = 0, - .mask = 0xfe, - .prec = 3, - .label = 13, - }, - // These are deprecated and/or returned to the address - // pool, so despite the RFC, treating them as special - // is probably wrong. - // { "", 11, 0xff, 1, 3 }, - // { "\xfe\xc0", 1, 0xc0, 1, 11 }, - // { "\x3f\xfe", 1, 0xff, 1, 12 }, - // Last rule must match all addresses to stop loop. - Policy{ - .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*, - .len = 0, - .mask = 0, - .prec = 40, - .label = 1, - }, -}; - -fn policyOf(a: [16]u8) *const Policy { - for (&defined_policies) |*policy| { - if (!mem.eql(u8, a[0..policy.len], policy.addr[0..policy.len])) continue; - if ((a[policy.len] & policy.mask) != policy.addr[policy.len]) continue; - return policy; - } - unreachable; -} - -fn scopeOf(a: [16]u8) u8 { - if (IN6_IS_ADDR_MULTICAST(a)) return a[1] & 15; - if (IN6_IS_ADDR_LINKLOCAL(a)) return 2; - if (IN6_IS_ADDR_LOOPBACK(a)) return 2; - if (IN6_IS_ADDR_SITELOCAL(a)) return 5; - return 14; -} - -fn prefixMatch(s: [16]u8, d: [16]u8) u8 { - // TODO: This FIXME inherited from porting from musl libc. - // I don't want this to go into zig std lib 1.0.0. - - // FIXME: The common prefix length should be limited to no greater - // than the nominal length of the prefix portion of the source - // address. However the definition of the source prefix length is - // not clear and thus this limiting is not yet implemented. - var i: u8 = 0; - while (i < 128 and ((s[i / 8] ^ d[i / 8]) & (@as(u8, 128) >> @as(u3, @intCast(i % 8)))) == 0) : (i += 1) {} - return i; -} - -fn labelOf(a: [16]u8) u8 { - return policyOf(a).label; -} - -fn IN6_IS_ADDR_MULTICAST(a: [16]u8) bool { - return a[0] == 0xff; -} - -fn IN6_IS_ADDR_LINKLOCAL(a: [16]u8) bool { - return a[0] == 0xfe and (a[1] & 0xc0) == 0x80; -} - -fn IN6_IS_ADDR_LOOPBACK(a: [16]u8) bool { - return a[0] == 0 and a[1] == 0 and - a[2] == 0 and - a[12] == 0 and a[13] == 0 and - a[14] == 0 and a[15] == 1; -} - -fn IN6_IS_ADDR_SITELOCAL(a: [16]u8) bool { - return a[0] == 0xfe and (a[1] & 0xc0) == 0xc0; -} - -// Parameters `b` and `a` swapped to make this descending. -fn addrCmpLessThan(context: void, b: LookupAddr, a: LookupAddr) bool { - _ = context; - return a.sortkey < b.sortkey; -} - -fn linuxLookupNameFromNull( - addrs: *ArrayList(LookupAddr), - family: posix.sa_family_t, - flags: posix.AI, - port: u16, -) void { - if (flags.PASSIVE) { - if (family != posix.AF.INET6) { - addrs.appendAssumeCapacity(.{ - .addr = Address.initIp4([1]u8{0} ** 4, port), - }); - } - if (family != posix.AF.INET) { - addrs.appendAssumeCapacity(.{ - .addr = Address.initIp6([1]u8{0} ** 16, port, 0, 0), - }); - } - } else { - if (family != posix.AF.INET6) { - addrs.appendAssumeCapacity(.{ - .addr = Address.initIp4([4]u8{ 127, 0, 0, 1 }, port), - }); - } - if (family != posix.AF.INET) { - addrs.appendAssumeCapacity(.{ - .addr = Address.initIp6(([1]u8{0} ** 15) ++ [1]u8{1}, port, 0, 0), - }); - } - } -} - -fn linuxLookupNameFromHosts( - gpa: Allocator, - addrs: *ArrayList(LookupAddr), - canon: *ArrayList(u8), - name: []const u8, - family: posix.sa_family_t, - port: u16, -) !void { - const file = fs.openFileAbsoluteZ("/etc/hosts", .{}) catch |err| switch (err) { - error.FileNotFound, - error.NotDir, - error.AccessDenied, - => return, - else => |e| return e, - }; - defer file.close(); - - var line_buf: [512]u8 = undefined; - var file_reader = file.reader(&line_buf); - return parseHosts(gpa, addrs, canon, name, family, port, &file_reader.interface) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ReadFailed => return file_reader.err.?, - }; -} - -fn parseHosts( - gpa: Allocator, - addrs: *ArrayList(LookupAddr), - canon: *ArrayList(u8), - name: []const u8, - family: posix.sa_family_t, - port: u16, - br: *Io.Reader, -) error{ OutOfMemory, ReadFailed }!void { - while (true) { - const line = br.takeDelimiter('\n') catch |err| switch (err) { - error.StreamTooLong => { - // Skip lines that are too long. - _ = br.discardDelimiterInclusive('\n') catch |e| switch (e) { - error.EndOfStream => break, - error.ReadFailed => return error.ReadFailed, - }; - continue; - }, - error.ReadFailed => return error.ReadFailed, - } orelse { - break; // end of stream - }; - var split_it = mem.splitScalar(u8, line, '#'); - const no_comment_line = split_it.first(); - - var line_it = mem.tokenizeAny(u8, no_comment_line, " \t"); - const ip_text = line_it.next() orelse continue; - var first_name_text: ?[]const u8 = null; - while (line_it.next()) |name_text| { - if (first_name_text == null) first_name_text = name_text; - if (mem.eql(u8, name_text, name)) { - break; - } - } else continue; - - const addr = Address.parseExpectingFamily(ip_text, family, port) catch |err| switch (err) { - error.Overflow, - error.InvalidEnd, - error.InvalidCharacter, - error.Incomplete, - error.InvalidIpAddressFormat, - error.InvalidIpv4Mapping, - error.NonCanonical, - => continue, - }; - try addrs.append(gpa, .{ .addr = addr }); - - // first name is canonical name - const name_text = first_name_text.?; - if (isValidHostName(name_text)) { - canon.items.len = 0; - try canon.appendSlice(gpa, name_text); - } - } -} - -test parseHosts { - if (builtin.os.tag == .wasi) { - // TODO parsing addresses should not have OS dependencies - return error.SkipZigTest; - } - var reader: Io.Reader = .fixed( - \\127.0.0.1 localhost - \\::1 localhost - \\127.0.0.2 abcd - ); - var addrs: ArrayList(LookupAddr) = .empty; - defer addrs.deinit(std.testing.allocator); - var canon: ArrayList(u8) = .empty; - defer canon.deinit(std.testing.allocator); - try parseHosts(std.testing.allocator, &addrs, &canon, "abcd", posix.AF.UNSPEC, 1234, &reader); - try std.testing.expectEqual(1, addrs.items.len); - try std.testing.expectFmt("127.0.0.2:1234", "{f}", .{addrs.items[0].addr}); -} - -pub fn isValidHostName(bytes: []const u8) bool { - _ = std.Io.net.HostName.init(bytes) catch return false; - return true; -} - -fn linuxLookupNameFromDnsSearch( - gpa: Allocator, - addrs: *ArrayList(LookupAddr), - canon: *ArrayList(u8), - name: []const u8, - family: posix.sa_family_t, - port: u16, -) !void { - var rc: ResolvConf = undefined; - rc.init(gpa) catch return error.ResolveConfParseFailed; - defer rc.deinit(); - - // Count dots, suppress search when >=ndots or name ends in - // a dot, which is an explicit request for global scope. - var dots: usize = 0; - for (name) |byte| { - if (byte == '.') dots += 1; - } - - const search = if (dots >= rc.ndots or mem.endsWith(u8, name, ".")) - "" - else - rc.search.items; - - var canon_name = name; - - // Strip final dot for canon, fail if multiple trailing dots. - if (mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1; - if (mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName; - - // Name with search domain appended is setup in canon[]. This both - // provides the desired default canonical name (if the requested - // name is not a CNAME record) and serves as a buffer for passing - // the full requested name to name_from_dns. - try canon.resize(gpa, canon_name.len); - @memcpy(canon.items, canon_name); - try canon.append(gpa, '.'); - - var tok_it = mem.tokenizeAny(u8, search, " \t"); - while (tok_it.next()) |tok| { - canon.shrinkRetainingCapacity(canon_name.len + 1); - try canon.appendSlice(gpa, tok); - try linuxLookupNameFromDns(gpa, addrs, canon, canon.items, family, rc, port); - if (addrs.items.len != 0) return; - } - - canon.shrinkRetainingCapacity(canon_name.len); - return linuxLookupNameFromDns(gpa, addrs, canon, name, family, rc, port); -} - -const dpc_ctx = struct { - gpa: Allocator, - addrs: *ArrayList(LookupAddr), - canon: *ArrayList(u8), - port: u16, -}; - -fn linuxLookupNameFromDns( - gpa: Allocator, - addrs: *ArrayList(LookupAddr), - canon: *ArrayList(u8), - name: []const u8, - family: posix.sa_family_t, - rc: ResolvConf, - port: u16, -) !void { - const ctx: dpc_ctx = .{ - .gpa = gpa, - .addrs = addrs, - .canon = canon, - .port = port, - }; - const AfRr = struct { - af: posix.sa_family_t, - rr: u8, - }; - const afrrs = [_]AfRr{ - .{ .af = posix.AF.INET6, .rr = posix.RR.A }, - .{ .af = posix.AF.INET, .rr = posix.RR.AAAA }, - }; - var qbuf: [2][280]u8 = undefined; - var abuf: [2][512]u8 = undefined; - var qp: [2][]const u8 = undefined; - const apbuf = [2][]u8{ &abuf[0], &abuf[1] }; - var nq: usize = 0; - - for (afrrs) |afrr| { - if (family != afrr.af) { - const len = posix.res_mkquery(0, name, 1, afrr.rr, &[_]u8{}, null, &qbuf[nq]); - qp[nq] = qbuf[nq][0..len]; - nq += 1; - } - } - - var ap = [2][]u8{ apbuf[0], apbuf[1] }; - ap[0].len = 0; - ap[1].len = 0; - - try rc.resMSendRc(qp[0..nq], ap[0..nq], apbuf[0..nq]); - - var i: usize = 0; - while (i < nq) : (i += 1) { - dnsParse(ap[i], ctx, dnsParseCallback) catch {}; - } - - if (addrs.items.len != 0) return; - if (ap[0].len < 4 or (ap[0][3] & 15) == 2) return error.TemporaryNameServerFailure; - if ((ap[0][3] & 15) == 0) return error.UnknownHostName; - if ((ap[0][3] & 15) == 3) return; - return error.NameServerFailure; -} - -const ResolvConf = struct { - gpa: Allocator, - attempts: u32, - ndots: u32, - timeout: u32, - search: ArrayList(u8), - /// TODO there are actually only allowed to be maximum 3 nameservers, no need - /// for an array list. - ns: ArrayList(LookupAddr), - - /// Returns `error.StreamTooLong` if a line is longer than 512 bytes. - /// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761 - fn init(rc: *ResolvConf, gpa: Allocator) !void { - rc.* = .{ - .gpa = gpa, - .ns = .empty, - .search = .empty, - .ndots = 1, - .timeout = 5, - .attempts = 2, - }; - errdefer rc.deinit(); - - const file = fs.openFileAbsoluteZ("/etc/resolv.conf", .{}) catch |err| switch (err) { - error.FileNotFound, - error.NotDir, - error.AccessDenied, - => return linuxLookupNameFromNumericUnspec(gpa, &rc.ns, "127.0.0.1", 53), - else => |e| return e, - }; - defer file.close(); - - var line_buf: [512]u8 = undefined; - var file_reader = file.reader(&line_buf); - return parse(rc, &file_reader.interface) catch |err| switch (err) { - error.ReadFailed => return file_reader.err.?, - else => |e| return e, - }; - } - - const Directive = enum { options, nameserver, domain, search }; - const Option = enum { ndots, attempts, timeout }; - - fn parse(rc: *ResolvConf, reader: *Io.Reader) !void { - const gpa = rc.gpa; - while (reader.takeSentinel('\n')) |line_with_comment| { - const line = line: { - var split = mem.splitScalar(u8, line_with_comment, '#'); - break :line split.first(); - }; - var line_it = mem.tokenizeAny(u8, line, " \t"); - - const token = line_it.next() orelse continue; - switch (std.meta.stringToEnum(Directive, token) orelse continue) { - .options => while (line_it.next()) |sub_tok| { - var colon_it = mem.splitScalar(u8, sub_tok, ':'); - const name = colon_it.first(); - const value_txt = colon_it.next() orelse continue; - const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) { - error.Overflow => 255, - error.InvalidCharacter => continue, - }; - switch (std.meta.stringToEnum(Option, name) orelse continue) { - .ndots => rc.ndots = @min(value, 15), - .attempts => rc.attempts = @min(value, 10), - .timeout => rc.timeout = @min(value, 60), - } - }, - .nameserver => { - const ip_txt = line_it.next() orelse continue; - try linuxLookupNameFromNumericUnspec(gpa, &rc.ns, ip_txt, 53); - }, - .domain, .search => { - rc.search.items.len = 0; - try rc.search.appendSlice(gpa, line_it.rest()); - }, - } - } else |err| switch (err) { - error.EndOfStream => if (reader.bufferedLen() != 0) return error.EndOfStream, - else => |e| return e, - } - - if (rc.ns.items.len == 0) { - return linuxLookupNameFromNumericUnspec(gpa, &rc.ns, "127.0.0.1", 53); - } - } - - fn resMSendRc( - rc: ResolvConf, - queries: []const []const u8, - answers: [][]u8, - answer_bufs: []const []u8, - ) !void { - const gpa = rc.gpa; - const timeout = 1000 * rc.timeout; - const attempts = rc.attempts; - - var sl: posix.socklen_t = @sizeOf(posix.sockaddr.in); - var family: posix.sa_family_t = posix.AF.INET; - - var ns_list: ArrayList(Address) = .empty; - defer ns_list.deinit(gpa); - - try ns_list.resize(gpa, rc.ns.items.len); - - for (ns_list.items, rc.ns.items) |*ns, iplit| { - ns.* = iplit.addr; - assert(ns.getPort() == 53); - if (iplit.addr.any.family != posix.AF.INET) { - family = posix.AF.INET6; - } - } - - const flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK; - const fd = posix.socket(family, flags, 0) catch |err| switch (err) { - error.AddressFamilyNotSupported => blk: { - // Handle case where system lacks IPv6 support - if (family == posix.AF.INET6) { - family = posix.AF.INET; - break :blk try posix.socket(posix.AF.INET, flags, 0); - } - return err; - }, - else => |e| return e, - }; - defer Stream.close(.{ .handle = fd }); - - // Past this point, there are no errors. Each individual query will - // yield either no reply (indicated by zero length) or an answer - // packet which is up to the caller to interpret. - - // Convert any IPv4 addresses in a mixed environment to v4-mapped - if (family == posix.AF.INET6) { - try posix.setsockopt( - fd, - posix.SOL.IPV6, - std.os.linux.IPV6.V6ONLY, - &mem.toBytes(@as(c_int, 0)), - ); - for (ns_list.items) |*ns| { - if (ns.any.family != posix.AF.INET) continue; - mem.writeInt(u32, ns.in6.sa.addr[12..], ns.in.sa.addr, native_endian); - ns.in6.sa.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*; - ns.any.family = posix.AF.INET6; - ns.in6.sa.flowinfo = 0; - ns.in6.sa.scope_id = 0; - } - sl = @sizeOf(posix.sockaddr.in6); - } - - // Get local address and open/bind a socket - var sa: Address = undefined; - @memset(@as([*]u8, @ptrCast(&sa))[0..@sizeOf(Address)], 0); - sa.any.family = family; - try posix.bind(fd, &sa.any, sl); - - var pfd = [1]posix.pollfd{posix.pollfd{ - .fd = fd, - .events = posix.POLL.IN, - .revents = undefined, - }}; - const retry_interval = timeout / attempts; - var next: u32 = 0; - var t2: u64 = @bitCast(std.time.milliTimestamp()); - const t0 = t2; - var t1 = t2 - retry_interval; - - var servfail_retry: usize = undefined; - - outer: while (t2 - t0 < timeout) : (t2 = @as(u64, @bitCast(std.time.milliTimestamp()))) { - if (t2 - t1 >= retry_interval) { - // Query all configured nameservers in parallel - var i: usize = 0; - while (i < queries.len) : (i += 1) { - if (answers[i].len == 0) { - for (ns_list.items) |*ns| { - _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns.any, sl) catch undefined; - } - } - } - t1 = t2; - servfail_retry = 2 * queries.len; - } - - // Wait for a response, or until time to retry - const clamped_timeout = @min(@as(u31, std.math.maxInt(u31)), t1 + retry_interval - t2); - const nevents = posix.poll(&pfd, clamped_timeout) catch 0; - if (nevents == 0) continue; - - while (true) { - var sl_copy = sl; - const rlen = posix.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break; - - // Ignore non-identifiable packets - if (rlen < 4) continue; - - // Ignore replies from addresses we didn't send to - const ns = for (ns_list.items) |*ns| { - if (ns.eql(sa)) break ns; - } else continue; - - // Find which query this answer goes with, if any - var i: usize = next; - while (i < queries.len and (answer_bufs[next][0] != queries[i][0] or - answer_bufs[next][1] != queries[i][1])) : (i += 1) - {} - - if (i == queries.len) continue; - if (answers[i].len != 0) continue; - - // Only accept positive or negative responses; - // retry immediately on server failure, and ignore - // all other codes such as refusal. - switch (answer_bufs[next][3] & 15) { - 0, 3 => {}, - 2 => if (servfail_retry != 0) { - servfail_retry -= 1; - _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns.any, sl) catch undefined; - }, - else => continue, - } - - // Store answer in the right slot, or update next - // available temp slot if it's already in place. - answers[i].len = rlen; - if (i == next) { - while (next < queries.len and answers[next].len != 0) : (next += 1) {} - } else { - @memcpy(answer_bufs[i][0..rlen], answer_bufs[next][0..rlen]); - } - - if (next == queries.len) break :outer; - } - } - } - - fn deinit(rc: *ResolvConf) void { - const gpa = rc.gpa; - rc.ns.deinit(gpa); - rc.search.deinit(gpa); - rc.* = undefined; - } -}; - -fn linuxLookupNameFromNumericUnspec( - gpa: Allocator, - addrs: *ArrayList(LookupAddr), - name: []const u8, - port: u16, -) !void { - const addr = try Address.resolveIp(name, port); - try addrs.append(gpa, .{ .addr = addr }); -} - -fn dnsParse( - r: []const u8, - ctx: anytype, - comptime callback: anytype, -) !void { - // This implementation is ported from musl libc. - // A more idiomatic "ziggy" implementation would be welcome. - if (r.len < 12) return error.InvalidDnsPacket; - if ((r[3] & 15) != 0) return; - var p = r.ptr + 12; - var qdcount = r[4] * @as(usize, 256) + r[5]; - var ancount = r[6] * @as(usize, 256) + r[7]; - if (qdcount + ancount > 64) return error.InvalidDnsPacket; - while (qdcount != 0) { - qdcount -= 1; - while (@intFromPtr(p) - @intFromPtr(r.ptr) < r.len and p[0] -% 1 < 127) p += 1; - if (p[0] > 193 or (p[0] == 193 and p[1] > 254) or @intFromPtr(p) > @intFromPtr(r.ptr) + r.len - 6) - return error.InvalidDnsPacket; - p += @as(usize, 5) + @intFromBool(p[0] != 0); - } - while (ancount != 0) { - ancount -= 1; - while (@intFromPtr(p) - @intFromPtr(r.ptr) < r.len and p[0] -% 1 < 127) p += 1; - if (p[0] > 193 or (p[0] == 193 and p[1] > 254) or @intFromPtr(p) > @intFromPtr(r.ptr) + r.len - 6) - return error.InvalidDnsPacket; - p += @as(usize, 1) + @intFromBool(p[0] != 0); - const len = p[8] * @as(usize, 256) + p[9]; - if (@intFromPtr(p) + len > @intFromPtr(r.ptr) + r.len) return error.InvalidDnsPacket; - try callback(ctx, p[1], p[10..][0..len], r); - p += 10 + len; - } -} - -fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void { - const gpa = ctx.gpa; - switch (rr) { - posix.RR.A => { - if (data.len != 4) return error.InvalidDnsARecord; - try ctx.addrs.append(gpa, .{ - .addr = Address.initIp4(data[0..4].*, ctx.port), - }); - }, - posix.RR.AAAA => { - if (data.len != 16) return error.InvalidDnsAAAARecord; - try ctx.addrs.append(gpa, .{ - .addr = Address.initIp6(data[0..16].*, ctx.port, 0, 0), - }); - }, - posix.RR.CNAME => { - var tmp: [256]u8 = undefined; - // Returns len of compressed name. strlen to get canon name. - _ = try posix.dn_expand(packet, data, &tmp); - const canon_name = mem.sliceTo(&tmp, 0); - if (isValidHostName(canon_name)) { - ctx.canon.items.len = 0; - try ctx.canon.appendSlice(gpa, canon_name); - } - }, - else => return, - } -} - -pub const Stream = struct { - /// Underlying platform-defined type which may or may not be - /// interchangeable with a file system file descriptor. - handle: Handle, - - pub const Handle = switch (native_os) { - .windows => windows.ws2_32.SOCKET, - else => posix.fd_t, - }; - - pub fn close(s: Stream) void { - switch (native_os) { - .windows => windows.closesocket(s.handle) catch unreachable, - else => posix.close(s.handle), - } - } - - pub const ReadError = posix.ReadError || error{ - SocketNotBound, - MessageTooBig, - NetworkSubsystemFailed, - ConnectionResetByPeer, - SocketUnconnected, - }; - - pub const WriteError = posix.SendMsgError || error{ - ConnectionResetByPeer, - SocketNotBound, - MessageTooBig, - NetworkSubsystemFailed, - SystemResources, - SocketUnconnected, - Unexpected, - }; - - pub const Reader = switch (native_os) { - .windows => struct { - /// Use `interface` for portable code. - interface_state: Io.Reader, - /// Use `getStream` for portable code. - net_stream: Stream, - /// Use `getError` for portable code. - error_state: ?Error, - - pub const Error = ReadError; - - pub fn getStream(r: *const Reader) Stream { - return r.net_stream; - } - - pub fn getError(r: *const Reader) ?Error { - return r.error_state; - } - - pub fn interface(r: *Reader) *Io.Reader { - return &r.interface_state; - } - - pub fn init(net_stream: Stream, buffer: []u8) Reader { - return .{ - .interface_state = .{ - .vtable = &.{ - .stream = stream, - .readVec = readVec, - }, - .buffer = buffer, - .seek = 0, - .end = 0, - }, - .net_stream = net_stream, - .error_state = null, - }; - } - - fn stream(io_r: *Io.Reader, io_w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { - const dest = limit.slice(try io_w.writableSliceGreedy(1)); - var bufs: [1][]u8 = .{dest}; - const n = try readVec(io_r, &bufs); - io_w.advance(n); - return n; - } - - fn readVec(io_r: *std.Io.Reader, data: [][]u8) Io.Reader.Error!usize { - const r: *Reader = @alignCast(@fieldParentPtr("interface_state", io_r)); - var iovecs: [max_buffers_len]windows.ws2_32.WSABUF = undefined; - const bufs_n, const data_size = try io_r.writableVectorWsa(&iovecs, data); - const bufs = iovecs[0..bufs_n]; - assert(bufs[0].len != 0); - const n = streamBufs(r, bufs) catch |err| { - r.error_state = err; - return error.ReadFailed; - }; - if (n == 0) return error.EndOfStream; - if (n > data_size) { - io_r.end += n - data_size; - return data_size; - } - return n; - } - - fn handleRecvError(winsock_error: windows.ws2_32.WinsockError) Error!void { - switch (winsock_error) { - .WSAECONNRESET => return error.ConnectionResetByPeer, - .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space. - .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2 - .WSAEINVAL => return error.SocketNotBound, - .WSAEMSGSIZE => return error.MessageTooBig, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAENETRESET => return error.ConnectionResetByPeer, - .WSAENOTCONN => return error.SocketUnconnected, - .WSAEWOULDBLOCK => return error.WouldBlock, - .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function - .WSA_IO_PENDING => unreachable, - .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O - else => |err| return windows.unexpectedWSAError(err), - } - } - - fn streamBufs(r: *Reader, bufs: []windows.ws2_32.WSABUF) Error!u32 { - var flags: u32 = 0; - var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED); - - var n: u32 = undefined; - if (windows.ws2_32.WSARecv( - r.net_stream.handle, - bufs.ptr, - @intCast(bufs.len), - &n, - &flags, - &overlapped, - null, - ) == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) { - .WSA_IO_PENDING => { - var result_flags: u32 = undefined; - if (windows.ws2_32.WSAGetOverlappedResult( - r.net_stream.handle, - &overlapped, - &n, - windows.TRUE, - &result_flags, - ) == windows.FALSE) try handleRecvError(windows.ws2_32.WSAGetLastError()); - }, - else => |winsock_error| try handleRecvError(winsock_error), - }; - - return n; - } - }, - else => struct { - /// Use `getStream`, `interface`, and `getError` for portable code. - file_reader: File.Reader, - - pub const Error = ReadError; - - pub fn interface(r: *Reader) *Io.Reader { - return &r.file_reader.interface; - } - - pub fn init(net_stream: Stream, buffer: []u8) Reader { - return .{ - .file_reader = .{ - .interface = File.Reader.initInterface(buffer), - .file = .{ .handle = net_stream.handle }, - .mode = .streaming, - .seek_err = error.Unseekable, - .size_err = error.Streaming, - }, - }; - } - - pub fn getStream(r: *const Reader) Stream { - return .{ .handle = r.file_reader.file.handle }; - } - - pub fn getError(r: *const Reader) ?Error { - return r.file_reader.err; - } - }, - }; - - pub const Writer = switch (native_os) { - .windows => struct { - /// This field is present on all systems. - interface: Io.Writer, - /// Use `getStream` for cross-platform support. - stream: Stream, - /// This field is present on all systems. - err: ?Error = null, - - pub const Error = WriteError; - - pub fn init(stream: Stream, buffer: []u8) Writer { - return .{ - .stream = stream, - .interface = .{ - .vtable = &.{ .drain = drain }, - .buffer = buffer, - }, - }; - } - - pub fn getStream(w: *const Writer) Stream { - return w.stream; - } - - fn addWsaBuf(v: []windows.ws2_32.WSABUF, i: *u32, bytes: []const u8) void { - const cap = std.math.maxInt(u32); - var remaining = bytes; - while (remaining.len > cap) { - if (v.len - i.* == 0) return; - v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = cap }; - i.* += 1; - remaining = remaining[cap..]; - } else { - @branchHint(.likely); - if (v.len - i.* == 0) return; - v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = @intCast(remaining.len) }; - i.* += 1; - } - } - - fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { - const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - const buffered = io_w.buffered(); - comptime assert(native_os == .windows); - var iovecs: [max_buffers_len]windows.ws2_32.WSABUF = undefined; - var len: u32 = 0; - addWsaBuf(&iovecs, &len, buffered); - for (data[0 .. data.len - 1]) |bytes| addWsaBuf(&iovecs, &len, bytes); - const pattern = data[data.len - 1]; - if (iovecs.len - len != 0) switch (splat) { - 0 => {}, - 1 => addWsaBuf(&iovecs, &len, pattern), - else => switch (pattern.len) { - 0 => {}, - 1 => { - const splat_buffer_candidate = io_w.buffer[io_w.end..]; - var backup_buffer: [64]u8 = undefined; - const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len) - splat_buffer_candidate - else - &backup_buffer; - const memset_len = @min(splat_buffer.len, splat); - const buf = splat_buffer[0..memset_len]; - @memset(buf, pattern[0]); - addWsaBuf(&iovecs, &len, buf); - var remaining_splat = splat - buf.len; - while (remaining_splat > splat_buffer.len and len < iovecs.len) { - addWsaBuf(&iovecs, &len, splat_buffer); - remaining_splat -= splat_buffer.len; - } - addWsaBuf(&iovecs, &len, splat_buffer[0..remaining_splat]); - }, - else => for (0..@min(splat, iovecs.len - len)) |_| { - addWsaBuf(&iovecs, &len, pattern); - }, - }, - }; - const n = sendBufs(w.stream.handle, iovecs[0..len]) catch |err| { - w.err = err; - return error.WriteFailed; - }; - return io_w.consume(n); - } - - fn handleSendError(winsock_error: windows.ws2_32.WinsockError) Error!void { - switch (winsock_error) { - .WSAECONNABORTED => return error.ConnectionResetByPeer, - .WSAECONNRESET => return error.ConnectionResetByPeer, - .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space. - .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2 - .WSAEINVAL => return error.SocketNotBound, - .WSAEMSGSIZE => return error.MessageTooBig, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAENETRESET => return error.ConnectionResetByPeer, - .WSAENOBUFS => return error.SystemResources, - .WSAENOTCONN => return error.SocketUnconnected, - .WSAENOTSOCK => unreachable, // not a socket - .WSAEOPNOTSUPP => unreachable, // only for message-oriented sockets - .WSAESHUTDOWN => unreachable, // cannot send on a socket after write shutdown - .WSAEWOULDBLOCK => return error.WouldBlock, - .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function - .WSA_IO_PENDING => unreachable, - .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O - else => |err| return windows.unexpectedWSAError(err), - } - } - - fn sendBufs(handle: Stream.Handle, bufs: []windows.ws2_32.WSABUF) Error!u32 { - var n: u32 = undefined; - var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED); - if (windows.ws2_32.WSASend( - handle, - bufs.ptr, - @intCast(bufs.len), - &n, - 0, - &overlapped, - null, - ) == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) { - .WSA_IO_PENDING => { - var result_flags: u32 = undefined; - if (windows.ws2_32.WSAGetOverlappedResult( - handle, - &overlapped, - &n, - windows.TRUE, - &result_flags, - ) == windows.FALSE) try handleSendError(windows.ws2_32.WSAGetLastError()); - }, - else => |winsock_error| try handleSendError(winsock_error), - }; - - return n; - } - }, - else => struct { - /// This field is present on all systems. - interface: Io.Writer, - - err: ?Error = null, - file_writer: File.Writer, - - pub const Error = WriteError; - - pub fn init(stream: Stream, buffer: []u8) Writer { - return .{ - .interface = .{ - .vtable = &.{ - .drain = drain, - .sendFile = sendFile, - }, - .buffer = buffer, - }, - .file_writer = .initStreaming(.{ .handle = stream.handle }, &.{}), - }; - } - - pub fn getStream(w: *const Writer) Stream { - return .{ .handle = w.file_writer.file.handle }; - } - - 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; - if (v.len - i.* == 0) return; - v[i.*] = .{ .base = bytes.ptr, .len = bytes.len }; - i.* += 1; - } - - fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { - const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - const buffered = io_w.buffered(); - var iovecs: [max_buffers_len]posix.iovec_const = undefined; - var msg: posix.msghdr_const = .{ - .name = null, - .namelen = 0, - .iov = &iovecs, - .iovlen = 0, - .control = null, - .controllen = 0, - .flags = 0, - }; - addBuf(&iovecs, &msg.iovlen, buffered); - for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &msg.iovlen, bytes); - const pattern = data[data.len - 1]; - if (iovecs.len - msg.iovlen != 0) switch (splat) { - 0 => {}, - 1 => addBuf(&iovecs, &msg.iovlen, pattern), - else => switch (pattern.len) { - 0 => {}, - 1 => { - const splat_buffer_candidate = io_w.buffer[io_w.end..]; - var backup_buffer: [64]u8 = undefined; - const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len) - splat_buffer_candidate - else - &backup_buffer; - const memset_len = @min(splat_buffer.len, splat); - const buf = splat_buffer[0..memset_len]; - @memset(buf, pattern[0]); - addBuf(&iovecs, &msg.iovlen, buf); - var remaining_splat = splat - buf.len; - while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) { - assert(buf.len == splat_buffer.len); - addBuf(&iovecs, &msg.iovlen, splat_buffer); - remaining_splat -= splat_buffer.len; - } - addBuf(&iovecs, &msg.iovlen, splat_buffer[0..remaining_splat]); - }, - else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| { - addBuf(&iovecs, &msg.iovlen, pattern); - }, - }, - }; - const flags = posix.MSG.NOSIGNAL; - return io_w.consume(posix.sendmsg(w.file_writer.file.handle, &msg, flags) catch |err| { - w.err = err; - return error.WriteFailed; - }); - } - - fn sendFile(io_w: *Io.Writer, file_reader: *File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { - const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - const n = try w.file_writer.interface.sendFileHeader(io_w.buffered(), file_reader, limit); - return io_w.consume(n); - } - }, - }; - - pub fn reader(stream: Stream, buffer: []u8) Reader { - return .init(stream, buffer); - } - - pub fn writer(stream: Stream, buffer: []u8) Writer { - return .init(stream, buffer); - } - - const max_buffers_len = 8; - - /// Deprecated in favor of `Reader`. - pub fn read(self: Stream, buffer: []u8) ReadError!usize { - if (native_os == .windows) { - return windows.ReadFile(self.handle, buffer, null); - } - - return posix.read(self.handle, buffer); - } - - /// Deprecated in favor of `Reader`. - pub fn readv(s: Stream, iovecs: []const posix.iovec) ReadError!usize { - if (native_os == .windows) { - if (iovecs.len == 0) return 0; - const first = iovecs[0]; - return windows.ReadFile(s.handle, first.base[0..first.len], null); - } - - return posix.readv(s.handle, iovecs); - } - - /// Deprecated in favor of `Reader`. - pub fn readAtLeast(s: Stream, buffer: []u8, len: usize) ReadError!usize { - assert(len <= buffer.len); - var index: usize = 0; - while (index < len) { - const amt = try s.read(buffer[index..]); - if (amt == 0) break; - index += amt; - } - return index; - } - - /// Deprecated in favor of `Writer`. - pub fn write(self: Stream, buffer: []const u8) WriteError!usize { - var stream_writer = self.writer(&.{}); - return stream_writer.interface.writeVec(&.{buffer}) catch return stream_writer.err.?; - } - - /// Deprecated in favor of `Writer`. - pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void { - var index: usize = 0; - while (index < bytes.len) { - index += try self.write(bytes[index..]); - } - } - - /// Deprecated in favor of `Writer`. - pub fn writev(self: Stream, iovecs: []const posix.iovec_const) WriteError!usize { - return @errorCast(posix.writev(self.handle, iovecs)); - } - - /// Deprecated in favor of `Writer`. - pub fn writevAll(self: Stream, iovecs: []posix.iovec_const) WriteError!void { - if (iovecs.len == 0) return; - - var i: usize = 0; - while (true) { - var amt = try self.writev(iovecs[i..]); - while (amt >= iovecs[i].len) { - amt -= iovecs[i].len; - i += 1; - if (i >= iovecs.len) return; - } - iovecs[i].base += amt; - iovecs[i].len -= amt; - } - } -}; - -/// A bound, listening TCP socket, ready to accept new connections. -pub const Server = struct { - listen_address: Address, - stream: Stream, - - pub const Connection = struct { - stream: Stream, - address: Address, - }; - - pub fn deinit(s: *Server) void { - s.stream.close(); - s.* = undefined; - } - - pub const AcceptError = posix.AcceptError; - - /// Blocks until a client connects to the server. The returned `Connection` has - /// an open stream. - pub fn accept(s: *Server) AcceptError!Connection { - var accepted_addr: Address = undefined; - var addr_len: posix.socklen_t = @sizeOf(Address); - const fd = try posix.accept(s.stream.handle, &accepted_addr.any, &addr_len, posix.SOCK.CLOEXEC); - return .{ - .stream = .{ .handle = fd }, - .address = accepted_addr, - }; - } -}; - -test { - if (builtin.os.tag != .wasi) { - _ = Server; - _ = Stream; - _ = Address; - _ = @import("net/test.zig"); - } -} diff --git a/lib/std/std.zig b/lib/std/std.zig index 6853109f26..d1727ef3be 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -85,7 +85,6 @@ pub const macho = @import("macho.zig"); pub const math = @import("math.zig"); pub const mem = @import("mem.zig"); pub const meta = @import("meta.zig"); -pub const net = @import("net.zig"); pub const os = @import("os.zig"); pub const once = @import("once.zig").once; pub const pdb = @import("pdb.zig");