authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-11 11:30:58-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-14 00:16:49-07:00
log9a1f4cb011f8e6d5bc8355a7aaed37b437453e18
treeb3b5b0ec794dc9fca6ca6ef35d9cba2577577ccd
parent5496901e713dc1a75de289d7e8e3c79e7fee9510

std.net: update to new I/O API


5 files changed, 745 insertions(+), 345 deletions(-)

lib/std/Io/Writer.zig+26
...@@ -393,6 +393,32 @@ pub fn writableVectorPosix(w: *Writer, buffer: []std.posix.iovec, limit: Limit)...@@ -393,6 +393,32 @@ pub fn writableVectorPosix(w: *Writer, buffer: []std.posix.iovec, limit: Limit)
393 return buffer[0..i];393 return buffer[0..i];
394}394}
395395
396pub fn writableVectorWsa(
397 w: *Writer,
398 buffer: []std.os.windows.ws2_32.WSABUF,
399 limit: Limit,
400) Error![]std.os.windows.ws2_32.WSABUF {
401 var it = try writableVectorIterator(w);
402 var i: usize = 0;
403 var remaining = limit;
404 while (it.next()) |full_buffer| {
405 if (!remaining.nonzero()) break;
406 if (buffer.len - i == 0) break;
407 const buf = remaining.slice(full_buffer);
408 if (buf.len == 0) continue;
409 if (std.math.cast(u32, buf.len)) |len| {
410 buffer[i] = .{ .buf = buf.ptr, .len = len };
411 i += 1;
412 remaining = remaining.subtract(len).?;
413 continue;
414 }
415 buffer[i] = .{ .buf = buf.ptr, .len = std.math.maxInt(u32) };
416 i += 1;
417 break;
418 }
419 return buffer[0..i];
420}
421
396pub fn ensureUnusedCapacity(w: *Writer, n: usize) Error!void {422pub fn ensureUnusedCapacity(w: *Writer, n: usize) Error!void {
397 _ = try writableSliceGreedy(w, n);423 _ = try writableSliceGreedy(w, n);
398}424}
lib/std/http/test.zig+15-6
...@@ -135,7 +135,8 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -135,7 +135,8 @@ test "HTTP server handles a chunked transfer coding request" {
135 const gpa = std.testing.allocator;135 const gpa = std.testing.allocator;
136 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());136 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
137 defer stream.close();137 defer stream.close();
138 try stream.writeAll(request_bytes);138 var stream_writer = stream.writer(&.{});
139 try stream_writer.interface.writeAll(request_bytes);
139140
140 const expected_response =141 const expected_response =
141 "HTTP/1.1 200 OK\r\n" ++142 "HTTP/1.1 200 OK\r\n" ++
...@@ -144,7 +145,9 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -144,7 +145,9 @@ test "HTTP server handles a chunked transfer coding request" {
144 "content-type: text/plain\r\n" ++145 "content-type: text/plain\r\n" ++
145 "\r\n" ++146 "\r\n" ++
146 "message from server!\n";147 "message from server!\n";
147 const response = try stream.reader().readAllAlloc(gpa, expected_response.len);148 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded
149 var stream_reader = stream.reader(&tiny_buffer);
150 const response = try stream_reader.interface().allocRemaining(gpa, .limited(expected_response.len));
148 defer gpa.free(response);151 defer gpa.free(response);
149 try expectEqualStrings(expected_response, response);152 try expectEqualStrings(expected_response, response);
150}153}
...@@ -276,9 +279,12 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -276,9 +279,12 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
276 const gpa = std.testing.allocator;279 const gpa = std.testing.allocator;
277 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());280 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
278 defer stream.close();281 defer stream.close();
279 try stream.writeAll(request_bytes);282 var stream_writer = stream.writer(&.{});
283 try stream_writer.interface.writeAll(request_bytes);
280284
281 const response = try stream.reader().readAllAlloc(gpa, 8192);285 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded
286 var stream_reader = stream.reader(&tiny_buffer);
287 const response = try stream_reader.interface().allocRemaining(gpa, .limited(8192));
282 defer gpa.free(response);288 defer gpa.free(response);
283289
284 var expected_response = std.ArrayList(u8).init(gpa);290 var expected_response = std.ArrayList(u8).init(gpa);
...@@ -339,9 +345,12 @@ test "receiving arbitrary http headers from the client" {...@@ -339,9 +345,12 @@ test "receiving arbitrary http headers from the client" {
339 const gpa = std.testing.allocator;345 const gpa = std.testing.allocator;
340 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());346 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
341 defer stream.close();347 defer stream.close();
342 try stream.writeAll(request_bytes);348 var stream_writer = stream.writer(&.{});
349 try stream_writer.interface.writeAll(request_bytes);
343350
344 const response = try stream.reader().readAllAlloc(gpa, 8192);351 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded
352 var stream_reader = stream.reader(&tiny_buffer);
353 const response = try stream_reader.interface().allocRemaining(gpa, .limited(8192));
345 defer gpa.free(response);354 defer gpa.free(response);
346355
347 var expected_response = std.ArrayList(u8).init(gpa);356 var expected_response = std.ArrayList(u8).init(gpa);
lib/std/net.zig+692-332
...@@ -11,6 +11,9 @@ const io = std.io;...@@ -11,6 +11,9 @@ const io = std.io;
11const native_endian = builtin.target.cpu.arch.endian();11const native_endian = builtin.target.cpu.arch.endian();
12const native_os = builtin.os.tag;12const native_os = builtin.os.tag;
13const windows = std.os.windows;13const windows = std.os.windows;
14const Allocator = std.mem.Allocator;
15const ArrayList = std.ArrayListUnmanaged;
16const File = std.fs.File;
1417
15// Windows 10 added support for unix sockets in build 17063, redstone 4 is the18// Windows 10 added support for unix sockets in build 17063, redstone 4 is the
16// first release to support them.19// first release to support them.
...@@ -719,7 +722,7 @@ pub fn connectUnixSocket(path: []const u8) !Stream {...@@ -719,7 +722,7 @@ pub fn connectUnixSocket(path: []const u8) !Stream {
719 );722 );
720 errdefer Stream.close(.{ .handle = sockfd });723 errdefer Stream.close(.{ .handle = sockfd });
721724
722 var addr = try std.net.Address.initUnix(path);725 var addr = try Address.initUnix(path);
723 try posix.connect(sockfd, &addr.any, addr.getOsSockLen());726 try posix.connect(sockfd, &addr.any, addr.getOsSockLen());
724727
725 return .{ .handle = sockfd };728 return .{ .handle = sockfd };
...@@ -787,7 +790,7 @@ pub const AddressList = struct {...@@ -787,7 +790,7 @@ pub const AddressList = struct {
787pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError;790pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError;
788791
789/// All memory allocated with `allocator` will be freed before this function returns.792/// All memory allocated with `allocator` will be freed before this function returns.
790pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) TcpConnectToHostError!Stream {793pub fn tcpConnectToHost(allocator: Allocator, name: []const u8, port: u16) TcpConnectToHostError!Stream {
791 const list = try getAddressList(allocator, name, port);794 const list = try getAddressList(allocator, name, port);
792 defer list.deinit();795 defer list.deinit();
793796
...@@ -818,9 +821,9 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {...@@ -818,9 +821,9 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
818 return Stream{ .handle = sockfd };821 return Stream{ .handle = sockfd };
819}822}
820823
821const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{824// TODO: Instead of having a massive error set, make the error set have categories, and then
822 // TODO: break this up into error sets from the various underlying functions825// store the sub-error as a diagnostic value.
823826const GetAddressListError = Allocator.Error || File.OpenError || File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{
824 TemporaryNameServerFailure,827 TemporaryNameServerFailure,
825 NameServerFailure,828 NameServerFailure,
826 AddressFamilyNotSupported,829 AddressFamilyNotSupported,
...@@ -840,12 +843,13 @@ const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError ||...@@ -840,12 +843,13 @@ const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError ||
840843
841 InterfaceNotFound,844 InterfaceNotFound,
842 FileSystem,845 FileSystem,
846 ResolveConfParseFailed,
843};847};
844848
845/// Call `AddressList.deinit` on the result.849/// Call `AddressList.deinit` on the result.
846pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) GetAddressListError!*AddressList {850pub fn getAddressList(gpa: Allocator, name: []const u8, port: u16) GetAddressListError!*AddressList {
847 const result = blk: {851 const result = blk: {
848 var arena = std.heap.ArenaAllocator.init(allocator);852 var arena = std.heap.ArenaAllocator.init(gpa);
849 errdefer arena.deinit();853 errdefer arena.deinit();
850854
851 const result = try arena.allocator().create(AddressList);855 const result = try arena.allocator().create(AddressList);
...@@ -860,11 +864,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get...@@ -860,11 +864,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get
860 errdefer result.deinit();864 errdefer result.deinit();
861865
862 if (native_os == .windows) {866 if (native_os == .windows) {
863 const name_c = try allocator.dupeZ(u8, name);867 const name_c = try gpa.dupeZ(u8, name);
864 defer allocator.free(name_c);868 defer gpa.free(name_c);
865869
866 const port_c = try std.fmt.allocPrintSentinel(allocator, "{}", .{port}, 0);870 const port_c = try std.fmt.allocPrintSentinel(gpa, "{d}", .{port}, 0);
867 defer allocator.free(port_c);871 defer gpa.free(port_c);
868872
869 const ws2_32 = windows.ws2_32;873 const ws2_32 = windows.ws2_32;
870 const hints: posix.addrinfo = .{874 const hints: posix.addrinfo = .{
...@@ -932,11 +936,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get...@@ -932,11 +936,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get
932 }936 }
933937
934 if (builtin.link_libc) {938 if (builtin.link_libc) {
935 const name_c = try allocator.dupeZ(u8, name);939 const name_c = try gpa.dupeZ(u8, name);
936 defer allocator.free(name_c);940 defer gpa.free(name_c);
937941
938 const port_c = try std.fmt.allocPrintSentinel(allocator, "{}", .{port}, 0);942 const port_c = try std.fmt.allocPrintSentinel(gpa, "{d}", .{port}, 0);
939 defer allocator.free(port_c);943 defer gpa.free(port_c);
940944
941 const hints: posix.addrinfo = .{945 const hints: posix.addrinfo = .{
942 .flags = .{ .NUMERICSERV = true },946 .flags = .{ .NUMERICSERV = true },
...@@ -999,17 +1003,17 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get...@@ -999,17 +1003,17 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get
9991003
1000 if (native_os == .linux) {1004 if (native_os == .linux) {
1001 const family = posix.AF.UNSPEC;1005 const family = posix.AF.UNSPEC;
1002 var lookup_addrs = std.ArrayList(LookupAddr).init(allocator);1006 var lookup_addrs: ArrayList(LookupAddr) = .empty;
1003 defer lookup_addrs.deinit();1007 defer lookup_addrs.deinit(gpa);
10041008
1005 var canon = std.ArrayList(u8).init(arena);1009 var canon: ArrayList(u8) = .empty;
1006 defer canon.deinit();1010 defer canon.deinit(gpa);
10071011
1008 try linuxLookupName(&lookup_addrs, &canon, name, family, .{ .NUMERICSERV = true }, port);1012 try linuxLookupName(gpa, &lookup_addrs, &canon, name, family, .{ .NUMERICSERV = true }, port);
10091013
1010 result.addrs = try arena.alloc(Address, lookup_addrs.items.len);1014 result.addrs = try arena.alloc(Address, lookup_addrs.items.len);
1011 if (canon.items.len != 0) {1015 if (canon.items.len != 0) {
1012 result.canon_name = try canon.toOwnedSlice();1016 result.canon_name = try arena.dupe(u8, canon.items);
1013 }1017 }
10141018
1015 for (lookup_addrs.items, 0..) |lookup_addr, i| {1019 for (lookup_addrs.items, 0..) |lookup_addr, i| {
...@@ -1036,8 +1040,9 @@ const DAS_PREFIX_SHIFT = 8;...@@ -1036,8 +1040,9 @@ const DAS_PREFIX_SHIFT = 8;
1036const DAS_ORDER_SHIFT = 0;1040const DAS_ORDER_SHIFT = 0;
10371041
1038fn linuxLookupName(1042fn linuxLookupName(
1039 addrs: *std.ArrayList(LookupAddr),1043 gpa: Allocator,
1040 canon: *std.ArrayList(u8),1044 addrs: *ArrayList(LookupAddr),
1045 canon: *ArrayList(u8),
1041 opt_name: ?[]const u8,1046 opt_name: ?[]const u8,
1042 family: posix.sa_family_t,1047 family: posix.sa_family_t,
1043 flags: posix.AI,1048 flags: posix.AI,
...@@ -1046,13 +1051,13 @@ fn linuxLookupName(...@@ -1046,13 +1051,13 @@ fn linuxLookupName(
1046 if (opt_name) |name| {1051 if (opt_name) |name| {
1047 // reject empty name and check len so it fits into temp bufs1052 // reject empty name and check len so it fits into temp bufs
1048 canon.items.len = 0;1053 canon.items.len = 0;
1049 try canon.appendSlice(name);1054 try canon.appendSlice(gpa, name);
1050 if (Address.parseExpectingFamily(name, family, port)) |addr| {1055 if (Address.parseExpectingFamily(name, family, port)) |addr| {
1051 try addrs.append(LookupAddr{ .addr = addr });1056 try addrs.append(gpa, .{ .addr = addr });
1052 } else |name_err| if (flags.NUMERICHOST) {1057 } else |name_err| if (flags.NUMERICHOST) {
1053 return name_err;1058 return name_err;
1054 } else {1059 } else {
1055 try linuxLookupNameFromHosts(addrs, canon, name, family, port);1060 try linuxLookupNameFromHosts(gpa, addrs, canon, name, family, port);
1056 if (addrs.items.len == 0) {1061 if (addrs.items.len == 0) {
1057 // RFC 6761 Section 6.3.31062 // RFC 6761 Section 6.3.3
1058 // Name resolution APIs and libraries SHOULD recognize localhost1063 // Name resolution APIs and libraries SHOULD recognize localhost
...@@ -1063,17 +1068,18 @@ fn linuxLookupName(...@@ -1063,17 +1068,18 @@ fn linuxLookupName(
1063 // Check for equal to "localhost(.)" or ends in ".localhost(.)"1068 // Check for equal to "localhost(.)" or ends in ".localhost(.)"
1064 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";1069 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
1065 if (mem.endsWith(u8, name, localhost) and (name.len == localhost.len or name[name.len - localhost.len] == '.')) {1070 if (mem.endsWith(u8, name, localhost) and (name.len == localhost.len or name[name.len - localhost.len] == '.')) {
1066 try addrs.append(LookupAddr{ .addr = .{ .in = Ip4Address.parse("127.0.0.1", port) catch unreachable } });1071 try addrs.append(gpa, .{ .addr = .{ .in = Ip4Address.parse("127.0.0.1", port) catch unreachable } });
1067 try addrs.append(LookupAddr{ .addr = .{ .in6 = Ip6Address.parse("::1", port) catch unreachable } });1072 try addrs.append(gpa, .{ .addr = .{ .in6 = Ip6Address.parse("::1", port) catch unreachable } });
1068 return;1073 return;
1069 }1074 }
10701075
1071 try linuxLookupNameFromDnsSearch(addrs, canon, name, family, port);1076 try linuxLookupNameFromDnsSearch(gpa, addrs, canon, name, family, port);
1072 }1077 }
1073 }1078 }
1074 } else {1079 } else {
1075 try canon.resize(0);1080 try canon.resize(gpa, 0);
1076 try linuxLookupNameFromNull(addrs, family, flags, port);1081 try addrs.ensureUnusedCapacity(gpa, 2);
1082 linuxLookupNameFromNull(addrs, family, flags, port);
1077 }1083 }
1078 if (addrs.items.len == 0) return error.UnknownHostName;1084 if (addrs.items.len == 0) return error.UnknownHostName;
10791085
...@@ -1279,39 +1285,40 @@ fn addrCmpLessThan(context: void, b: LookupAddr, a: LookupAddr) bool {...@@ -1279,39 +1285,40 @@ fn addrCmpLessThan(context: void, b: LookupAddr, a: LookupAddr) bool {
1279}1285}
12801286
1281fn linuxLookupNameFromNull(1287fn linuxLookupNameFromNull(
1282 addrs: *std.ArrayList(LookupAddr),1288 addrs: *ArrayList(LookupAddr),
1283 family: posix.sa_family_t,1289 family: posix.sa_family_t,
1284 flags: posix.AI,1290 flags: posix.AI,
1285 port: u16,1291 port: u16,
1286) !void {1292) void {
1287 if (flags.PASSIVE) {1293 if (flags.PASSIVE) {
1288 if (family != posix.AF.INET6) {1294 if (family != posix.AF.INET6) {
1289 (try addrs.addOne()).* = LookupAddr{1295 addrs.appendAssumeCapacity(.{
1290 .addr = Address.initIp4([1]u8{0} ** 4, port),1296 .addr = Address.initIp4([1]u8{0} ** 4, port),
1291 };1297 });
1292 }1298 }
1293 if (family != posix.AF.INET) {1299 if (family != posix.AF.INET) {
1294 (try addrs.addOne()).* = LookupAddr{1300 addrs.appendAssumeCapacity(.{
1295 .addr = Address.initIp6([1]u8{0} ** 16, port, 0, 0),1301 .addr = Address.initIp6([1]u8{0} ** 16, port, 0, 0),
1296 };1302 });
1297 }1303 }
1298 } else {1304 } else {
1299 if (family != posix.AF.INET6) {1305 if (family != posix.AF.INET6) {
1300 (try addrs.addOne()).* = LookupAddr{1306 addrs.appendAssumeCapacity(.{
1301 .addr = Address.initIp4([4]u8{ 127, 0, 0, 1 }, port),1307 .addr = Address.initIp4([4]u8{ 127, 0, 0, 1 }, port),
1302 };1308 });
1303 }1309 }
1304 if (family != posix.AF.INET) {1310 if (family != posix.AF.INET) {
1305 (try addrs.addOne()).* = LookupAddr{1311 addrs.appendAssumeCapacity(.{
1306 .addr = Address.initIp6(([1]u8{0} ** 15) ++ [1]u8{1}, port, 0, 0),1312 .addr = Address.initIp6(([1]u8{0} ** 15) ++ [1]u8{1}, port, 0, 0),
1307 };1313 });
1308 }1314 }
1309 }1315 }
1310}1316}
13111317
1312fn linuxLookupNameFromHosts(1318fn linuxLookupNameFromHosts(
1313 addrs: *std.ArrayList(LookupAddr),1319 gpa: Allocator,
1314 canon: *std.ArrayList(u8),1320 addrs: *ArrayList(LookupAddr),
1321 canon: *ArrayList(u8),
1315 name: []const u8,1322 name: []const u8,
1316 family: posix.sa_family_t,1323 family: posix.sa_family_t,
1317 port: u16,1324 port: u16,
...@@ -1325,18 +1332,36 @@ fn linuxLookupNameFromHosts(...@@ -1325,18 +1332,36 @@ fn linuxLookupNameFromHosts(
1325 };1332 };
1326 defer file.close();1333 defer file.close();
13271334
1328 var buffered_reader = std.io.bufferedReader(file.deprecatedReader());
1329 const reader = buffered_reader.reader();
1330 var line_buf: [512]u8 = undefined;1335 var line_buf: [512]u8 = undefined;
1331 while (reader.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) {1336 var file_reader = file.reader(&line_buf);
1332 error.StreamTooLong => blk: {1337 return parseHosts(gpa, addrs, canon, name, family, port, &file_reader.interface) catch |err| switch (err) {
1333 // Skip to the delimiter in the reader, to fix parsing1338 error.OutOfMemory => return error.OutOfMemory,
1334 try reader.skipUntilDelimiterOrEof('\n');1339 error.ReadFailed => return file_reader.err.?,
1335 // Use the truncated line. A truncated comment or hostname will be handled correctly.1340 };
1336 break :blk &line_buf;1341}
1337 },1342
1338 else => |e| return e,1343fn parseHosts(
1339 }) |line| {1344 gpa: Allocator,
1345 addrs: *ArrayList(LookupAddr),
1346 canon: *ArrayList(u8),
1347 name: []const u8,
1348 family: posix.sa_family_t,
1349 port: u16,
1350 br: *io.Reader,
1351) error{ OutOfMemory, ReadFailed }!void {
1352 while (true) {
1353 const line = br.takeDelimiterExclusive('\n') catch |err| switch (err) {
1354 error.StreamTooLong => {
1355 // Skip lines that are too long.
1356 _ = br.discardDelimiterInclusive('\n') catch |e| switch (e) {
1357 error.EndOfStream => break,
1358 error.ReadFailed => return error.ReadFailed,
1359 };
1360 continue;
1361 },
1362 error.ReadFailed => return error.ReadFailed,
1363 error.EndOfStream => break,
1364 };
1340 var split_it = mem.splitScalar(u8, line, '#');1365 var split_it = mem.splitScalar(u8, line, '#');
1341 const no_comment_line = split_it.first();1366 const no_comment_line = split_it.first();
13421367
...@@ -1360,17 +1385,32 @@ fn linuxLookupNameFromHosts(...@@ -1360,17 +1385,32 @@ fn linuxLookupNameFromHosts(
1360 error.NonCanonical,1385 error.NonCanonical,
1361 => continue,1386 => continue,
1362 };1387 };
1363 try addrs.append(LookupAddr{ .addr = addr });1388 try addrs.append(gpa, .{ .addr = addr });
13641389
1365 // first name is canonical name1390 // first name is canonical name
1366 const name_text = first_name_text.?;1391 const name_text = first_name_text.?;
1367 if (isValidHostName(name_text)) {1392 if (isValidHostName(name_text)) {
1368 canon.items.len = 0;1393 canon.items.len = 0;
1369 try canon.appendSlice(name_text);1394 try canon.appendSlice(gpa, name_text);
1370 }1395 }
1371 }1396 }
1372}1397}
13731398
1399test parseHosts {
1400 var reader: std.io.Reader = .fixed(
1401 \\127.0.0.1 localhost
1402 \\::1 localhost
1403 \\127.0.0.2 abcd
1404 );
1405 var addrs: ArrayList(LookupAddr) = .empty;
1406 defer addrs.deinit(std.testing.allocator);
1407 var canon: ArrayList(u8) = .empty;
1408 defer canon.deinit(std.testing.allocator);
1409 try parseHosts(std.testing.allocator, &addrs, &canon, "abcd", posix.AF.UNSPEC, 1234, &reader);
1410 try std.testing.expectEqual(1, addrs.items.len);
1411 try std.testing.expectFmt("127.0.0.2:1234", "{f}", .{addrs.items[0].addr});
1412}
1413
1374pub fn isValidHostName(hostname: []const u8) bool {1414pub fn isValidHostName(hostname: []const u8) bool {
1375 if (hostname.len >= 254) return false;1415 if (hostname.len >= 254) return false;
1376 if (!std.unicode.utf8ValidateSlice(hostname)) return false;1416 if (!std.unicode.utf8ValidateSlice(hostname)) return false;
...@@ -1384,14 +1424,15 @@ pub fn isValidHostName(hostname: []const u8) bool {...@@ -1384,14 +1424,15 @@ pub fn isValidHostName(hostname: []const u8) bool {
1384}1424}
13851425
1386fn linuxLookupNameFromDnsSearch(1426fn linuxLookupNameFromDnsSearch(
1387 addrs: *std.ArrayList(LookupAddr),1427 gpa: Allocator,
1388 canon: *std.ArrayList(u8),1428 addrs: *ArrayList(LookupAddr),
1429 canon: *ArrayList(u8),
1389 name: []const u8,1430 name: []const u8,
1390 family: posix.sa_family_t,1431 family: posix.sa_family_t,
1391 port: u16,1432 port: u16,
1392) !void {1433) !void {
1393 var rc: ResolvConf = undefined;1434 var rc: ResolvConf = undefined;
1394 try getResolvConf(addrs.allocator, &rc);1435 rc.init(gpa) catch return error.ResolveConfParseFailed;
1395 defer rc.deinit();1436 defer rc.deinit();
13961437
1397 // Count dots, suppress search when >=ndots or name ends in1438 // Count dots, suppress search when >=ndots or name ends in
...@@ -1416,37 +1457,40 @@ fn linuxLookupNameFromDnsSearch(...@@ -1416,37 +1457,40 @@ fn linuxLookupNameFromDnsSearch(
1416 // provides the desired default canonical name (if the requested1457 // provides the desired default canonical name (if the requested
1417 // name is not a CNAME record) and serves as a buffer for passing1458 // name is not a CNAME record) and serves as a buffer for passing
1418 // the full requested name to name_from_dns.1459 // the full requested name to name_from_dns.
1419 try canon.resize(canon_name.len);1460 try canon.resize(gpa, canon_name.len);
1420 @memcpy(canon.items, canon_name);1461 @memcpy(canon.items, canon_name);
1421 try canon.append('.');1462 try canon.append(gpa, '.');
14221463
1423 var tok_it = mem.tokenizeAny(u8, search, " \t");1464 var tok_it = mem.tokenizeAny(u8, search, " \t");
1424 while (tok_it.next()) |tok| {1465 while (tok_it.next()) |tok| {
1425 canon.shrinkRetainingCapacity(canon_name.len + 1);1466 canon.shrinkRetainingCapacity(canon_name.len + 1);
1426 try canon.appendSlice(tok);1467 try canon.appendSlice(gpa, tok);
1427 try linuxLookupNameFromDns(addrs, canon, canon.items, family, rc, port);1468 try linuxLookupNameFromDns(gpa, addrs, canon, canon.items, family, rc, port);
1428 if (addrs.items.len != 0) return;1469 if (addrs.items.len != 0) return;
1429 }1470 }
14301471
1431 canon.shrinkRetainingCapacity(canon_name.len);1472 canon.shrinkRetainingCapacity(canon_name.len);
1432 return linuxLookupNameFromDns(addrs, canon, name, family, rc, port);1473 return linuxLookupNameFromDns(gpa, addrs, canon, name, family, rc, port);
1433}1474}
14341475
1435const dpc_ctx = struct {1476const dpc_ctx = struct {
1436 addrs: *std.ArrayList(LookupAddr),1477 gpa: Allocator,
1437 canon: *std.ArrayList(u8),1478 addrs: *ArrayList(LookupAddr),
1479 canon: *ArrayList(u8),
1438 port: u16,1480 port: u16,
1439};1481};
14401482
1441fn linuxLookupNameFromDns(1483fn linuxLookupNameFromDns(
1442 addrs: *std.ArrayList(LookupAddr),1484 gpa: Allocator,
1443 canon: *std.ArrayList(u8),1485 addrs: *ArrayList(LookupAddr),
1486 canon: *ArrayList(u8),
1444 name: []const u8,1487 name: []const u8,
1445 family: posix.sa_family_t,1488 family: posix.sa_family_t,
1446 rc: ResolvConf,1489 rc: ResolvConf,
1447 port: u16,1490 port: u16,
1448) !void {1491) !void {
1449 const ctx = dpc_ctx{1492 const ctx: dpc_ctx = .{
1493 .gpa = gpa,
1450 .addrs = addrs,1494 .addrs = addrs,
1451 .canon = canon,1495 .canon = canon,
1452 .port = port,1496 .port = port,
...@@ -1456,8 +1500,8 @@ fn linuxLookupNameFromDns(...@@ -1456,8 +1500,8 @@ fn linuxLookupNameFromDns(
1456 rr: u8,1500 rr: u8,
1457 };1501 };
1458 const afrrs = [_]AfRr{1502 const afrrs = [_]AfRr{
1459 AfRr{ .af = posix.AF.INET6, .rr = posix.RR.A },1503 .{ .af = posix.AF.INET6, .rr = posix.RR.A },
1460 AfRr{ .af = posix.AF.INET, .rr = posix.RR.AAAA },1504 .{ .af = posix.AF.INET, .rr = posix.RR.AAAA },
1461 };1505 };
1462 var qbuf: [2][280]u8 = undefined;1506 var qbuf: [2][280]u8 = undefined;
1463 var abuf: [2][512]u8 = undefined;1507 var abuf: [2][512]u8 = undefined;
...@@ -1477,7 +1521,7 @@ fn linuxLookupNameFromDns(...@@ -1477,7 +1521,7 @@ fn linuxLookupNameFromDns(
1477 ap[0].len = 0;1521 ap[0].len = 0;
1478 ap[1].len = 0;1522 ap[1].len = 0;
14791523
1480 try resMSendRc(qp[0..nq], ap[0..nq], apbuf[0..nq], rc);1524 try rc.resMSendRc(qp[0..nq], ap[0..nq], apbuf[0..nq]);
14811525
1482 var i: usize = 0;1526 var i: usize = 0;
1483 while (i < nq) : (i += 1) {1527 while (i < nq) : (i += 1) {
...@@ -1492,248 +1536,257 @@ fn linuxLookupNameFromDns(...@@ -1492,248 +1536,257 @@ fn linuxLookupNameFromDns(
1492}1536}
14931537
1494const ResolvConf = struct {1538const ResolvConf = struct {
1539 gpa: Allocator,
1495 attempts: u32,1540 attempts: u32,
1496 ndots: u32,1541 ndots: u32,
1497 timeout: u32,1542 timeout: u32,
1498 search: std.ArrayList(u8),1543 search: ArrayList(u8),
1499 ns: std.ArrayList(LookupAddr),1544 /// TODO there are actually only allowed to be maximum 3 nameservers, no need
1545 /// for an array list.
1546 ns: ArrayList(LookupAddr),
1547
1548 /// Returns `error.StreamTooLong` if a line is longer than 512 bytes.
1549 /// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761
1550 fn init(rc: *ResolvConf, gpa: Allocator) !void {
1551 rc.* = .{
1552 .gpa = gpa,
1553 .ns = .empty,
1554 .search = .empty,
1555 .ndots = 1,
1556 .timeout = 5,
1557 .attempts = 2,
1558 };
1559 errdefer rc.deinit();
1560
1561 const file = fs.openFileAbsoluteZ("/etc/resolv.conf", .{}) catch |err| switch (err) {
1562 error.FileNotFound,
1563 error.NotDir,
1564 error.AccessDenied,
1565 => return linuxLookupNameFromNumericUnspec(gpa, &rc.ns, "127.0.0.1", 53),
1566 else => |e| return e,
1567 };
1568 defer file.close();
15001569
1501 fn deinit(rc: *ResolvConf) void {1570 var line_buf: [512]u8 = undefined;
1502 rc.ns.deinit();1571 var file_reader = file.reader(&line_buf);
1503 rc.search.deinit();1572 return parse(rc, &file_reader.interface) catch |err| switch (err) {
1504 rc.* = undefined;1573 error.ReadFailed => return file_reader.err.?,
1574 else => |e| return e,
1575 };
1505 }1576 }
1506};
1507
1508/// Ignores lines longer than 512 bytes.
1509/// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761
1510fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void {
1511 rc.* = ResolvConf{
1512 .ns = std.ArrayList(LookupAddr).init(allocator),
1513 .search = std.ArrayList(u8).init(allocator),
1514 .ndots = 1,
1515 .timeout = 5,
1516 .attempts = 2,
1517 };
1518 errdefer rc.deinit();
15191577
1520 const file = fs.openFileAbsoluteZ("/etc/resolv.conf", .{}) catch |err| switch (err) {1578 const Directive = enum { options, nameserver, domain, search };
1521 error.FileNotFound,1579 const Option = enum { ndots, attempts, timeout };
1522 error.NotDir,
1523 error.AccessDenied,
1524 => return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1", 53),
1525 else => |e| return e,
1526 };
1527 defer file.close();
1528
1529 var buf_reader = std.io.bufferedReader(file.deprecatedReader());
1530 const stream = buf_reader.reader();
1531 var line_buf: [512]u8 = undefined;
1532 while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) {
1533 error.StreamTooLong => blk: {
1534 // Skip to the delimiter in the stream, to fix parsing
1535 try stream.skipUntilDelimiterOrEof('\n');
1536 // Give an empty line to the while loop, which will be skipped.
1537 break :blk line_buf[0..0];
1538 },
1539 else => |e| return e,
1540 }) |line| {
1541 const no_comment_line = no_comment_line: {
1542 var split = mem.splitScalar(u8, line, '#');
1543 break :no_comment_line split.first();
1544 };
1545 var line_it = mem.tokenizeAny(u8, no_comment_line, " \t");
15461580
1547 const token = line_it.next() orelse continue;1581 fn parse(rc: *ResolvConf, reader: *io.Reader) !void {
1548 if (mem.eql(u8, token, "options")) {1582 const gpa = rc.gpa;
1549 while (line_it.next()) |sub_tok| {1583 while (reader.takeSentinel('\n')) |line_with_comment| {
1550 var colon_it = mem.splitScalar(u8, sub_tok, ':');1584 const line = line: {
1551 const name = colon_it.first();1585 var split = mem.splitScalar(u8, line_with_comment, '#');
1552 const value_txt = colon_it.next() orelse continue;1586 break :line split.first();
1553 const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) {1587 };
1554 // TODO https://github.com/ziglang/zig/issues/118121588 var line_it = mem.tokenizeAny(u8, line, " \t");
1555 error.Overflow => @as(u8, 255),1589
1556 error.InvalidCharacter => continue,1590 const token = line_it.next() orelse continue;
1557 };1591 switch (std.meta.stringToEnum(Directive, token) orelse continue) {
1558 if (mem.eql(u8, name, "ndots")) {1592 .options => while (line_it.next()) |sub_tok| {
1559 rc.ndots = @min(value, 15);1593 var colon_it = mem.splitScalar(u8, sub_tok, ':');
1560 } else if (mem.eql(u8, name, "attempts")) {1594 const name = colon_it.first();
1561 rc.attempts = @min(value, 10);1595 const value_txt = colon_it.next() orelse continue;
1562 } else if (mem.eql(u8, name, "timeout")) {1596 const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) {
1563 rc.timeout = @min(value, 60);1597 error.Overflow => 255,
1564 }1598 error.InvalidCharacter => continue,
1599 };
1600 switch (std.meta.stringToEnum(Option, name) orelse continue) {
1601 .ndots => rc.ndots = @min(value, 15),
1602 .attempts => rc.attempts = @min(value, 10),
1603 .timeout => rc.timeout = @min(value, 60),
1604 }
1605 },
1606 .nameserver => {
1607 const ip_txt = line_it.next() orelse continue;
1608 try linuxLookupNameFromNumericUnspec(gpa, &rc.ns, ip_txt, 53);
1609 },
1610 .domain, .search => {
1611 rc.search.items.len = 0;
1612 try rc.search.appendSlice(gpa, line_it.rest());
1613 },
1565 }1614 }
1566 } else if (mem.eql(u8, token, "nameserver")) {1615 } else |err| switch (err) {
1567 const ip_txt = line_it.next() orelse continue;1616 error.EndOfStream => if (reader.bufferedLen() != 0) return error.EndOfStream,
1568 try linuxLookupNameFromNumericUnspec(&rc.ns, ip_txt, 53);1617 else => |e| return e,
1569 } else if (mem.eql(u8, token, "domain") or mem.eql(u8, token, "search")) {
1570 rc.search.items.len = 0;
1571 try rc.search.appendSlice(line_it.rest());
1572 }1618 }
1573 }
15741619
1575 if (rc.ns.items.len == 0) {1620 if (rc.ns.items.len == 0) {
1576 return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1", 53);1621 return linuxLookupNameFromNumericUnspec(gpa, &rc.ns, "127.0.0.1", 53);
1622 }
1577 }1623 }
1578}
15791624
1580fn linuxLookupNameFromNumericUnspec(1625 fn resMSendRc(
1581 addrs: *std.ArrayList(LookupAddr),1626 rc: ResolvConf,
1582 name: []const u8,1627 queries: []const []const u8,
1583 port: u16,1628 answers: [][]u8,
1584) !void {1629 answer_bufs: []const []u8,
1585 const addr = try Address.resolveIp(name, port);1630 ) !void {
1586 (try addrs.addOne()).* = LookupAddr{ .addr = addr };1631 const gpa = rc.gpa;
1587}1632 const timeout = 1000 * rc.timeout;
1633 const attempts = rc.attempts;
15881634
1589fn resMSendRc(1635 var sl: posix.socklen_t = @sizeOf(posix.sockaddr.in);
1590 queries: []const []const u8,1636 var family: posix.sa_family_t = posix.AF.INET;
1591 answers: [][]u8,
1592 answer_bufs: []const []u8,
1593 rc: ResolvConf,
1594) !void {
1595 const timeout = 1000 * rc.timeout;
1596 const attempts = rc.attempts;
15971637
1598 var sl: posix.socklen_t = @sizeOf(posix.sockaddr.in);1638 var ns_list: ArrayList(Address) = .empty;
1599 var family: posix.sa_family_t = posix.AF.INET;1639 defer ns_list.deinit(gpa);
16001640
1601 var ns_list = std.ArrayList(Address).init(rc.ns.allocator);1641 try ns_list.resize(gpa, rc.ns.items.len);
1602 defer ns_list.deinit();
16031642
1604 try ns_list.resize(rc.ns.items.len);1643 for (ns_list.items, rc.ns.items) |*ns, iplit| {
1605 const ns = ns_list.items;1644 ns.* = iplit.addr;
16061645 assert(ns.getPort() == 53);
1607 for (rc.ns.items, 0..) |iplit, i| {1646 if (iplit.addr.any.family != posix.AF.INET) {
1608 ns[i] = iplit.addr;1647 family = posix.AF.INET6;
1609 assert(ns[i].getPort() == 53);1648 }
1610 if (iplit.addr.any.family != posix.AF.INET) {
1611 family = posix.AF.INET6;
1612 }1649 }
1613 }
16141650
1615 const flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK;1651 const flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK;
1616 const fd = posix.socket(family, flags, 0) catch |err| switch (err) {1652 const fd = posix.socket(family, flags, 0) catch |err| switch (err) {
1617 error.AddressFamilyNotSupported => blk: {1653 error.AddressFamilyNotSupported => blk: {
1618 // Handle case where system lacks IPv6 support1654 // Handle case where system lacks IPv6 support
1619 if (family == posix.AF.INET6) {1655 if (family == posix.AF.INET6) {
1620 family = posix.AF.INET;1656 family = posix.AF.INET;
1621 break :blk try posix.socket(posix.AF.INET, flags, 0);1657 break :blk try posix.socket(posix.AF.INET, flags, 0);
1658 }
1659 return err;
1660 },
1661 else => |e| return e,
1662 };
1663 defer Stream.close(.{ .handle = fd });
1664
1665 // Past this point, there are no errors. Each individual query will
1666 // yield either no reply (indicated by zero length) or an answer
1667 // packet which is up to the caller to interpret.
1668
1669 // Convert any IPv4 addresses in a mixed environment to v4-mapped
1670 if (family == posix.AF.INET6) {
1671 try posix.setsockopt(
1672 fd,
1673 posix.SOL.IPV6,
1674 std.os.linux.IPV6.V6ONLY,
1675 &mem.toBytes(@as(c_int, 0)),
1676 );
1677 for (ns_list.items) |*ns| {
1678 if (ns.any.family != posix.AF.INET) continue;
1679 mem.writeInt(u32, ns.in6.sa.addr[12..], ns.in.sa.addr, native_endian);
1680 ns.in6.sa.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
1681 ns.any.family = posix.AF.INET6;
1682 ns.in6.sa.flowinfo = 0;
1683 ns.in6.sa.scope_id = 0;
1622 }1684 }
1623 return err;1685 sl = @sizeOf(posix.sockaddr.in6);
1624 },
1625 else => |e| return e,
1626 };
1627 defer Stream.close(.{ .handle = fd });
1628
1629 // Past this point, there are no errors. Each individual query will
1630 // yield either no reply (indicated by zero length) or an answer
1631 // packet which is up to the caller to interpret.
1632
1633 // Convert any IPv4 addresses in a mixed environment to v4-mapped
1634 if (family == posix.AF.INET6) {
1635 try posix.setsockopt(
1636 fd,
1637 posix.SOL.IPV6,
1638 std.os.linux.IPV6.V6ONLY,
1639 &mem.toBytes(@as(c_int, 0)),
1640 );
1641 for (0..ns.len) |i| {
1642 if (ns[i].any.family != posix.AF.INET) continue;
1643 mem.writeInt(u32, ns[i].in6.sa.addr[12..], ns[i].in.sa.addr, native_endian);
1644 ns[i].in6.sa.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
1645 ns[i].any.family = posix.AF.INET6;
1646 ns[i].in6.sa.flowinfo = 0;
1647 ns[i].in6.sa.scope_id = 0;
1648 }1686 }
1649 sl = @sizeOf(posix.sockaddr.in6);1687
1650 }1688 // Get local address and open/bind a socket
16511689 var sa: Address = undefined;
1652 // Get local address and open/bind a socket1690 @memset(@as([*]u8, @ptrCast(&sa))[0..@sizeOf(Address)], 0);
1653 var sa: Address = undefined;1691 sa.any.family = family;
1654 @memset(@as([*]u8, @ptrCast(&sa))[0..@sizeOf(Address)], 0);1692 try posix.bind(fd, &sa.any, sl);
1655 sa.any.family = family;1693
1656 try posix.bind(fd, &sa.any, sl);1694 var pfd = [1]posix.pollfd{posix.pollfd{
16571695 .fd = fd,
1658 var pfd = [1]posix.pollfd{posix.pollfd{1696 .events = posix.POLL.IN,
1659 .fd = fd,1697 .revents = undefined,
1660 .events = posix.POLL.IN,1698 }};
1661 .revents = undefined,1699 const retry_interval = timeout / attempts;
1662 }};1700 var next: u32 = 0;
1663 const retry_interval = timeout / attempts;1701 var t2: u64 = @bitCast(std.time.milliTimestamp());
1664 var next: u32 = 0;1702 const t0 = t2;
1665 var t2: u64 = @bitCast(std.time.milliTimestamp());1703 var t1 = t2 - retry_interval;
1666 const t0 = t2;1704
1667 var t1 = t2 - retry_interval;1705 var servfail_retry: usize = undefined;
16681706
1669 var servfail_retry: usize = undefined;1707 outer: while (t2 - t0 < timeout) : (t2 = @as(u64, @bitCast(std.time.milliTimestamp()))) {
16701708 if (t2 - t1 >= retry_interval) {
1671 outer: while (t2 - t0 < timeout) : (t2 = @as(u64, @bitCast(std.time.milliTimestamp()))) {1709 // Query all configured nameservers in parallel
1672 if (t2 - t1 >= retry_interval) {1710 var i: usize = 0;
1673 // Query all configured nameservers in parallel1711 while (i < queries.len) : (i += 1) {
1674 var i: usize = 0;1712 if (answers[i].len == 0) {
1675 while (i < queries.len) : (i += 1) {1713 for (ns_list.items) |*ns| {
1676 if (answers[i].len == 0) {1714 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns.any, sl) catch undefined;
1677 var j: usize = 0;1715 }
1678 while (j < ns.len) : (j += 1) {
1679 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined;
1680 }1716 }
1681 }1717 }
1718 t1 = t2;
1719 servfail_retry = 2 * queries.len;
1682 }1720 }
1683 t1 = t2;
1684 servfail_retry = 2 * queries.len;
1685 }
16861721
1687 // Wait for a response, or until time to retry1722 // Wait for a response, or until time to retry
1688 const clamped_timeout = @min(@as(u31, std.math.maxInt(u31)), t1 + retry_interval - t2);1723 const clamped_timeout = @min(@as(u31, std.math.maxInt(u31)), t1 + retry_interval - t2);
1689 const nevents = posix.poll(&pfd, clamped_timeout) catch 0;1724 const nevents = posix.poll(&pfd, clamped_timeout) catch 0;
1690 if (nevents == 0) continue;1725 if (nevents == 0) continue;
1726
1727 while (true) {
1728 var sl_copy = sl;
1729 const rlen = posix.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;
1730
1731 // Ignore non-identifiable packets
1732 if (rlen < 4) continue;
1733
1734 // Ignore replies from addresses we didn't send to
1735 const ns = for (ns_list.items) |*ns| {
1736 if (ns.eql(sa)) break ns;
1737 } else continue;
1738
1739 // Find which query this answer goes with, if any
1740 var i: usize = next;
1741 while (i < queries.len and (answer_bufs[next][0] != queries[i][0] or
1742 answer_bufs[next][1] != queries[i][1])) : (i += 1)
1743 {}
1744
1745 if (i == queries.len) continue;
1746 if (answers[i].len != 0) continue;
1747
1748 // Only accept positive or negative responses;
1749 // retry immediately on server failure, and ignore
1750 // all other codes such as refusal.
1751 switch (answer_bufs[next][3] & 15) {
1752 0, 3 => {},
1753 2 => if (servfail_retry != 0) {
1754 servfail_retry -= 1;
1755 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns.any, sl) catch undefined;
1756 },
1757 else => continue,
1758 }
16911759
1692 while (true) {1760 // Store answer in the right slot, or update next
1693 var sl_copy = sl;1761 // available temp slot if it's already in place.
1694 const rlen = posix.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;1762 answers[i].len = rlen;
16951763 if (i == next) {
1696 // Ignore non-identifiable packets1764 while (next < queries.len and answers[next].len != 0) : (next += 1) {}
1697 if (rlen < 4) continue;1765 } else {
16981766 @memcpy(answer_bufs[i][0..rlen], answer_bufs[next][0..rlen]);
1699 // Ignore replies from addresses we didn't send to1767 }
1700 var j: usize = 0;
1701 while (j < ns.len and !ns[j].eql(sa)) : (j += 1) {}
1702 if (j == ns.len) continue;
1703
1704 // Find which query this answer goes with, if any
1705 var i: usize = next;
1706 while (i < queries.len and (answer_bufs[next][0] != queries[i][0] or
1707 answer_bufs[next][1] != queries[i][1])) : (i += 1)
1708 {}
1709
1710 if (i == queries.len) continue;
1711 if (answers[i].len != 0) continue;
1712
1713 // Only accept positive or negative responses;
1714 // retry immediately on server failure, and ignore
1715 // all other codes such as refusal.
1716 switch (answer_bufs[next][3] & 15) {
1717 0, 3 => {},
1718 2 => if (servfail_retry != 0) {
1719 servfail_retry -= 1;
1720 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined;
1721 },
1722 else => continue,
1723 }
17241768
1725 // Store answer in the right slot, or update next1769 if (next == queries.len) break :outer;
1726 // available temp slot if it's already in place.
1727 answers[i].len = rlen;
1728 if (i == next) {
1729 while (next < queries.len and answers[next].len != 0) : (next += 1) {}
1730 } else {
1731 @memcpy(answer_bufs[i][0..rlen], answer_bufs[next][0..rlen]);
1732 }1770 }
1733
1734 if (next == queries.len) break :outer;
1735 }1771 }
1736 }1772 }
1773
1774 fn deinit(rc: *ResolvConf) void {
1775 const gpa = rc.gpa;
1776 rc.ns.deinit(gpa);
1777 rc.search.deinit(gpa);
1778 rc.* = undefined;
1779 }
1780};
1781
1782fn linuxLookupNameFromNumericUnspec(
1783 gpa: Allocator,
1784 addrs: *ArrayList(LookupAddr),
1785 name: []const u8,
1786 port: u16,
1787) !void {
1788 const addr = try Address.resolveIp(name, port);
1789 try addrs.append(gpa, .{ .addr = addr });
1737}1790}
17381791
1739fn dnsParse(1792fn dnsParse(
...@@ -1770,20 +1823,19 @@ fn dnsParse(...@@ -1770,20 +1823,19 @@ fn dnsParse(
1770}1823}
17711824
1772fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void {1825fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void {
1826 const gpa = ctx.gpa;
1773 switch (rr) {1827 switch (rr) {
1774 posix.RR.A => {1828 posix.RR.A => {
1775 if (data.len != 4) return error.InvalidDnsARecord;1829 if (data.len != 4) return error.InvalidDnsARecord;
1776 const new_addr = try ctx.addrs.addOne();1830 try ctx.addrs.append(gpa, .{
1777 new_addr.* = LookupAddr{
1778 .addr = Address.initIp4(data[0..4].*, ctx.port),1831 .addr = Address.initIp4(data[0..4].*, ctx.port),
1779 };1832 });
1780 },1833 },
1781 posix.RR.AAAA => {1834 posix.RR.AAAA => {
1782 if (data.len != 16) return error.InvalidDnsAAAARecord;1835 if (data.len != 16) return error.InvalidDnsAAAARecord;
1783 const new_addr = try ctx.addrs.addOne();1836 try ctx.addrs.append(gpa, .{
1784 new_addr.* = LookupAddr{
1785 .addr = Address.initIp6(data[0..16].*, ctx.port, 0, 0),1837 .addr = Address.initIp6(data[0..16].*, ctx.port, 0, 0),
1786 };1838 });
1787 },1839 },
1788 posix.RR.CNAME => {1840 posix.RR.CNAME => {
1789 var tmp: [256]u8 = undefined;1841 var tmp: [256]u8 = undefined;
...@@ -1792,7 +1844,7 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)...@@ -1792,7 +1844,7 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
1792 const canon_name = mem.sliceTo(&tmp, 0);1844 const canon_name = mem.sliceTo(&tmp, 0);
1793 if (isValidHostName(canon_name)) {1845 if (isValidHostName(canon_name)) {
1794 ctx.canon.items.len = 0;1846 ctx.canon.items.len = 0;
1795 try ctx.canon.appendSlice(canon_name);1847 try ctx.canon.appendSlice(gpa, canon_name);
1796 }1848 }
1797 },1849 },
1798 else => return,1850 else => return,
...@@ -1802,7 +1854,12 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)...@@ -1802,7 +1854,12 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
1802pub const Stream = struct {1854pub const Stream = struct {
1803 /// Underlying platform-defined type which may or may not be1855 /// Underlying platform-defined type which may or may not be
1804 /// interchangeable with a file system file descriptor.1856 /// interchangeable with a file system file descriptor.
1805 handle: posix.socket_t,1857 handle: Handle,
1858
1859 pub const Handle = switch (native_os) {
1860 .windows => windows.ws2_32.SOCKET,
1861 else => posix.fd_t,
1862 };
18061863
1807 pub fn close(s: Stream) void {1864 pub fn close(s: Stream) void {
1808 switch (native_os) {1865 switch (native_os) {
...@@ -1811,20 +1868,342 @@ pub const Stream = struct {...@@ -1811,20 +1868,342 @@ pub const Stream = struct {
1811 }1868 }
1812 }1869 }
18131870
1814 pub const ReadError = posix.ReadError;1871 pub const ReadError = posix.ReadError || error{
1815 pub const WriteError = posix.WriteError;1872 SocketNotBound,
1873 MessageTooBig,
1874 NetworkSubsystemFailed,
1875 ConnectionResetByPeer,
1876 SocketNotConnected,
1877 };
1878
1879 pub const WriteError = posix.SendMsgError || error{
1880 ConnectionResetByPeer,
1881 SocketNotBound,
1882 MessageTooBig,
1883 NetworkSubsystemFailed,
1884 SystemResources,
1885 SocketNotConnected,
1886 Unexpected,
1887 };
1888
1889 pub const Reader = switch (native_os) {
1890 .windows => struct {
1891 /// Use `interface` for portable code.
1892 interface_state: io.Reader,
1893 /// Use `getStream` for portable code.
1894 net_stream: Stream,
1895 /// Use `getError` for portable code.
1896 error_state: ?Error,
1897
1898 pub const Error = ReadError;
1899
1900 pub fn getStream(r: *const Reader) Stream {
1901 return r.stream;
1902 }
1903
1904 pub fn getError(r: *const Reader) ?Error {
1905 return r.error_state;
1906 }
1907
1908 pub fn interface(r: *Reader) *io.Reader {
1909 return &r.interface_state;
1910 }
1911
1912 pub fn init(net_stream: Stream, buffer: []u8) Reader {
1913 return .{
1914 .interface_state = .{
1915 .vtable = &.{ .stream = stream },
1916 .buffer = buffer,
1917 .seek = 0,
1918 .end = 0,
1919 },
1920 .net_stream = net_stream,
1921 .error_state = null,
1922 };
1923 }
1924
1925 fn stream(io_r: *io.Reader, io_w: *io.Writer, limit: io.Limit) io.Reader.StreamError!usize {
1926 const r: *Reader = @fieldParentPtr("interface_state", io_r);
1927 var iovecs: [max_buffers_len]windows.ws2_32.WSABUF = undefined;
1928 const bufs = try io_w.writableVectorWsa(&iovecs, limit);
1929 assert(bufs[0].len != 0);
1930 const n = streamBufs(r, bufs) catch |err| {
1931 r.error_state = err;
1932 return error.ReadFailed;
1933 };
1934 if (n == 0) return error.EndOfStream;
1935 return n;
1936 }
1937
1938 fn streamBufs(r: *Reader, bufs: []windows.ws2_32.WSABUF) Error!u32 {
1939 var n: u32 = undefined;
1940 var flags: u32 = 0;
1941 const rc = windows.ws2_32.WSARecvFrom(r.net_stream.handle, bufs.ptr, @intCast(bufs.len), &n, &flags, null, null, null, null);
1942 if (rc != 0) switch (windows.ws2_32.WSAGetLastError()) {
1943 .WSAECONNRESET => return error.ConnectionResetByPeer,
1944 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
1945 .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2
1946 .WSAEINVAL => return error.SocketNotBound,
1947 .WSAEMSGSIZE => return error.MessageTooBig,
1948 .WSAENETDOWN => return error.NetworkSubsystemFailed,
1949 .WSAENETRESET => return error.ConnectionResetByPeer,
1950 .WSAENOTCONN => return error.SocketNotConnected,
1951 .WSAEWOULDBLOCK => return error.WouldBlock,
1952 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
1953 .WSA_IO_PENDING => unreachable, // not using overlapped I/O
1954 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
1955 else => |err| return windows.unexpectedWSAError(err),
1956 };
1957 return n;
1958 }
1959 },
1960 else => struct {
1961 /// Use `getStream`, `interface`, and `getError` for portable code.
1962 file_reader: File.Reader,
1963
1964 pub const Error = ReadError;
1965
1966 pub fn interface(r: *Reader) *io.Reader {
1967 return &r.file_reader.interface;
1968 }
1969
1970 pub fn init(net_stream: Stream, buffer: []u8) Reader {
1971 return .{
1972 .file_reader = .{
1973 .interface = File.Reader.initInterface(buffer),
1974 .file = .{ .handle = net_stream.handle },
1975 .mode = .streaming,
1976 .seek_err = error.Unseekable,
1977 },
1978 };
1979 }
1980
1981 pub fn getStream(r: *const Reader) Stream {
1982 return .{ .handle = r.file_reader.file.handle };
1983 }
1984
1985 pub fn getError(r: *const Reader) ?Error {
1986 return r.file_reader.err;
1987 }
1988 },
1989 };
1990
1991 pub const Writer = switch (native_os) {
1992 .windows => struct {
1993 /// This field is present on all systems.
1994 interface: io.Writer,
1995 /// Use `getStream` for cross-platform support.
1996 stream: Stream,
1997 /// This field is present on all systems.
1998 err: ?Error = null,
1999
2000 pub const Error = WriteError;
2001
2002 pub fn init(stream: Stream, buffer: []u8) Writer {
2003 return .{
2004 .stream = stream,
2005 .interface = .{
2006 .vtable = &.{ .drain = drain },
2007 .buffer = buffer,
2008 },
2009 };
2010 }
2011
2012 pub fn getStream(w: *const Writer) Stream {
2013 return w.stream;
2014 }
18162015
1817 pub const Reader = io.GenericReader(Stream, ReadError, read);2016 fn addWsaBuf(v: []windows.ws2_32.WSABUF, i: *u32, bytes: []const u8) void {
1818 pub const Writer = io.GenericWriter(Stream, WriteError, write);2017 const cap = std.math.maxInt(u32);
2018 var remaining = bytes;
2019 while (remaining.len > cap) {
2020 if (v.len - i.* == 0) return;
2021 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = cap };
2022 i.* += 1;
2023 remaining = remaining[cap..];
2024 } else {
2025 @branchHint(.likely);
2026 if (v.len - i.* == 0) return;
2027 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = @intCast(remaining.len) };
2028 i.* += 1;
2029 }
2030 }
18192031
1820 pub fn reader(self: Stream) Reader {2032 fn drain(io_w: *io.Writer, data: []const []const u8, splat: usize) io.Writer.Error!usize {
1821 return .{ .context = self };2033 const w: *Writer = @fieldParentPtr("interface", io_w);
2034 const buffered = io_w.buffered();
2035 comptime assert(native_os == .windows);
2036 var iovecs: [max_buffers_len]windows.ws2_32.WSABUF = undefined;
2037 var len: u32 = 0;
2038 addWsaBuf(&iovecs, &len, buffered);
2039 for (data[0 .. data.len - 1]) |bytes| addWsaBuf(&iovecs, &len, bytes);
2040 const pattern = data[data.len - 1];
2041 if (iovecs.len - len != 0) switch (splat) {
2042 0 => {},
2043 1 => addWsaBuf(&iovecs, &len, pattern),
2044 else => switch (pattern.len) {
2045 0 => {},
2046 1 => {
2047 const splat_buffer_candidate = io_w.buffer[io_w.end..];
2048 var backup_buffer: [64]u8 = undefined;
2049 const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
2050 splat_buffer_candidate
2051 else
2052 &backup_buffer;
2053 const memset_len = @min(splat_buffer.len, splat);
2054 const buf = splat_buffer[0..memset_len];
2055 @memset(buf, pattern[0]);
2056 addWsaBuf(&iovecs, &len, buf);
2057 var remaining_splat = splat - buf.len;
2058 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
2059 addWsaBuf(&iovecs, &len, splat_buffer);
2060 remaining_splat -= splat_buffer.len;
2061 }
2062 addWsaBuf(&iovecs, &len, splat_buffer[0..remaining_splat]);
2063 },
2064 else => for (0..@min(splat, iovecs.len - len)) |_| {
2065 addWsaBuf(&iovecs, &len, pattern);
2066 },
2067 },
2068 };
2069 const n = sendBufs(w.stream.handle, iovecs[0..len]) catch |err| {
2070 w.err = err;
2071 return error.WriteFailed;
2072 };
2073 return io_w.consume(n);
2074 }
2075
2076 fn sendBufs(handle: Stream.Handle, bufs: []windows.ws2_32.WSABUF) Error!u32 {
2077 var n: u32 = undefined;
2078 const rc = windows.ws2_32.WSASend(handle, bufs.ptr, @intCast(bufs.len), &n, 0, null, null);
2079 if (rc == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
2080 .WSAECONNABORTED => return error.ConnectionResetByPeer,
2081 .WSAECONNRESET => return error.ConnectionResetByPeer,
2082 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
2083 .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2
2084 .WSAEINVAL => return error.SocketNotBound,
2085 .WSAEMSGSIZE => return error.MessageTooBig,
2086 .WSAENETDOWN => return error.NetworkSubsystemFailed,
2087 .WSAENETRESET => return error.ConnectionResetByPeer,
2088 .WSAENOBUFS => return error.SystemResources,
2089 .WSAENOTCONN => return error.SocketNotConnected,
2090 .WSAENOTSOCK => unreachable, // not a socket
2091 .WSAEOPNOTSUPP => unreachable, // only for message-oriented sockets
2092 .WSAESHUTDOWN => unreachable, // cannot send on a socket after write shutdown
2093 .WSAEWOULDBLOCK => return error.WouldBlock,
2094 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
2095 .WSA_IO_PENDING => unreachable, // not using overlapped I/O
2096 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
2097 else => |err| return windows.unexpectedWSAError(err),
2098 };
2099 return n;
2100 }
2101 },
2102 else => struct {
2103 /// This field is present on all systems.
2104 interface: io.Writer,
2105
2106 err: ?Error = null,
2107 file_writer: File.Writer,
2108
2109 pub const Error = WriteError;
2110
2111 pub fn init(stream: Stream, buffer: []u8) Writer {
2112 return .{
2113 .interface = .{
2114 .vtable = &.{
2115 .drain = drain,
2116 .sendFile = sendFile,
2117 },
2118 .buffer = buffer,
2119 },
2120 .file_writer = .initMode(.{ .handle = stream.handle }, &.{}, .streaming),
2121 };
2122 }
2123
2124 pub fn getStream(w: *const Writer) Stream {
2125 return .{ .handle = w.file_writer.file.handle };
2126 }
2127
2128 fn addBuf(v: []posix.iovec_const, i: *usize, bytes: []const u8) void {
2129 // OS checks ptr addr before length so zero length vectors must be omitted.
2130 if (bytes.len == 0) return;
2131 if (v.len - i.* == 0) return;
2132 v[i.*] = .{ .base = bytes.ptr, .len = bytes.len };
2133 i.* += 1;
2134 }
2135
2136 fn drain(io_w: *io.Writer, data: []const []const u8, splat: usize) io.Writer.Error!usize {
2137 const w: *Writer = @fieldParentPtr("interface", io_w);
2138 const buffered = io_w.buffered();
2139 var iovecs: [max_buffers_len]posix.iovec_const = undefined;
2140 var msg: posix.msghdr_const = .{
2141 .name = null,
2142 .namelen = 0,
2143 .iov = &iovecs,
2144 .iovlen = 0,
2145 .control = null,
2146 .controllen = 0,
2147 .flags = 0,
2148 };
2149 addBuf(&iovecs, &msg.iovlen, buffered);
2150 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &msg.iovlen, bytes);
2151 const pattern = data[data.len - 1];
2152 if (iovecs.len - msg.iovlen != 0) switch (splat) {
2153 0 => {},
2154 1 => addBuf(&iovecs, &msg.iovlen, pattern),
2155 else => switch (pattern.len) {
2156 0 => {},
2157 1 => {
2158 const splat_buffer_candidate = io_w.buffer[io_w.end..];
2159 var backup_buffer: [64]u8 = undefined;
2160 const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
2161 splat_buffer_candidate
2162 else
2163 &backup_buffer;
2164 const memset_len = @min(splat_buffer.len, splat);
2165 const buf = splat_buffer[0..memset_len];
2166 @memset(buf, pattern[0]);
2167 addBuf(&iovecs, &msg.iovlen, buf);
2168 var remaining_splat = splat - buf.len;
2169 while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) {
2170 assert(buf.len == splat_buffer.len);
2171 addBuf(&iovecs, &msg.iovlen, splat_buffer);
2172 remaining_splat -= splat_buffer.len;
2173 }
2174 addBuf(&iovecs, &msg.iovlen, splat_buffer[0..remaining_splat]);
2175 },
2176 else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| {
2177 addBuf(&iovecs, &msg.iovlen, pattern);
2178 },
2179 },
2180 };
2181 const flags = posix.MSG.NOSIGNAL;
2182 return io_w.consume(posix.sendmsg(w.file_writer.file.handle, &msg, flags) catch |err| {
2183 w.err = err;
2184 return error.WriteFailed;
2185 });
2186 }
2187
2188 fn sendFile(io_w: *io.Writer, file_reader: *File.Reader, limit: io.Limit) io.Writer.FileError!usize {
2189 const w: *Writer = @fieldParentPtr("interface", io_w);
2190 const n = try w.file_writer.interface.sendFileHeader(io_w.buffered(), file_reader, limit);
2191 return io_w.consume(n);
2192 }
2193 },
2194 };
2195
2196 pub fn reader(stream: Stream, buffer: []u8) Reader {
2197 return .init(stream, buffer);
1822 }2198 }
18232199
1824 pub fn writer(self: Stream) Writer {2200 pub fn writer(stream: Stream, buffer: []u8) Writer {
1825 return .{ .context = self };2201 return .init(stream, buffer);
1826 }2202 }
18272203
2204 const max_buffers_len = 8;
2205
2206 /// Deprecated in favor of `Reader`.
1828 pub fn read(self: Stream, buffer: []u8) ReadError!usize {2207 pub fn read(self: Stream, buffer: []u8) ReadError!usize {
1829 if (native_os == .windows) {2208 if (native_os == .windows) {
1830 return windows.ReadFile(self.handle, buffer, null);2209 return windows.ReadFile(self.handle, buffer, null);
...@@ -1833,10 +2212,10 @@ pub const Stream = struct {...@@ -1833,10 +2212,10 @@ pub const Stream = struct {
1833 return posix.read(self.handle, buffer);2212 return posix.read(self.handle, buffer);
1834 }2213 }
18352214
2215 /// Deprecated in favor of `Reader`.
1836 pub fn readv(s: Stream, iovecs: []const posix.iovec) ReadError!usize {2216 pub fn readv(s: Stream, iovecs: []const posix.iovec) ReadError!usize {
1837 if (native_os == .windows) {2217 if (native_os == .windows) {
1838 // TODO improve this to use ReadFileScatter2218 if (iovecs.len == 0) return 0;
1839 if (iovecs.len == 0) return @as(usize, 0);
1840 const first = iovecs[0];2219 const first = iovecs[0];
1841 return windows.ReadFile(s.handle, first.base[0..first.len], null);2220 return windows.ReadFile(s.handle, first.base[0..first.len], null);
1842 }2221 }
...@@ -1844,18 +2223,7 @@ pub const Stream = struct {...@@ -1844,18 +2223,7 @@ pub const Stream = struct {
1844 return posix.readv(s.handle, iovecs);2223 return posix.readv(s.handle, iovecs);
1845 }2224 }
18462225
1847 /// Returns the number of bytes read. If the number read is smaller than2226 /// Deprecated in favor of `Reader`.
1848 /// `buffer.len`, it means the stream reached the end. Reaching the end of
1849 /// a stream is not an error condition.
1850 pub fn readAll(s: Stream, buffer: []u8) ReadError!usize {
1851 return readAtLeast(s, buffer, buffer.len);
1852 }
1853
1854 /// Returns the number of bytes read, calling the underlying read function
1855 /// the minimal number of times until the buffer has at least `len` bytes
1856 /// filled. If the number read is less than `len` it means the stream
1857 /// reached the end. Reaching the end of the stream is not an error
1858 /// condition.
1859 pub fn readAtLeast(s: Stream, buffer: []u8, len: usize) ReadError!usize {2227 pub fn readAtLeast(s: Stream, buffer: []u8, len: usize) ReadError!usize {
1860 assert(len <= buffer.len);2228 assert(len <= buffer.len);
1861 var index: usize = 0;2229 var index: usize = 0;
...@@ -1867,17 +2235,13 @@ pub const Stream = struct {...@@ -1867,17 +2235,13 @@ pub const Stream = struct {
1867 return index;2235 return index;
1868 }2236 }
18692237
1870 /// TODO in evented I/O mode, this implementation incorrectly uses the event loop's2238 /// Deprecated in favor of `Writer`.
1871 /// file system thread instead of non-blocking. It needs to be reworked to properly
1872 /// use non-blocking I/O.
1873 pub fn write(self: Stream, buffer: []const u8) WriteError!usize {2239 pub fn write(self: Stream, buffer: []const u8) WriteError!usize {
1874 if (native_os == .windows) {2240 var stream_writer = self.writer(&.{});
1875 return windows.WriteFile(self.handle, buffer, null);2241 return stream_writer.interface.writeVec(&.{buffer}) catch return stream_writer.err.?;
1876 }
1877
1878 return posix.write(self.handle, buffer);
1879 }2242 }
18802243
2244 /// Deprecated in favor of `Writer`.
1881 pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void {2245 pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void {
1882 var index: usize = 0;2246 var index: usize = 0;
1883 while (index < bytes.len) {2247 while (index < bytes.len) {
...@@ -1885,16 +2249,12 @@ pub const Stream = struct {...@@ -1885,16 +2249,12 @@ pub const Stream = struct {
1885 }2249 }
1886 }2250 }
18872251
1888 /// See https://github.com/ziglang/zig/issues/76992252 /// Deprecated in favor of `Writer`.
1889 /// See equivalent function: `std.fs.File.writev`.
1890 pub fn writev(self: Stream, iovecs: []const posix.iovec_const) WriteError!usize {2253 pub fn writev(self: Stream, iovecs: []const posix.iovec_const) WriteError!usize {
1891 return posix.writev(self.handle, iovecs);2254 return @errorCast(posix.writev(self.handle, iovecs));
1892 }2255 }
18932256
1894 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in2257 /// Deprecated in favor of `Writer`.
1895 /// order to handle partial writes from the underlying OS layer.
1896 /// See https://github.com/ziglang/zig/issues/7699
1897 /// See equivalent function: `std.fs.File.writevAll`.
1898 pub fn writevAll(self: Stream, iovecs: []posix.iovec_const) WriteError!void {2258 pub fn writevAll(self: Stream, iovecs: []posix.iovec_const) WriteError!void {
1899 if (iovecs.len == 0) return;2259 if (iovecs.len == 0) return;
19002260
...@@ -1914,10 +2274,10 @@ pub const Stream = struct {...@@ -1914,10 +2274,10 @@ pub const Stream = struct {
19142274
1915pub const Server = struct {2275pub const Server = struct {
1916 listen_address: Address,2276 listen_address: Address,
1917 stream: std.net.Stream,2277 stream: Stream,
19182278
1919 pub const Connection = struct {2279 pub const Connection = struct {
1920 stream: std.net.Stream,2280 stream: Stream,
1921 address: Address,2281 address: Address,
1922 };2282 };
19232283
lib/std/net/test.zig+8-4
...@@ -208,7 +208,8 @@ test "listen on a port, send bytes, receive bytes" {...@@ -208,7 +208,8 @@ test "listen on a port, send bytes, receive bytes" {
208 const socket = try net.tcpConnectToAddress(server_address);208 const socket = try net.tcpConnectToAddress(server_address);
209 defer socket.close();209 defer socket.close();
210210
211 _ = try socket.writer().writeAll("Hello world!");211 var stream_writer = socket.writer(&.{});
212 try stream_writer.interface.writeAll("Hello world!");
212 }213 }
213 };214 };
214215
...@@ -218,7 +219,8 @@ test "listen on a port, send bytes, receive bytes" {...@@ -218,7 +219,8 @@ test "listen on a port, send bytes, receive bytes" {
218 var client = try server.accept();219 var client = try server.accept();
219 defer client.stream.close();220 defer client.stream.close();
220 var buf: [16]u8 = undefined;221 var buf: [16]u8 = undefined;
221 const n = try client.stream.reader().read(&buf);222 var stream_reader = client.stream.reader(&.{});
223 const n = try stream_reader.interface().readSliceShort(&buf);
222224
223 try testing.expectEqual(@as(usize, 12), n);225 try testing.expectEqual(@as(usize, 12), n);
224 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);226 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
...@@ -299,7 +301,8 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -299,7 +301,8 @@ test "listen on a unix socket, send bytes, receive bytes" {
299 const socket = try net.connectUnixSocket(path);301 const socket = try net.connectUnixSocket(path);
300 defer socket.close();302 defer socket.close();
301303
302 _ = try socket.writer().writeAll("Hello world!");304 var stream_writer = socket.writer(&.{});
305 try stream_writer.interface.writeAll("Hello world!");
303 }306 }
304 };307 };
305308
...@@ -309,7 +312,8 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -309,7 +312,8 @@ test "listen on a unix socket, send bytes, receive bytes" {
309 var client = try server.accept();312 var client = try server.accept();
310 defer client.stream.close();313 defer client.stream.close();
311 var buf: [16]u8 = undefined;314 var buf: [16]u8 = undefined;
312 const n = try client.stream.reader().read(&buf);315 var stream_reader = client.stream.reader(&.{});
316 const n = try stream_reader.interface().readSliceShort(&buf);
313317
314 try testing.expectEqual(@as(usize, 12), n);318 try testing.expectEqual(@as(usize, 12), n);
315 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);319 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
src/IncrementalDebugServer.zig+4-3
...@@ -55,14 +55,15 @@ fn runThread(ids: *IncrementalDebugServer) void {...@@ -55,14 +55,15 @@ fn runThread(ids: *IncrementalDebugServer) void {
55 const conn = server.accept() catch @panic("IncrementalDebugServer: failed to accept");55 const conn = server.accept() catch @panic("IncrementalDebugServer: failed to accept");
56 defer conn.stream.close();56 defer conn.stream.close();
5757
58 var stream_reader = conn.stream.reader(&cmd_buf);
59
58 while (ids.running.load(.monotonic)) {60 while (ids.running.load(.monotonic)) {
59 conn.stream.writeAll("zig> ") catch @panic("IncrementalDebugServer: failed to write");61 conn.stream.writeAll("zig> ") catch @panic("IncrementalDebugServer: failed to write");
60 var fbs = std.io.fixedBufferStream(&cmd_buf);62 const untrimmed = stream_reader.interface().takeSentinel('\n') catch |err| switch (err) {
61 conn.stream.reader().streamUntilDelimiter(fbs.writer(), '\n', cmd_buf.len) catch |err| switch (err) {
62 error.EndOfStream => break,63 error.EndOfStream => break,
63 else => @panic("IncrementalDebugServer: failed to read command"),64 else => @panic("IncrementalDebugServer: failed to read command"),
64 };65 };
65 const cmd_and_arg = std.mem.trim(u8, fbs.getWritten(), " \t\r\n");66 const cmd_and_arg = std.mem.trim(u8, untrimmed, " \t\r\n");
66 const cmd: []const u8, const arg: []const u8 = if (std.mem.indexOfScalar(u8, cmd_and_arg, ' ')) |i|67 const cmd: []const u8, const arg: []const u8 = if (std.mem.indexOfScalar(u8, cmd_and_arg, ' ')) |i|
67 .{ cmd_and_arg[0..i], cmd_and_arg[i + 1 ..] }68 .{ cmd_and_arg[0..i], cmd_and_arg[i + 1 ..] }
68 else69 else