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)
393393 return buffer[0..i];
394394}
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
396422pub fn ensureUnusedCapacity(w: *Writer, n: usize) Error!void {
397423 _ = try writableSliceGreedy(w, n);
398424}
lib/std/http/test.zig+15-6
......@@ -135,7 +135,8 @@ test "HTTP server handles a chunked transfer coding request" {
135135 const gpa = std.testing.allocator;
136136 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
137137 defer stream.close();
138 try stream.writeAll(request_bytes);
138 var stream_writer = stream.writer(&.{});
139 try stream_writer.interface.writeAll(request_bytes);
139140
140141 const expected_response =
141142 "HTTP/1.1 200 OK\r\n" ++
......@@ -144,7 +145,9 @@ test "HTTP server handles a chunked transfer coding request" {
144145 "content-type: text/plain\r\n" ++
145146 "\r\n" ++
146147 "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));
148151 defer gpa.free(response);
149152 try expectEqualStrings(expected_response, response);
150153}
......@@ -276,9 +279,12 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
276279 const gpa = std.testing.allocator;
277280 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
278281 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));
282288 defer gpa.free(response);
283289
284290 var expected_response = std.ArrayList(u8).init(gpa);
......@@ -339,9 +345,12 @@ test "receiving arbitrary http headers from the client" {
339345 const gpa = std.testing.allocator;
340346 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
341347 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));
345354 defer gpa.free(response);
346355
347356 var expected_response = std.ArrayList(u8).init(gpa);
lib/std/net.zig+692-332
......@@ -11,6 +11,9 @@ const io = std.io;
1111const native_endian = builtin.target.cpu.arch.endian();
1212const native_os = builtin.os.tag;
1313const windows = std.os.windows;
14const Allocator = std.mem.Allocator;
15const ArrayList = std.ArrayListUnmanaged;
16const File = std.fs.File;
1417
1518// Windows 10 added support for unix sockets in build 17063, redstone 4 is the
1619// first release to support them.
......@@ -719,7 +722,7 @@ pub fn connectUnixSocket(path: []const u8) !Stream {
719722 );
720723 errdefer Stream.close(.{ .handle = sockfd });
721724
722 var addr = try std.net.Address.initUnix(path);
725 var addr = try Address.initUnix(path);
723726 try posix.connect(sockfd, &addr.any, addr.getOsSockLen());
724727
725728 return .{ .handle = sockfd };
......@@ -787,7 +790,7 @@ pub const AddressList = struct {
787790pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError;
788791
789792/// 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 {
791794 const list = try getAddressList(allocator, name, port);
792795 defer list.deinit();
793796
......@@ -818,9 +821,9 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
818821 return Stream{ .handle = sockfd };
819822}
820823
821const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{
822 // TODO: break this up into error sets from the various underlying functions
823
824// TODO: Instead of having a massive error set, make the error set have categories, and then
825// store the sub-error as a diagnostic value.
826const GetAddressListError = Allocator.Error || File.OpenError || File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{
824827 TemporaryNameServerFailure,
825828 NameServerFailure,
826829 AddressFamilyNotSupported,
......@@ -840,12 +843,13 @@ const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError ||
840843
841844 InterfaceNotFound,
842845 FileSystem,
846 ResolveConfParseFailed,
843847};
844848
845849/// 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 {
847851 const result = blk: {
848 var arena = std.heap.ArenaAllocator.init(allocator);
852 var arena = std.heap.ArenaAllocator.init(gpa);
849853 errdefer arena.deinit();
850854
851855 const result = try arena.allocator().create(AddressList);
......@@ -860,11 +864,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get
860864 errdefer result.deinit();
861865
862866 if (native_os == .windows) {
863 const name_c = try allocator.dupeZ(u8, name);
864 defer allocator.free(name_c);
867 const name_c = try gpa.dupeZ(u8, name);
868 defer gpa.free(name_c);
865869
866 const port_c = try std.fmt.allocPrintSentinel(allocator, "{}", .{port}, 0);
867 defer allocator.free(port_c);
870 const port_c = try std.fmt.allocPrintSentinel(gpa, "{d}", .{port}, 0);
871 defer gpa.free(port_c);
868872
869873 const ws2_32 = windows.ws2_32;
870874 const hints: posix.addrinfo = .{
......@@ -932,11 +936,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get
932936 }
933937
934938 if (builtin.link_libc) {
935 const name_c = try allocator.dupeZ(u8, name);
936 defer allocator.free(name_c);
939 const name_c = try gpa.dupeZ(u8, name);
940 defer gpa.free(name_c);
937941
938 const port_c = try std.fmt.allocPrintSentinel(allocator, "{}", .{port}, 0);
939 defer allocator.free(port_c);
942 const port_c = try std.fmt.allocPrintSentinel(gpa, "{d}", .{port}, 0);
943 defer gpa.free(port_c);
940944
941945 const hints: posix.addrinfo = .{
942946 .flags = .{ .NUMERICSERV = true },
......@@ -999,17 +1003,17 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get
9991003
10001004 if (native_os == .linux) {
10011005 const family = posix.AF.UNSPEC;
1002 var lookup_addrs = std.ArrayList(LookupAddr).init(allocator);
1003 defer lookup_addrs.deinit();
1006 var lookup_addrs: ArrayList(LookupAddr) = .empty;
1007 defer lookup_addrs.deinit(gpa);
10041008
1005 var canon = std.ArrayList(u8).init(arena);
1006 defer canon.deinit();
1009 var canon: ArrayList(u8) = .empty;
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
10101014 result.addrs = try arena.alloc(Address, lookup_addrs.items.len);
10111015 if (canon.items.len != 0) {
1012 result.canon_name = try canon.toOwnedSlice();
1016 result.canon_name = try arena.dupe(u8, canon.items);
10131017 }
10141018
10151019 for (lookup_addrs.items, 0..) |lookup_addr, i| {
......@@ -1036,8 +1040,9 @@ const DAS_PREFIX_SHIFT = 8;
10361040const DAS_ORDER_SHIFT = 0;
10371041
10381042fn linuxLookupName(
1039 addrs: *std.ArrayList(LookupAddr),
1040 canon: *std.ArrayList(u8),
1043 gpa: Allocator,
1044 addrs: *ArrayList(LookupAddr),
1045 canon: *ArrayList(u8),
10411046 opt_name: ?[]const u8,
10421047 family: posix.sa_family_t,
10431048 flags: posix.AI,
......@@ -1046,13 +1051,13 @@ fn linuxLookupName(
10461051 if (opt_name) |name| {
10471052 // reject empty name and check len so it fits into temp bufs
10481053 canon.items.len = 0;
1049 try canon.appendSlice(name);
1054 try canon.appendSlice(gpa, name);
10501055 if (Address.parseExpectingFamily(name, family, port)) |addr| {
1051 try addrs.append(LookupAddr{ .addr = addr });
1056 try addrs.append(gpa, .{ .addr = addr });
10521057 } else |name_err| if (flags.NUMERICHOST) {
10531058 return name_err;
10541059 } else {
1055 try linuxLookupNameFromHosts(addrs, canon, name, family, port);
1060 try linuxLookupNameFromHosts(gpa, addrs, canon, name, family, port);
10561061 if (addrs.items.len == 0) {
10571062 // RFC 6761 Section 6.3.3
10581063 // Name resolution APIs and libraries SHOULD recognize localhost
......@@ -1063,17 +1068,18 @@ fn linuxLookupName(
10631068 // Check for equal to "localhost(.)" or ends in ".localhost(.)"
10641069 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
10651070 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 } });
1067 try addrs.append(LookupAddr{ .addr = .{ .in6 = Ip6Address.parse("::1", port) catch unreachable } });
1071 try addrs.append(gpa, .{ .addr = .{ .in = Ip4Address.parse("127.0.0.1", port) catch unreachable } });
1072 try addrs.append(gpa, .{ .addr = .{ .in6 = Ip6Address.parse("::1", port) catch unreachable } });
10681073 return;
10691074 }
10701075
1071 try linuxLookupNameFromDnsSearch(addrs, canon, name, family, port);
1076 try linuxLookupNameFromDnsSearch(gpa, addrs, canon, name, family, port);
10721077 }
10731078 }
10741079 } else {
1075 try canon.resize(0);
1076 try linuxLookupNameFromNull(addrs, family, flags, port);
1080 try canon.resize(gpa, 0);
1081 try addrs.ensureUnusedCapacity(gpa, 2);
1082 linuxLookupNameFromNull(addrs, family, flags, port);
10771083 }
10781084 if (addrs.items.len == 0) return error.UnknownHostName;
10791085
......@@ -1279,39 +1285,40 @@ fn addrCmpLessThan(context: void, b: LookupAddr, a: LookupAddr) bool {
12791285}
12801286
12811287fn linuxLookupNameFromNull(
1282 addrs: *std.ArrayList(LookupAddr),
1288 addrs: *ArrayList(LookupAddr),
12831289 family: posix.sa_family_t,
12841290 flags: posix.AI,
12851291 port: u16,
1286) !void {
1292) void {
12871293 if (flags.PASSIVE) {
12881294 if (family != posix.AF.INET6) {
1289 (try addrs.addOne()).* = LookupAddr{
1295 addrs.appendAssumeCapacity(.{
12901296 .addr = Address.initIp4([1]u8{0} ** 4, port),
1291 };
1297 });
12921298 }
12931299 if (family != posix.AF.INET) {
1294 (try addrs.addOne()).* = LookupAddr{
1300 addrs.appendAssumeCapacity(.{
12951301 .addr = Address.initIp6([1]u8{0} ** 16, port, 0, 0),
1296 };
1302 });
12971303 }
12981304 } else {
12991305 if (family != posix.AF.INET6) {
1300 (try addrs.addOne()).* = LookupAddr{
1306 addrs.appendAssumeCapacity(.{
13011307 .addr = Address.initIp4([4]u8{ 127, 0, 0, 1 }, port),
1302 };
1308 });
13031309 }
13041310 if (family != posix.AF.INET) {
1305 (try addrs.addOne()).* = LookupAddr{
1311 addrs.appendAssumeCapacity(.{
13061312 .addr = Address.initIp6(([1]u8{0} ** 15) ++ [1]u8{1}, port, 0, 0),
1307 };
1313 });
13081314 }
13091315 }
13101316}
13111317
13121318fn linuxLookupNameFromHosts(
1313 addrs: *std.ArrayList(LookupAddr),
1314 canon: *std.ArrayList(u8),
1319 gpa: Allocator,
1320 addrs: *ArrayList(LookupAddr),
1321 canon: *ArrayList(u8),
13151322 name: []const u8,
13161323 family: posix.sa_family_t,
13171324 port: u16,
......@@ -1325,18 +1332,36 @@ fn linuxLookupNameFromHosts(
13251332 };
13261333 defer file.close();
13271334
1328 var buffered_reader = std.io.bufferedReader(file.deprecatedReader());
1329 const reader = buffered_reader.reader();
13301335 var line_buf: [512]u8 = undefined;
1331 while (reader.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) {
1332 error.StreamTooLong => blk: {
1333 // Skip to the delimiter in the reader, to fix parsing
1334 try reader.skipUntilDelimiterOrEof('\n');
1335 // Use the truncated line. A truncated comment or hostname will be handled correctly.
1336 break :blk &line_buf;
1337 },
1338 else => |e| return e,
1339 }) |line| {
1336 var file_reader = file.reader(&line_buf);
1337 return parseHosts(gpa, addrs, canon, name, family, port, &file_reader.interface) catch |err| switch (err) {
1338 error.OutOfMemory => return error.OutOfMemory,
1339 error.ReadFailed => return file_reader.err.?,
1340 };
1341}
1342
1343fn parseHosts(
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 };
13401365 var split_it = mem.splitScalar(u8, line, '#');
13411366 const no_comment_line = split_it.first();
13421367
......@@ -1360,17 +1385,32 @@ fn linuxLookupNameFromHosts(
13601385 error.NonCanonical,
13611386 => continue,
13621387 };
1363 try addrs.append(LookupAddr{ .addr = addr });
1388 try addrs.append(gpa, .{ .addr = addr });
13641389
13651390 // first name is canonical name
13661391 const name_text = first_name_text.?;
13671392 if (isValidHostName(name_text)) {
13681393 canon.items.len = 0;
1369 try canon.appendSlice(name_text);
1394 try canon.appendSlice(gpa, name_text);
13701395 }
13711396 }
13721397}
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
13741414pub fn isValidHostName(hostname: []const u8) bool {
13751415 if (hostname.len >= 254) return false;
13761416 if (!std.unicode.utf8ValidateSlice(hostname)) return false;
......@@ -1384,14 +1424,15 @@ pub fn isValidHostName(hostname: []const u8) bool {
13841424}
13851425
13861426fn linuxLookupNameFromDnsSearch(
1387 addrs: *std.ArrayList(LookupAddr),
1388 canon: *std.ArrayList(u8),
1427 gpa: Allocator,
1428 addrs: *ArrayList(LookupAddr),
1429 canon: *ArrayList(u8),
13891430 name: []const u8,
13901431 family: posix.sa_family_t,
13911432 port: u16,
13921433) !void {
13931434 var rc: ResolvConf = undefined;
1394 try getResolvConf(addrs.allocator, &rc);
1435 rc.init(gpa) catch return error.ResolveConfParseFailed;
13951436 defer rc.deinit();
13961437
13971438 // Count dots, suppress search when >=ndots or name ends in
......@@ -1416,37 +1457,40 @@ fn linuxLookupNameFromDnsSearch(
14161457 // provides the desired default canonical name (if the requested
14171458 // name is not a CNAME record) and serves as a buffer for passing
14181459 // the full requested name to name_from_dns.
1419 try canon.resize(canon_name.len);
1460 try canon.resize(gpa, canon_name.len);
14201461 @memcpy(canon.items, canon_name);
1421 try canon.append('.');
1462 try canon.append(gpa, '.');
14221463
14231464 var tok_it = mem.tokenizeAny(u8, search, " \t");
14241465 while (tok_it.next()) |tok| {
14251466 canon.shrinkRetainingCapacity(canon_name.len + 1);
1426 try canon.appendSlice(tok);
1427 try linuxLookupNameFromDns(addrs, canon, canon.items, family, rc, port);
1467 try canon.appendSlice(gpa, tok);
1468 try linuxLookupNameFromDns(gpa, addrs, canon, canon.items, family, rc, port);
14281469 if (addrs.items.len != 0) return;
14291470 }
14301471
14311472 canon.shrinkRetainingCapacity(canon_name.len);
1432 return linuxLookupNameFromDns(addrs, canon, name, family, rc, port);
1473 return linuxLookupNameFromDns(gpa, addrs, canon, name, family, rc, port);
14331474}
14341475
14351476const dpc_ctx = struct {
1436 addrs: *std.ArrayList(LookupAddr),
1437 canon: *std.ArrayList(u8),
1477 gpa: Allocator,
1478 addrs: *ArrayList(LookupAddr),
1479 canon: *ArrayList(u8),
14381480 port: u16,
14391481};
14401482
14411483fn linuxLookupNameFromDns(
1442 addrs: *std.ArrayList(LookupAddr),
1443 canon: *std.ArrayList(u8),
1484 gpa: Allocator,
1485 addrs: *ArrayList(LookupAddr),
1486 canon: *ArrayList(u8),
14441487 name: []const u8,
14451488 family: posix.sa_family_t,
14461489 rc: ResolvConf,
14471490 port: u16,
14481491) !void {
1449 const ctx = dpc_ctx{
1492 const ctx: dpc_ctx = .{
1493 .gpa = gpa,
14501494 .addrs = addrs,
14511495 .canon = canon,
14521496 .port = port,
......@@ -1456,8 +1500,8 @@ fn linuxLookupNameFromDns(
14561500 rr: u8,
14571501 };
14581502 const afrrs = [_]AfRr{
1459 AfRr{ .af = posix.AF.INET6, .rr = posix.RR.A },
1460 AfRr{ .af = posix.AF.INET, .rr = posix.RR.AAAA },
1503 .{ .af = posix.AF.INET6, .rr = posix.RR.A },
1504 .{ .af = posix.AF.INET, .rr = posix.RR.AAAA },
14611505 };
14621506 var qbuf: [2][280]u8 = undefined;
14631507 var abuf: [2][512]u8 = undefined;
......@@ -1477,7 +1521,7 @@ fn linuxLookupNameFromDns(
14771521 ap[0].len = 0;
14781522 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
14821526 var i: usize = 0;
14831527 while (i < nq) : (i += 1) {
......@@ -1492,248 +1536,257 @@ fn linuxLookupNameFromDns(
14921536}
14931537
14941538const ResolvConf = struct {
1539 gpa: Allocator,
14951540 attempts: u32,
14961541 ndots: u32,
14971542 timeout: u32,
1498 search: std.ArrayList(u8),
1499 ns: std.ArrayList(LookupAddr),
1543 search: ArrayList(u8),
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 {
1502 rc.ns.deinit();
1503 rc.search.deinit();
1504 rc.* = undefined;
1570 var line_buf: [512]u8 = undefined;
1571 var file_reader = file.reader(&line_buf);
1572 return parse(rc, &file_reader.interface) catch |err| switch (err) {
1573 error.ReadFailed => return file_reader.err.?,
1574 else => |e| return e,
1575 };
15051576 }
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) {
1521 error.FileNotFound,
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");
1578 const Directive = enum { options, nameserver, domain, search };
1579 const Option = enum { ndots, attempts, timeout };
15461580
1547 const token = line_it.next() orelse continue;
1548 if (mem.eql(u8, token, "options")) {
1549 while (line_it.next()) |sub_tok| {
1550 var colon_it = mem.splitScalar(u8, sub_tok, ':');
1551 const name = colon_it.first();
1552 const value_txt = colon_it.next() orelse continue;
1553 const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) {
1554 // TODO https://github.com/ziglang/zig/issues/11812
1555 error.Overflow => @as(u8, 255),
1556 error.InvalidCharacter => continue,
1557 };
1558 if (mem.eql(u8, name, "ndots")) {
1559 rc.ndots = @min(value, 15);
1560 } else if (mem.eql(u8, name, "attempts")) {
1561 rc.attempts = @min(value, 10);
1562 } else if (mem.eql(u8, name, "timeout")) {
1563 rc.timeout = @min(value, 60);
1564 }
1581 fn parse(rc: *ResolvConf, reader: *io.Reader) !void {
1582 const gpa = rc.gpa;
1583 while (reader.takeSentinel('\n')) |line_with_comment| {
1584 const line = line: {
1585 var split = mem.splitScalar(u8, line_with_comment, '#');
1586 break :line split.first();
1587 };
1588 var line_it = mem.tokenizeAny(u8, line, " \t");
1589
1590 const token = line_it.next() orelse continue;
1591 switch (std.meta.stringToEnum(Directive, token) orelse continue) {
1592 .options => while (line_it.next()) |sub_tok| {
1593 var colon_it = mem.splitScalar(u8, sub_tok, ':');
1594 const name = colon_it.first();
1595 const value_txt = colon_it.next() orelse continue;
1596 const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) {
1597 error.Overflow => 255,
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 },
15651614 }
1566 } else if (mem.eql(u8, token, "nameserver")) {
1567 const ip_txt = line_it.next() orelse continue;
1568 try linuxLookupNameFromNumericUnspec(&rc.ns, ip_txt, 53);
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());
1615 } else |err| switch (err) {
1616 error.EndOfStream => if (reader.bufferedLen() != 0) return error.EndOfStream,
1617 else => |e| return e,
15721618 }
1573 }
15741619
1575 if (rc.ns.items.len == 0) {
1576 return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1", 53);
1620 if (rc.ns.items.len == 0) {
1621 return linuxLookupNameFromNumericUnspec(gpa, &rc.ns, "127.0.0.1", 53);
1622 }
15771623 }
1578}
15791624
1580fn linuxLookupNameFromNumericUnspec(
1581 addrs: *std.ArrayList(LookupAddr),
1582 name: []const u8,
1583 port: u16,
1584) !void {
1585 const addr = try Address.resolveIp(name, port);
1586 (try addrs.addOne()).* = LookupAddr{ .addr = addr };
1587}
1625 fn resMSendRc(
1626 rc: ResolvConf,
1627 queries: []const []const u8,
1628 answers: [][]u8,
1629 answer_bufs: []const []u8,
1630 ) !void {
1631 const gpa = rc.gpa;
1632 const timeout = 1000 * rc.timeout;
1633 const attempts = rc.attempts;
15881634
1589fn resMSendRc(
1590 queries: []const []const u8,
1591 answers: [][]u8,
1592 answer_bufs: []const []u8,
1593 rc: ResolvConf,
1594) !void {
1595 const timeout = 1000 * rc.timeout;
1596 const attempts = rc.attempts;
1635 var sl: posix.socklen_t = @sizeOf(posix.sockaddr.in);
1636 var family: posix.sa_family_t = posix.AF.INET;
15971637
1598 var sl: posix.socklen_t = @sizeOf(posix.sockaddr.in);
1599 var family: posix.sa_family_t = posix.AF.INET;
1638 var ns_list: ArrayList(Address) = .empty;
1639 defer ns_list.deinit(gpa);
16001640
1601 var ns_list = std.ArrayList(Address).init(rc.ns.allocator);
1602 defer ns_list.deinit();
1641 try ns_list.resize(gpa, rc.ns.items.len);
16031642
1604 try ns_list.resize(rc.ns.items.len);
1605 const ns = ns_list.items;
1606
1607 for (rc.ns.items, 0..) |iplit, i| {
1608 ns[i] = iplit.addr;
1609 assert(ns[i].getPort() == 53);
1610 if (iplit.addr.any.family != posix.AF.INET) {
1611 family = posix.AF.INET6;
1643 for (ns_list.items, rc.ns.items) |*ns, iplit| {
1644 ns.* = iplit.addr;
1645 assert(ns.getPort() == 53);
1646 if (iplit.addr.any.family != posix.AF.INET) {
1647 family = posix.AF.INET6;
1648 }
16121649 }
1613 }
16141650
1615 const flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK;
1616 const fd = posix.socket(family, flags, 0) catch |err| switch (err) {
1617 error.AddressFamilyNotSupported => blk: {
1618 // Handle case where system lacks IPv6 support
1619 if (family == posix.AF.INET6) {
1620 family = posix.AF.INET;
1621 break :blk try posix.socket(posix.AF.INET, flags, 0);
1651 const flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK;
1652 const fd = posix.socket(family, flags, 0) catch |err| switch (err) {
1653 error.AddressFamilyNotSupported => blk: {
1654 // Handle case where system lacks IPv6 support
1655 if (family == posix.AF.INET6) {
1656 family = posix.AF.INET;
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;
16221684 }
1623 return err;
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;
1685 sl = @sizeOf(posix.sockaddr.in6);
16481686 }
1649 sl = @sizeOf(posix.sockaddr.in6);
1650 }
1651
1652 // Get local address and open/bind a socket
1653 var sa: Address = undefined;
1654 @memset(@as([*]u8, @ptrCast(&sa))[0..@sizeOf(Address)], 0);
1655 sa.any.family = family;
1656 try posix.bind(fd, &sa.any, sl);
1657
1658 var pfd = [1]posix.pollfd{posix.pollfd{
1659 .fd = fd,
1660 .events = posix.POLL.IN,
1661 .revents = undefined,
1662 }};
1663 const retry_interval = timeout / attempts;
1664 var next: u32 = 0;
1665 var t2: u64 = @bitCast(std.time.milliTimestamp());
1666 const t0 = t2;
1667 var t1 = t2 - retry_interval;
1668
1669 var servfail_retry: usize = undefined;
1670
1671 outer: while (t2 - t0 < timeout) : (t2 = @as(u64, @bitCast(std.time.milliTimestamp()))) {
1672 if (t2 - t1 >= retry_interval) {
1673 // Query all configured nameservers in parallel
1674 var i: usize = 0;
1675 while (i < queries.len) : (i += 1) {
1676 if (answers[i].len == 0) {
1677 var j: usize = 0;
1678 while (j < ns.len) : (j += 1) {
1679 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined;
1687
1688 // Get local address and open/bind a socket
1689 var sa: Address = undefined;
1690 @memset(@as([*]u8, @ptrCast(&sa))[0..@sizeOf(Address)], 0);
1691 sa.any.family = family;
1692 try posix.bind(fd, &sa.any, sl);
1693
1694 var pfd = [1]posix.pollfd{posix.pollfd{
1695 .fd = fd,
1696 .events = posix.POLL.IN,
1697 .revents = undefined,
1698 }};
1699 const retry_interval = timeout / attempts;
1700 var next: u32 = 0;
1701 var t2: u64 = @bitCast(std.time.milliTimestamp());
1702 const t0 = t2;
1703 var t1 = t2 - retry_interval;
1704
1705 var servfail_retry: usize = undefined;
1706
1707 outer: while (t2 - t0 < timeout) : (t2 = @as(u64, @bitCast(std.time.milliTimestamp()))) {
1708 if (t2 - t1 >= retry_interval) {
1709 // Query all configured nameservers in parallel
1710 var i: usize = 0;
1711 while (i < queries.len) : (i += 1) {
1712 if (answers[i].len == 0) {
1713 for (ns_list.items) |*ns| {
1714 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns.any, sl) catch undefined;
1715 }
16801716 }
16811717 }
1718 t1 = t2;
1719 servfail_retry = 2 * queries.len;
16821720 }
1683 t1 = t2;
1684 servfail_retry = 2 * queries.len;
1685 }
16861721
1687 // Wait for a response, or until time to retry
1688 const clamped_timeout = @min(@as(u31, std.math.maxInt(u31)), t1 + retry_interval - t2);
1689 const nevents = posix.poll(&pfd, clamped_timeout) catch 0;
1690 if (nevents == 0) continue;
1722 // Wait for a response, or until time to retry
1723 const clamped_timeout = @min(@as(u31, std.math.maxInt(u31)), t1 + retry_interval - t2);
1724 const nevents = posix.poll(&pfd, clamped_timeout) catch 0;
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) {
1693 var sl_copy = sl;
1694 const rlen = posix.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;
1695
1696 // Ignore non-identifiable packets
1697 if (rlen < 4) continue;
1698
1699 // Ignore replies from addresses we didn't send to
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 }
1760 // Store answer in the right slot, or update next
1761 // available temp slot if it's already in place.
1762 answers[i].len = rlen;
1763 if (i == next) {
1764 while (next < queries.len and answers[next].len != 0) : (next += 1) {}
1765 } else {
1766 @memcpy(answer_bufs[i][0..rlen], answer_bufs[next][0..rlen]);
1767 }
17241768
1725 // Store answer in the right slot, or update next
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]);
1769 if (next == queries.len) break :outer;
17321770 }
1733
1734 if (next == queries.len) break :outer;
17351771 }
17361772 }
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 });
17371790}
17381791
17391792fn dnsParse(
......@@ -1770,20 +1823,19 @@ fn dnsParse(
17701823}
17711824
17721825fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void {
1826 const gpa = ctx.gpa;
17731827 switch (rr) {
17741828 posix.RR.A => {
17751829 if (data.len != 4) return error.InvalidDnsARecord;
1776 const new_addr = try ctx.addrs.addOne();
1777 new_addr.* = LookupAddr{
1830 try ctx.addrs.append(gpa, .{
17781831 .addr = Address.initIp4(data[0..4].*, ctx.port),
1779 };
1832 });
17801833 },
17811834 posix.RR.AAAA => {
17821835 if (data.len != 16) return error.InvalidDnsAAAARecord;
1783 const new_addr = try ctx.addrs.addOne();
1784 new_addr.* = LookupAddr{
1836 try ctx.addrs.append(gpa, .{
17851837 .addr = Address.initIp6(data[0..16].*, ctx.port, 0, 0),
1786 };
1838 });
17871839 },
17881840 posix.RR.CNAME => {
17891841 var tmp: [256]u8 = undefined;
......@@ -1792,7 +1844,7 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
17921844 const canon_name = mem.sliceTo(&tmp, 0);
17931845 if (isValidHostName(canon_name)) {
17941846 ctx.canon.items.len = 0;
1795 try ctx.canon.appendSlice(canon_name);
1847 try ctx.canon.appendSlice(gpa, canon_name);
17961848 }
17971849 },
17981850 else => return,
......@@ -1802,7 +1854,12 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
18021854pub const Stream = struct {
18031855 /// Underlying platform-defined type which may or may not be
18041856 /// 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
18071864 pub fn close(s: Stream) void {
18081865 switch (native_os) {
......@@ -1811,20 +1868,342 @@ pub const Stream = struct {
18111868 }
18121869 }
18131870
1814 pub const ReadError = posix.ReadError;
1815 pub const WriteError = posix.WriteError;
1871 pub const ReadError = posix.ReadError || error{
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);
1818 pub const Writer = io.GenericWriter(Stream, WriteError, write);
2016 fn addWsaBuf(v: []windows.ws2_32.WSABUF, i: *u32, bytes: []const u8) void {
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 {
1821 return .{ .context = self };
2032 fn drain(io_w: *io.Writer, data: []const []const u8, splat: usize) io.Writer.Error!usize {
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);
18222198 }
18232199
1824 pub fn writer(self: Stream) Writer {
1825 return .{ .context = self };
2200 pub fn writer(stream: Stream, buffer: []u8) Writer {
2201 return .init(stream, buffer);
18262202 }
18272203
2204 const max_buffers_len = 8;
2205
2206 /// Deprecated in favor of `Reader`.
18282207 pub fn read(self: Stream, buffer: []u8) ReadError!usize {
18292208 if (native_os == .windows) {
18302209 return windows.ReadFile(self.handle, buffer, null);
......@@ -1833,10 +2212,10 @@ pub const Stream = struct {
18332212 return posix.read(self.handle, buffer);
18342213 }
18352214
2215 /// Deprecated in favor of `Reader`.
18362216 pub fn readv(s: Stream, iovecs: []const posix.iovec) ReadError!usize {
18372217 if (native_os == .windows) {
1838 // TODO improve this to use ReadFileScatter
1839 if (iovecs.len == 0) return @as(usize, 0);
2218 if (iovecs.len == 0) return 0;
18402219 const first = iovecs[0];
18412220 return windows.ReadFile(s.handle, first.base[0..first.len], null);
18422221 }
......@@ -1844,18 +2223,7 @@ pub const Stream = struct {
18442223 return posix.readv(s.handle, iovecs);
18452224 }
18462225
1847 /// Returns the number of bytes read. If the number read is smaller than
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.
2226 /// Deprecated in favor of `Reader`.
18592227 pub fn readAtLeast(s: Stream, buffer: []u8, len: usize) ReadError!usize {
18602228 assert(len <= buffer.len);
18612229 var index: usize = 0;
......@@ -1867,17 +2235,13 @@ pub const Stream = struct {
18672235 return index;
18682236 }
18692237
1870 /// TODO in evented I/O mode, this implementation incorrectly uses the event loop's
1871 /// file system thread instead of non-blocking. It needs to be reworked to properly
1872 /// use non-blocking I/O.
2238 /// Deprecated in favor of `Writer`.
18732239 pub fn write(self: Stream, buffer: []const u8) WriteError!usize {
1874 if (native_os == .windows) {
1875 return windows.WriteFile(self.handle, buffer, null);
1876 }
1877
1878 return posix.write(self.handle, buffer);
2240 var stream_writer = self.writer(&.{});
2241 return stream_writer.interface.writeVec(&.{buffer}) catch return stream_writer.err.?;
18792242 }
18802243
2244 /// Deprecated in favor of `Writer`.
18812245 pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void {
18822246 var index: usize = 0;
18832247 while (index < bytes.len) {
......@@ -1885,16 +2249,12 @@ pub const Stream = struct {
18852249 }
18862250 }
18872251
1888 /// See https://github.com/ziglang/zig/issues/7699
1889 /// See equivalent function: `std.fs.File.writev`.
2252 /// Deprecated in favor of `Writer`.
18902253 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));
18922255 }
18932256
1894 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
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`.
2257 /// Deprecated in favor of `Writer`.
18982258 pub fn writevAll(self: Stream, iovecs: []posix.iovec_const) WriteError!void {
18992259 if (iovecs.len == 0) return;
19002260
......@@ -1914,10 +2274,10 @@ pub const Stream = struct {
19142274
19152275pub const Server = struct {
19162276 listen_address: Address,
1917 stream: std.net.Stream,
2277 stream: Stream,
19182278
19192279 pub const Connection = struct {
1920 stream: std.net.Stream,
2280 stream: Stream,
19212281 address: Address,
19222282 };
19232283
lib/std/net/test.zig+8-4
......@@ -208,7 +208,8 @@ test "listen on a port, send bytes, receive bytes" {
208208 const socket = try net.tcpConnectToAddress(server_address);
209209 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!");
212213 }
213214 };
214215
......@@ -218,7 +219,8 @@ test "listen on a port, send bytes, receive bytes" {
218219 var client = try server.accept();
219220 defer client.stream.close();
220221 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
223225 try testing.expectEqual(@as(usize, 12), n);
224226 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
......@@ -299,7 +301,8 @@ test "listen on a unix socket, send bytes, receive bytes" {
299301 const socket = try net.connectUnixSocket(path);
300302 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!");
303306 }
304307 };
305308
......@@ -309,7 +312,8 @@ test "listen on a unix socket, send bytes, receive bytes" {
309312 var client = try server.accept();
310313 defer client.stream.close();
311314 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
314318 try testing.expectEqual(@as(usize, 12), n);
315319 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
src/IncrementalDebugServer.zig+4-3
......@@ -55,14 +55,15 @@ fn runThread(ids: *IncrementalDebugServer) void {
5555 const conn = server.accept() catch @panic("IncrementalDebugServer: failed to accept");
5656 defer conn.stream.close();
5757
58 var stream_reader = conn.stream.reader(&cmd_buf);
59
5860 while (ids.running.load(.monotonic)) {
5961 conn.stream.writeAll("zig> ") catch @panic("IncrementalDebugServer: failed to write");
60 var fbs = std.io.fixedBufferStream(&cmd_buf);
61 conn.stream.reader().streamUntilDelimiter(fbs.writer(), '\n', cmd_buf.len) catch |err| switch (err) {
62 const untrimmed = stream_reader.interface().takeSentinel('\n') catch |err| switch (err) {
6263 error.EndOfStream => break,
6364 else => @panic("IncrementalDebugServer: failed to read command"),
6465 };
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");
6667 const cmd: []const u8, const arg: []const u8 = if (std.mem.indexOfScalar(u8, cmd_and_arg, ' ')) |i|
6768 .{ cmd_and_arg[0..i], cmd_and_arg[i + 1 ..] }
6869 else