| author | |
| committer | |
| log | 35ce907c06d5758adab276927ad8dbe730d6130d |
| tree | c7d3839b953c4c0a7e5beee7090713d28fff04b3 |
| parent | 1382e4122603fd2b57e4feb0eff76ba2d73a913a |
Unfortunately this can't be implemented "above the vtable" because
various operating systems don't provide low level DNS resolution
primitives such as just putting the list of nameservers in a file.
Without libc on Linux it works great though!
Anyway this also changes the API to be based on Io.Queue. By using a
large enough buffer, reusable code can be written that does not require
concurrent, yet takes advantage of responding to DNS queries as they
come in. I sketched out a new implementation of `HostName.connect` to
demonstrate this, but it will require an additional API (`Io.Select`) to
be implemented in a future commit.
This commit also introduces "uncancelable" variants for mutex locking,
waiting on a condition, and putting items into a queue.9 files changed, 778 insertions(+), 617 deletions(-)
BRANCH_TODO+3| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | * Threaded: rename Pool to Threaded | ||
| 1 | * Threaded: finish linux impl (all tests passing) | 2 | * Threaded: finish linux impl (all tests passing) |
| 2 | * Threaded: finish macos impl | 3 | * Threaded: finish macos impl |
| 3 | * Threaded: finish windows impl | 4 | * Threaded: finish windows impl |
| ... | @@ -14,4 +15,6 @@ | ... | @@ -14,4 +15,6 @@ |
| 14 | * move fs.File.Writer to Io | 15 | * move fs.File.Writer to Io |
| 15 | * add non-blocking flag to net and fs operations, handle EAGAIN | 16 | * add non-blocking flag to net and fs operations, handle EAGAIN |
| 16 | * finish moving std.fs to Io | 17 | * finish moving std.fs to Io |
| 18 | * migrate child process into std.Io | ||
| 19 | * eliminate std.Io.poll (it should be replaced by "select" functionality) | ||
| 17 | * finish moving all of std.posix into Threaded | 20 | * finish moving all of std.posix into Threaded |
lib/std/Io.zig+65-44| ... | @@ -649,9 +649,11 @@ pub const VTable = struct { | ... | @@ -649,9 +649,11 @@ pub const VTable = struct { |
| 649 | select: *const fn (?*anyopaque, futures: []const *AnyFuture) usize, | 649 | select: *const fn (?*anyopaque, futures: []const *AnyFuture) usize, |
| 650 | 650 | ||
| 651 | mutexLock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) Cancelable!void, | 651 | mutexLock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) Cancelable!void, |
| 652 | mutexLockUncancelable: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void, | ||
| 652 | mutexUnlock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void, | 653 | mutexUnlock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void, |
| 653 | 654 | ||
| 654 | conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void, | 655 | conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void, |
| 656 | conditionWaitUncancelable: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) void, | ||
| 655 | conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void, | 657 | conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void, |
| 656 | 658 | ||
| 657 | dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void, | 659 | dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void, |
| ... | @@ -686,6 +688,7 @@ pub const VTable = struct { | ... | @@ -686,6 +688,7 @@ pub const VTable = struct { |
| 686 | netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void, | 688 | netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void, |
| 687 | netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, | 689 | netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, |
| 688 | netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name, | 690 | netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name, |
| 691 | netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) void, | ||
| 689 | }; | 692 | }; |
| 690 | 693 | ||
| 691 | pub const Cancelable = error{ | 694 | pub const Cancelable = error{ |
| ... | @@ -1030,7 +1033,7 @@ pub const Group = struct { | ... | @@ -1030,7 +1033,7 @@ pub const Group = struct { |
| 1030 | } | 1033 | } |
| 1031 | }; | 1034 | }; |
| 1032 | 1035 | ||
| 1033 | pub const Mutex = if (true) struct { | 1036 | pub const Mutex = struct { |
| 1034 | state: State, | 1037 | state: State, |
| 1035 | 1038 | ||
| 1036 | pub const State = enum(usize) { | 1039 | pub const State = enum(usize) { |
| ... | @@ -1073,54 +1076,32 @@ pub const Mutex = if (true) struct { | ... | @@ -1073,54 +1076,32 @@ pub const Mutex = if (true) struct { |
| 1073 | return io.vtable.mutexLock(io.userdata, prev_state, mutex); | 1076 | return io.vtable.mutexLock(io.userdata, prev_state, mutex); |
| 1074 | } | 1077 | } |
| 1075 | 1078 | ||
| 1079 | /// Same as `lock` but cannot be canceled. | ||
| 1080 | pub fn lockUncancelable(mutex: *Mutex, io: std.Io) void { | ||
| 1081 | const prev_state: State = @enumFromInt(@atomicRmw( | ||
| 1082 | usize, | ||
| 1083 | @as(*usize, @ptrCast(&mutex.state)), | ||
| 1084 | .And, | ||
| 1085 | ~@intFromEnum(State.unlocked), | ||
| 1086 | .acquire, | ||
| 1087 | )); | ||
| 1088 | if (prev_state.isUnlocked()) { | ||
| 1089 | @branchHint(.likely); | ||
| 1090 | return; | ||
| 1091 | } | ||
| 1092 | return io.vtable.mutexLockUncancelable(io.userdata, prev_state, mutex); | ||
| 1093 | } | ||
| 1094 | |||
| 1076 | pub fn unlock(mutex: *Mutex, io: std.Io) void { | 1095 | pub fn unlock(mutex: *Mutex, io: std.Io) void { |
| 1077 | const prev_state = @cmpxchgWeak(State, &mutex.state, .locked_once, .unlocked, .release, .acquire) orelse { | 1096 | const prev_state = @cmpxchgWeak(State, &mutex.state, .locked_once, .unlocked, .release, .acquire) orelse { |
| 1078 | @branchHint(.likely); | 1097 | @branchHint(.likely); |
| 1079 | return; | 1098 | return; |
| 1080 | }; | 1099 | }; |
| 1081 | std.debug.assert(prev_state != .unlocked); // mutex not locked | 1100 | assert(prev_state != .unlocked); // mutex not locked |
| 1082 | return io.vtable.mutexUnlock(io.userdata, prev_state, mutex); | 1101 | return io.vtable.mutexUnlock(io.userdata, prev_state, mutex); |
| 1083 | } | 1102 | } |
| 1084 | } else struct { | ||
| 1085 | state: std.atomic.Value(u32), | ||
| 1086 | |||
| 1087 | pub const State = void; | ||
| 1088 | |||
| 1089 | pub const init: Mutex = .{ .state = .init(unlocked) }; | ||
| 1090 | |||
| 1091 | pub const unlocked: u32 = 0b00; | ||
| 1092 | pub const locked: u32 = 0b01; | ||
| 1093 | pub const contended: u32 = 0b11; // must contain the `locked` bit for x86 optimization below | ||
| 1094 | |||
| 1095 | pub fn tryLock(m: *Mutex) bool { | ||
| 1096 | // On x86, use `lock bts` instead of `lock cmpxchg` as: | ||
| 1097 | // - they both seem to mark the cache-line as modified regardless: https://stackoverflow.com/a/63350048 | ||
| 1098 | // - `lock bts` is smaller instruction-wise which makes it better for inlining | ||
| 1099 | if (builtin.target.cpu.arch.isX86()) { | ||
| 1100 | const locked_bit = @ctz(locked); | ||
| 1101 | return m.state.bitSet(locked_bit, .acquire) == 0; | ||
| 1102 | } | ||
| 1103 | |||
| 1104 | // Acquire barrier ensures grabbing the lock happens before the critical section | ||
| 1105 | // and that the previous lock holder's critical section happens before we grab the lock. | ||
| 1106 | return m.state.cmpxchgWeak(unlocked, locked, .acquire, .monotonic) == null; | ||
| 1107 | } | ||
| 1108 | |||
| 1109 | /// Avoids the vtable for uncontended locks. | ||
| 1110 | pub fn lock(m: *Mutex, io: Io) Cancelable!void { | ||
| 1111 | if (!m.tryLock()) { | ||
| 1112 | @branchHint(.unlikely); | ||
| 1113 | try io.vtable.mutexLock(io.userdata, {}, m); | ||
| 1114 | } | ||
| 1115 | } | ||
| 1116 | |||
| 1117 | pub fn unlock(m: *Mutex, io: Io) void { | ||
| 1118 | io.vtable.mutexUnlock(io.userdata, {}, m); | ||
| 1119 | } | ||
| 1120 | }; | 1103 | }; |
| 1121 | 1104 | ||
| 1122 | /// Supports exactly 1 waiter. More than 1 simultaneous wait on the same | ||
| 1123 | /// condition is illegal. | ||
| 1124 | pub const Condition = struct { | 1105 | pub const Condition = struct { |
| 1125 | state: u64 = 0, | 1106 | state: u64 = 0, |
| 1126 | 1107 | ||
| ... | @@ -1128,6 +1109,10 @@ pub const Condition = struct { | ... | @@ -1128,6 +1109,10 @@ pub const Condition = struct { |
| 1128 | return io.vtable.conditionWait(io.userdata, cond, mutex); | 1109 | return io.vtable.conditionWait(io.userdata, cond, mutex); |
| 1129 | } | 1110 | } |
| 1130 | 1111 | ||
| 1112 | pub fn waitUncancelable(cond: *Condition, io: Io, mutex: *Mutex) void { | ||
| 1113 | return io.vtable.conditionWaitUncancelable(io.userdata, cond, mutex); | ||
| 1114 | } | ||
| 1115 | |||
| 1131 | pub fn signal(cond: *Condition, io: Io) void { | 1116 | pub fn signal(cond: *Condition, io: Io) void { |
| 1132 | io.vtable.conditionWake(io.userdata, cond, .one); | 1117 | io.vtable.conditionWake(io.userdata, cond, .one); |
| 1133 | } | 1118 | } |
| ... | @@ -1137,9 +1122,9 @@ pub const Condition = struct { | ... | @@ -1137,9 +1122,9 @@ pub const Condition = struct { |
| 1137 | } | 1122 | } |
| 1138 | 1123 | ||
| 1139 | pub const Wake = enum { | 1124 | pub const Wake = enum { |
| 1140 | /// wake up only one thread | 1125 | /// Wake up only one thread. |
| 1141 | one, | 1126 | one, |
| 1142 | /// wake up all thread | 1127 | /// Wake up all threads. |
| 1143 | all, | 1128 | all, |
| 1144 | }; | 1129 | }; |
| 1145 | }; | 1130 | }; |
| ... | @@ -1180,10 +1165,24 @@ pub const TypeErasedQueue = struct { | ... | @@ -1180,10 +1165,24 @@ pub const TypeErasedQueue = struct { |
| 1180 | 1165 | ||
| 1181 | pub fn put(q: *TypeErasedQueue, io: Io, elements: []const u8, min: usize) Cancelable!usize { | 1166 | pub fn put(q: *TypeErasedQueue, io: Io, elements: []const u8, min: usize) Cancelable!usize { |
| 1182 | assert(elements.len >= min); | 1167 | assert(elements.len >= min); |
| 1183 | 1168 | if (elements.len == 0) return 0; | |
| 1184 | try q.mutex.lock(io); | 1169 | try q.mutex.lock(io); |
| 1185 | defer q.mutex.unlock(io); | 1170 | defer q.mutex.unlock(io); |
| 1171 | return putLocked(q, io, elements, min, false); | ||
| 1172 | } | ||
| 1173 | |||
| 1174 | /// Same as `put` but cannot be canceled. | ||
| 1175 | pub fn putUncancelable(q: *TypeErasedQueue, io: Io, elements: []const u8, min: usize) usize { | ||
| 1176 | assert(elements.len >= min); | ||
| 1177 | if (elements.len == 0) return 0; | ||
| 1178 | q.mutex.lockUncancelable(io); | ||
| 1179 | defer q.mutex.unlock(io); | ||
| 1180 | return putLocked(q, io, elements, min, true) catch |err| switch (err) { | ||
| 1181 | error.Canceled => unreachable, | ||
| 1182 | }; | ||
| 1183 | } | ||
| 1186 | 1184 | ||
| 1185 | fn putLocked(q: *TypeErasedQueue, io: Io, elements: []const u8, min: usize, uncancelable: bool) Cancelable!usize { | ||
| 1187 | // Getters have first priority on the data, and only when the getters | 1186 | // Getters have first priority on the data, and only when the getters |
| 1188 | // queue is empty do we start populating the buffer. | 1187 | // queue is empty do we start populating the buffer. |
| 1189 | 1188 | ||
| ... | @@ -1226,7 +1225,10 @@ pub const TypeErasedQueue = struct { | ... | @@ -1226,7 +1225,10 @@ pub const TypeErasedQueue = struct { |
| 1226 | 1225 | ||
| 1227 | var pending: Put = .{ .remaining = remaining, .condition = .{}, .node = .{} }; | 1226 | var pending: Put = .{ .remaining = remaining, .condition = .{}, .node = .{} }; |
| 1228 | q.putters.append(&pending.node); | 1227 | q.putters.append(&pending.node); |
| 1229 | try pending.condition.wait(io, &q.mutex); | 1228 | if (uncancelable) |
| 1229 | pending.condition.waitUncancelable(io, &q.mutex) | ||
| 1230 | else | ||
| 1231 | try pending.condition.wait(io, &q.mutex); | ||
| 1230 | remaining = pending.remaining; | 1232 | remaining = pending.remaining; |
| 1231 | } | 1233 | } |
| 1232 | } | 1234 | } |
| ... | @@ -1347,6 +1349,16 @@ pub fn Queue(Elem: type) type { | ... | @@ -1347,6 +1349,16 @@ pub fn Queue(Elem: type) type { |
| 1347 | return @divExact(try q.type_erased.put(io, @ptrCast(elements), min * @sizeOf(Elem)), @sizeOf(Elem)); | 1349 | return @divExact(try q.type_erased.put(io, @ptrCast(elements), min * @sizeOf(Elem)), @sizeOf(Elem)); |
| 1348 | } | 1350 | } |
| 1349 | 1351 | ||
| 1352 | /// Same as `put` but blocks until all elements have been added to the queue. | ||
| 1353 | pub fn putAll(q: *@This(), io: Io, elements: []const Elem) Cancelable!void { | ||
| 1354 | assert(try q.put(io, elements, elements.len) == elements.len); | ||
| 1355 | } | ||
| 1356 | |||
| 1357 | /// Same as `put` but cannot be interrupted. | ||
| 1358 | pub fn putUncancelable(q: *@This(), io: Io, elements: []const Elem, min: usize) usize { | ||
| 1359 | return @divExact(q.type_erased.putUncancelable(io, @ptrCast(elements), min * @sizeOf(Elem)), @sizeOf(Elem)); | ||
| 1360 | } | ||
| 1361 | |||
| 1350 | /// Receives elements from the beginning of the queue. The function | 1362 | /// Receives elements from the beginning of the queue. The function |
| 1351 | /// returns when at least `min` elements have been populated inside | 1363 | /// returns when at least `min` elements have been populated inside |
| 1352 | /// `buffer`. | 1364 | /// `buffer`. |
| ... | @@ -1362,11 +1374,20 @@ pub fn Queue(Elem: type) type { | ... | @@ -1362,11 +1374,20 @@ pub fn Queue(Elem: type) type { |
| 1362 | assert(try q.put(io, &.{item}, 1) == 1); | 1374 | assert(try q.put(io, &.{item}, 1) == 1); |
| 1363 | } | 1375 | } |
| 1364 | 1376 | ||
| 1377 | pub fn putOneUncancelable(q: *@This(), io: Io, item: Elem) void { | ||
| 1378 | assert(q.putUncancelable(io, &.{item}, 1) == 1); | ||
| 1379 | } | ||
| 1380 | |||
| 1365 | pub fn getOne(q: *@This(), io: Io) Cancelable!Elem { | 1381 | pub fn getOne(q: *@This(), io: Io) Cancelable!Elem { |
| 1366 | var buf: [1]Elem = undefined; | 1382 | var buf: [1]Elem = undefined; |
| 1367 | assert(try q.get(io, &buf, 1) == 1); | 1383 | assert(try q.get(io, &buf, 1) == 1); |
| 1368 | return buf[0]; | 1384 | return buf[0]; |
| 1369 | } | 1385 | } |
| 1386 | |||
| 1387 | /// Returns buffer length in `Elem` units. | ||
| 1388 | pub fn capacity(q: *const @This()) usize { | ||
| 1389 | return @divExact(q.type_erased.buffer.len, @sizeOf(Elem)); | ||
| 1390 | } | ||
| 1370 | }; | 1391 | }; |
| 1371 | } | 1392 | } |
| 1372 | 1393 |
lib/std/Io/EventLoop.zig+1-1| ... | @@ -1410,7 +1410,7 @@ fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.o | ... | @@ -1410,7 +1410,7 @@ fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.o |
| 1410 | .NOMEM => return error.SystemResources, | 1410 | .NOMEM => return error.SystemResources, |
| 1411 | .NOTCONN => return error.SocketUnconnected, | 1411 | .NOTCONN => return error.SocketUnconnected, |
| 1412 | .CONNRESET => return error.ConnectionResetByPeer, | 1412 | .CONNRESET => return error.ConnectionResetByPeer, |
| 1413 | .TIMEDOUT => return error.ConnectionTimedOut, | 1413 | .TIMEDOUT => return error.Timeout, |
| 1414 | .NXIO => return error.Unseekable, | 1414 | .NXIO => return error.Unseekable, |
| 1415 | .SPIPE => return error.Unseekable, | 1415 | .SPIPE => return error.Unseekable, |
| 1416 | .OVERFLOW => return error.Unseekable, | 1416 | .OVERFLOW => return error.Unseekable, |
lib/std/Io/File.zig+1-1| ... | @@ -153,7 +153,7 @@ pub const ReadStreamingError = error{ | ... | @@ -153,7 +153,7 @@ pub const ReadStreamingError = error{ |
| 153 | IsDir, | 153 | IsDir, |
| 154 | BrokenPipe, | 154 | BrokenPipe, |
| 155 | ConnectionResetByPeer, | 155 | ConnectionResetByPeer, |
| 156 | ConnectionTimedOut, | 156 | Timeout, |
| 157 | NotOpenForReading, | 157 | NotOpenForReading, |
| 158 | SocketUnconnected, | 158 | SocketUnconnected, |
| 159 | /// This error occurs when no global event loop is configured, | 159 | /// This error occurs when no global event loop is configured, |
lib/std/Io/Threaded.zig+642-48| ... | @@ -8,6 +8,8 @@ const windows = std.os.windows; | ... | @@ -8,6 +8,8 @@ const windows = std.os.windows; |
| 8 | const std = @import("../std.zig"); | 8 | const std = @import("../std.zig"); |
| 9 | const Io = std.Io; | 9 | const Io = std.Io; |
| 10 | const net = std.Io.net; | 10 | const net = std.Io.net; |
| 11 | const HostName = std.Io.net.HostName; | ||
| 12 | const IpAddress = std.Io.net.IpAddress; | ||
| 11 | const Allocator = std.mem.Allocator; | 13 | const Allocator = std.mem.Allocator; |
| 12 | const assert = std.debug.assert; | 14 | const assert = std.debug.assert; |
| 13 | const posix = std.posix; | 15 | const posix = std.posix; |
| ... | @@ -156,9 +158,11 @@ pub fn io(pool: *Pool) Io { | ... | @@ -156,9 +158,11 @@ pub fn io(pool: *Pool) Io { |
| 156 | .groupCancel = groupCancel, | 158 | .groupCancel = groupCancel, |
| 157 | 159 | ||
| 158 | .mutexLock = mutexLock, | 160 | .mutexLock = mutexLock, |
| 161 | .mutexLockUncancelable = mutexLockUncancelable, | ||
| 159 | .mutexUnlock = mutexUnlock, | 162 | .mutexUnlock = mutexUnlock, |
| 160 | 163 | ||
| 161 | .conditionWait = conditionWait, | 164 | .conditionWait = conditionWait, |
| 165 | .conditionWaitUncancelable = conditionWaitUncancelable, | ||
| 162 | .conditionWake = conditionWake, | 166 | .conditionWake = conditionWake, |
| 163 | 167 | ||
| 164 | .dirMake = switch (builtin.os.tag) { | 168 | .dirMake = switch (builtin.os.tag) { |
| ... | @@ -235,6 +239,7 @@ pub fn io(pool: *Pool) Io { | ... | @@ -235,6 +239,7 @@ pub fn io(pool: *Pool) Io { |
| 235 | .netReceive = netReceive, | 239 | .netReceive = netReceive, |
| 236 | .netInterfaceNameResolve = netInterfaceNameResolve, | 240 | .netInterfaceNameResolve = netInterfaceNameResolve, |
| 237 | .netInterfaceName = netInterfaceName, | 241 | .netInterfaceName = netInterfaceName, |
| 242 | .netLookup = netLookup, | ||
| 238 | }, | 243 | }, |
| 239 | }; | 244 | }; |
| 240 | } | 245 | } |
| ... | @@ -653,26 +658,63 @@ fn checkCancel(pool: *Pool) error{Canceled}!void { | ... | @@ -653,26 +658,63 @@ fn checkCancel(pool: *Pool) error{Canceled}!void { |
| 653 | if (cancelRequested(pool)) return error.Canceled; | 658 | if (cancelRequested(pool)) return error.Canceled; |
| 654 | } | 659 | } |
| 655 | 660 | ||
| 656 | fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) error{Canceled}!void { | 661 | fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) Io.Cancelable!void { |
| 662 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | ||
| 663 | if (prev_state == .contended) { | ||
| 664 | try pool.checkCancel(); | ||
| 665 | futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); | ||
| 666 | } | ||
| 667 | while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { | ||
| 668 | try pool.checkCancel(); | ||
| 669 | futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); | ||
| 670 | } | ||
| 671 | } | ||
| 672 | |||
| 673 | fn mutexLockUncancelable(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void { | ||
| 657 | _ = userdata; | 674 | _ = userdata; |
| 658 | if (prev_state == .contended) { | 675 | if (prev_state == .contended) { |
| 659 | std.Thread.Futex.wait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); | 676 | futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 660 | } | 677 | } |
| 661 | while (@atomicRmw( | 678 | while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { |
| 662 | Io.Mutex.State, | 679 | futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 663 | &mutex.state, | ||
| 664 | .Xchg, | ||
| 665 | .contended, | ||
| 666 | .acquire, | ||
| 667 | ) != .unlocked) { | ||
| 668 | std.Thread.Futex.wait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); | ||
| 669 | } | 680 | } |
| 670 | } | 681 | } |
| 682 | |||
| 671 | fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void { | 683 | fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void { |
| 672 | _ = userdata; | 684 | _ = userdata; |
| 673 | _ = prev_state; | 685 | _ = prev_state; |
| 674 | if (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .unlocked, .release) == .contended) { | 686 | if (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .unlocked, .release) == .contended) { |
| 675 | std.Thread.Futex.wake(@ptrCast(&mutex.state), 1); | 687 | futexWake(@ptrCast(&mutex.state), 1); |
| 688 | } | ||
| 689 | } | ||
| 690 | |||
| 691 | fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) void { | ||
| 692 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | ||
| 693 | const pool_io = pool.io(); | ||
| 694 | comptime assert(@TypeOf(cond.state) == u64); | ||
| 695 | const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state); | ||
| 696 | const cond_state = &ints[0]; | ||
| 697 | const cond_epoch = &ints[1]; | ||
| 698 | const one_waiter = 1; | ||
| 699 | const waiter_mask = 0xffff; | ||
| 700 | const one_signal = 1 << 16; | ||
| 701 | const signal_mask = 0xffff << 16; | ||
| 702 | var epoch = cond_epoch.load(.acquire); | ||
| 703 | var state = cond_state.fetchAdd(one_waiter, .monotonic); | ||
| 704 | assert(state & waiter_mask != waiter_mask); | ||
| 705 | state += one_waiter; | ||
| 706 | |||
| 707 | mutex.unlock(pool_io); | ||
| 708 | defer mutex.lockUncancelable(pool_io); | ||
| 709 | |||
| 710 | while (true) { | ||
| 711 | futexWait(cond_epoch, epoch); | ||
| 712 | epoch = cond_epoch.load(.acquire); | ||
| 713 | state = cond_state.load(.monotonic); | ||
| 714 | while (state & signal_mask != 0) { | ||
| 715 | const new_state = state - one_waiter - one_signal; | ||
| 716 | state = cond_state.cmpxchgWeak(state, new_state, .acquire, .monotonic) orelse return; | ||
| 717 | } | ||
| 676 | } | 718 | } |
| 677 | } | 719 | } |
| 678 | 720 | ||
| ... | @@ -702,20 +744,18 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I | ... | @@ -702,20 +744,18 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I |
| 702 | state += one_waiter; | 744 | state += one_waiter; |
| 703 | 745 | ||
| 704 | mutex.unlock(pool.io()); | 746 | mutex.unlock(pool.io()); |
| 705 | defer mutex.lock(pool.io()) catch @panic("TODO"); | 747 | defer mutex.lockUncancelable(pool.io()); |
| 706 | |||
| 707 | var futex_deadline = std.Thread.Futex.Deadline.init(null); | ||
| 708 | 748 | ||
| 709 | while (true) { | 749 | while (true) { |
| 710 | futex_deadline.wait(cond_epoch, epoch) catch |err| switch (err) { | 750 | try pool.checkCancel(); |
| 711 | error.Timeout => unreachable, | 751 | futexWait(cond_epoch, epoch); |
| 712 | }; | ||
| 713 | 752 | ||
| 714 | epoch = cond_epoch.load(.acquire); | 753 | epoch = cond_epoch.load(.acquire); |
| 715 | state = cond_state.load(.monotonic); | 754 | state = cond_state.load(.monotonic); |
| 716 | 755 | ||
| 717 | // Try to wake up by consuming a signal and decremented the waiter we added previously. | 756 | // Try to wake up by consuming a signal and decremented the waiter we |
| 718 | // Acquire barrier ensures code before the wake() which added the signal happens before we decrement it and return. | 757 | // added previously. Acquire barrier ensures code before the wake() |
| 758 | // which added the signal happens before we decrement it and return. | ||
| 719 | while (state & signal_mask != 0) { | 759 | while (state & signal_mask != 0) { |
| 720 | const new_state = state - one_waiter - one_signal; | 760 | const new_state = state - one_waiter - one_signal; |
| 721 | state = cond_state.cmpxchgWeak(state, new_state, .acquire, .monotonic) orelse return; | 761 | state = cond_state.cmpxchgWeak(state, new_state, .acquire, .monotonic) orelse return; |
| ... | @@ -740,8 +780,10 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition. | ... | @@ -740,8 +780,10 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition. |
| 740 | const signals = (state & signal_mask) / one_signal; | 780 | const signals = (state & signal_mask) / one_signal; |
| 741 | 781 | ||
| 742 | // Reserves which waiters to wake up by incrementing the signals count. | 782 | // Reserves which waiters to wake up by incrementing the signals count. |
| 743 | // Therefore, the signals count is always less than or equal to the waiters count. | 783 | // Therefore, the signals count is always less than or equal to the |
| 744 | // We don't need to Futex.wake if there's nothing to wake up or if other wake() threads have reserved to wake up the current waiters. | 784 | // waiters count. We don't need to Futex.wake if there's nothing to |
| 785 | // wake up or if other wake() threads have reserved to wake up the | ||
| 786 | // current waiters. | ||
| 745 | const wakeable = waiters - signals; | 787 | const wakeable = waiters - signals; |
| 746 | if (wakeable == 0) { | 788 | if (wakeable == 0) { |
| 747 | return; | 789 | return; |
| ... | @@ -752,16 +794,23 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition. | ... | @@ -752,16 +794,23 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition. |
| 752 | .all => wakeable, | 794 | .all => wakeable, |
| 753 | }; | 795 | }; |
| 754 | 796 | ||
| 755 | // Reserve the amount of waiters to wake by incrementing the signals count. | 797 | // Reserve the amount of waiters to wake by incrementing the signals |
| 756 | // Release barrier ensures code before the wake() happens before the signal it posted and consumed by the wait() threads. | 798 | // count. Release barrier ensures code before the wake() happens before |
| 799 | // the signal it posted and consumed by the wait() threads. | ||
| 757 | const new_state = state + (one_signal * to_wake); | 800 | const new_state = state + (one_signal * to_wake); |
| 758 | state = cond_state.cmpxchgWeak(state, new_state, .release, .monotonic) orelse { | 801 | state = cond_state.cmpxchgWeak(state, new_state, .release, .monotonic) orelse { |
| 759 | // Wake up the waiting threads we reserved above by changing the epoch value. | 802 | // Wake up the waiting threads we reserved above by changing the epoch value. |
| 760 | // NOTE: a waiting thread could miss a wake up if *exactly* ((1<<32)-1) wake()s happen between it observing the epoch and sleeping on it. | ||
| 761 | // This is very unlikely due to how many precise amount of Futex.wake() calls that would be between the waiting thread's potential preemption. | ||
| 762 | // | 803 | // |
| 763 | // Release barrier ensures the signal being added to the state happens before the epoch is changed. | 804 | // A waiting thread could miss a wake up if *exactly* ((1<<32)-1) |
| 764 | // If not, the waiting thread could potentially deadlock from missing both the state and epoch change: | 805 | // wake()s happen between it observing the epoch and sleeping on |
| 806 | // it. This is very unlikely due to how many precise amount of | ||
| 807 | // Futex.wake() calls that would be between the waiting thread's | ||
| 808 | // potential preemption. | ||
| 809 | // | ||
| 810 | // Release barrier ensures the signal being added to the state | ||
| 811 | // happens before the epoch is changed. If not, the waiting thread | ||
| 812 | // could potentially deadlock from missing both the state and epoch | ||
| 813 | // change: | ||
| 765 | // | 814 | // |
| 766 | // - T2: UPDATE(&epoch, 1) (reordered before the state change) | 815 | // - T2: UPDATE(&epoch, 1) (reordered before the state change) |
| 767 | // - T1: e = LOAD(&epoch) | 816 | // - T1: e = LOAD(&epoch) |
| ... | @@ -769,7 +818,7 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition. | ... | @@ -769,7 +818,7 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition. |
| 769 | // - T2: UPDATE(&state, signal) + FUTEX_WAKE(&epoch) | 818 | // - T2: UPDATE(&state, signal) + FUTEX_WAKE(&epoch) |
| 770 | // - T1: s & signals == 0 -> FUTEX_WAIT(&epoch, e) (missed both epoch change and state change) | 819 | // - T1: s & signals == 0 -> FUTEX_WAIT(&epoch, e) (missed both epoch change and state change) |
| 771 | _ = cond_epoch.fetchAdd(1, .release); | 820 | _ = cond_epoch.fetchAdd(1, .release); |
| 772 | std.Thread.Futex.wake(cond_epoch, to_wake); | 821 | futexWake(cond_epoch, to_wake); |
| 773 | return; | 822 | return; |
| 774 | }; | 823 | }; |
| 775 | } | 824 | } |
| ... | @@ -1298,7 +1347,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File | ... | @@ -1298,7 +1347,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File |
| 1298 | .NOMEM => return error.SystemResources, | 1347 | .NOMEM => return error.SystemResources, |
| 1299 | .NOTCONN => return error.SocketUnconnected, | 1348 | .NOTCONN => return error.SocketUnconnected, |
| 1300 | .CONNRESET => return error.ConnectionResetByPeer, | 1349 | .CONNRESET => return error.ConnectionResetByPeer, |
| 1301 | .TIMEDOUT => return error.ConnectionTimedOut, | 1350 | .TIMEDOUT => return error.Timeout, |
| 1302 | .NOTCAPABLE => return error.AccessDenied, | 1351 | .NOTCAPABLE => return error.AccessDenied, |
| 1303 | else => |err| return posix.unexpectedErrno(err), | 1352 | else => |err| return posix.unexpectedErrno(err), |
| 1304 | } | 1353 | } |
| ... | @@ -1321,7 +1370,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File | ... | @@ -1321,7 +1370,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File |
| 1321 | .NOMEM => return error.SystemResources, | 1370 | .NOMEM => return error.SystemResources, |
| 1322 | .NOTCONN => return error.SocketUnconnected, | 1371 | .NOTCONN => return error.SocketUnconnected, |
| 1323 | .CONNRESET => return error.ConnectionResetByPeer, | 1372 | .CONNRESET => return error.ConnectionResetByPeer, |
| 1324 | .TIMEDOUT => return error.ConnectionTimedOut, | 1373 | .TIMEDOUT => return error.Timeout, |
| 1325 | else => |err| return posix.unexpectedErrno(err), | 1374 | else => |err| return posix.unexpectedErrno(err), |
| 1326 | } | 1375 | } |
| 1327 | } | 1376 | } |
| ... | @@ -1420,7 +1469,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset | ... | @@ -1420,7 +1469,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset |
| 1420 | .NOMEM => return error.SystemResources, | 1469 | .NOMEM => return error.SystemResources, |
| 1421 | .NOTCONN => return error.SocketUnconnected, | 1470 | .NOTCONN => return error.SocketUnconnected, |
| 1422 | .CONNRESET => return error.ConnectionResetByPeer, | 1471 | .CONNRESET => return error.ConnectionResetByPeer, |
| 1423 | .TIMEDOUT => return error.ConnectionTimedOut, | 1472 | .TIMEDOUT => return error.Timeout, |
| 1424 | .NXIO => return error.Unseekable, | 1473 | .NXIO => return error.Unseekable, |
| 1425 | .SPIPE => return error.Unseekable, | 1474 | .SPIPE => return error.Unseekable, |
| 1426 | .OVERFLOW => return error.Unseekable, | 1475 | .OVERFLOW => return error.Unseekable, |
| ... | @@ -1446,7 +1495,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset | ... | @@ -1446,7 +1495,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset |
| 1446 | .NOMEM => return error.SystemResources, | 1495 | .NOMEM => return error.SystemResources, |
| 1447 | .NOTCONN => return error.SocketUnconnected, | 1496 | .NOTCONN => return error.SocketUnconnected, |
| 1448 | .CONNRESET => return error.ConnectionResetByPeer, | 1497 | .CONNRESET => return error.ConnectionResetByPeer, |
| 1449 | .TIMEDOUT => return error.ConnectionTimedOut, | 1498 | .TIMEDOUT => return error.Timeout, |
| 1450 | .NXIO => return error.Unseekable, | 1499 | .NXIO => return error.Unseekable, |
| 1451 | .SPIPE => return error.Unseekable, | 1500 | .SPIPE => return error.Unseekable, |
| 1452 | .OVERFLOW => return error.Unseekable, | 1501 | .OVERFLOW => return error.Unseekable, |
| ... | @@ -1693,9 +1742,9 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { | ... | @@ -1693,9 +1742,9 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { |
| 1693 | 1742 | ||
| 1694 | fn netListenIpPosix( | 1743 | fn netListenIpPosix( |
| 1695 | userdata: ?*anyopaque, | 1744 | userdata: ?*anyopaque, |
| 1696 | address: net.IpAddress, | 1745 | address: IpAddress, |
| 1697 | options: net.IpAddress.ListenOptions, | 1746 | options: IpAddress.ListenOptions, |
| 1698 | ) net.IpAddress.ListenError!net.Server { | 1747 | ) IpAddress.ListenError!net.Server { |
| 1699 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | 1748 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 1700 | const family = posixAddressFamily(&address); | 1749 | const family = posixAddressFamily(&address); |
| 1701 | const socket_fd = try openSocketPosix(pool, family, .{ | 1750 | const socket_fd = try openSocketPosix(pool, family, .{ |
| ... | @@ -1831,7 +1880,7 @@ fn posixConnect(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.socka | ... | @@ -1831,7 +1880,7 @@ fn posixConnect(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.socka |
| 1831 | .NETUNREACH => return error.NetworkUnreachable, | 1880 | .NETUNREACH => return error.NetworkUnreachable, |
| 1832 | .NOTSOCK => |err| return errnoBug(err), | 1881 | .NOTSOCK => |err| return errnoBug(err), |
| 1833 | .PROTOTYPE => |err| return errnoBug(err), | 1882 | .PROTOTYPE => |err| return errnoBug(err), |
| 1834 | .TIMEDOUT => return error.ConnectionTimedOut, | 1883 | .TIMEDOUT => return error.Timeout, |
| 1835 | .CONNABORTED => |err| return errnoBug(err), | 1884 | .CONNABORTED => |err| return errnoBug(err), |
| 1836 | .ACCES => return error.AccessDenied, | 1885 | .ACCES => return error.AccessDenied, |
| 1837 | .PERM => |err| return errnoBug(err), | 1886 | .PERM => |err| return errnoBug(err), |
| ... | @@ -1904,9 +1953,9 @@ fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, optio | ... | @@ -1904,9 +1953,9 @@ fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, optio |
| 1904 | 1953 | ||
| 1905 | fn netConnectIpPosix( | 1954 | fn netConnectIpPosix( |
| 1906 | userdata: ?*anyopaque, | 1955 | userdata: ?*anyopaque, |
| 1907 | address: *const net.IpAddress, | 1956 | address: *const IpAddress, |
| 1908 | options: net.IpAddress.ConnectOptions, | 1957 | options: IpAddress.ConnectOptions, |
| 1909 | ) net.IpAddress.ConnectError!net.Stream { | 1958 | ) IpAddress.ConnectError!net.Stream { |
| 1910 | if (options.timeout != .none) @panic("TODO"); | 1959 | if (options.timeout != .none) @panic("TODO"); |
| 1911 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | 1960 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 1912 | const family = posixAddressFamily(address); | 1961 | const family = posixAddressFamily(address); |
| ... | @@ -1941,9 +1990,9 @@ fn netConnectUnix( | ... | @@ -1941,9 +1990,9 @@ fn netConnectUnix( |
| 1941 | 1990 | ||
| 1942 | fn netBindIpPosix( | 1991 | fn netBindIpPosix( |
| 1943 | userdata: ?*anyopaque, | 1992 | userdata: ?*anyopaque, |
| 1944 | address: *const net.IpAddress, | 1993 | address: *const IpAddress, |
| 1945 | options: net.IpAddress.BindOptions, | 1994 | options: IpAddress.BindOptions, |
| 1946 | ) net.IpAddress.BindError!net.Socket { | 1995 | ) IpAddress.BindError!net.Socket { |
| 1947 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | 1996 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 1948 | const family = posixAddressFamily(address); | 1997 | const family = posixAddressFamily(address); |
| 1949 | const socket_fd = try openSocketPosix(pool, family, options); | 1998 | const socket_fd = try openSocketPosix(pool, family, options); |
| ... | @@ -1958,7 +2007,7 @@ fn netBindIpPosix( | ... | @@ -1958,7 +2007,7 @@ fn netBindIpPosix( |
| 1958 | }; | 2007 | }; |
| 1959 | } | 2008 | } |
| 1960 | 2009 | ||
| 1961 | fn openSocketPosix(pool: *Pool, family: posix.sa_family_t, options: net.IpAddress.BindOptions) !posix.socket_t { | 2010 | fn openSocketPosix(pool: *Pool, family: posix.sa_family_t, options: IpAddress.BindOptions) !posix.socket_t { |
| 1962 | const mode = posixSocketMode(options.mode); | 2011 | const mode = posixSocketMode(options.mode); |
| 1963 | const protocol = posixProtocol(options.protocol); | 2012 | const protocol = posixProtocol(options.protocol); |
| 1964 | const socket_fd = while (true) { | 2013 | const socket_fd = while (true) { |
| ... | @@ -2081,7 +2130,7 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. | ... | @@ -2081,7 +2130,7 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. |
| 2081 | .NOMEM => return error.SystemResources, | 2130 | .NOMEM => return error.SystemResources, |
| 2082 | .NOTCONN => return error.SocketUnconnected, | 2131 | .NOTCONN => return error.SocketUnconnected, |
| 2083 | .CONNRESET => return error.ConnectionResetByPeer, | 2132 | .CONNRESET => return error.ConnectionResetByPeer, |
| 2084 | .TIMEDOUT => return error.ConnectionTimedOut, | 2133 | .TIMEDOUT => return error.Timeout, |
| 2085 | .NOTCAPABLE => return error.AccessDenied, | 2134 | .NOTCAPABLE => return error.AccessDenied, |
| 2086 | else => |err| return posix.unexpectedErrno(err), | 2135 | else => |err| return posix.unexpectedErrno(err), |
| 2087 | } | 2136 | } |
| ... | @@ -2102,7 +2151,7 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. | ... | @@ -2102,7 +2151,7 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. |
| 2102 | .NOMEM => return error.SystemResources, | 2151 | .NOMEM => return error.SystemResources, |
| 2103 | .NOTCONN => return error.SocketUnconnected, | 2152 | .NOTCONN => return error.SocketUnconnected, |
| 2104 | .CONNRESET => return error.ConnectionResetByPeer, | 2153 | .CONNRESET => return error.ConnectionResetByPeer, |
| 2105 | .TIMEDOUT => return error.ConnectionTimedOut, | 2154 | .TIMEDOUT => return error.Timeout, |
| 2106 | .PIPE => return error.BrokenPipe, | 2155 | .PIPE => return error.BrokenPipe, |
| 2107 | .NETDOWN => return error.NetworkDown, | 2156 | .NETDOWN => return error.NetworkDown, |
| 2108 | else => |err| return posix.unexpectedErrno(err), | 2157 | else => |err| return posix.unexpectedErrno(err), |
| ... | @@ -2563,6 +2612,118 @@ fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interfa | ... | @@ -2563,6 +2612,118 @@ fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interfa |
| 2563 | @panic("unimplemented"); | 2612 | @panic("unimplemented"); |
| 2564 | } | 2613 | } |
| 2565 | 2614 | ||
| 2615 | fn netLookup( | ||
| 2616 | userdata: ?*anyopaque, | ||
| 2617 | host_name: HostName, | ||
| 2618 | resolved: *Io.Queue(HostName.LookupResult), | ||
| 2619 | options: HostName.LookupOptions, | ||
| 2620 | ) void { | ||
| 2621 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | ||
| 2622 | const pool_io = pool.io(); | ||
| 2623 | resolved.putOneUncancelable(pool_io, .{ .end = netLookupFallible(pool, host_name, resolved, options) }); | ||
| 2624 | } | ||
| 2625 | |||
| 2626 | fn netLookupFallible( | ||
| 2627 | pool: *Pool, | ||
| 2628 | host_name: HostName, | ||
| 2629 | resolved: *Io.Queue(HostName.LookupResult), | ||
| 2630 | options: HostName.LookupOptions, | ||
| 2631 | ) !void { | ||
| 2632 | const pool_io = pool.io(); | ||
| 2633 | const name = host_name.bytes; | ||
| 2634 | assert(name.len <= HostName.max_len); | ||
| 2635 | |||
| 2636 | if (is_windows) { | ||
| 2637 | // TODO use GetAddrInfoExW / GetAddrInfoExCancel | ||
| 2638 | @compileError("TODO"); | ||
| 2639 | } | ||
| 2640 | |||
| 2641 | // On Linux, glibc provides getaddrinfo_a which is capable of supporting our semantics. | ||
| 2642 | // However, musl's POSIX-compliant getaddrinfo is not, so we bypass it. | ||
| 2643 | |||
| 2644 | if (builtin.target.isGnuLibC()) { | ||
| 2645 | // TODO use getaddrinfo_a / gai_cancel | ||
| 2646 | } | ||
| 2647 | |||
| 2648 | if (native_os == .linux) { | ||
| 2649 | if (options.family != .ip4) { | ||
| 2650 | if (IpAddress.parseIp6(name, options.port)) |addr| { | ||
| 2651 | try resolved.putAll(pool_io, &.{ | ||
| 2652 | .{ .address = addr }, | ||
| 2653 | .{ .canonical_name = copyCanon(options.canonical_name_buffer, name) }, | ||
| 2654 | }); | ||
| 2655 | return; | ||
| 2656 | } else |_| {} | ||
| 2657 | } | ||
| 2658 | |||
| 2659 | if (options.family != .ip6) { | ||
| 2660 | if (IpAddress.parseIp4(name, options.port)) |addr| { | ||
| 2661 | try resolved.putAll(pool_io, &.{ | ||
| 2662 | .{ .address = addr }, | ||
| 2663 | .{ .canonical_name = copyCanon(options.canonical_name_buffer, name) }, | ||
| 2664 | }); | ||
| 2665 | } else |_| {} | ||
| 2666 | } | ||
| 2667 | |||
| 2668 | lookupHosts(pool, host_name, resolved, options) catch |err| switch (err) { | ||
| 2669 | error.UnknownHostName => {}, | ||
| 2670 | else => |e| return e, | ||
| 2671 | }; | ||
| 2672 | |||
| 2673 | // RFC 6761 Section 6.3.3 | ||
| 2674 | // Name resolution APIs and libraries SHOULD recognize | ||
| 2675 | // localhost names as special and SHOULD always return the IP | ||
| 2676 | // loopback address for address queries and negative responses | ||
| 2677 | // for all other query types. | ||
| 2678 | |||
| 2679 | // Check for equal to "localhost(.)" or ends in ".localhost(.)" | ||
| 2680 | const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost"; | ||
| 2681 | if (std.mem.endsWith(u8, name, localhost) and | ||
| 2682 | (name.len == localhost.len or name[name.len - localhost.len] == '.')) | ||
| 2683 | { | ||
| 2684 | var results_buffer: [3]HostName.LookupResult = undefined; | ||
| 2685 | var results_index: usize = 0; | ||
| 2686 | if (options.family != .ip4) { | ||
| 2687 | results_buffer[results_index] = .{ .address = .{ .ip6 = .loopback(options.port) } }; | ||
| 2688 | results_index += 1; | ||
| 2689 | } | ||
| 2690 | if (options.family != .ip6) { | ||
| 2691 | results_buffer[results_index] = .{ .address = .{ .ip4 = .loopback(options.port) } }; | ||
| 2692 | results_index += 1; | ||
| 2693 | } | ||
| 2694 | const canon_name = "localhost"; | ||
| 2695 | const canon_name_dest = options.canonical_name_buffer[0..canon_name.len]; | ||
| 2696 | canon_name_dest.* = canon_name.*; | ||
| 2697 | results_buffer[results_index] = .{ .canonical_name = .{ .bytes = canon_name_dest } }; | ||
| 2698 | results_index += 1; | ||
| 2699 | try resolved.putAll(pool_io, results_buffer[0..results_index]); | ||
| 2700 | return; | ||
| 2701 | } | ||
| 2702 | |||
| 2703 | return lookupDnsSearch(pool, host_name, resolved, options); | ||
| 2704 | } | ||
| 2705 | |||
| 2706 | if (native_os == .openbsd) { | ||
| 2707 | // TODO use getaddrinfo_async / asr_abort | ||
| 2708 | } | ||
| 2709 | |||
| 2710 | if (native_os == .freebsd) { | ||
| 2711 | // TODO use dnsres_getaddrinfo | ||
| 2712 | } | ||
| 2713 | |||
| 2714 | if (native_os.isDarwin()) { | ||
| 2715 | // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution | ||
| 2716 | } | ||
| 2717 | |||
| 2718 | if (builtin.link_libc) { | ||
| 2719 | // This operating system lacks a way to resolve asynchronously. We are | ||
| 2720 | // stuck with getaddrinfo. | ||
| 2721 | @compileError("TODO"); | ||
| 2722 | } | ||
| 2723 | |||
| 2724 | return error.OptionUnsupported; | ||
| 2725 | } | ||
| 2726 | |||
| 2566 | const PosixAddress = extern union { | 2727 | const PosixAddress = extern union { |
| 2567 | any: posix.sockaddr, | 2728 | any: posix.sockaddr, |
| 2568 | in: posix.sockaddr.in, | 2729 | in: posix.sockaddr.in, |
| ... | @@ -2574,14 +2735,14 @@ const UnixAddress = extern union { | ... | @@ -2574,14 +2735,14 @@ const UnixAddress = extern union { |
| 2574 | un: posix.sockaddr.un, | 2735 | un: posix.sockaddr.un, |
| 2575 | }; | 2736 | }; |
| 2576 | 2737 | ||
| 2577 | fn posixAddressFamily(a: *const net.IpAddress) posix.sa_family_t { | 2738 | fn posixAddressFamily(a: *const IpAddress) posix.sa_family_t { |
| 2578 | return switch (a.*) { | 2739 | return switch (a.*) { |
| 2579 | .ip4 => posix.AF.INET, | 2740 | .ip4 => posix.AF.INET, |
| 2580 | .ip6 => posix.AF.INET6, | 2741 | .ip6 => posix.AF.INET6, |
| 2581 | }; | 2742 | }; |
| 2582 | } | 2743 | } |
| 2583 | 2744 | ||
| 2584 | fn addressFromPosix(posix_address: *PosixAddress) net.IpAddress { | 2745 | fn addressFromPosix(posix_address: *PosixAddress) IpAddress { |
| 2585 | return switch (posix_address.any.family) { | 2746 | return switch (posix_address.any.family) { |
| 2586 | posix.AF.INET => .{ .ip4 = address4FromPosix(&posix_address.in) }, | 2747 | posix.AF.INET => .{ .ip4 = address4FromPosix(&posix_address.in) }, |
| 2587 | posix.AF.INET6 => .{ .ip6 = address6FromPosix(&posix_address.in6) }, | 2748 | posix.AF.INET6 => .{ .ip6 = address6FromPosix(&posix_address.in6) }, |
| ... | @@ -2589,7 +2750,7 @@ fn addressFromPosix(posix_address: *PosixAddress) net.IpAddress { | ... | @@ -2589,7 +2750,7 @@ fn addressFromPosix(posix_address: *PosixAddress) net.IpAddress { |
| 2589 | }; | 2750 | }; |
| 2590 | } | 2751 | } |
| 2591 | 2752 | ||
| 2592 | fn addressToPosix(a: *const net.IpAddress, storage: *PosixAddress) posix.socklen_t { | 2753 | fn addressToPosix(a: *const IpAddress, storage: *PosixAddress) posix.socklen_t { |
| 2593 | return switch (a.*) { | 2754 | return switch (a.*) { |
| 2594 | .ip4 => |ip4| { | 2755 | .ip4 => |ip4| { |
| 2595 | storage.in = address4ToPosix(ip4); | 2756 | storage.in = address4ToPosix(ip4); |
| ... | @@ -2789,3 +2950,436 @@ fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Io.Dir.PathNa | ... | @@ -2789,3 +2950,436 @@ fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Io.Dir.PathNa |
| 2789 | buffer[file_path.len] = 0; | 2950 | buffer[file_path.len] = 0; |
| 2790 | return buffer[0..file_path.len :0]; | 2951 | return buffer[0..file_path.len :0]; |
| 2791 | } | 2952 | } |
| 2953 | |||
| 2954 | fn lookupDnsSearch( | ||
| 2955 | pool: *Pool, | ||
| 2956 | host_name: HostName, | ||
| 2957 | resolved: *Io.Queue(HostName.LookupResult), | ||
| 2958 | options: HostName.LookupOptions, | ||
| 2959 | ) HostName.LookupError!void { | ||
| 2960 | const pool_io = pool.io(); | ||
| 2961 | const rc = HostName.ResolvConf.init(pool_io) catch return error.ResolvConfParseFailed; | ||
| 2962 | |||
| 2963 | // Count dots, suppress search when >=ndots or name ends in | ||
| 2964 | // a dot, which is an explicit request for global scope. | ||
| 2965 | const dots = std.mem.countScalar(u8, host_name.bytes, '.'); | ||
| 2966 | const search_len = if (dots >= rc.ndots or std.mem.endsWith(u8, host_name.bytes, ".")) 0 else rc.search_len; | ||
| 2967 | const search = rc.search_buffer[0..search_len]; | ||
| 2968 | |||
| 2969 | var canon_name = host_name.bytes; | ||
| 2970 | |||
| 2971 | // Strip final dot for canon, fail if multiple trailing dots. | ||
| 2972 | if (std.mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1; | ||
| 2973 | if (std.mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName; | ||
| 2974 | |||
| 2975 | // Name with search domain appended is set up in `canon_name`. This | ||
| 2976 | // both provides the desired default canonical name (if the requested | ||
| 2977 | // name is not a CNAME record) and serves as a buffer for passing the | ||
| 2978 | // full requested name to `lookupDns`. | ||
| 2979 | @memcpy(options.canonical_name_buffer[0..canon_name.len], canon_name); | ||
| 2980 | options.canonical_name_buffer[canon_name.len] = '.'; | ||
| 2981 | var it = std.mem.tokenizeAny(u8, search, " \t"); | ||
| 2982 | while (it.next()) |token| { | ||
| 2983 | @memcpy(options.canonical_name_buffer[canon_name.len + 1 ..][0..token.len], token); | ||
| 2984 | const lookup_canon_name = options.canonical_name_buffer[0 .. canon_name.len + 1 + token.len]; | ||
| 2985 | if (lookupDns(pool, lookup_canon_name, &rc, resolved, options)) |result| { | ||
| 2986 | return result; | ||
| 2987 | } else |err| switch (err) { | ||
| 2988 | error.UnknownHostName => continue, | ||
| 2989 | else => |e| return e, | ||
| 2990 | } | ||
| 2991 | } | ||
| 2992 | |||
| 2993 | const lookup_canon_name = options.canonical_name_buffer[0..canon_name.len]; | ||
| 2994 | return lookupDns(pool, lookup_canon_name, &rc, resolved, options); | ||
| 2995 | } | ||
| 2996 | |||
| 2997 | fn lookupDns( | ||
| 2998 | pool: *Pool, | ||
| 2999 | lookup_canon_name: []const u8, | ||
| 3000 | rc: *const HostName.ResolvConf, | ||
| 3001 | resolved: *Io.Queue(HostName.LookupResult), | ||
| 3002 | options: HostName.LookupOptions, | ||
| 3003 | ) HostName.LookupError!void { | ||
| 3004 | const pool_io = pool.io(); | ||
| 3005 | const family_records: [2]struct { af: IpAddress.Family, rr: u8 } = .{ | ||
| 3006 | .{ .af = .ip6, .rr = std.posix.RR.A }, | ||
| 3007 | .{ .af = .ip4, .rr = std.posix.RR.AAAA }, | ||
| 3008 | }; | ||
| 3009 | var query_buffers: [2][280]u8 = undefined; | ||
| 3010 | var answer_buffer: [2 * 512]u8 = undefined; | ||
| 3011 | var queries_buffer: [2][]const u8 = undefined; | ||
| 3012 | var answers_buffer: [2][]const u8 = undefined; | ||
| 3013 | var nq: usize = 0; | ||
| 3014 | var answer_buffer_i: usize = 0; | ||
| 3015 | |||
| 3016 | for (family_records) |fr| { | ||
| 3017 | if (options.family != fr.af) { | ||
| 3018 | const entropy = std.crypto.random.array(u8, 2); | ||
| 3019 | const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy); | ||
| 3020 | queries_buffer[nq] = query_buffers[nq][0..len]; | ||
| 3021 | nq += 1; | ||
| 3022 | } | ||
| 3023 | } | ||
| 3024 | |||
| 3025 | var ip4_mapped: [HostName.ResolvConf.max_nameservers]IpAddress = undefined; | ||
| 3026 | var any_ip6 = false; | ||
| 3027 | for (rc.nameservers(), &ip4_mapped) |*ns, *m| { | ||
| 3028 | m.* = .{ .ip6 = .fromAny(ns.*) }; | ||
| 3029 | any_ip6 = any_ip6 or ns.* == .ip6; | ||
| 3030 | } | ||
| 3031 | var socket = s: { | ||
| 3032 | if (any_ip6) ip6: { | ||
| 3033 | const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) }; | ||
| 3034 | const socket = ip6_addr.bind(pool_io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) { | ||
| 3035 | error.AddressFamilyUnsupported => break :ip6, | ||
| 3036 | else => |e| return e, | ||
| 3037 | }; | ||
| 3038 | break :s socket; | ||
| 3039 | } | ||
| 3040 | any_ip6 = false; | ||
| 3041 | const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) }; | ||
| 3042 | const socket = try ip4_addr.bind(pool_io, .{ .mode = .dgram }); | ||
| 3043 | break :s socket; | ||
| 3044 | }; | ||
| 3045 | defer socket.close(pool_io); | ||
| 3046 | |||
| 3047 | const mapped_nameservers = if (any_ip6) ip4_mapped[0..rc.nameservers_len] else rc.nameservers(); | ||
| 3048 | const queries = queries_buffer[0..nq]; | ||
| 3049 | const answers = answers_buffer[0..queries.len]; | ||
| 3050 | var answers_remaining = answers.len; | ||
| 3051 | for (answers) |*answer| answer.len = 0; | ||
| 3052 | |||
| 3053 | // boot clock is chosen because time the computer is suspended should count | ||
| 3054 | // against time spent waiting for external messages to arrive. | ||
| 3055 | const clock: Io.Clock = .boot; | ||
| 3056 | var now_ts = try clock.now(pool_io); | ||
| 3057 | const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds)); | ||
| 3058 | const attempt_duration: Io.Duration = .{ | ||
| 3059 | .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts, | ||
| 3060 | }; | ||
| 3061 | |||
| 3062 | send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = try clock.now(pool_io)) { | ||
| 3063 | const max_messages = queries_buffer.len * HostName.ResolvConf.max_nameservers; | ||
| 3064 | { | ||
| 3065 | var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined; | ||
| 3066 | var message_i: usize = 0; | ||
| 3067 | for (queries, answers) |query, *answer| { | ||
| 3068 | if (answer.len != 0) continue; | ||
| 3069 | for (mapped_nameservers) |*ns| { | ||
| 3070 | message_buffer[message_i] = .{ | ||
| 3071 | .address = ns, | ||
| 3072 | .data_ptr = query.ptr, | ||
| 3073 | .data_len = query.len, | ||
| 3074 | }; | ||
| 3075 | message_i += 1; | ||
| 3076 | } | ||
| 3077 | } | ||
| 3078 | _ = netSend(pool, socket.handle, message_buffer[0..message_i], .{}); | ||
| 3079 | } | ||
| 3080 | |||
| 3081 | const timeout: Io.Timeout = .{ .deadline = .{ | ||
| 3082 | .raw = now_ts.addDuration(attempt_duration), | ||
| 3083 | .clock = clock, | ||
| 3084 | } }; | ||
| 3085 | |||
| 3086 | while (true) { | ||
| 3087 | var message_buffer: [max_messages]Io.net.IncomingMessage = undefined; | ||
| 3088 | const buf = answer_buffer[answer_buffer_i..]; | ||
| 3089 | const recv_err, const recv_n = socket.receiveManyTimeout(pool_io, &message_buffer, buf, .{}, timeout); | ||
| 3090 | for (message_buffer[0..recv_n]) |*received_message| { | ||
| 3091 | const reply = received_message.data; | ||
| 3092 | // Ignore non-identifiable packets. | ||
| 3093 | if (reply.len < 4) continue; | ||
| 3094 | |||
| 3095 | // Ignore replies from addresses we didn't send to. | ||
| 3096 | const ns = for (mapped_nameservers) |*ns| { | ||
| 3097 | if (received_message.from.eql(ns)) break ns; | ||
| 3098 | } else { | ||
| 3099 | continue; | ||
| 3100 | }; | ||
| 3101 | |||
| 3102 | // Find which query this answer goes with, if any. | ||
| 3103 | const query, const answer = for (queries, answers) |query, *answer| { | ||
| 3104 | if (reply[0] == query[0] and reply[1] == query[1]) break .{ query, answer }; | ||
| 3105 | } else { | ||
| 3106 | continue; | ||
| 3107 | }; | ||
| 3108 | if (answer.len != 0) continue; | ||
| 3109 | |||
| 3110 | // Only accept positive or negative responses; retry immediately on | ||
| 3111 | // server failure, and ignore all other codes such as refusal. | ||
| 3112 | switch (reply[3] & 15) { | ||
| 3113 | 0, 3 => { | ||
| 3114 | answer.* = reply; | ||
| 3115 | answer_buffer_i += reply.len; | ||
| 3116 | answers_remaining -= 1; | ||
| 3117 | if (answer_buffer.len - answer_buffer_i == 0) break :send; | ||
| 3118 | if (answers_remaining == 0) break :send; | ||
| 3119 | }, | ||
| 3120 | 2 => { | ||
| 3121 | var retry_message: Io.net.OutgoingMessage = .{ | ||
| 3122 | .address = ns, | ||
| 3123 | .data_ptr = query.ptr, | ||
| 3124 | .data_len = query.len, | ||
| 3125 | }; | ||
| 3126 | _ = netSend(pool, socket.handle, (&retry_message)[0..1], .{}); | ||
| 3127 | continue; | ||
| 3128 | }, | ||
| 3129 | else => continue, | ||
| 3130 | } | ||
| 3131 | } | ||
| 3132 | if (recv_err) |err| switch (err) { | ||
| 3133 | error.Canceled => return error.Canceled, | ||
| 3134 | error.Timeout => continue :send, | ||
| 3135 | else => continue, | ||
| 3136 | }; | ||
| 3137 | } | ||
| 3138 | } else { | ||
| 3139 | return error.NameServerFailure; | ||
| 3140 | } | ||
| 3141 | |||
| 3142 | var addresses_len: usize = 0; | ||
| 3143 | var canonical_name: ?HostName = null; | ||
| 3144 | |||
| 3145 | for (answers) |answer| { | ||
| 3146 | var it = HostName.DnsResponse.init(answer) catch { | ||
| 3147 | // TODO accept a diagnostics struct and append warnings | ||
| 3148 | continue; | ||
| 3149 | }; | ||
| 3150 | while (it.next() catch { | ||
| 3151 | // TODO accept a diagnostics struct and append warnings | ||
| 3152 | continue; | ||
| 3153 | }) |record| switch (record.rr) { | ||
| 3154 | std.posix.RR.A => { | ||
| 3155 | const data = record.packet[record.data_off..][0..record.data_len]; | ||
| 3156 | if (data.len != 4) return error.InvalidDnsARecord; | ||
| 3157 | try resolved.putOne(pool_io, .{ .address = .{ .ip4 = .{ | ||
| 3158 | .bytes = data[0..4].*, | ||
| 3159 | .port = options.port, | ||
| 3160 | } } }); | ||
| 3161 | addresses_len += 1; | ||
| 3162 | }, | ||
| 3163 | std.posix.RR.AAAA => { | ||
| 3164 | const data = record.packet[record.data_off..][0..record.data_len]; | ||
| 3165 | if (data.len != 16) return error.InvalidDnsAAAARecord; | ||
| 3166 | try resolved.putOne(pool_io, .{ .address = .{ .ip6 = .{ | ||
| 3167 | .bytes = data[0..16].*, | ||
| 3168 | .port = options.port, | ||
| 3169 | } } }); | ||
| 3170 | addresses_len += 1; | ||
| 3171 | }, | ||
| 3172 | std.posix.RR.CNAME => { | ||
| 3173 | _, canonical_name = HostName.expand(record.packet, record.data_off, options.canonical_name_buffer) catch | ||
| 3174 | return error.InvalidDnsCnameRecord; | ||
| 3175 | }, | ||
| 3176 | else => continue, | ||
| 3177 | }; | ||
| 3178 | } | ||
| 3179 | |||
| 3180 | try resolved.putOne(pool_io, .{ .canonical_name = canonical_name orelse .{ .bytes = lookup_canon_name } }); | ||
| 3181 | if (addresses_len == 0) return error.NameServerFailure; | ||
| 3182 | } | ||
| 3183 | |||
| 3184 | fn lookupHosts( | ||
| 3185 | pool: *Pool, | ||
| 3186 | host_name: HostName, | ||
| 3187 | resolved: *Io.Queue(HostName.LookupResult), | ||
| 3188 | options: HostName.LookupOptions, | ||
| 3189 | ) !void { | ||
| 3190 | const pool_io = pool.io(); | ||
| 3191 | const file = Io.File.openAbsolute(pool_io, "/etc/hosts", .{}) catch |err| switch (err) { | ||
| 3192 | error.FileNotFound, | ||
| 3193 | error.NotDir, | ||
| 3194 | error.AccessDenied, | ||
| 3195 | => return error.UnknownHostName, | ||
| 3196 | |||
| 3197 | error.Canceled => |e| return e, | ||
| 3198 | |||
| 3199 | else => { | ||
| 3200 | // TODO populate optional diagnostic struct | ||
| 3201 | return error.DetectingNetworkConfigurationFailed; | ||
| 3202 | }, | ||
| 3203 | }; | ||
| 3204 | defer file.close(pool_io); | ||
| 3205 | |||
| 3206 | var line_buf: [512]u8 = undefined; | ||
| 3207 | var file_reader = file.reader(pool_io, &line_buf); | ||
| 3208 | return lookupHostsReader(pool, host_name, resolved, options, &file_reader.interface) catch |err| switch (err) { | ||
| 3209 | error.ReadFailed => switch (file_reader.err.?) { | ||
| 3210 | error.Canceled => |e| return e, | ||
| 3211 | else => { | ||
| 3212 | // TODO populate optional diagnostic struct | ||
| 3213 | return error.DetectingNetworkConfigurationFailed; | ||
| 3214 | }, | ||
| 3215 | }, | ||
| 3216 | error.Canceled => |e| return e, | ||
| 3217 | error.UnknownHostName => |e| return e, | ||
| 3218 | }; | ||
| 3219 | } | ||
| 3220 | |||
| 3221 | fn lookupHostsReader( | ||
| 3222 | pool: *Pool, | ||
| 3223 | host_name: HostName, | ||
| 3224 | resolved: *Io.Queue(HostName.LookupResult), | ||
| 3225 | options: HostName.LookupOptions, | ||
| 3226 | reader: *Io.Reader, | ||
| 3227 | ) error{ ReadFailed, Canceled, UnknownHostName }!void { | ||
| 3228 | const pool_io = pool.io(); | ||
| 3229 | var addresses_len: usize = 0; | ||
| 3230 | var canonical_name: ?HostName = null; | ||
| 3231 | while (true) { | ||
| 3232 | const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) { | ||
| 3233 | error.StreamTooLong => { | ||
| 3234 | // Skip lines that are too long. | ||
| 3235 | _ = reader.discardDelimiterInclusive('\n') catch |e| switch (e) { | ||
| 3236 | error.EndOfStream => break, | ||
| 3237 | error.ReadFailed => return error.ReadFailed, | ||
| 3238 | }; | ||
| 3239 | continue; | ||
| 3240 | }, | ||
| 3241 | error.ReadFailed => return error.ReadFailed, | ||
| 3242 | error.EndOfStream => break, | ||
| 3243 | }; | ||
| 3244 | reader.toss(1); | ||
| 3245 | var split_it = std.mem.splitScalar(u8, line, '#'); | ||
| 3246 | const no_comment_line = split_it.first(); | ||
| 3247 | |||
| 3248 | var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t"); | ||
| 3249 | const ip_text = line_it.next() orelse continue; | ||
| 3250 | var first_name_text: ?[]const u8 = null; | ||
| 3251 | while (line_it.next()) |name_text| { | ||
| 3252 | if (std.mem.eql(u8, name_text, host_name.bytes)) { | ||
| 3253 | if (first_name_text == null) first_name_text = name_text; | ||
| 3254 | break; | ||
| 3255 | } | ||
| 3256 | } else continue; | ||
| 3257 | |||
| 3258 | if (canonical_name == null) { | ||
| 3259 | if (HostName.init(first_name_text.?)) |name_text| { | ||
| 3260 | if (name_text.bytes.len <= options.canonical_name_buffer.len) { | ||
| 3261 | const canonical_name_dest = options.canonical_name_buffer[0..name_text.bytes.len]; | ||
| 3262 | @memcpy(canonical_name_dest, name_text.bytes); | ||
| 3263 | canonical_name = .{ .bytes = canonical_name_dest }; | ||
| 3264 | } | ||
| 3265 | } else |_| {} | ||
| 3266 | } | ||
| 3267 | |||
| 3268 | if (options.family != .ip6) { | ||
| 3269 | if (IpAddress.parseIp4(ip_text, options.port)) |addr| { | ||
| 3270 | try resolved.putOne(pool_io, .{ .address = addr }); | ||
| 3271 | addresses_len += 1; | ||
| 3272 | } else |_| {} | ||
| 3273 | } | ||
| 3274 | if (options.family != .ip4) { | ||
| 3275 | if (IpAddress.parseIp6(ip_text, options.port)) |addr| { | ||
| 3276 | try resolved.putOne(pool_io, .{ .address = addr }); | ||
| 3277 | addresses_len += 1; | ||
| 3278 | } else |_| {} | ||
| 3279 | } | ||
| 3280 | } | ||
| 3281 | |||
| 3282 | if (canonical_name) |canon_name| try resolved.putOne(pool_io, .{ .canonical_name = canon_name }); | ||
| 3283 | if (addresses_len == 0) return error.UnknownHostName; | ||
| 3284 | } | ||
| 3285 | |||
| 3286 | /// Writes DNS resolution query packet data to `w`; at most 280 bytes. | ||
| 3287 | fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: u8, entropy: [2]u8) usize { | ||
| 3288 | // This implementation is ported from musl libc. | ||
| 3289 | // A more idiomatic "ziggy" implementation would be welcome. | ||
| 3290 | var name = dname; | ||
| 3291 | if (std.mem.endsWith(u8, name, ".")) name.len -= 1; | ||
| 3292 | assert(name.len <= 253); | ||
| 3293 | const n = 17 + name.len + @intFromBool(name.len != 0); | ||
| 3294 | |||
| 3295 | // Construct query template - ID will be filled later | ||
| 3296 | q[0..2].* = entropy; | ||
| 3297 | @memset(q[2..n], 0); | ||
| 3298 | q[2] = @as(u8, op) * 8 + 1; | ||
| 3299 | q[5] = 1; | ||
| 3300 | @memcpy(q[13..][0..name.len], name); | ||
| 3301 | var i: usize = 13; | ||
| 3302 | var j: usize = undefined; | ||
| 3303 | while (q[i] != 0) : (i = j + 1) { | ||
| 3304 | j = i; | ||
| 3305 | while (q[j] != 0 and q[j] != '.') : (j += 1) {} | ||
| 3306 | // TODO determine the circumstances for this and whether or | ||
| 3307 | // not this should be an error. | ||
| 3308 | if (j - i - 1 > 62) unreachable; | ||
| 3309 | q[i - 1] = @intCast(j - i); | ||
| 3310 | } | ||
| 3311 | q[i + 1] = ty; | ||
| 3312 | q[i + 3] = class; | ||
| 3313 | return n; | ||
| 3314 | } | ||
| 3315 | |||
| 3316 | fn copyCanon(canonical_name_buffer: *[HostName.max_len]u8, name: []const u8) HostName { | ||
| 3317 | const dest = canonical_name_buffer[0..name.len]; | ||
| 3318 | @memcpy(dest, name); | ||
| 3319 | return .{ .bytes = dest }; | ||
| 3320 | } | ||
| 3321 | |||
| 3322 | pub fn futexWait(ptr: *const std.atomic.Value(u32), expect: u32) void { | ||
| 3323 | @branchHint(.cold); | ||
| 3324 | |||
| 3325 | if (native_os == .linux) { | ||
| 3326 | const linux = std.os.linux; | ||
| 3327 | const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); | ||
| 3328 | if (builtin.mode == .Debug) switch (linux.E.init(rc)) { | ||
| 3329 | .SUCCESS => {}, // notified by `wake()` | ||
| 3330 | .INTR => {}, // gives caller a chance to check cancellation | ||
| 3331 | .AGAIN => {}, // ptr.* != expect | ||
| 3332 | .INVAL => {}, // possibly timeout overflow | ||
| 3333 | .TIMEDOUT => unreachable, | ||
| 3334 | .FAULT => unreachable, // ptr was invalid | ||
| 3335 | else => unreachable, | ||
| 3336 | }; | ||
| 3337 | return; | ||
| 3338 | } | ||
| 3339 | |||
| 3340 | @compileError("TODO"); | ||
| 3341 | } | ||
| 3342 | |||
| 3343 | pub fn futexWaitDuration(ptr: *const std.atomic.Value(u32), expect: u32, timeout: Io.Duration) void { | ||
| 3344 | @branchHint(.cold); | ||
| 3345 | |||
| 3346 | if (native_os == .linux) { | ||
| 3347 | const linux = std.os.linux; | ||
| 3348 | var ts = timestampToPosix(timeout.toNanoseconds()); | ||
| 3349 | const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, &ts); | ||
| 3350 | if (builtin.mode == .Debug) switch (linux.E.init(rc)) { | ||
| 3351 | .SUCCESS => {}, // notified by `wake()` | ||
| 3352 | .INTR => {}, // gives caller a chance to check cancellation | ||
| 3353 | .AGAIN => {}, // ptr.* != expect | ||
| 3354 | .TIMEDOUT => {}, | ||
| 3355 | .INVAL => {}, // possibly timeout overflow | ||
| 3356 | .FAULT => unreachable, // ptr was invalid | ||
| 3357 | else => unreachable, | ||
| 3358 | }; | ||
| 3359 | return; | ||
| 3360 | } | ||
| 3361 | |||
| 3362 | @compileError("TODO"); | ||
| 3363 | } | ||
| 3364 | |||
| 3365 | pub fn futexWake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void { | ||
| 3366 | @branchHint(.cold); | ||
| 3367 | |||
| 3368 | if (native_os == .linux) { | ||
| 3369 | const linux = std.os.linux; | ||
| 3370 | const rc = linux.futex_3arg( | ||
| 3371 | &ptr.raw, | ||
| 3372 | .{ .cmd = .WAKE, .private = true }, | ||
| 3373 | @min(max_waiters, std.math.maxInt(i32)), | ||
| 3374 | ); | ||
| 3375 | if (builtin.mode == .Debug) switch (linux.E.init(rc)) { | ||
| 3376 | .SUCCESS => {}, // successful wake up | ||
| 3377 | .INVAL => {}, // invalid futex_wait() on ptr done elsewhere | ||
| 3378 | .FAULT => {}, // pointer became invalid while doing the wake | ||
| 3379 | else => unreachable, | ||
| 3380 | }; | ||
| 3381 | return; | ||
| 3382 | } | ||
| 3383 | |||
| 3384 | @compileError("TODO"); | ||
| 3385 | } |
lib/std/Io/net.zig+2-3| ... | @@ -281,7 +281,6 @@ pub const IpAddress = union(enum) { | ... | @@ -281,7 +281,6 @@ pub const IpAddress = union(enum) { |
| 281 | } | 281 | } |
| 282 | 282 | ||
| 283 | pub const ConnectError = error{ | 283 | pub const ConnectError = error{ |
| 284 | AddressInUse, | ||
| 285 | AddressUnavailable, | 284 | AddressUnavailable, |
| 286 | AddressFamilyUnsupported, | 285 | AddressFamilyUnsupported, |
| 287 | /// Insufficient memory or other resource internal to the operating system. | 286 | /// Insufficient memory or other resource internal to the operating system. |
| ... | @@ -291,7 +290,7 @@ pub const IpAddress = union(enum) { | ... | @@ -291,7 +290,7 @@ pub const IpAddress = union(enum) { |
| 291 | ConnectionResetByPeer, | 290 | ConnectionResetByPeer, |
| 292 | HostUnreachable, | 291 | HostUnreachable, |
| 293 | NetworkUnreachable, | 292 | NetworkUnreachable, |
| 294 | ConnectionTimedOut, | 293 | Timeout, |
| 295 | /// One of the `ConnectOptions` is not supported by the Io | 294 | /// One of the `ConnectOptions` is not supported by the Io |
| 296 | /// implementation. | 295 | /// implementation. |
| 297 | OptionUnsupported, | 296 | OptionUnsupported, |
| ... | @@ -1165,7 +1164,7 @@ pub const Stream = struct { | ... | @@ -1165,7 +1164,7 @@ pub const Stream = struct { |
| 1165 | SystemResources, | 1164 | SystemResources, |
| 1166 | BrokenPipe, | 1165 | BrokenPipe, |
| 1167 | ConnectionResetByPeer, | 1166 | ConnectionResetByPeer, |
| 1168 | ConnectionTimedOut, | 1167 | Timeout, |
| 1169 | SocketUnconnected, | 1168 | SocketUnconnected, |
| 1170 | /// The file descriptor does not hold the required rights to read | 1169 | /// The file descriptor does not hold the required rights to read |
| 1171 | /// from it. | 1170 | /// from it. |
lib/std/Io/net/HostName.zig+48-504| ... | @@ -63,8 +63,6 @@ pub fn eql(a: HostName, b: HostName) bool { | ... | @@ -63,8 +63,6 @@ pub fn eql(a: HostName, b: HostName) bool { |
| 63 | 63 | ||
| 64 | pub const LookupOptions = struct { | 64 | pub const LookupOptions = struct { |
| 65 | port: u16, | 65 | port: u16, |
| 66 | /// Must have at least length 2. | ||
| 67 | addresses_buffer: []IpAddress, | ||
| 68 | canonical_name_buffer: *[max_len]u8, | 66 | canonical_name_buffer: *[max_len]u8, |
| 69 | /// `null` means either. | 67 | /// `null` means either. |
| 70 | family: ?IpAddress.Family = null, | 68 | family: ?IpAddress.Family = null, |
| ... | @@ -81,487 +79,23 @@ pub const LookupError = error{ | ... | @@ -81,487 +79,23 @@ pub const LookupError = error{ |
| 81 | DetectingNetworkConfigurationFailed, | 79 | DetectingNetworkConfigurationFailed, |
| 82 | } || Io.Clock.Error || IpAddress.BindError || Io.Cancelable; | 80 | } || Io.Clock.Error || IpAddress.BindError || Io.Cancelable; |
| 83 | 81 | ||
| 84 | pub const LookupResult = struct { | 82 | pub const LookupResult = union(enum) { |
| 85 | /// How many `LookupOptions.addresses_buffer` elements are populated. | 83 | address: IpAddress, |
| 86 | addresses_len: usize, | ||
| 87 | canonical_name: HostName, | 84 | canonical_name: HostName, |
| 88 | 85 | end: LookupError!void, | |
| 89 | pub const empty: LookupResult = .{ | ||
| 90 | .addresses_len = 0, | ||
| 91 | .canonical_name = undefined, | ||
| 92 | }; | ||
| 93 | }; | 86 | }; |
| 94 | 87 | ||
| 95 | pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult { | 88 | /// Adds any number of `IpAddress` into resolved, exactly one canonical_name, |
| 96 | const name = host_name.bytes; | 89 | /// and then always finishes by adding one `LookupResult.end` entry. |
| 97 | assert(name.len <= max_len); | 90 | /// |
| 98 | assert(options.addresses_buffer.len >= 2); | 91 | /// Guaranteed not to block if provided queue has capacity at least 8. |
| 99 | 92 | pub fn lookup( | |
| 100 | if (native_os == .windows) @compileError("TODO"); | 93 | host_name: HostName, |
| 101 | if (builtin.link_libc) @compileError("TODO"); | 94 | io: Io, |
| 102 | if (native_os == .linux) { | 95 | resolved: *Io.Queue(LookupResult), |
| 103 | if (options.family != .ip6) { | 96 | options: LookupOptions, |
| 104 | if (IpAddress.parseIp4(name, options.port)) |addr| { | 97 | ) void { |
| 105 | options.addresses_buffer[0] = addr; | 98 | return io.vtable.netLookup(io.userdata, host_name, resolved, options); |
| 106 | return .{ .addresses_len = 1, .canonical_name = copyCanon(options.canonical_name_buffer, name) }; | ||
| 107 | } else |_| {} | ||
| 108 | } | ||
| 109 | if (options.family != .ip4) { | ||
| 110 | if (IpAddress.parseIp6(name, options.port)) |addr| { | ||
| 111 | options.addresses_buffer[0] = addr; | ||
| 112 | return .{ .addresses_len = 1, .canonical_name = copyCanon(options.canonical_name_buffer, name) }; | ||
| 113 | } else |_| {} | ||
| 114 | } | ||
| 115 | { | ||
| 116 | const result = try lookupHosts(host_name, io, options); | ||
| 117 | if (result.addresses_len > 0) return sortLookupResults(options, result); | ||
| 118 | } | ||
| 119 | { | ||
| 120 | // RFC 6761 Section 6.3.3 | ||
| 121 | // Name resolution APIs and libraries SHOULD recognize | ||
| 122 | // localhost names as special and SHOULD always return the IP | ||
| 123 | // loopback address for address queries and negative responses | ||
| 124 | // for all other query types. | ||
| 125 | |||
| 126 | // Check for equal to "localhost(.)" or ends in ".localhost(.)" | ||
| 127 | const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost"; | ||
| 128 | if (std.mem.endsWith(u8, name, localhost) and | ||
| 129 | (name.len == localhost.len or name[name.len - localhost.len] == '.')) | ||
| 130 | { | ||
| 131 | var i: usize = 0; | ||
| 132 | if (options.family != .ip6) { | ||
| 133 | options.addresses_buffer[i] = .{ .ip4 = .loopback(options.port) }; | ||
| 134 | i += 1; | ||
| 135 | } | ||
| 136 | if (options.family != .ip4) { | ||
| 137 | options.addresses_buffer[i] = .{ .ip6 = .loopback(options.port) }; | ||
| 138 | i += 1; | ||
| 139 | } | ||
| 140 | const canon_name = "localhost"; | ||
| 141 | const canon_name_dest = options.canonical_name_buffer[0..canon_name.len]; | ||
| 142 | canon_name_dest.* = canon_name.*; | ||
| 143 | return sortLookupResults(options, .{ | ||
| 144 | .addresses_len = i, | ||
| 145 | .canonical_name = .{ .bytes = canon_name_dest }, | ||
| 146 | }); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | { | ||
| 150 | const result = try lookupDnsSearch(host_name, io, options); | ||
| 151 | if (result.addresses_len > 0) return sortLookupResults(options, result); | ||
| 152 | } | ||
| 153 | return error.UnknownHostName; | ||
| 154 | } | ||
| 155 | @compileError("unimplemented"); | ||
| 156 | } | ||
| 157 | |||
| 158 | fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult { | ||
| 159 | const addresses = options.addresses_buffer[0..result.addresses_len]; | ||
| 160 | // No further processing is needed if there are fewer than 2 results or | ||
| 161 | // if there are only IPv4 results. | ||
| 162 | if (addresses.len < 2) return result; | ||
| 163 | const all_ip4 = for (addresses) |a| switch (a) { | ||
| 164 | .ip4 => continue, | ||
| 165 | .ip6 => break false, | ||
| 166 | } else true; | ||
| 167 | if (all_ip4) return result; | ||
| 168 | |||
| 169 | // RFC 3484/6724 describes how destination address selection is | ||
| 170 | // supposed to work. However, to implement it requires making a bunch | ||
| 171 | // of networking syscalls, which is unnecessarily high latency, | ||
| 172 | // especially if implemented serially. Furthermore, rules 3, 4, and 7 | ||
| 173 | // have excessive runtime and code size cost and dubious benefit. | ||
| 174 | // | ||
| 175 | // Therefore, this logic sorts only using values available without | ||
| 176 | // doing any syscalls, relying on the calling code to have a | ||
| 177 | // meta-strategy such as attempting connection to multiple results at | ||
| 178 | // once and keeping the fastest response while canceling the others. | ||
| 179 | |||
| 180 | const S = struct { | ||
| 181 | pub fn lessThan(s: @This(), lhs: IpAddress, rhs: IpAddress) bool { | ||
| 182 | return sortKey(s, lhs) < sortKey(s, rhs); | ||
| 183 | } | ||
| 184 | |||
| 185 | fn sortKey(s: @This(), a: IpAddress) i32 { | ||
| 186 | _ = s; | ||
| 187 | var da6: Ip6Address = .{ | ||
| 188 | .port = 65535, | ||
| 189 | .bytes = undefined, | ||
| 190 | }; | ||
| 191 | switch (a) { | ||
| 192 | .ip6 => |ip6| { | ||
| 193 | da6.bytes = ip6.bytes; | ||
| 194 | da6.interface = ip6.interface; | ||
| 195 | }, | ||
| 196 | .ip4 => |ip4| { | ||
| 197 | da6.bytes[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*; | ||
| 198 | da6.bytes[12..].* = ip4.bytes; | ||
| 199 | }, | ||
| 200 | } | ||
| 201 | const da6_scope: i32 = da6.scope(); | ||
| 202 | const da6_prec: i32 = da6.policy().prec; | ||
| 203 | var key: i32 = 0; | ||
| 204 | key |= da6_prec << 20; | ||
| 205 | key |= (15 - da6_scope) << 16; | ||
| 206 | return key; | ||
| 207 | } | ||
| 208 | }; | ||
| 209 | std.mem.sort(IpAddress, addresses, @as(S, .{}), S.lessThan); | ||
| 210 | return result; | ||
| 211 | } | ||
| 212 | |||
| 213 | fn lookupDnsSearch(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult { | ||
| 214 | const rc = ResolvConf.init(io) catch return error.ResolvConfParseFailed; | ||
| 215 | |||
| 216 | // Count dots, suppress search when >=ndots or name ends in | ||
| 217 | // a dot, which is an explicit request for global scope. | ||
| 218 | const dots = std.mem.countScalar(u8, host_name.bytes, '.'); | ||
| 219 | const search_len = if (dots >= rc.ndots or std.mem.endsWith(u8, host_name.bytes, ".")) 0 else rc.search_len; | ||
| 220 | const search = rc.search_buffer[0..search_len]; | ||
| 221 | |||
| 222 | var canon_name = host_name.bytes; | ||
| 223 | |||
| 224 | // Strip final dot for canon, fail if multiple trailing dots. | ||
| 225 | if (std.mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1; | ||
| 226 | if (std.mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName; | ||
| 227 | |||
| 228 | // Name with search domain appended is set up in `canon_name`. This | ||
| 229 | // both provides the desired default canonical name (if the requested | ||
| 230 | // name is not a CNAME record) and serves as a buffer for passing the | ||
| 231 | // full requested name to `lookupDns`. | ||
| 232 | @memcpy(options.canonical_name_buffer[0..canon_name.len], canon_name); | ||
| 233 | options.canonical_name_buffer[canon_name.len] = '.'; | ||
| 234 | var it = std.mem.tokenizeAny(u8, search, " \t"); | ||
| 235 | while (it.next()) |token| { | ||
| 236 | @memcpy(options.canonical_name_buffer[canon_name.len + 1 ..][0..token.len], token); | ||
| 237 | const lookup_canon_name = options.canonical_name_buffer[0 .. canon_name.len + 1 + token.len]; | ||
| 238 | const result = try lookupDns(io, lookup_canon_name, &rc, options); | ||
| 239 | if (result.addresses_len > 0) return sortLookupResults(options, result); | ||
| 240 | } | ||
| 241 | |||
| 242 | const lookup_canon_name = options.canonical_name_buffer[0..canon_name.len]; | ||
| 243 | return lookupDns(io, lookup_canon_name, &rc, options); | ||
| 244 | } | ||
| 245 | |||
| 246 | fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, options: LookupOptions) LookupError!LookupResult { | ||
| 247 | const family_records: [2]struct { af: IpAddress.Family, rr: u8 } = .{ | ||
| 248 | .{ .af = .ip6, .rr = std.posix.RR.A }, | ||
| 249 | .{ .af = .ip4, .rr = std.posix.RR.AAAA }, | ||
| 250 | }; | ||
| 251 | var query_buffers: [2][280]u8 = undefined; | ||
| 252 | var answer_buffer: [2 * 512]u8 = undefined; | ||
| 253 | var queries_buffer: [2][]const u8 = undefined; | ||
| 254 | var answers_buffer: [2][]const u8 = undefined; | ||
| 255 | var nq: usize = 0; | ||
| 256 | var answer_buffer_i: usize = 0; | ||
| 257 | |||
| 258 | for (family_records) |fr| { | ||
| 259 | if (options.family != fr.af) { | ||
| 260 | const entropy = std.crypto.random.array(u8, 2); | ||
| 261 | const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy); | ||
| 262 | queries_buffer[nq] = query_buffers[nq][0..len]; | ||
| 263 | nq += 1; | ||
| 264 | } | ||
| 265 | } | ||
| 266 | |||
| 267 | var ip4_mapped: [ResolvConf.max_nameservers]IpAddress = undefined; | ||
| 268 | var any_ip6 = false; | ||
| 269 | for (rc.nameservers(), &ip4_mapped) |*ns, *m| { | ||
| 270 | m.* = .{ .ip6 = .fromAny(ns.*) }; | ||
| 271 | any_ip6 = any_ip6 or ns.* == .ip6; | ||
| 272 | } | ||
| 273 | var socket = s: { | ||
| 274 | if (any_ip6) ip6: { | ||
| 275 | const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) }; | ||
| 276 | const socket = ip6_addr.bind(io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) { | ||
| 277 | error.AddressFamilyUnsupported => break :ip6, | ||
| 278 | else => |e| return e, | ||
| 279 | }; | ||
| 280 | break :s socket; | ||
| 281 | } | ||
| 282 | any_ip6 = false; | ||
| 283 | const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) }; | ||
| 284 | const socket = try ip4_addr.bind(io, .{ .mode = .dgram }); | ||
| 285 | break :s socket; | ||
| 286 | }; | ||
| 287 | defer socket.close(io); | ||
| 288 | |||
| 289 | const mapped_nameservers = if (any_ip6) ip4_mapped[0..rc.nameservers_len] else rc.nameservers(); | ||
| 290 | const queries = queries_buffer[0..nq]; | ||
| 291 | const answers = answers_buffer[0..queries.len]; | ||
| 292 | var answers_remaining = answers.len; | ||
| 293 | for (answers) |*answer| answer.len = 0; | ||
| 294 | |||
| 295 | // boot clock is chosen because time the computer is suspended should count | ||
| 296 | // against time spent waiting for external messages to arrive. | ||
| 297 | const clock: Io.Clock = .boot; | ||
| 298 | var now_ts = try clock.now(io); | ||
| 299 | const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds)); | ||
| 300 | const attempt_duration: Io.Duration = .{ | ||
| 301 | .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts, | ||
| 302 | }; | ||
| 303 | |||
| 304 | send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = try clock.now(io)) { | ||
| 305 | const max_messages = queries_buffer.len * ResolvConf.max_nameservers; | ||
| 306 | { | ||
| 307 | var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined; | ||
| 308 | var message_i: usize = 0; | ||
| 309 | for (queries, answers) |query, *answer| { | ||
| 310 | if (answer.len != 0) continue; | ||
| 311 | for (mapped_nameservers) |*ns| { | ||
| 312 | message_buffer[message_i] = .{ | ||
| 313 | .address = ns, | ||
| 314 | .data_ptr = query.ptr, | ||
| 315 | .data_len = query.len, | ||
| 316 | }; | ||
| 317 | message_i += 1; | ||
| 318 | } | ||
| 319 | } | ||
| 320 | _ = io.vtable.netSend(io.userdata, socket.handle, message_buffer[0..message_i], .{}); | ||
| 321 | } | ||
| 322 | |||
| 323 | const timeout: Io.Timeout = .{ .deadline = .{ | ||
| 324 | .raw = now_ts.addDuration(attempt_duration), | ||
| 325 | .clock = clock, | ||
| 326 | } }; | ||
| 327 | |||
| 328 | while (true) { | ||
| 329 | var message_buffer: [max_messages]Io.net.IncomingMessage = undefined; | ||
| 330 | const buf = answer_buffer[answer_buffer_i..]; | ||
| 331 | const recv_err, const recv_n = socket.receiveManyTimeout(io, &message_buffer, buf, .{}, timeout); | ||
| 332 | for (message_buffer[0..recv_n]) |*received_message| { | ||
| 333 | const reply = received_message.data; | ||
| 334 | // Ignore non-identifiable packets. | ||
| 335 | if (reply.len < 4) continue; | ||
| 336 | |||
| 337 | // Ignore replies from addresses we didn't send to. | ||
| 338 | const ns = for (mapped_nameservers) |*ns| { | ||
| 339 | if (received_message.from.eql(ns)) break ns; | ||
| 340 | } else { | ||
| 341 | continue; | ||
| 342 | }; | ||
| 343 | |||
| 344 | // Find which query this answer goes with, if any. | ||
| 345 | const query, const answer = for (queries, answers) |query, *answer| { | ||
| 346 | if (reply[0] == query[0] and reply[1] == query[1]) break .{ query, answer }; | ||
| 347 | } else { | ||
| 348 | continue; | ||
| 349 | }; | ||
| 350 | if (answer.len != 0) continue; | ||
| 351 | |||
| 352 | // Only accept positive or negative responses; retry immediately on | ||
| 353 | // server failure, and ignore all other codes such as refusal. | ||
| 354 | switch (reply[3] & 15) { | ||
| 355 | 0, 3 => { | ||
| 356 | answer.* = reply; | ||
| 357 | answer_buffer_i += reply.len; | ||
| 358 | answers_remaining -= 1; | ||
| 359 | if (answer_buffer.len - answer_buffer_i == 0) break :send; | ||
| 360 | if (answers_remaining == 0) break :send; | ||
| 361 | }, | ||
| 362 | 2 => { | ||
| 363 | var retry_message: Io.net.OutgoingMessage = .{ | ||
| 364 | .address = ns, | ||
| 365 | .data_ptr = query.ptr, | ||
| 366 | .data_len = query.len, | ||
| 367 | }; | ||
| 368 | _ = io.vtable.netSend(io.userdata, socket.handle, (&retry_message)[0..1], .{}); | ||
| 369 | continue; | ||
| 370 | }, | ||
| 371 | else => continue, | ||
| 372 | } | ||
| 373 | } | ||
| 374 | if (recv_err) |err| switch (err) { | ||
| 375 | error.Canceled => return error.Canceled, | ||
| 376 | error.Timeout => continue :send, | ||
| 377 | else => continue, | ||
| 378 | }; | ||
| 379 | } | ||
| 380 | } else { | ||
| 381 | return error.NameServerFailure; | ||
| 382 | } | ||
| 383 | |||
| 384 | var addresses_len: usize = 0; | ||
| 385 | var canonical_name: ?HostName = null; | ||
| 386 | |||
| 387 | for (answers) |answer| { | ||
| 388 | var it = DnsResponse.init(answer) catch { | ||
| 389 | // TODO accept a diagnostics struct and append warnings | ||
| 390 | continue; | ||
| 391 | }; | ||
| 392 | while (it.next() catch { | ||
| 393 | // TODO accept a diagnostics struct and append warnings | ||
| 394 | continue; | ||
| 395 | }) |record| switch (record.rr) { | ||
| 396 | std.posix.RR.A => { | ||
| 397 | const data = record.packet[record.data_off..][0..record.data_len]; | ||
| 398 | if (data.len != 4) return error.InvalidDnsARecord; | ||
| 399 | if (addresses_len < options.addresses_buffer.len) { | ||
| 400 | options.addresses_buffer[addresses_len] = .{ .ip4 = .{ | ||
| 401 | .bytes = data[0..4].*, | ||
| 402 | .port = options.port, | ||
| 403 | } }; | ||
| 404 | addresses_len += 1; | ||
| 405 | } | ||
| 406 | }, | ||
| 407 | std.posix.RR.AAAA => { | ||
| 408 | const data = record.packet[record.data_off..][0..record.data_len]; | ||
| 409 | if (data.len != 16) return error.InvalidDnsAAAARecord; | ||
| 410 | if (addresses_len < options.addresses_buffer.len) { | ||
| 411 | options.addresses_buffer[addresses_len] = .{ .ip6 = .{ | ||
| 412 | .bytes = data[0..16].*, | ||
| 413 | .port = options.port, | ||
| 414 | } }; | ||
| 415 | addresses_len += 1; | ||
| 416 | } | ||
| 417 | }, | ||
| 418 | std.posix.RR.CNAME => { | ||
| 419 | _, canonical_name = expand(record.packet, record.data_off, options.canonical_name_buffer) catch | ||
| 420 | return error.InvalidDnsCnameRecord; | ||
| 421 | }, | ||
| 422 | else => continue, | ||
| 423 | }; | ||
| 424 | } | ||
| 425 | |||
| 426 | if (addresses_len != 0) return .{ | ||
| 427 | .addresses_len = addresses_len, | ||
| 428 | .canonical_name = canonical_name orelse .{ .bytes = lookup_canon_name }, | ||
| 429 | }; | ||
| 430 | |||
| 431 | return error.NameServerFailure; | ||
| 432 | } | ||
| 433 | |||
| 434 | fn lookupHosts(host_name: HostName, io: Io, options: LookupOptions) !LookupResult { | ||
| 435 | const file = Io.File.openAbsolute(io, "/etc/hosts", .{}) catch |err| switch (err) { | ||
| 436 | error.FileNotFound, | ||
| 437 | error.NotDir, | ||
| 438 | error.AccessDenied, | ||
| 439 | => return .empty, | ||
| 440 | |||
| 441 | error.Canceled => |e| return e, | ||
| 442 | |||
| 443 | else => { | ||
| 444 | // TODO populate optional diagnostic struct | ||
| 445 | return error.DetectingNetworkConfigurationFailed; | ||
| 446 | }, | ||
| 447 | }; | ||
| 448 | defer file.close(io); | ||
| 449 | |||
| 450 | var line_buf: [512]u8 = undefined; | ||
| 451 | var file_reader = file.reader(io, &line_buf); | ||
| 452 | return lookupHostsReader(host_name, options, &file_reader.interface) catch |err| switch (err) { | ||
| 453 | error.ReadFailed => switch (file_reader.err.?) { | ||
| 454 | error.Canceled => |e| return e, | ||
| 455 | else => { | ||
| 456 | // TODO populate optional diagnostic struct | ||
| 457 | return error.DetectingNetworkConfigurationFailed; | ||
| 458 | }, | ||
| 459 | }, | ||
| 460 | }; | ||
| 461 | } | ||
| 462 | |||
| 463 | fn lookupHostsReader(host_name: HostName, options: LookupOptions, reader: *Io.Reader) error{ReadFailed}!LookupResult { | ||
| 464 | var addresses_len: usize = 0; | ||
| 465 | var canonical_name: ?HostName = null; | ||
| 466 | while (true) { | ||
| 467 | const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) { | ||
| 468 | error.StreamTooLong => { | ||
| 469 | // Skip lines that are too long. | ||
| 470 | _ = reader.discardDelimiterInclusive('\n') catch |e| switch (e) { | ||
| 471 | error.EndOfStream => break, | ||
| 472 | error.ReadFailed => return error.ReadFailed, | ||
| 473 | }; | ||
| 474 | continue; | ||
| 475 | }, | ||
| 476 | error.ReadFailed => return error.ReadFailed, | ||
| 477 | error.EndOfStream => break, | ||
| 478 | }; | ||
| 479 | reader.toss(1); | ||
| 480 | var split_it = std.mem.splitScalar(u8, line, '#'); | ||
| 481 | const no_comment_line = split_it.first(); | ||
| 482 | |||
| 483 | var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t"); | ||
| 484 | const ip_text = line_it.next() orelse continue; | ||
| 485 | var first_name_text: ?[]const u8 = null; | ||
| 486 | while (line_it.next()) |name_text| { | ||
| 487 | if (std.mem.eql(u8, name_text, host_name.bytes)) { | ||
| 488 | if (first_name_text == null) first_name_text = name_text; | ||
| 489 | break; | ||
| 490 | } | ||
| 491 | } else continue; | ||
| 492 | |||
| 493 | if (canonical_name == null) { | ||
| 494 | if (HostName.init(first_name_text.?)) |name_text| { | ||
| 495 | if (name_text.bytes.len <= options.canonical_name_buffer.len) { | ||
| 496 | const canonical_name_dest = options.canonical_name_buffer[0..name_text.bytes.len]; | ||
| 497 | @memcpy(canonical_name_dest, name_text.bytes); | ||
| 498 | canonical_name = .{ .bytes = canonical_name_dest }; | ||
| 499 | } | ||
| 500 | } else |_| {} | ||
| 501 | } | ||
| 502 | |||
| 503 | if (options.family != .ip6) { | ||
| 504 | if (IpAddress.parseIp4(ip_text, options.port)) |addr| { | ||
| 505 | options.addresses_buffer[addresses_len] = addr; | ||
| 506 | addresses_len += 1; | ||
| 507 | if (options.addresses_buffer.len - addresses_len == 0) return .{ | ||
| 508 | .addresses_len = addresses_len, | ||
| 509 | .canonical_name = canonical_name orelse copyCanon(options.canonical_name_buffer, ip_text), | ||
| 510 | }; | ||
| 511 | } else |_| {} | ||
| 512 | } | ||
| 513 | if (options.family != .ip4) { | ||
| 514 | if (IpAddress.parseIp6(ip_text, options.port)) |addr| { | ||
| 515 | options.addresses_buffer[addresses_len] = addr; | ||
| 516 | addresses_len += 1; | ||
| 517 | if (options.addresses_buffer.len - addresses_len == 0) return .{ | ||
| 518 | .addresses_len = addresses_len, | ||
| 519 | .canonical_name = canonical_name orelse copyCanon(options.canonical_name_buffer, ip_text), | ||
| 520 | }; | ||
| 521 | } else |_| {} | ||
| 522 | } | ||
| 523 | } | ||
| 524 | if (canonical_name == null) assert(addresses_len == 0); | ||
| 525 | return .{ | ||
| 526 | .addresses_len = addresses_len, | ||
| 527 | .canonical_name = canonical_name orelse undefined, | ||
| 528 | }; | ||
| 529 | } | ||
| 530 | |||
| 531 | fn copyCanon(canonical_name_buffer: *[max_len]u8, name: []const u8) HostName { | ||
| 532 | const dest = canonical_name_buffer[0..name.len]; | ||
| 533 | @memcpy(dest, name); | ||
| 534 | return .{ .bytes = dest }; | ||
| 535 | } | ||
| 536 | |||
| 537 | /// Writes DNS resolution query packet data to `w`; at most 280 bytes. | ||
| 538 | fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: u8, entropy: [2]u8) usize { | ||
| 539 | // This implementation is ported from musl libc. | ||
| 540 | // A more idiomatic "ziggy" implementation would be welcome. | ||
| 541 | var name = dname; | ||
| 542 | if (std.mem.endsWith(u8, name, ".")) name.len -= 1; | ||
| 543 | assert(name.len <= 253); | ||
| 544 | const n = 17 + name.len + @intFromBool(name.len != 0); | ||
| 545 | |||
| 546 | // Construct query template - ID will be filled later | ||
| 547 | q[0..2].* = entropy; | ||
| 548 | @memset(q[2..n], 0); | ||
| 549 | q[2] = @as(u8, op) * 8 + 1; | ||
| 550 | q[5] = 1; | ||
| 551 | @memcpy(q[13..][0..name.len], name); | ||
| 552 | var i: usize = 13; | ||
| 553 | var j: usize = undefined; | ||
| 554 | while (q[i] != 0) : (i = j + 1) { | ||
| 555 | j = i; | ||
| 556 | while (q[j] != 0 and q[j] != '.') : (j += 1) {} | ||
| 557 | // TODO determine the circumstances for this and whether or | ||
| 558 | // not this should be an error. | ||
| 559 | if (j - i - 1 > 62) unreachable; | ||
| 560 | q[i - 1] = @intCast(j - i); | ||
| 561 | } | ||
| 562 | q[i + 1] = ty; | ||
| 563 | q[i + 3] = class; | ||
| 564 | return n; | ||
| 565 | } | 99 | } |
| 566 | 100 | ||
| 567 | pub const ExpandError = error{InvalidDnsPacket} || ValidateError; | 101 | pub const ExpandError = error{InvalidDnsPacket} || ValidateError; |
| ... | @@ -672,33 +206,43 @@ pub fn connect( | ... | @@ -672,33 +206,43 @@ pub fn connect( |
| 672 | port: u16, | 206 | port: u16, |
| 673 | options: IpAddress.ConnectOptions, | 207 | options: IpAddress.ConnectOptions, |
| 674 | ) ConnectError!Stream { | 208 | ) ConnectError!Stream { |
| 675 | var addresses_buffer: [32]IpAddress = undefined; | 209 | var canonical_name_buffer: [max_len]u8 = undefined; |
| 676 | var canonical_name_buffer: [HostName.max_len]u8 = undefined; | 210 | var results_buffer: [32]HostName.LookupResult = undefined; |
| 211 | var results: Io.Queue(LookupResult) = .init(&results_buffer); | ||
| 677 | 212 | ||
| 678 | const results = try lookup(host_name, io, .{ | 213 | var lookup_task = io.async(HostName.lookup, .{ host_name, io, &results, .{ |
| 679 | .port = port, | 214 | .port = port, |
| 680 | .addresses_buffer = &addresses_buffer, | ||
| 681 | .canonical_name_buffer = &canonical_name_buffer, | 215 | .canonical_name_buffer = &canonical_name_buffer, |
| 682 | }); | 216 | } }); |
| 683 | const addresses = addresses_buffer[0..results.addresses_len]; | 217 | defer lookup_task.cancel(io); |
| 684 | 218 | ||
| 685 | if (addresses.len == 0) return error.UnknownHostName; | 219 | var select: Io.Select(union(enum) { ip_connect: IpAddress.ConnectError!Stream }) = .init; |
| 220 | defer select.cancel(io); | ||
| 221 | |||
| 222 | while (results.getOne(io)) |result| switch (result) { | ||
| 223 | .address => |address| select.async(io, .ip_connect, IpAddress.connect, .{ address, io, options }), | ||
| 224 | .canonical_name => continue, | ||
| 225 | .end => |lookup_result| { | ||
| 226 | try lookup_result; | ||
| 227 | break; | ||
| 228 | }, | ||
| 229 | } else |err| return err; | ||
| 686 | 230 | ||
| 687 | // TODO instead of serially, use a Select API to send out | 231 | var aggregate_error: ConnectError = error.UnknownHostName; |
| 688 | // the connections simultaneously and then keep the first | ||
| 689 | // successful one, canceling the rest. | ||
| 690 | 232 | ||
| 691 | // TODO On Linux this should additionally use an Io.Queue based | 233 | while (select.remaining != 0) switch (select.wait(io)) { |
| 692 | // DNS resolution API in order to send out a connection after | 234 | .ip_connect => |ip_connect| if (ip_connect) |stream| return stream else |err| switch (err) { |
| 693 | // each DNS response before waiting for the rest of them. | 235 | error.SystemResources => |e| return e, |
| 236 | error.OptionUnsupported => |e| return e, | ||
| 237 | error.ProcessFdQuotaExceeded => |e| return e, | ||
| 238 | error.SystemFdQuotaExceeded => |e| return e, | ||
| 239 | error.Canceled => |e| return e, | ||
| 240 | error.WouldBlock => return error.Unexpected, | ||
| 241 | else => |e| aggregate_error = e, | ||
| 242 | }, | ||
| 243 | }; | ||
| 694 | 244 | ||
| 695 | for (addresses) |*addr| { | 245 | return aggregate_error; |
| 696 | return addr.connect(io, options) catch |err| switch (err) { | ||
| 697 | error.ConnectionRefused => continue, | ||
| 698 | else => |e| return e, | ||
| 699 | }; | ||
| 700 | } | ||
| 701 | return error.ConnectionRefused; | ||
| 702 | } | 246 | } |
| 703 | 247 | ||
| 704 | pub const ResolvConf = struct { | 248 | pub const ResolvConf = struct { |
| ... | @@ -713,7 +257,7 @@ pub const ResolvConf = struct { | ... | @@ -713,7 +257,7 @@ pub const ResolvConf = struct { |
| 713 | pub const max_nameservers = 3; | 257 | pub const max_nameservers = 3; |
| 714 | 258 | ||
| 715 | /// Returns `error.StreamTooLong` if a line is longer than 512 bytes. | 259 | /// Returns `error.StreamTooLong` if a line is longer than 512 bytes. |
| 716 | fn init(io: Io) !ResolvConf { | 260 | pub fn init(io: Io) !ResolvConf { |
| 717 | var rc: ResolvConf = .{ | 261 | var rc: ResolvConf = .{ |
| 718 | .nameservers_buffer = undefined, | 262 | .nameservers_buffer = undefined, |
| 719 | .nameservers_len = 0, | 263 | .nameservers_len = 0, |
| ... | @@ -749,7 +293,7 @@ pub const ResolvConf = struct { | ... | @@ -749,7 +293,7 @@ pub const ResolvConf = struct { |
| 749 | const Directive = enum { options, nameserver, domain, search }; | 293 | const Directive = enum { options, nameserver, domain, search }; |
| 750 | const Option = enum { ndots, attempts, timeout }; | 294 | const Option = enum { ndots, attempts, timeout }; |
| 751 | 295 | ||
| 752 | fn parse(rc: *ResolvConf, io: Io, reader: *Io.Reader) !void { | 296 | pub fn parse(rc: *ResolvConf, io: Io, reader: *Io.Reader) !void { |
| 753 | while (reader.takeSentinel('\n')) |line_with_comment| { | 297 | while (reader.takeSentinel('\n')) |line_with_comment| { |
| 754 | const line = line: { | 298 | const line = line: { |
| 755 | var split = std.mem.splitScalar(u8, line_with_comment, '#'); | 299 | var split = std.mem.splitScalar(u8, line_with_comment, '#'); |
| ... | @@ -799,7 +343,7 @@ pub const ResolvConf = struct { | ... | @@ -799,7 +343,7 @@ pub const ResolvConf = struct { |
| 799 | rc.nameservers_len += 1; | 343 | rc.nameservers_len += 1; |
| 800 | } | 344 | } |
| 801 | 345 | ||
| 802 | fn nameservers(rc: *const ResolvConf) []const IpAddress { | 346 | pub fn nameservers(rc: *const ResolvConf) []const IpAddress { |
| 803 | return rc.nameservers_buffer[0..rc.nameservers_len]; | 347 | return rc.nameservers_buffer[0..rc.nameservers_len]; |
| 804 | } | 348 | } |
| 805 | }; | 349 | }; |
lib/std/posix.zig+15-15| ... | @@ -845,7 +845,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { | ... | @@ -845,7 +845,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { |
| 845 | .NOMEM => return error.SystemResources, | 845 | .NOMEM => return error.SystemResources, |
| 846 | .NOTCONN => return error.SocketUnconnected, | 846 | .NOTCONN => return error.SocketUnconnected, |
| 847 | .CONNRESET => return error.ConnectionResetByPeer, | 847 | .CONNRESET => return error.ConnectionResetByPeer, |
| 848 | .TIMEDOUT => return error.ConnectionTimedOut, | 848 | .TIMEDOUT => return error.Timeout, |
| 849 | .NOTCAPABLE => return error.AccessDenied, | 849 | .NOTCAPABLE => return error.AccessDenied, |
| 850 | else => |err| return unexpectedErrno(err), | 850 | else => |err| return unexpectedErrno(err), |
| 851 | } | 851 | } |
| ... | @@ -874,7 +874,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { | ... | @@ -874,7 +874,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { |
| 874 | .NOMEM => return error.SystemResources, | 874 | .NOMEM => return error.SystemResources, |
| 875 | .NOTCONN => return error.SocketUnconnected, | 875 | .NOTCONN => return error.SocketUnconnected, |
| 876 | .CONNRESET => return error.ConnectionResetByPeer, | 876 | .CONNRESET => return error.ConnectionResetByPeer, |
| 877 | .TIMEDOUT => return error.ConnectionTimedOut, | 877 | .TIMEDOUT => return error.Timeout, |
| 878 | else => |err| return unexpectedErrno(err), | 878 | else => |err| return unexpectedErrno(err), |
| 879 | } | 879 | } |
| 880 | } | 880 | } |
| ... | @@ -914,7 +914,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { | ... | @@ -914,7 +914,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { |
| 914 | .NOMEM => return error.SystemResources, | 914 | .NOMEM => return error.SystemResources, |
| 915 | .NOTCONN => return error.SocketUnconnected, | 915 | .NOTCONN => return error.SocketUnconnected, |
| 916 | .CONNRESET => return error.ConnectionResetByPeer, | 916 | .CONNRESET => return error.ConnectionResetByPeer, |
| 917 | .TIMEDOUT => return error.ConnectionTimedOut, | 917 | .TIMEDOUT => return error.Timeout, |
| 918 | .NOTCAPABLE => return error.AccessDenied, | 918 | .NOTCAPABLE => return error.AccessDenied, |
| 919 | else => |err| return unexpectedErrno(err), | 919 | else => |err| return unexpectedErrno(err), |
| 920 | } | 920 | } |
| ... | @@ -936,7 +936,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { | ... | @@ -936,7 +936,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { |
| 936 | .NOMEM => return error.SystemResources, | 936 | .NOMEM => return error.SystemResources, |
| 937 | .NOTCONN => return error.SocketUnconnected, | 937 | .NOTCONN => return error.SocketUnconnected, |
| 938 | .CONNRESET => return error.ConnectionResetByPeer, | 938 | .CONNRESET => return error.ConnectionResetByPeer, |
| 939 | .TIMEDOUT => return error.ConnectionTimedOut, | 939 | .TIMEDOUT => return error.Timeout, |
| 940 | else => |err| return unexpectedErrno(err), | 940 | else => |err| return unexpectedErrno(err), |
| 941 | } | 941 | } |
| 942 | } | 942 | } |
| ... | @@ -983,7 +983,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { | ... | @@ -983,7 +983,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { |
| 983 | .NOMEM => return error.SystemResources, | 983 | .NOMEM => return error.SystemResources, |
| 984 | .NOTCONN => return error.SocketUnconnected, | 984 | .NOTCONN => return error.SocketUnconnected, |
| 985 | .CONNRESET => return error.ConnectionResetByPeer, | 985 | .CONNRESET => return error.ConnectionResetByPeer, |
| 986 | .TIMEDOUT => return error.ConnectionTimedOut, | 986 | .TIMEDOUT => return error.Timeout, |
| 987 | .NXIO => return error.Unseekable, | 987 | .NXIO => return error.Unseekable, |
| 988 | .SPIPE => return error.Unseekable, | 988 | .SPIPE => return error.Unseekable, |
| 989 | .OVERFLOW => return error.Unseekable, | 989 | .OVERFLOW => return error.Unseekable, |
| ... | @@ -1016,7 +1016,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { | ... | @@ -1016,7 +1016,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { |
| 1016 | .NOMEM => return error.SystemResources, | 1016 | .NOMEM => return error.SystemResources, |
| 1017 | .NOTCONN => return error.SocketUnconnected, | 1017 | .NOTCONN => return error.SocketUnconnected, |
| 1018 | .CONNRESET => return error.ConnectionResetByPeer, | 1018 | .CONNRESET => return error.ConnectionResetByPeer, |
| 1019 | .TIMEDOUT => return error.ConnectionTimedOut, | 1019 | .TIMEDOUT => return error.Timeout, |
| 1020 | .NXIO => return error.Unseekable, | 1020 | .NXIO => return error.Unseekable, |
| 1021 | .SPIPE => return error.Unseekable, | 1021 | .SPIPE => return error.Unseekable, |
| 1022 | .OVERFLOW => return error.Unseekable, | 1022 | .OVERFLOW => return error.Unseekable, |
| ... | @@ -1134,7 +1134,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { | ... | @@ -1134,7 +1134,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { |
| 1134 | .NOMEM => return error.SystemResources, | 1134 | .NOMEM => return error.SystemResources, |
| 1135 | .NOTCONN => return error.SocketUnconnected, | 1135 | .NOTCONN => return error.SocketUnconnected, |
| 1136 | .CONNRESET => return error.ConnectionResetByPeer, | 1136 | .CONNRESET => return error.ConnectionResetByPeer, |
| 1137 | .TIMEDOUT => return error.ConnectionTimedOut, | 1137 | .TIMEDOUT => return error.Timeout, |
| 1138 | .NXIO => return error.Unseekable, | 1138 | .NXIO => return error.Unseekable, |
| 1139 | .SPIPE => return error.Unseekable, | 1139 | .SPIPE => return error.Unseekable, |
| 1140 | .OVERFLOW => return error.Unseekable, | 1140 | .OVERFLOW => return error.Unseekable, |
| ... | @@ -1160,7 +1160,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { | ... | @@ -1160,7 +1160,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { |
| 1160 | .NOMEM => return error.SystemResources, | 1160 | .NOMEM => return error.SystemResources, |
| 1161 | .NOTCONN => return error.SocketUnconnected, | 1161 | .NOTCONN => return error.SocketUnconnected, |
| 1162 | .CONNRESET => return error.ConnectionResetByPeer, | 1162 | .CONNRESET => return error.ConnectionResetByPeer, |
| 1163 | .TIMEDOUT => return error.ConnectionTimedOut, | 1163 | .TIMEDOUT => return error.Timeout, |
| 1164 | .NXIO => return error.Unseekable, | 1164 | .NXIO => return error.Unseekable, |
| 1165 | .SPIPE => return error.Unseekable, | 1165 | .SPIPE => return error.Unseekable, |
| 1166 | .OVERFLOW => return error.Unseekable, | 1166 | .OVERFLOW => return error.Unseekable, |
| ... | @@ -4205,7 +4205,7 @@ pub const ConnectError = error{ | ... | @@ -4205,7 +4205,7 @@ pub const ConnectError = error{ |
| 4205 | 4205 | ||
| 4206 | /// Timeout while attempting connection. The server may be too busy to accept new connections. Note | 4206 | /// Timeout while attempting connection. The server may be too busy to accept new connections. Note |
| 4207 | /// that for IP sockets the timeout may be very long when syncookies are enabled on the server. | 4207 | /// that for IP sockets the timeout may be very long when syncookies are enabled on the server. |
| 4208 | ConnectionTimedOut, | 4208 | Timeout, |
| 4209 | 4209 | ||
| 4210 | /// This error occurs when no global event loop is configured, | 4210 | /// This error occurs when no global event loop is configured, |
| 4211 | /// and connecting to the socket would block. | 4211 | /// and connecting to the socket would block. |
| ... | @@ -4236,7 +4236,7 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne | ... | @@ -4236,7 +4236,7 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne |
| 4236 | .WSAEADDRNOTAVAIL => return error.AddressNotAvailable, | 4236 | .WSAEADDRNOTAVAIL => return error.AddressNotAvailable, |
| 4237 | .WSAECONNREFUSED => return error.ConnectionRefused, | 4237 | .WSAECONNREFUSED => return error.ConnectionRefused, |
| 4238 | .WSAECONNRESET => return error.ConnectionResetByPeer, | 4238 | .WSAECONNRESET => return error.ConnectionResetByPeer, |
| 4239 | .WSAETIMEDOUT => return error.ConnectionTimedOut, | 4239 | .WSAETIMEDOUT => return error.Timeout, |
| 4240 | .WSAEHOSTUNREACH, // TODO: should we return NetworkUnreachable in this case as well? | 4240 | .WSAEHOSTUNREACH, // TODO: should we return NetworkUnreachable in this case as well? |
| 4241 | .WSAENETUNREACH, | 4241 | .WSAENETUNREACH, |
| 4242 | => return error.NetworkUnreachable, | 4242 | => return error.NetworkUnreachable, |
| ... | @@ -4273,7 +4273,7 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne | ... | @@ -4273,7 +4273,7 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne |
| 4273 | .NETUNREACH => return error.NetworkUnreachable, | 4273 | .NETUNREACH => return error.NetworkUnreachable, |
| 4274 | .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. | 4274 | .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. |
| 4275 | .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. | 4275 | .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. |
| 4276 | .TIMEDOUT => return error.ConnectionTimedOut, | 4276 | .TIMEDOUT => return error.Timeout, |
| 4277 | .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist. | 4277 | .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist. |
| 4278 | .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused. | 4278 | .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused. |
| 4279 | else => |err| return unexpectedErrno(err), | 4279 | else => |err| return unexpectedErrno(err), |
| ... | @@ -4333,7 +4333,7 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void { | ... | @@ -4333,7 +4333,7 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void { |
| 4333 | .NETUNREACH => return error.NetworkUnreachable, | 4333 | .NETUNREACH => return error.NetworkUnreachable, |
| 4334 | .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. | 4334 | .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. |
| 4335 | .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. | 4335 | .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. |
| 4336 | .TIMEDOUT => return error.ConnectionTimedOut, | 4336 | .TIMEDOUT => return error.Timeout, |
| 4337 | .CONNRESET => return error.ConnectionResetByPeer, | 4337 | .CONNRESET => return error.ConnectionResetByPeer, |
| 4338 | else => |err| return unexpectedErrno(err), | 4338 | else => |err| return unexpectedErrno(err), |
| 4339 | }, | 4339 | }, |
| ... | @@ -6465,7 +6465,7 @@ pub const RecvFromError = error{ | ... | @@ -6465,7 +6465,7 @@ pub const RecvFromError = error{ |
| 6465 | SystemResources, | 6465 | SystemResources, |
| 6466 | 6466 | ||
| 6467 | ConnectionResetByPeer, | 6467 | ConnectionResetByPeer, |
| 6468 | ConnectionTimedOut, | 6468 | Timeout, |
| 6469 | 6469 | ||
| 6470 | /// The socket has not been bound. | 6470 | /// The socket has not been bound. |
| 6471 | SocketNotBound, | 6471 | SocketNotBound, |
| ... | @@ -6508,7 +6508,7 @@ pub fn recvfrom( | ... | @@ -6508,7 +6508,7 @@ pub fn recvfrom( |
| 6508 | .WSAENETDOWN => return error.NetworkDown, | 6508 | .WSAENETDOWN => return error.NetworkDown, |
| 6509 | .WSAENOTCONN => return error.SocketUnconnected, | 6509 | .WSAENOTCONN => return error.SocketUnconnected, |
| 6510 | .WSAEWOULDBLOCK => return error.WouldBlock, | 6510 | .WSAEWOULDBLOCK => return error.WouldBlock, |
| 6511 | .WSAETIMEDOUT => return error.ConnectionTimedOut, | 6511 | .WSAETIMEDOUT => return error.Timeout, |
| 6512 | // TODO: handle more errors | 6512 | // TODO: handle more errors |
| 6513 | else => |err| return windows.unexpectedWSAError(err), | 6513 | else => |err| return windows.unexpectedWSAError(err), |
| 6514 | } | 6514 | } |
| ... | @@ -6528,7 +6528,7 @@ pub fn recvfrom( | ... | @@ -6528,7 +6528,7 @@ pub fn recvfrom( |
| 6528 | .NOMEM => return error.SystemResources, | 6528 | .NOMEM => return error.SystemResources, |
| 6529 | .CONNREFUSED => return error.ConnectionRefused, | 6529 | .CONNREFUSED => return error.ConnectionRefused, |
| 6530 | .CONNRESET => return error.ConnectionResetByPeer, | 6530 | .CONNRESET => return error.ConnectionResetByPeer, |
| 6531 | .TIMEDOUT => return error.ConnectionTimedOut, | 6531 | .TIMEDOUT => return error.Timeout, |
| 6532 | .PIPE => return error.BrokenPipe, | 6532 | .PIPE => return error.BrokenPipe, |
| 6533 | else => |err| return unexpectedErrno(err), | 6533 | else => |err| return unexpectedErrno(err), |
| 6534 | } | 6534 | } |
lib/std/zig/system.zig+1-1| ... | @@ -428,7 +428,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { | ... | @@ -428,7 +428,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { |
| 428 | error.WouldBlock => return error.Unexpected, | 428 | error.WouldBlock => return error.Unexpected, |
| 429 | error.BrokenPipe => return error.Unexpected, | 429 | error.BrokenPipe => return error.Unexpected, |
| 430 | error.ConnectionResetByPeer => return error.Unexpected, | 430 | error.ConnectionResetByPeer => return error.Unexpected, |
| 431 | error.ConnectionTimedOut => return error.Unexpected, | 431 | error.Timeout => return error.Unexpected, |
| 432 | error.NotOpenForReading => return error.Unexpected, | 432 | error.NotOpenForReading => return error.Unexpected, |
| 433 | error.SocketUnconnected => return error.Unexpected, | 433 | error.SocketUnconnected => return error.Unexpected, |
| 434 | 434 |