commit 61d5a0bf48d034208aea37d72dac5b3531334be7 (tree)
parent 6a15e8a7a771bcbf2534cceecd77231344aafbf8
Author: Andrew Kelley <andrew@ziglang.org>
Date: Wed, 30 Oct 2019 21:30:16 -0400
Merge branch 'std.net'
Diffstat:
30 files changed, 2840 insertions(+), 856 deletions(-)
diff --git a/lib/std/buffer.zig b/lib/std/buffer.zig
@@ -72,11 +72,11 @@ pub const Buffer = struct {
self.list.deinit();
}
- pub fn toSlice(self: *const Buffer) []u8 {
+ pub fn toSlice(self: Buffer) []u8 {
return self.list.toSlice()[0..self.len()];
}
- pub fn toSliceConst(self: *const Buffer) []const u8 {
+ pub fn toSliceConst(self: Buffer) []const u8 {
return self.list.toSliceConst()[0..self.len()];
}
@@ -91,11 +91,11 @@ pub const Buffer = struct {
self.list.items[self.len()] = 0;
}
- pub fn isNull(self: *const Buffer) bool {
+ pub fn isNull(self: Buffer) bool {
return self.list.len == 0;
}
- pub fn len(self: *const Buffer) usize {
+ pub fn len(self: Buffer) usize {
return self.list.len - 1;
}
@@ -111,16 +111,16 @@ pub const Buffer = struct {
self.list.toSlice()[old_len] = byte;
}
- pub fn eql(self: *const Buffer, m: []const u8) bool {
+ pub fn eql(self: Buffer, m: []const u8) bool {
return mem.eql(u8, self.toSliceConst(), m);
}
- pub fn startsWith(self: *const Buffer, m: []const u8) bool {
+ pub fn startsWith(self: Buffer, m: []const u8) bool {
if (self.len() < m.len) return false;
return mem.eql(u8, self.list.items[0..m.len], m);
}
- pub fn endsWith(self: *const Buffer, m: []const u8) bool {
+ pub fn endsWith(self: Buffer, m: []const u8) bool {
const l = self.len();
if (l < m.len) return false;
const start = l - m.len;
@@ -133,7 +133,7 @@ pub const Buffer = struct {
}
/// For passing to C functions.
- pub fn ptr(self: *const Buffer) [*]u8 {
+ pub fn ptr(self: Buffer) [*]u8 {
return self.list.items.ptr;
}
};
diff --git a/lib/std/c.zig b/lib/std/c.zig
@@ -117,6 +117,26 @@ pub extern "c" fn getsockname(sockfd: fd_t, noalias addr: *sockaddr, noalias add
pub extern "c" fn connect(sockfd: fd_t, sock_addr: *const sockaddr, addrlen: socklen_t) c_int;
pub extern "c" fn accept4(sockfd: fd_t, addr: *sockaddr, addrlen: *socklen_t, flags: c_uint) c_int;
pub extern "c" fn getsockopt(sockfd: fd_t, level: c_int, optname: c_int, optval: *c_void, optlen: *socklen_t) c_int;
+pub extern "c" fn send(sockfd: fd_t, buf: *const c_void, len: usize, flags: u32) isize;
+pub extern "c" fn sendto(
+ sockfd: fd_t,
+ buf: *const c_void,
+ len: usize,
+ flags: u32,
+ dest_addr: *const sockaddr,
+ addrlen: socklen_t,
+) isize;
+
+pub extern fn recv(sockfd: fd_t, arg1: ?*c_void, arg2: usize, arg3: c_int) isize;
+pub extern fn recvfrom(
+ sockfd: fd_t,
+ noalias buf: *c_void,
+ len: usize,
+ flags: u32,
+ noalias src_addr: ?*sockaddr,
+ noalias addrlen: ?*socklen_t,
+) isize;
+
pub extern "c" fn kill(pid: pid_t, sig: c_int) c_int;
pub extern "c" fn getdirentries(fd: fd_t, buf_ptr: [*]u8, nbytes: usize, basep: *i64) isize;
pub extern "c" fn setgid(ruid: c_uint, euid: c_uint) c_int;
@@ -149,3 +169,34 @@ pub extern "c" fn kevent(
nevents: c_int,
timeout: ?*const timespec,
) c_int;
+
+pub extern "c" fn getaddrinfo(
+ noalias node: [*]const u8,
+ noalias service: [*]const u8,
+ noalias hints: *const addrinfo,
+ noalias res: **addrinfo,
+) c_int;
+
+pub extern "c" fn freeaddrinfo(res: *addrinfo) void;
+
+pub extern "c" fn getnameinfo(
+ noalias addr: *const sockaddr,
+ addrlen: socklen_t,
+ noalias host: [*]u8,
+ hostlen: socklen_t,
+ noalias serv: [*]u8,
+ servlen: socklen_t,
+ flags: u32,
+) c_int;
+
+pub extern "c" fn gai_strerror(errcode: c_int) [*]const u8;
+
+pub extern "c" fn poll(fds: [*]pollfd, nfds: nfds_t, timeout: c_int) c_int;
+
+pub extern "c" fn dn_expand(
+ msg: [*]const u8,
+ eomorig: [*]const u8,
+ comp_dn: [*]const u8,
+ exp_dn: [*]u8,
+ length: c_int,
+) c_int;
diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig
@@ -56,3 +56,58 @@ pub fn sigaddset(set: *sigset_t, signo: u5) void {
}
pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;
+
+/// get address to use bind()
+pub const AI_PASSIVE = 0x00000001;
+
+/// fill ai_canonname
+pub const AI_CANONNAME = 0x00000002;
+
+/// prevent host name resolution
+pub const AI_NUMERICHOST = 0x00000004;
+
+/// prevent service name resolution
+pub const AI_NUMERICSERV = 0x00001000;
+
+/// address family for hostname not supported
+pub const EAI_ADDRFAMILY = 1;
+
+/// temporary failure in name resolution
+pub const EAI_AGAIN = 2;
+
+/// invalid value for ai_flags
+pub const EAI_BADFLAGS = 3;
+
+/// non-recoverable failure in name resolution
+pub const EAI_FAIL = 4;
+
+/// ai_family not supported
+pub const EAI_FAMILY = 5;
+
+/// memory allocation failure
+pub const EAI_MEMORY = 6;
+
+/// no address associated with hostname
+pub const EAI_NODATA = 7;
+
+/// hostname nor servname provided, or not known
+pub const EAI_NONAME = 8;
+
+/// servname not supported for ai_socktype
+pub const EAI_SERVICE = 9;
+
+/// ai_socktype not supported
+pub const EAI_SOCKTYPE = 10;
+
+/// system error returned in errno
+pub const EAI_SYSTEM = 11;
+
+/// invalid value for hints
+pub const EAI_BADHINTS = 12;
+
+/// resolved protocol is unknown
+pub const EAI_PROTOCOL = 13;
+
+/// argument buffer overflow
+pub const EAI_OVERFLOW = 14;
+pub const EAI_MAX = 15;
diff --git a/lib/std/c/linux.zig b/lib/std/c/linux.zig
@@ -17,6 +17,41 @@ pub const _errno = switch (builtin.abi) {
pub const MAP_FAILED = @intToPtr(*c_void, maxInt(usize));
+pub const AI_PASSIVE = 0x01;
+pub const AI_CANONNAME = 0x02;
+pub const AI_NUMERICHOST = 0x04;
+pub const AI_V4MAPPED = 0x08;
+pub const AI_ALL = 0x10;
+pub const AI_ADDRCONFIG = 0x20;
+pub const AI_NUMERICSERV = 0x400;
+
+pub const NI_NUMERICHOST = 0x01;
+pub const NI_NUMERICSERV = 0x02;
+pub const NI_NOFQDN = 0x04;
+pub const NI_NAMEREQD = 0x08;
+pub const NI_DGRAM = 0x10;
+pub const NI_NUMERICSCOPE = 0x100;
+
+pub const EAI_BADFLAGS = -1;
+pub const EAI_NONAME = -2;
+pub const EAI_AGAIN = -3;
+pub const EAI_FAIL = -4;
+pub const EAI_FAMILY = -6;
+pub const EAI_SOCKTYPE = -7;
+pub const EAI_SERVICE = -8;
+pub const EAI_MEMORY = -10;
+pub const EAI_SYSTEM = -11;
+pub const EAI_OVERFLOW = -12;
+
+pub const EAI_NODATA = -5;
+pub const EAI_ADDRFAMILY = -9;
+pub const EAI_INPROGRESS = -100;
+pub const EAI_CANCELED = -101;
+pub const EAI_NOTCANCELED = -102;
+pub const EAI_ALLDONE = -103;
+pub const EAI_INTR = -104;
+pub const EAI_IDN_ENCODE = -105;
+
pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize;
pub extern "c" fn sched_getaffinity(pid: c_int, size: usize, set: *cpu_set_t) c_int;
pub extern "c" fn eventfd(initval: c_uint, flags: c_uint) c_int;
diff --git a/lib/std/event.zig b/lib/std/event.zig
@@ -7,7 +7,6 @@ pub const RwLock = @import("event/rwlock.zig").RwLock;
pub const RwLocked = @import("event/rwlocked.zig").RwLocked;
pub const Loop = @import("event/loop.zig").Loop;
pub const fs = @import("event/fs.zig");
-pub const net = @import("event/net.zig");
test "import event tests" {
_ = @import("event/channel.zig");
@@ -19,5 +18,4 @@ test "import event tests" {
_ = @import("event/rwlock.zig");
_ = @import("event/rwlocked.zig");
_ = @import("event/loop.zig");
- _ = @import("event/net.zig");
}
diff --git a/lib/std/event/channel.zig b/lib/std/event/channel.zig
@@ -4,9 +4,11 @@ const assert = std.debug.assert;
const testing = std.testing;
const Loop = std.event.Loop;
-/// many producer, many consumer, thread-safe, runtime configurable buffer size
-/// when buffer is empty, consumers suspend and are resumed by producers
-/// when buffer is full, producers suspend and are resumed by consumers
+/// Many producer, many consumer, thread-safe, runtime configurable buffer size.
+/// When buffer is empty, consumers suspend and are resumed by producers.
+/// When buffer is full, producers suspend and are resumed by consumers.
+/// TODO now that async function rewrite has landed, this API should be adjusted
+/// to not use the event loop's allocator, and to not require allocation.
pub fn Channel(comptime T: type) type {
return struct {
loop: *Loop,
@@ -48,7 +50,7 @@ pub fn Channel(comptime T: type) type {
tick_node: *Loop.NextTickNode,
};
- /// call destroy when done
+ /// Call `destroy` when done.
pub fn create(loop: *Loop, capacity: usize) !*SelfChannel {
const buffer_nodes = try loop.allocator.alloc(T, capacity);
errdefer loop.allocator.free(buffer_nodes);
diff --git a/lib/std/event/loop.zig b/lib/std/event/loop.zig
@@ -448,22 +448,67 @@ pub const Loop = struct {
self.finishOneEvent();
}
- pub fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void {
- defer self.linuxRemoveFd(fd);
+ pub fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) void {
+ assert(flags & os.EPOLLET == os.EPOLLET);
+ assert(flags & os.EPOLLONESHOT == os.EPOLLONESHOT);
+ var resume_node = ResumeNode.Basic{
+ .base = ResumeNode{
+ .id = .Basic,
+ .handle = @frame(),
+ .overlapped = ResumeNode.overlapped_init,
+ },
+ };
+ var need_to_delete = false;
+ defer if (need_to_delete) self.linuxRemoveFd(fd);
+
suspend {
- var resume_node = ResumeNode.Basic{
- .base = ResumeNode{
- .id = .Basic,
- .handle = @frame(),
- .overlapped = ResumeNode.overlapped_init,
+ if (self.linuxAddFd(fd, &resume_node.base, flags)) |_| {
+ need_to_delete = true;
+ } else |err| switch (err) {
+ error.FileDescriptorNotRegistered => unreachable,
+ error.OperationCausesCircularLoop => unreachable,
+ error.FileDescriptorIncompatibleWithEpoll => unreachable,
+ error.FileDescriptorAlreadyPresentInSet => unreachable, // evented writes to the same fd is not thread-safe
+
+ error.SystemResources,
+ error.UserResourceLimitReached,
+ error.Unexpected,
+ => {
+ // Fall back to a blocking poll(). Ideally this codepath is never hit, since
+ // epoll should be just fine. But this is better than incorrect behavior.
+ var poll_flags: i16 = 0;
+ if ((flags & os.EPOLLIN) != 0) poll_flags |= os.POLLIN;
+ if ((flags & os.EPOLLOUT) != 0) poll_flags |= os.POLLOUT;
+ var pfd = [1]os.pollfd{os.pollfd{
+ .fd = fd,
+ .events = poll_flags,
+ .revents = undefined,
+ }};
+ _ = os.poll(&pfd, -1) catch |poll_err| switch (poll_err) {
+ error.SystemResources,
+ error.Unexpected,
+ => {
+ // Even poll() didn't work. The best we can do now is sleep for a
+ // small duration and then hope that something changed.
+ std.time.sleep(1 * std.time.millisecond);
+ },
+ };
+ resume @frame();
},
- };
- try self.linuxAddFd(fd, &resume_node.base, flags);
+ }
}
}
- pub fn waitUntilFdReadable(self: *Loop, fd: os.fd_t) !void {
- return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLIN);
+ pub fn waitUntilFdReadable(self: *Loop, fd: os.fd_t) void {
+ return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLONESHOT | os.EPOLLIN);
+ }
+
+ pub fn waitUntilFdWritable(self: *Loop, fd: os.fd_t) void {
+ return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLONESHOT | os.EPOLLOUT);
+ }
+
+ pub fn waitUntilFdWritableOrReadable(self: *Loop, fd: os.fd_t) void {
+ return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLONESHOT | os.EPOLLOUT | os.EPOLLIN);
}
pub async fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, fflags: u32) !os.Kevent {
@@ -642,7 +687,7 @@ pub const Loop = struct {
.linux => {
self.posixFsRequest(&self.os_data.fs_end_request);
// writing 8 bytes to an eventfd cannot fail
- os.write(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
+ noasync os.write(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
return;
},
.macosx, .freebsd, .netbsd, .dragonfly => {
@@ -790,6 +835,8 @@ pub const Loop = struct {
}
}
+ // TODO make this whole function noasync
+ // https://github.com/ziglang/zig/issues/3157
fn posixFsRun(self: *Loop) void {
while (true) {
if (builtin.os == .linux) {
@@ -799,27 +846,27 @@ pub const Loop = struct {
switch (node.data.msg) {
.End => return,
.WriteV => |*msg| {
- msg.result = os.writev(msg.fd, msg.iov);
+ msg.result = noasync os.writev(msg.fd, msg.iov);
},
.PWriteV => |*msg| {
- msg.result = os.pwritev(msg.fd, msg.iov, msg.offset);
+ msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset);
},
.PReadV => |*msg| {
- msg.result = os.preadv(msg.fd, msg.iov, msg.offset);
+ msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset);
},
.Open => |*msg| {
- msg.result = os.openC(msg.path.ptr, msg.flags, msg.mode);
+ msg.result = noasync os.openC(msg.path.ptr, msg.flags, msg.mode);
},
- .Close => |*msg| os.close(msg.fd),
+ .Close => |*msg| noasync os.close(msg.fd),
.WriteFile => |*msg| blk: {
const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT |
os.O_CLOEXEC | os.O_TRUNC;
- const fd = os.openC(msg.path.ptr, flags, msg.mode) catch |err| {
+ const fd = noasync os.openC(msg.path.ptr, flags, msg.mode) catch |err| {
msg.result = err;
break :blk;
};
- defer os.close(fd);
- msg.result = os.write(fd, msg.contents);
+ defer noasync os.close(fd);
+ msg.result = noasync os.write(fd, msg.contents);
},
}
switch (node.data.finish) {
diff --git a/lib/std/event/net.zig b/lib/std/event/net.zig
@@ -1,358 +0,0 @@
-const std = @import("../std.zig");
-const builtin = @import("builtin");
-const testing = std.testing;
-const event = std.event;
-const mem = std.mem;
-const os = std.os;
-const Loop = std.event.Loop;
-const File = std.fs.File;
-const fd_t = os.fd_t;
-
-pub const Server = struct {
- handleRequestFn: async fn (*Server, *const std.net.Address, File) void,
-
- loop: *Loop,
- sockfd: ?i32,
- accept_frame: ?anyframe,
- listen_address: std.net.Address,
-
- waiting_for_emfile_node: PromiseNode,
- listen_resume_node: event.Loop.ResumeNode,
-
- const PromiseNode = std.TailQueue(anyframe).Node;
-
- pub fn init(loop: *Loop) Server {
- // TODO can't initialize handler here because we need well defined copy elision
- return Server{
- .loop = loop,
- .sockfd = null,
- .accept_frame = null,
- .handleRequestFn = undefined,
- .waiting_for_emfile_node = undefined,
- .listen_address = undefined,
- .listen_resume_node = event.Loop.ResumeNode{
- .id = event.Loop.ResumeNode.Id.Basic,
- .handle = undefined,
- .overlapped = event.Loop.ResumeNode.overlapped_init,
- },
- };
- }
-
- pub fn listen(
- self: *Server,
- address: *const std.net.Address,
- handleRequestFn: async fn (*Server, *const std.net.Address, File) void,
- ) !void {
- self.handleRequestFn = handleRequestFn;
-
- const sockfd = try os.socket(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK, os.PROTO_tcp);
- errdefer os.close(sockfd);
- self.sockfd = sockfd;
-
- try os.bind(sockfd, &address.os_addr);
- try os.listen(sockfd, os.SOMAXCONN);
- self.listen_address = std.net.Address.initPosix(try os.getsockname(sockfd));
-
- self.accept_frame = async Server.handler(self);
- errdefer await self.accept_frame.?;
-
- self.listen_resume_node.handle = self.accept_frame.?;
- try self.loop.linuxAddFd(sockfd, &self.listen_resume_node, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
- errdefer self.loop.removeFd(sockfd);
- }
-
- /// Stop listening
- pub fn close(self: *Server) void {
- self.loop.linuxRemoveFd(self.sockfd.?);
- if (self.sockfd) |fd| {
- os.close(fd);
- self.sockfd = null;
- }
- }
-
- pub fn deinit(self: *Server) void {
- if (self.accept_frame) |accept_frame| await accept_frame;
- if (self.sockfd) |sockfd| os.close(sockfd);
- }
-
- pub async fn handler(self: *Server) void {
- while (true) {
- var accepted_addr: std.net.Address = undefined;
- // TODO just inline the following function here and don't expose it as posixAsyncAccept
- if (os.accept4_async(self.sockfd.?, &accepted_addr.os_addr, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC)) |accepted_fd| {
- if (accepted_fd == -1) {
- // would block
- suspend; // we will get resumed by epoll_wait in the event loop
- continue;
- }
- var socket = File.openHandle(accepted_fd);
- self.handleRequestFn(self, &accepted_addr, socket);
- } else |err| switch (err) {
- error.ProcessFdQuotaExceeded => @panic("TODO handle this error"),
- error.ConnectionAborted => continue,
-
- error.FileDescriptorNotASocket => unreachable,
- error.OperationNotSupported => unreachable,
-
- error.SystemFdQuotaExceeded, error.SystemResources, error.ProtocolFailure, error.BlockedByFirewall, error.Unexpected => {
- @panic("TODO handle this error");
- },
- }
- }
- }
-};
-
-pub async fn connectUnixSocket(loop: *Loop, path: []const u8) !i32 {
- const sockfd = try os.socket(
- os.AF_UNIX,
- os.SOCK_STREAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK,
- 0,
- );
- errdefer os.close(sockfd);
-
- var sock_addr = os.sockaddr_un{
- .family = os.AF_UNIX,
- .path = undefined,
- };
-
- if (path.len > @typeOf(sock_addr.path).len) return error.NameTooLong;
- mem.copy(u8, sock_addr.path[0..], path);
- const size = @intCast(u32, @sizeOf(os.sa_family_t) + path.len);
- try os.connect_async(sockfd, &sock_addr, size);
- try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
- try os.getsockoptError(sockfd);
-
- return sockfd;
-}
-
-pub const ReadError = error{
- SystemResources,
- Unexpected,
- UserResourceLimitReached,
- InputOutput,
-
- FileDescriptorNotRegistered, // TODO remove this possibility
- OperationCausesCircularLoop, // TODO remove this possibility
- FileDescriptorAlreadyPresentInSet, // TODO remove this possibility
- FileDescriptorIncompatibleWithEpoll, // TODO remove this possibility
-};
-
-/// returns number of bytes read. 0 means EOF.
-pub async fn read(loop: *std.event.Loop, fd: fd_t, buffer: []u8) ReadError!usize {
- const iov = os.iovec{
- .iov_base = buffer.ptr,
- .iov_len = buffer.len,
- };
- const iovs: *const [1]os.iovec = &iov;
- return readvPosix(loop, fd, iovs, 1);
-}
-
-pub const WriteError = error{};
-
-pub async fn write(loop: *std.event.Loop, fd: fd_t, buffer: []const u8) WriteError!void {
- const iov = os.iovec_const{
- .iov_base = buffer.ptr,
- .iov_len = buffer.len,
- };
- const iovs: *const [1]os.iovec_const = &iov;
- return writevPosix(loop, fd, iovs, 1);
-}
-
-pub async fn writevPosix(loop: *Loop, fd: i32, iov: [*]const os.iovec_const, count: usize) !void {
- while (true) {
- switch (builtin.os) {
- .macosx, .linux => {
- switch (os.errno(os.system.writev(fd, iov, count))) {
- 0 => return,
- os.EINTR => continue,
- os.ESPIPE => unreachable,
- os.EINVAL => unreachable,
- os.EFAULT => unreachable,
- os.EAGAIN => {
- try loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLOUT);
- continue;
- },
- os.EBADF => unreachable, // always a race condition
- os.EDESTADDRREQ => unreachable, // connect was never called
- os.EDQUOT => unreachable,
- os.EFBIG => unreachable,
- os.EIO => return error.InputOutput,
- os.ENOSPC => unreachable,
- os.EPERM => return error.AccessDenied,
- os.EPIPE => unreachable,
- else => |err| return os.unexpectedErrno(err),
- }
- },
- else => @compileError("Unsupported OS"),
- }
- }
-}
-
-/// returns number of bytes read. 0 means EOF.
-pub async fn readvPosix(loop: *std.event.Loop, fd: i32, iov: [*]os.iovec, count: usize) !usize {
- while (true) {
- switch (builtin.os) {
- builtin.Os.linux, builtin.Os.freebsd, builtin.Os.macosx => {
- const rc = os.system.readv(fd, iov, count);
- switch (os.errno(rc)) {
- 0 => return rc,
- os.EINTR => continue,
- os.EINVAL => unreachable,
- os.EFAULT => unreachable,
- os.EAGAIN => {
- try loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLIN);
- continue;
- },
- os.EBADF => unreachable, // always a race condition
- os.EIO => return error.InputOutput,
- os.EISDIR => unreachable,
- os.ENOBUFS => return error.SystemResources,
- os.ENOMEM => return error.SystemResources,
- else => |err| return os.unexpectedErrno(err),
- }
- },
- else => @compileError("Unsupported OS"),
- }
- }
-}
-
-pub async fn writev(loop: *Loop, fd: fd_t, data: []const []const u8) !void {
- const iovecs = try loop.allocator.alloc(os.iovec_const, data.len);
- defer loop.allocator.free(iovecs);
-
- for (data) |buf, i| {
- iovecs[i] = os.iovec_const{
- .iov_base = buf.ptr,
- .iov_len = buf.len,
- };
- }
-
- return writevPosix(loop, fd, iovecs.ptr, data.len);
-}
-
-pub async fn readv(loop: *Loop, fd: fd_t, data: []const []u8) !usize {
- const iovecs = try loop.allocator.alloc(os.iovec, data.len);
- defer loop.allocator.free(iovecs);
-
- for (data) |buf, i| {
- iovecs[i] = os.iovec{
- .iov_base = buf.ptr,
- .iov_len = buf.len,
- };
- }
-
- return readvPosix(loop, fd, iovecs.ptr, data.len);
-}
-
-pub async fn connect(loop: *Loop, _address: *const std.net.Address) !File {
- var address = _address.*; // TODO https://github.com/ziglang/zig/issues/1592
-
- const sockfd = try os.socket(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK, os.PROTO_tcp);
- errdefer os.close(sockfd);
-
- try os.connect_async(sockfd, &address.os_addr, @sizeOf(os.sockaddr_in));
- try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
- try os.getsockoptError(sockfd);
-
- return File.openHandle(sockfd);
-}
-
-test "listen on a port, send bytes, receive bytes" {
- // https://github.com/ziglang/zig/issues/2377
- if (true) return error.SkipZigTest;
-
- if (builtin.os != builtin.Os.linux) {
- // TODO build abstractions for other operating systems
- return error.SkipZigTest;
- }
-
- const MyServer = struct {
- tcp_server: Server,
-
- const Self = @This();
- async fn handler(tcp_server: *Server, _addr: *const std.net.Address, _socket: File) void {
- const self = @fieldParentPtr(Self, "tcp_server", tcp_server);
- var socket = _socket; // TODO https://github.com/ziglang/zig/issues/1592
- defer socket.close();
- const next_handler = errorableHandler(self, _addr, socket) catch |err| {
- std.debug.panic("unable to handle connection: {}\n", err);
- };
- }
- async fn errorableHandler(self: *Self, _addr: *const std.net.Address, _socket: File) !void {
- const addr = _addr.*; // TODO https://github.com/ziglang/zig/issues/1592
- var socket = _socket; // TODO https://github.com/ziglang/zig/issues/1592
-
- const stream = &socket.outStream().stream;
- try stream.print("hello from server\n");
- }
- };
-
- const ip4addr = std.net.parseIp4("127.0.0.1") catch unreachable;
- const addr = std.net.Address.initIp4(ip4addr, 0);
-
- var loop: Loop = undefined;
- try loop.initSingleThreaded(std.debug.global_allocator);
- var server = MyServer{ .tcp_server = Server.init(&loop) };
- defer server.tcp_server.deinit();
- try server.tcp_server.listen(&addr, MyServer.handler);
-
- _ = async doAsyncTest(&loop, &server.tcp_server.listen_address, &server.tcp_server);
- loop.run();
-}
-
-async fn doAsyncTest(loop: *Loop, address: *const std.net.Address, server: *Server) void {
- errdefer @panic("test failure");
-
- var socket_file = try connect(loop, address);
- defer socket_file.close();
-
- var buf: [512]u8 = undefined;
- const amt_read = try socket_file.read(buf[0..]);
- const msg = buf[0..amt_read];
- testing.expect(mem.eql(u8, msg, "hello from server\n"));
- server.close();
-}
-
-pub const OutStream = struct {
- fd: fd_t,
- stream: Stream,
- loop: *Loop,
-
- pub const Error = WriteError;
- pub const Stream = event.io.OutStream(Error);
-
- pub fn init(loop: *Loop, fd: fd_t) OutStream {
- return OutStream{
- .fd = fd,
- .loop = loop,
- .stream = Stream{ .writeFn = writeFn },
- };
- }
-
- async fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
- const self = @fieldParentPtr(OutStream, "stream", out_stream);
- return write(self.loop, self.fd, bytes);
- }
-};
-
-pub const InStream = struct {
- fd: fd_t,
- stream: Stream,
- loop: *Loop,
-
- pub const Error = ReadError;
- pub const Stream = event.io.InStream(Error);
-
- pub fn init(loop: *Loop, fd: fd_t) InStream {
- return InStream{
- .fd = fd,
- .loop = loop,
- .stream = Stream{ .readFn = readFn },
- };
- }
-
- async fn readFn(in_stream: *Stream, bytes: []u8) Error!usize {
- const self = @fieldParentPtr(InStream, "stream", in_stream);
- return read(self.loop, self.fd, bytes);
- }
-};
diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig
@@ -53,7 +53,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool {
/// The format string must be comptime known and may contain placeholders following
/// this format:
/// `{[position][specifier]:[fill][alignment][width].[precision]}`
-///
+///
/// Each word between `[` and `]` is a parameter you have to replace with something:
///
/// - *position* is the index of the argument that should be inserted
@@ -78,7 +78,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool {
/// - `d`: output numeric value in decimal notation
/// - `b`: output integer value in binary notation
/// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max.
-/// - `*`: output the address of the value instead of the value itself.
+/// - `*`: output the address of the value instead of the value itself.
///
/// If a formatted user type contains a function of the type
/// ```
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
@@ -704,7 +704,7 @@ pub const Dir = struct {
/// Call `File.close` on the result when done.
pub fn openReadC(self: Dir, sub_path: [*]const u8) File.OpenError!File {
- const flags = os.O_LARGEFILE | os.O_RDONLY;
+ const flags = os.O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC;
const fd = try os.openatC(self.fd, sub_path, flags, 0);
return File.openHandle(fd);
}
diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig
@@ -41,7 +41,7 @@ pub const File = struct {
const path_w = try windows.cStrToPrefixedFileW(path);
return openReadW(&path_w);
}
- const flags = os.O_LARGEFILE | os.O_RDONLY;
+ const flags = os.O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC;
const fd = try os.openC(path, flags, 0);
return openHandle(fd);
}
diff --git a/lib/std/io.zig b/lib/std/io.zig
@@ -64,68 +64,7 @@ pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
pub const SliceSeekableInStream = @import("io/seekable_stream.zig").SliceSeekableInStream;
pub const COutStream = @import("io/c_out_stream.zig").COutStream;
pub const InStream = @import("io/in_stream.zig").InStream;
-
-pub fn OutStream(comptime WriteError: type) type {
- return struct {
- const Self = @This();
- pub const Error = WriteError;
-
- writeFn: fn (self: *Self, bytes: []const u8) Error!void,
-
- pub fn print(self: *Self, comptime format: []const u8, args: ...) Error!void {
- return std.fmt.format(self, Error, self.writeFn, format, args);
- }
-
- pub fn write(self: *Self, bytes: []const u8) Error!void {
- return self.writeFn(self, bytes);
- }
-
- pub fn writeByte(self: *Self, byte: u8) Error!void {
- const slice = (*const [1]u8)(&byte)[0..];
- return self.writeFn(self, slice);
- }
-
- pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void {
- const slice = (*const [1]u8)(&byte)[0..];
- var i: usize = 0;
- while (i < n) : (i += 1) {
- try self.writeFn(self, slice);
- }
- }
-
- /// Write a native-endian integer.
- pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void {
- var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
- mem.writeIntNative(T, &bytes, value);
- return self.writeFn(self, bytes);
- }
-
- /// Write a foreign-endian integer.
- pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void {
- var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
- mem.writeIntForeign(T, &bytes, value);
- return self.writeFn(self, bytes);
- }
-
- pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void {
- var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
- mem.writeIntLittle(T, &bytes, value);
- return self.writeFn(self, bytes);
- }
-
- pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void {
- var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
- mem.writeIntBig(T, &bytes, value);
- return self.writeFn(self, bytes);
- }
-
- pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void {
- var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
- mem.writeInt(T, &bytes, value, endian);
- return self.writeFn(self, bytes);
- }
- };
-}
+pub const OutStream = @import("io/out_stream.zig").OutStream;
/// TODO move this to `std.fs` and add a version to `std.fs.Dir`.
pub fn writeFile(path: []const u8, data: []const u8) !void {
diff --git a/lib/std/io/in_stream.zig b/lib/std/io/in_stream.zig
@@ -11,7 +11,6 @@ pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_InStream"))
root.stack_size_std_io_InStream
else
default_stack_size;
-pub const stack_align = 16;
pub fn InStream(comptime ReadError: type) type {
return struct {
@@ -34,7 +33,7 @@ pub fn InStream(comptime ReadError: type) type {
if (std.io.is_async) {
// Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream read.
@setRuntimeSafety(false);
- var stack_frame: [stack_size]u8 align(stack_align) = undefined;
+ var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
return await @asyncCall(&stack_frame, {}, self.readFn, self, buffer);
} else {
return self.readFn(self, buffer);
@@ -130,6 +129,47 @@ pub fn InStream(comptime ReadError: type) type {
return buf.toOwnedSlice();
}
+ /// Reads from the stream until specified byte is found. If the buffer is not
+ /// large enough to hold the entire contents, `error.StreamTooLong` is returned.
+ /// If end-of-stream is found, returns the rest of the stream. If this
+ /// function is called again after that, returns null.
+ /// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The
+ /// delimiter byte is not included in the returned slice.
+ pub fn readUntilDelimiterOrEof(self: *Self, buf: []u8, delimiter: u8) !?[]u8 {
+ var index: usize = 0;
+ while (true) {
+ const byte = self.readByte() catch |err| switch (err) {
+ error.EndOfStream => {
+ if (index == 0) {
+ return null;
+ } else {
+ return buf[0..index];
+ }
+ },
+ else => |e| return e,
+ };
+
+ if (byte == delimiter) return buf[0..index];
+ if (index >= buf.len) return error.StreamTooLong;
+
+ buf[index] = byte;
+ index += 1;
+ }
+ }
+
+ /// Reads from the stream until specified byte is found, discarding all data,
+ /// including the delimiter.
+ /// If end-of-stream is found, this function succeeds.
+ pub fn skipUntilDelimiterOrEof(self: *Self, delimiter: u8) !void {
+ while (true) {
+ const byte = self.readByte() catch |err| switch (err) {
+ error.EndOfStream => return,
+ else => |e| return e,
+ };
+ if (byte == delimiter) return;
+ }
+ }
+
/// Reads 1 byte from the stream or returns `error.EndOfStream`.
pub fn readByte(self: *Self) !u8 {
var result: [1]u8 = undefined;
diff --git a/lib/std/io/out_stream.zig b/lib/std/io/out_stream.zig
@@ -0,0 +1,87 @@
+const std = @import("../std.zig");
+const builtin = @import("builtin");
+const root = @import("root");
+const mem = std.mem;
+
+pub const default_stack_size = 1 * 1024 * 1024;
+pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream"))
+ root.stack_size_std_io_OutStream
+else
+ default_stack_size;
+
+/// TODO this is not integrated with evented I/O yet.
+/// https://github.com/ziglang/zig/issues/3557
+pub fn OutStream(comptime WriteError: type) type {
+ return struct {
+ const Self = @This();
+ pub const Error = WriteError;
+ // TODO https://github.com/ziglang/zig/issues/3557
+ pub const WriteFn = if (std.io.is_async and false)
+ async fn (self: *Self, bytes: []const u8) Error!void
+ else
+ fn (self: *Self, bytes: []const u8) Error!void;
+
+ writeFn: WriteFn,
+
+ pub fn write(self: *Self, bytes: []const u8) Error!void {
+ // TODO https://github.com/ziglang/zig/issues/3557
+ if (std.io.is_async and false) {
+ // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.
+ @setRuntimeSafety(false);
+ var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
+ return await @asyncCall(&stack_frame, {}, self.writeFn, self, bytes);
+ } else {
+ return self.writeFn(self, bytes);
+ }
+ }
+
+ pub fn print(self: *Self, comptime format: []const u8, args: ...) Error!void {
+ return std.fmt.format(self, Error, self.writeFn, format, args);
+ }
+
+ pub fn writeByte(self: *Self, byte: u8) Error!void {
+ const slice = (*const [1]u8)(&byte)[0..];
+ return self.writeFn(self, slice);
+ }
+
+ pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void {
+ const slice = (*const [1]u8)(&byte)[0..];
+ var i: usize = 0;
+ while (i < n) : (i += 1) {
+ try self.writeFn(self, slice);
+ }
+ }
+
+ /// Write a native-endian integer.
+ pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void {
+ var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+ mem.writeIntNative(T, &bytes, value);
+ return self.writeFn(self, bytes);
+ }
+
+ /// Write a foreign-endian integer.
+ pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void {
+ var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+ mem.writeIntForeign(T, &bytes, value);
+ return self.writeFn(self, bytes);
+ }
+
+ pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void {
+ var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+ mem.writeIntLittle(T, &bytes, value);
+ return self.writeFn(self, bytes);
+ }
+
+ pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void {
+ var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+ mem.writeIntBig(T, &bytes, value);
+ return self.writeFn(self, bytes);
+ }
+
+ pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void {
+ var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+ mem.writeInt(T, &bytes, value, endian);
+ return self.writeFn(self, bytes);
+ }
+ };
+}
diff --git a/lib/std/mem.zig b/lib/std/mem.zig
@@ -99,7 +99,7 @@ pub const Allocator = struct {
/// memory is no longer needed, to avoid a resource leak. If the
/// `Allocator` implementation is unknown, then correct code will
/// call `free` when done.
- ///
+ ///
/// For allocating a single item, see `create`.
pub fn alloc(self: *Allocator, comptime T: type, n: usize) Error![]T {
return self.alignedAlloc(T, null, n);
diff --git a/lib/std/net.zig b/lib/std/net.zig
@@ -4,244 +4,1329 @@ const assert = std.debug.assert;
const net = @This();
const mem = std.mem;
const os = std.os;
+const fs = std.fs;
-pub const TmpWinAddr = struct {
- family: u8,
- data: [14]u8,
-};
+test "" {
+ _ = @import("net/test.zig");
+}
-pub const OsAddress = switch (builtin.os) {
- builtin.Os.windows => TmpWinAddr,
- else => os.sockaddr,
-};
+pub const IpAddress = extern union {
+ any: os.sockaddr,
+ in: os.sockaddr_in,
+ in6: os.sockaddr_in6,
-pub const Address = struct {
- os_addr: OsAddress,
-
- pub fn initIp4(ip4: u32, _port: u16) Address {
- return Address{
- .os_addr = os.sockaddr{
- .in = os.sockaddr_in{
- .family = os.AF_INET,
- .port = mem.nativeToBig(u16, _port),
- .addr = ip4,
- .zero = [_]u8{0} ** 8,
- },
+ // TODO this crashed the compiler
+ //pub const localhost = initIp4(parseIp4("127.0.0.1") catch unreachable, 0);
+
+ pub fn parse(name: []const u8, port: u16) !IpAddress {
+ if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
+ error.Overflow,
+ error.InvalidEnd,
+ error.InvalidCharacter,
+ error.Incomplete,
+ => {},
+ }
+
+ if (parseIp6(name, port)) |ip6| return ip6 else |err| switch (err) {
+ error.Overflow,
+ error.InvalidEnd,
+ error.InvalidCharacter,
+ error.Incomplete,
+ => {},
+ }
+
+ return error.InvalidIPAddressFormat;
+ }
+
+ pub fn parseExpectingFamily(name: []const u8, family: os.sa_family_t, port: u16) !IpAddress {
+ switch (family) {
+ os.AF_INET => return parseIp4(name, port),
+ os.AF_INET6 => return parseIp6(name, port),
+ os.AF_UNSPEC => return parse(name, port),
+ else => unreachable,
+ }
+ }
+
+ pub fn parseIp6(buf: []const u8, port: u16) !IpAddress {
+ var result = IpAddress{
+ .in6 = os.sockaddr_in6{
+ .scope_id = undefined,
+ .port = mem.nativeToBig(u16, port),
+ .flowinfo = 0,
+ .addr = undefined,
+ },
+ };
+ const ip_slice = result.in6.addr[0..];
+
+ var x: u16 = 0;
+ var saw_any_digits = false;
+ var index: u8 = 0;
+ var scope_id = false;
+ for (buf) |c| {
+ if (scope_id) {
+ if (c >= '0' and c <= '9') {
+ const digit = c - '0';
+ if (@mulWithOverflow(u32, result.in6.scope_id, 10, &result.in6.scope_id)) {
+ return error.Overflow;
+ }
+ if (@addWithOverflow(u32, result.in6.scope_id, digit, &result.in6.scope_id)) {
+ return error.Overflow;
+ }
+ } else {
+ return error.InvalidCharacter;
+ }
+ } else if (c == ':') {
+ if (!saw_any_digits) {
+ return error.InvalidCharacter;
+ }
+ if (index == 14) {
+ return error.InvalidEnd;
+ }
+ ip_slice[index] = @truncate(u8, x >> 8);
+ index += 1;
+ ip_slice[index] = @truncate(u8, x);
+ index += 1;
+
+ x = 0;
+ saw_any_digits = false;
+ } else if (c == '%') {
+ if (!saw_any_digits) {
+ return error.InvalidCharacter;
+ }
+ if (index == 14) {
+ ip_slice[index] = @truncate(u8, x >> 8);
+ index += 1;
+ ip_slice[index] = @truncate(u8, x);
+ index += 1;
+ }
+ scope_id = true;
+ saw_any_digits = false;
+ } else {
+ const digit = try std.fmt.charToDigit(c, 16);
+ if (@mulWithOverflow(u16, x, 16, &x)) {
+ return error.Overflow;
+ }
+ if (@addWithOverflow(u16, x, digit, &x)) {
+ return error.Overflow;
+ }
+ saw_any_digits = true;
+ }
+ }
+
+ if (!saw_any_digits) {
+ return error.Incomplete;
+ }
+
+ if (scope_id) {
+ return result;
+ }
+
+ if (index == 14) {
+ ip_slice[14] = @truncate(u8, x >> 8);
+ ip_slice[15] = @truncate(u8, x);
+ return result;
+ }
+
+ return error.Incomplete;
+ }
+
+ pub fn parseIp4(buf: []const u8, port: u16) !IpAddress {
+ var result = IpAddress{
+ .in = os.sockaddr_in{
+ .port = mem.nativeToBig(u16, port),
+ .addr = undefined,
},
};
+ const out_ptr = @sliceToBytes((*[1]u32)(&result.in.addr)[0..]);
+
+ var x: u8 = 0;
+ var index: u8 = 0;
+ var saw_any_digits = false;
+ for (buf) |c| {
+ if (c == '.') {
+ if (!saw_any_digits) {
+ return error.InvalidCharacter;
+ }
+ if (index == 3) {
+ return error.InvalidEnd;
+ }
+ out_ptr[index] = x;
+ index += 1;
+ x = 0;
+ saw_any_digits = false;
+ } else if (c >= '0' and c <= '9') {
+ saw_any_digits = true;
+ x = try std.math.mul(u8, x, 10);
+ x = try std.math.add(u8, x, c - '0');
+ } else {
+ return error.InvalidCharacter;
+ }
+ }
+ if (index == 3 and saw_any_digits) {
+ out_ptr[index] = x;
+ return result;
+ }
+
+ return error.Incomplete;
}
- pub fn initIp6(ip6: *const Ip6Addr, _port: u16) Address {
- return Address{
- .os_addr = os.sockaddr{
- .in6 = os.sockaddr_in6{
- .family = os.AF_INET6,
- .port = mem.nativeToBig(u16, _port),
- .flowinfo = 0,
- .addr = ip6.addr,
- .scope_id = ip6.scope_id,
- },
+ pub fn initIp4(addr: [4]u8, port: u16) IpAddress {
+ return IpAddress{
+ .in = os.sockaddr_in{
+ .port = mem.nativeToBig(u16, port),
+ .addr = @ptrCast(*align(1) const u32, &addr).*,
},
};
}
- pub fn port(self: Address) u16 {
- return mem.bigToNative(u16, self.os_addr.in.port);
+ pub fn initIp6(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) IpAddress {
+ return IpAddress{
+ .in6 = os.sockaddr_in6{
+ .addr = addr,
+ .port = mem.nativeToBig(u16, port),
+ .flowinfo = flowinfo,
+ .scope_id = scope_id,
+ },
+ };
+ }
+
+ /// Returns the port in native endian.
+ pub fn getPort(self: IpAddress) u16 {
+ const big_endian_port = switch (self.any.family) {
+ os.AF_INET => self.in.port,
+ os.AF_INET6 => self.in6.port,
+ else => unreachable,
+ };
+ return mem.bigToNative(u16, big_endian_port);
+ }
+
+ /// `port` is native-endian.
+ pub fn setPort(self: *IpAddress, port: u16) void {
+ const ptr = switch (self.any.family) {
+ os.AF_INET => &self.in.port,
+ os.AF_INET6 => &self.in6.port,
+ else => unreachable,
+ };
+ ptr.* = mem.nativeToBig(u16, port);
}
- pub fn initPosix(addr: os.sockaddr) Address {
- return Address{ .os_addr = addr };
+ /// Asserts that `addr` is an IP address.
+ /// This function will read past the end of the pointer, with a size depending
+ /// on the address family.
+ pub fn initPosix(addr: *align(4) const os.sockaddr) IpAddress {
+ switch (addr.family) {
+ os.AF_INET => return IpAddress{ .in = @ptrCast(*const os.sockaddr_in, addr).* },
+ os.AF_INET6 => return IpAddress{ .in6 = @ptrCast(*const os.sockaddr_in6, addr).* },
+ else => unreachable,
+ }
}
- pub fn format(self: *const Address, out_stream: var) !void {
- switch (self.os_addr.in.family) {
+ pub fn format(
+ self: IpAddress,
+ comptime fmt: []const u8,
+ options: std.fmt.FormatOptions,
+ context: var,
+ comptime Errors: type,
+ output: fn (@typeOf(context), []const u8) Errors!void,
+ ) !void {
+ switch (self.any.family) {
os.AF_INET => {
- const native_endian_port = mem.bigToNative(u16, self.os_addr.in.port);
- const bytes = ([]const u8)((*self.os_addr.in.addr)[0..1]);
- try out_stream.print("{}.{}.{}.{}:{}", bytes[0], bytes[1], bytes[2], bytes[3], native_endian_port);
+ const port = mem.bigToNative(u16, self.in.port);
+ const bytes = @ptrCast(*const [4]u8, &self.in.addr);
+ try std.fmt.format(
+ context,
+ Errors,
+ output,
+ "{}.{}.{}.{}:{}",
+ bytes[0],
+ bytes[1],
+ bytes[2],
+ bytes[3],
+ port,
+ );
},
os.AF_INET6 => {
- const native_endian_port = mem.bigToNative(u16, self.os_addr.in6.port);
- try out_stream.print("[TODO render ip6 address]:{}", native_endian_port);
+ const ZeroRun = struct {
+ index: usize,
+ count: usize,
+ };
+ const port = mem.bigToNative(u16, self.in6.port);
+ const big_endian_parts = @ptrCast(*align(1) const [8]u16, &self.in6.addr);
+ const native_endian_parts = switch (builtin.endian) {
+ .Big => big_endian_parts.*,
+ .Little => blk: {
+ var buf: [8]u16 = undefined;
+ for (big_endian_parts) |part, i| {
+ buf[i] = mem.bigToNative(u16, part);
+ }
+ break :blk buf;
+ },
+ };
+
+ var longest_zero_run: ?ZeroRun = null;
+ var this_zero_run: ?ZeroRun = null;
+ for (native_endian_parts) |part, i| {
+ if (part == 0) {
+ if (this_zero_run) |*zr| {
+ zr.count += 1;
+ } else {
+ this_zero_run = ZeroRun{
+ .index = i,
+ .count = 1,
+ };
+ }
+ } else if (this_zero_run) |zr| {
+ if (longest_zero_run) |lzr| {
+ if (zr.count > lzr.count and zr.count > 1) {
+ longest_zero_run = zr;
+ }
+ } else {
+ longest_zero_run = zr;
+ }
+ }
+ }
+ try output(context, "[");
+ var i: usize = 0;
+ while (i < native_endian_parts.len) {
+ if (i != 0) try output(context, ":");
+
+ if (longest_zero_run) |lzr| {
+ if (lzr.index == i) {
+ i += lzr.count;
+ continue;
+ }
+ }
+
+ const part = native_endian_parts[i];
+ try std.fmt.format(context, Errors, output, "{x}", part);
+ i += 1;
+ }
+ try std.fmt.format(context, Errors, output, "]:{}", port);
},
- else => try out_stream.write("(unrecognized address family)"),
+ else => unreachable,
+ }
+ }
+
+ pub fn eql(a: IpAddress, b: IpAddress) bool {
+ const a_bytes = @ptrCast([*]const u8, &a.any)[0..a.getOsSockLen()];
+ const b_bytes = @ptrCast([*]const u8, &b.any)[0..b.getOsSockLen()];
+ return mem.eql(u8, a_bytes, b_bytes);
+ }
+
+ fn getOsSockLen(self: IpAddress) os.socklen_t {
+ switch (self.any.family) {
+ os.AF_INET => return @sizeOf(os.sockaddr_in),
+ os.AF_INET6 => return @sizeOf(os.sockaddr_in6),
+ else => unreachable,
}
}
};
-pub fn parseIp4(buf: []const u8) !u32 {
- var result: u32 = undefined;
- const out_ptr = @sliceToBytes((*[1]u32)(&result)[0..]);
+pub fn connectUnixSocket(path: []const u8) !fs.File {
+ const opt_non_block = if (std.io.mode == .evented) os.SOCK_NONBLOCK else 0;
+ const sockfd = try os.socket(
+ os.AF_UNIX,
+ os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block,
+ 0,
+ );
+ errdefer os.close(sockfd);
- var x: u8 = 0;
- var index: u8 = 0;
- var saw_any_digits = false;
- for (buf) |c| {
- if (c == '.') {
- if (!saw_any_digits) {
- return error.InvalidCharacter;
- }
- if (index == 3) {
- return error.InvalidEnd;
- }
- out_ptr[index] = x;
- index += 1;
- x = 0;
- saw_any_digits = false;
- } else if (c >= '0' and c <= '9') {
- saw_any_digits = true;
- const digit = c - '0';
- if (@mulWithOverflow(u8, x, 10, &x)) {
- return error.Overflow;
+ var sock_addr = os.sockaddr_un{
+ .family = os.AF_UNIX,
+ .path = undefined,
+ };
+
+ if (path.len > sock_addr.path.len) return error.NameTooLong;
+ mem.copy(u8, &sock_addr.path, path);
+
+ const size = @intCast(u32, @sizeOf(os.sockaddr_un) - sock_addr.path.len + path.len);
+ try os.connect(sockfd, &sock_addr, size);
+
+ return fs.File.openHandle(sockfd);
+}
+
+pub const AddressList = struct {
+ arena: std.heap.ArenaAllocator,
+ addrs: []IpAddress,
+ canon_name: ?[]u8,
+
+ fn deinit(self: *AddressList) void {
+ // Here we copy the arena allocator into stack memory, because
+ // otherwise it would destroy itself while it was still working.
+ var arena = self.arena;
+ arena.deinit();
+ // self is destroyed
+ }
+};
+
+/// All memory allocated with `allocator` will be freed before this function returns.
+pub fn tcpConnectToHost(allocator: *mem.Allocator, name: []const u8, port: u16) !fs.File {
+ const list = getAddressList(allocator, name, port);
+ defer list.deinit();
+
+ const addrs = list.addrs.toSliceConst();
+ if (addrs.len == 0) return error.UnknownHostName;
+
+ return tcpConnectToAddress(addrs[0], port);
+}
+
+pub fn tcpConnectToAddress(address: IpAddress) !fs.File {
+ const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0;
+ const sock_flags = os.SOCK_STREAM | os.SOCK_CLOEXEC | nonblock;
+ const sockfd = try os.socket(address.any.family, sock_flags, os.IPPROTO_TCP);
+ errdefer os.close(sockfd);
+ try os.connect(sockfd, &address.any, address.getOsSockLen());
+
+ return fs.File{ .handle = sockfd };
+}
+
+/// Call `AddressList.deinit` on the result.
+pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !*AddressList {
+ const result = blk: {
+ var arena = std.heap.ArenaAllocator.init(allocator);
+ errdefer arena.deinit();
+
+ const result = try arena.allocator.create(AddressList);
+ result.* = AddressList{
+ .arena = arena,
+ .addrs = undefined,
+ .canon_name = null,
+ };
+ break :blk result;
+ };
+ const arena = &result.arena.allocator;
+ errdefer result.arena.deinit();
+
+ if (builtin.link_libc) {
+ const c = std.c;
+ const name_c = try std.cstr.addNullByte(allocator, name);
+ defer allocator.free(name_c);
+
+ const port_c = try std.fmt.allocPrint(allocator, "{}\x00", port);
+ defer allocator.free(port_c);
+
+ const hints = os.addrinfo{
+ .flags = c.AI_NUMERICSERV,
+ .family = os.AF_UNSPEC,
+ .socktype = os.SOCK_STREAM,
+ .protocol = os.IPPROTO_TCP,
+ .canonname = null,
+ .addr = null,
+ .addrlen = 0,
+ .next = null,
+ };
+ var res: *os.addrinfo = undefined;
+ switch (os.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) {
+ 0 => {},
+ c.EAI_ADDRFAMILY => return error.HostLacksNetworkAddresses,
+ c.EAI_AGAIN => return error.TemporaryNameServerFailure,
+ c.EAI_BADFLAGS => unreachable, // Invalid hints
+ c.EAI_FAIL => return error.NameServerFailure,
+ c.EAI_FAMILY => return error.AddressFamilyNotSupported,
+ c.EAI_MEMORY => return error.OutOfMemory,
+ c.EAI_NODATA => return error.HostLacksNetworkAddresses,
+ c.EAI_NONAME => return error.UnknownHostName,
+ c.EAI_SERVICE => return error.ServiceUnavailable,
+ c.EAI_SOCKTYPE => unreachable, // Invalid socket type requested in hints
+ c.EAI_SYSTEM => switch (os.errno(-1)) {
+ else => |e| return os.unexpectedErrno(e),
+ },
+ else => unreachable,
+ }
+ defer os.system.freeaddrinfo(res);
+
+ const addr_count = blk: {
+ var count: usize = 0;
+ var it: ?*os.addrinfo = res;
+ while (it) |info| : (it = info.next) {
+ if (info.addr != null) {
+ count += 1;
+ }
}
- if (@addWithOverflow(u8, x, digit, &x)) {
- return error.Overflow;
+ break :blk count;
+ };
+ result.addrs = try arena.alloc(IpAddress, addr_count);
+
+ var it: ?*os.addrinfo = res;
+ var i: usize = 0;
+ while (it) |info| : (it = info.next) {
+ const addr = info.addr orelse continue;
+ result.addrs[i] = IpAddress.initPosix(@alignCast(4, addr));
+
+ if (info.canonname) |n| {
+ if (result.canon_name == null) {
+ result.canon_name = try mem.dupe(arena, u8, mem.toSliceConst(u8, n));
+ }
}
- } else {
- return error.InvalidCharacter;
+ i += 1;
}
- }
- if (index == 3 and saw_any_digits) {
- out_ptr[index] = x;
+
return result;
}
+ if (builtin.os == .linux) {
+ const flags = std.c.AI_NUMERICSERV;
+ const family = os.AF_UNSPEC;
+ var lookup_addrs = std.ArrayList(LookupAddr).init(allocator);
+ defer lookup_addrs.deinit();
+
+ var canon = std.Buffer.initNull(arena);
+ defer canon.deinit();
+
+ try linuxLookupName(&lookup_addrs, &canon, name, family, flags, port);
+
+ result.addrs = try arena.alloc(IpAddress, lookup_addrs.len);
+ if (!canon.isNull()) {
+ result.canon_name = canon.toOwnedSlice();
+ }
- return error.Incomplete;
+ for (lookup_addrs.toSliceConst()) |lookup_addr, i| {
+ result.addrs[i] = lookup_addr.addr;
+ assert(result.addrs[i].getPort() == port);
+ }
+
+ return result;
+ }
+ @compileError("std.net.getAddresses unimplemented for this OS");
}
-pub const Ip6Addr = struct {
- scope_id: u32,
- addr: [16]u8,
+const LookupAddr = struct {
+ addr: IpAddress,
+ sortkey: i32 = 0,
};
-pub fn parseIp6(buf: []const u8) !Ip6Addr {
- var result: Ip6Addr = undefined;
- result.scope_id = 0;
- const ip_slice = result.addr[0..];
+const DAS_USABLE = 0x40000000;
+const DAS_MATCHINGSCOPE = 0x20000000;
+const DAS_MATCHINGLABEL = 0x10000000;
+const DAS_PREC_SHIFT = 20;
+const DAS_SCOPE_SHIFT = 16;
+const DAS_PREFIX_SHIFT = 8;
+const DAS_ORDER_SHIFT = 0;
- var x: u16 = 0;
- var saw_any_digits = false;
- var index: u8 = 0;
- var scope_id = false;
- for (buf) |c| {
- if (scope_id) {
- if (c >= '0' and c <= '9') {
- const digit = c - '0';
- if (@mulWithOverflow(u32, result.scope_id, 10, &result.scope_id)) {
- return error.Overflow;
- }
- if (@addWithOverflow(u32, result.scope_id, digit, &result.scope_id)) {
- return error.Overflow;
- }
- } else {
- return error.InvalidCharacter;
- }
- } else if (c == ':') {
- if (!saw_any_digits) {
- return error.InvalidCharacter;
- }
- if (index == 14) {
- return error.InvalidEnd;
- }
- ip_slice[index] = @truncate(u8, x >> 8);
- index += 1;
- ip_slice[index] = @truncate(u8, x);
- index += 1;
-
- x = 0;
- saw_any_digits = false;
- } else if (c == '%') {
- if (!saw_any_digits) {
- return error.InvalidCharacter;
- }
- if (index == 14) {
- ip_slice[index] = @truncate(u8, x >> 8);
- index += 1;
- ip_slice[index] = @truncate(u8, x);
- index += 1;
+fn linuxLookupName(
+ addrs: *std.ArrayList(LookupAddr),
+ canon: *std.Buffer,
+ opt_name: ?[]const u8,
+ family: os.sa_family_t,
+ flags: u32,
+ port: u16,
+) !void {
+ if (opt_name) |name| {
+ // reject empty name and check len so it fits into temp bufs
+ try canon.replaceContents(name);
+ if (IpAddress.parseExpectingFamily(name, family, port)) |addr| {
+ try addrs.append(LookupAddr{ .addr = addr });
+ } else |name_err| if ((flags & std.c.AI_NUMERICHOST) != 0) {
+ return name_err;
+ } else {
+ try linuxLookupNameFromHosts(addrs, canon, name, family, port);
+ if (addrs.len == 0) {
+ try linuxLookupNameFromDnsSearch(addrs, canon, name, family, port);
}
- scope_id = true;
- saw_any_digits = false;
+ }
+ } else {
+ try canon.resize(0);
+ try linuxLookupNameFromNull(addrs, family, flags, port);
+ }
+ if (addrs.len == 0) return error.UnknownHostName;
+
+ // No further processing is needed if there are fewer than 2
+ // results or if there are only IPv4 results.
+ if (addrs.len == 1 or family == os.AF_INET) return;
+ const all_ip4 = for (addrs.toSliceConst()) |addr| {
+ if (addr.addr.any.family != os.AF_INET) break false;
+ } else true;
+ if (all_ip4) return;
+
+ // The following implements a subset of RFC 3484/6724 destination
+ // address selection by generating a single 31-bit sort key for
+ // each address. Rules 3, 4, and 7 are omitted for having
+ // excessive runtime and code size cost and dubious benefit.
+ // So far the label/precedence table cannot be customized.
+ // This implementation is ported from musl libc.
+ // A more idiomatic "ziggy" implementation would be welcome.
+ for (addrs.toSlice()) |*addr, i| {
+ var key: i32 = 0;
+ var sa6: os.sockaddr_in6 = undefined;
+ @memset(@ptrCast([*]u8, &sa6), 0, @sizeOf(os.sockaddr_in6));
+ var da6 = os.sockaddr_in6{
+ .family = os.AF_INET6,
+ .scope_id = addr.addr.in6.scope_id,
+ .port = 65535,
+ .flowinfo = 0,
+ .addr = [1]u8{0} ** 16,
+ };
+ var sa4: os.sockaddr_in = undefined;
+ @memset(@ptrCast([*]u8, &sa4), 0, @sizeOf(os.sockaddr_in));
+ var da4 = os.sockaddr_in{
+ .family = os.AF_INET,
+ .port = 65535,
+ .addr = 0,
+ .zero = [1]u8{0} ** 8,
+ };
+ var sa: *align(4) os.sockaddr = undefined;
+ var da: *align(4) os.sockaddr = undefined;
+ var salen: os.socklen_t = undefined;
+ var dalen: os.socklen_t = undefined;
+ if (addr.addr.any.family == os.AF_INET6) {
+ mem.copy(u8, &da6.addr, &addr.addr.in6.addr);
+ da = @ptrCast(*os.sockaddr, &da6);
+ dalen = @sizeOf(os.sockaddr_in6);
+ sa = @ptrCast(*os.sockaddr, &sa6);
+ salen = @sizeOf(os.sockaddr_in6);
} else {
- const digit = try std.fmt.charToDigit(c, 16);
- if (@mulWithOverflow(u16, x, 16, &x)) {
- return error.Overflow;
+ mem.copy(u8, &sa6.addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff");
+ mem.copy(u8, &da6.addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff");
+ // TODO https://github.com/ziglang/zig/issues/863
+ mem.writeIntNative(u32, @ptrCast(*[4]u8, da6.addr[12..].ptr), addr.addr.in.addr);
+ da4.addr = addr.addr.in.addr;
+ da = @ptrCast(*os.sockaddr, &da4);
+ dalen = @sizeOf(os.sockaddr_in);
+ sa = @ptrCast(*os.sockaddr, &sa4);
+ salen = @sizeOf(os.sockaddr_in);
+ }
+ const dpolicy = policyOf(da6.addr);
+ const dscope: i32 = scopeOf(da6.addr);
+ const dlabel = dpolicy.label;
+ const dprec: i32 = dpolicy.prec;
+ const MAXADDRS = 3;
+ var prefixlen: i32 = 0;
+ const sock_flags = os.SOCK_DGRAM | os.SOCK_CLOEXEC;
+ if (os.socket(addr.addr.any.family, sock_flags, os.IPPROTO_UDP)) |fd| syscalls: {
+ defer os.close(fd);
+ os.connect(fd, da, dalen) catch break :syscalls;
+ key |= DAS_USABLE;
+ os.getsockname(fd, sa, &salen) catch break :syscalls;
+ if (addr.addr.any.family == os.AF_INET) {
+ // TODO sa6.addr[12..16] should return *[4]u8, making this cast unnecessary.
+ mem.writeIntNative(u32, @ptrCast(*[4]u8, &sa6.addr[12]), sa4.addr);
}
- if (@addWithOverflow(u16, x, digit, &x)) {
- return error.Overflow;
+ if (dscope == i32(scopeOf(sa6.addr))) key |= DAS_MATCHINGSCOPE;
+ if (dlabel == labelOf(sa6.addr)) key |= DAS_MATCHINGLABEL;
+ prefixlen = prefixMatch(sa6.addr, da6.addr);
+ } else |_| {}
+ key |= dprec << DAS_PREC_SHIFT;
+ key |= (15 - dscope) << DAS_SCOPE_SHIFT;
+ key |= prefixlen << DAS_PREFIX_SHIFT;
+ key |= (MAXADDRS - @intCast(i32, i)) << DAS_ORDER_SHIFT;
+ addr.sortkey = key;
+ }
+ std.sort.sort(LookupAddr, addrs.toSlice(), addrCmpLessThan);
+}
+
+const Policy = struct {
+ addr: [16]u8,
+ len: u8,
+ mask: u8,
+ prec: u8,
+ label: u8,
+};
+
+const defined_policies = [_]Policy{
+ Policy{
+ .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
+ .len = 15,
+ .mask = 0xff,
+ .prec = 50,
+ .label = 0,
+ },
+ Policy{
+ .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00",
+ .len = 11,
+ .mask = 0xff,
+ .prec = 35,
+ .label = 4,
+ },
+ Policy{
+ .addr = "\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ .len = 1,
+ .mask = 0xff,
+ .prec = 30,
+ .label = 2,
+ },
+ Policy{
+ .addr = "\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ .len = 3,
+ .mask = 0xff,
+ .prec = 5,
+ .label = 5,
+ },
+ Policy{
+ .addr = "\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ .len = 0,
+ .mask = 0xfe,
+ .prec = 3,
+ .label = 13,
+ },
+ // These are deprecated and/or returned to the address
+ // pool, so despite the RFC, treating them as special
+ // is probably wrong.
+ // { "", 11, 0xff, 1, 3 },
+ // { "\xfe\xc0", 1, 0xc0, 1, 11 },
+ // { "\x3f\xfe", 1, 0xff, 1, 12 },
+ // Last rule must match all addresses to stop loop.
+ Policy{
+ .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ .len = 0,
+ .mask = 0,
+ .prec = 40,
+ .label = 1,
+ },
+};
+
+fn policyOf(a: [16]u8) *const Policy {
+ for (defined_policies) |*policy| {
+ if (!mem.eql(u8, a[0..policy.len], policy.addr[0..policy.len])) continue;
+ if ((a[policy.len] & policy.mask) != policy.addr[policy.len]) continue;
+ return policy;
+ }
+ unreachable;
+}
+
+fn scopeOf(a: [16]u8) u8 {
+ if (IN6_IS_ADDR_MULTICAST(a)) return a[1] & 15;
+ if (IN6_IS_ADDR_LINKLOCAL(a)) return 2;
+ if (IN6_IS_ADDR_LOOPBACK(a)) return 2;
+ if (IN6_IS_ADDR_SITELOCAL(a)) return 5;
+ return 14;
+}
+
+fn prefixMatch(s: [16]u8, d: [16]u8) u8 {
+ // TODO: This FIXME inherited from porting from musl libc.
+ // I don't want this to go into zig std lib 1.0.0.
+
+ // FIXME: The common prefix length should be limited to no greater
+ // than the nominal length of the prefix portion of the source
+ // address. However the definition of the source prefix length is
+ // not clear and thus this limiting is not yet implemented.
+ var i: u8 = 0;
+ while (i < 128 and ((s[i / 8] ^ d[i / 8]) & (u8(128) >> @intCast(u3, i % 8))) == 0) : (i += 1) {}
+ return i;
+}
+
+fn labelOf(a: [16]u8) u8 {
+ return policyOf(a).label;
+}
+
+fn IN6_IS_ADDR_MULTICAST(a: [16]u8) bool {
+ return a[0] == 0xff;
+}
+
+fn IN6_IS_ADDR_LINKLOCAL(a: [16]u8) bool {
+ return a[0] == 0xfe and (a[1] & 0xc0) == 0x80;
+}
+
+fn IN6_IS_ADDR_LOOPBACK(a: [16]u8) bool {
+ return a[0] == 0 and a[1] == 0 and
+ a[2] == 0 and
+ a[12] == 0 and a[13] == 0 and
+ a[14] == 0 and a[15] == 1;
+}
+
+fn IN6_IS_ADDR_SITELOCAL(a: [16]u8) bool {
+ return a[0] == 0xfe and (a[1] & 0xc0) == 0xc0;
+}
+
+// Parameters `b` and `a` swapped to make this descending.
+fn addrCmpLessThan(b: LookupAddr, a: LookupAddr) bool {
+ return a.sortkey < b.sortkey;
+}
+
+fn linuxLookupNameFromNull(
+ addrs: *std.ArrayList(LookupAddr),
+ family: os.sa_family_t,
+ flags: u32,
+ port: u16,
+) !void {
+ if ((flags & std.c.AI_PASSIVE) != 0) {
+ if (family != os.AF_INET6) {
+ (try addrs.addOne()).* = LookupAddr{
+ .addr = IpAddress.initIp4([1]u8{0} ** 4, port),
+ };
+ }
+ if (family != os.AF_INET) {
+ (try addrs.addOne()).* = LookupAddr{
+ .addr = IpAddress.initIp6([1]u8{0} ** 16, port, 0, 0),
+ };
+ }
+ } else {
+ if (family != os.AF_INET6) {
+ (try addrs.addOne()).* = LookupAddr{
+ .addr = IpAddress.initIp4([4]u8{ 127, 0, 0, 1 }, port),
+ };
+ }
+ if (family != os.AF_INET) {
+ (try addrs.addOne()).* = LookupAddr{
+ .addr = IpAddress.initIp6(([1]u8{0} ** 15) ++ [1]u8{1}, port, 0, 0),
+ };
+ }
+ }
+}
+
+fn linuxLookupNameFromHosts(
+ addrs: *std.ArrayList(LookupAddr),
+ canon: *std.Buffer,
+ name: []const u8,
+ family: os.sa_family_t,
+ port: u16,
+) !void {
+ const file = fs.File.openReadC(c"/etc/hosts") catch |err| switch (err) {
+ error.FileNotFound,
+ error.NotDir,
+ error.AccessDenied,
+ => return,
+ else => |e| return e,
+ };
+ defer file.close();
+
+ const stream = &std.io.BufferedInStream(fs.File.ReadError).init(&file.inStream().stream).stream;
+ var line_buf: [512]u8 = undefined;
+ while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) {
+ error.StreamTooLong => blk: {
+ // Skip to the delimiter in the stream, to fix parsing
+ try stream.skipUntilDelimiterOrEof('\n');
+ // Use the truncated line. A truncated comment or hostname will be handled correctly.
+ break :blk line_buf[0..];
+ },
+ else => |e| return e,
+ }) |line| {
+ const no_comment_line = mem.separate(line, "#").next().?;
+
+ var line_it = mem.tokenize(no_comment_line, " \t");
+ const ip_text = line_it.next() orelse continue;
+ var first_name_text: ?[]const u8 = null;
+ while (line_it.next()) |name_text| {
+ if (first_name_text == null) first_name_text = name_text;
+ if (mem.eql(u8, name_text, name)) {
+ break;
}
- saw_any_digits = true;
+ } else continue;
+
+ const addr = IpAddress.parseExpectingFamily(ip_text, family, port) catch |err| switch (err) {
+ error.Overflow,
+ error.InvalidEnd,
+ error.InvalidCharacter,
+ error.Incomplete,
+ error.InvalidIPAddressFormat,
+ => continue,
+ };
+ try addrs.append(LookupAddr{ .addr = addr });
+
+ // first name is canonical name
+ const name_text = first_name_text.?;
+ if (isValidHostName(name_text)) {
+ try canon.replaceContents(name_text);
}
}
+}
- if (!saw_any_digits) {
- return error.Incomplete;
+pub fn isValidHostName(hostname: []const u8) bool {
+ if (hostname.len >= 254) return false;
+ if (!std.unicode.utf8ValidateSlice(hostname)) return false;
+ for (hostname) |byte| {
+ if (byte >= 0x80 or byte == '.' or byte == '-' or std.ascii.isAlNum(byte)) {
+ continue;
+ }
+ return false;
}
+ return true;
+}
- if (scope_id) {
- return result;
+fn linuxLookupNameFromDnsSearch(
+ addrs: *std.ArrayList(LookupAddr),
+ canon: *std.Buffer,
+ name: []const u8,
+ family: os.sa_family_t,
+ port: u16,
+) !void {
+ var rc: ResolvConf = undefined;
+ try getResolvConf(addrs.allocator, &rc);
+ defer rc.deinit();
+
+ // Count dots, suppress search when >=ndots or name ends in
+ // a dot, which is an explicit request for global scope.
+ var dots: usize = 0;
+ for (name) |byte| {
+ if (byte == '.') dots += 1;
}
- if (index == 14) {
- ip_slice[14] = @truncate(u8, x >> 8);
- ip_slice[15] = @truncate(u8, x);
- return result;
+ const search = if (rc.search.isNull() or dots >= rc.ndots or mem.endsWith(u8, name, "."))
+ [_]u8{}
+ else
+ rc.search.toSliceConst();
+
+ var canon_name = name;
+
+ // Strip final dot for canon, fail if multiple trailing dots.
+ if (mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1;
+ if (mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName;
+
+ // Name with search domain appended is setup in canon[]. This both
+ // provides the desired default canonical name (if the requested
+ // name is not a CNAME record) and serves as a buffer for passing
+ // the full requested name to name_from_dns.
+ try canon.resize(canon_name.len);
+ mem.copy(u8, canon.toSlice(), canon_name);
+ try canon.appendByte('.');
+
+ var tok_it = mem.tokenize(search, " \t");
+ while (tok_it.next()) |tok| {
+ canon.shrink(canon_name.len + 1);
+ try canon.append(tok);
+ try linuxLookupNameFromDns(addrs, canon, canon.toSliceConst(), family, rc, port);
+ if (addrs.len != 0) return;
}
- return error.Incomplete;
+ canon.shrink(canon_name.len);
+ return linuxLookupNameFromDns(addrs, canon, name, family, rc, port);
}
-test "std.net.parseIp4" {
- assert((try parseIp4("127.0.0.1")) == mem.bigToNative(u32, 0x7f000001));
+const dpc_ctx = struct {
+ addrs: *std.ArrayList(LookupAddr),
+ canon: *std.Buffer,
+ port: u16,
+};
- testParseIp4Fail("256.0.0.1", error.Overflow);
- testParseIp4Fail("x.0.0.1", error.InvalidCharacter);
- testParseIp4Fail("127.0.0.1.1", error.InvalidEnd);
- testParseIp4Fail("127.0.0.", error.Incomplete);
- testParseIp4Fail("100..0.1", error.InvalidCharacter);
+fn linuxLookupNameFromDns(
+ addrs: *std.ArrayList(LookupAddr),
+ canon: *std.Buffer,
+ name: []const u8,
+ family: os.sa_family_t,
+ rc: ResolvConf,
+ port: u16,
+) !void {
+ var ctx = dpc_ctx{
+ .addrs = addrs,
+ .canon = canon,
+ .port = port,
+ };
+ const AfRr = struct {
+ af: os.sa_family_t,
+ rr: u8,
+ };
+ const afrrs = [_]AfRr{
+ AfRr{ .af = os.AF_INET6, .rr = os.RR_A },
+ AfRr{ .af = os.AF_INET, .rr = os.RR_AAAA },
+ };
+ var qbuf: [2][280]u8 = undefined;
+ var abuf: [2][512]u8 = undefined;
+ var qp: [2][]const u8 = undefined;
+ const apbuf = [2][]u8{ &abuf[0], &abuf[1] };
+ var nq: usize = 0;
+
+ for (afrrs) |afrr| {
+ if (family != afrr.af) {
+ const len = os.res_mkquery(0, name, 1, afrr.rr, [_]u8{}, null, &qbuf[nq]);
+ qp[nq] = qbuf[nq][0..len];
+ nq += 1;
+ }
+ }
+
+ var ap = [2][]u8{ apbuf[0][0..0], apbuf[1][0..0] };
+ try resMSendRc(qp[0..nq], ap[0..nq], apbuf[0..nq], rc);
+
+ var i: usize = 0;
+ while (i < nq) : (i += 1) {
+ dnsParse(ap[i], ctx, dnsParseCallback) catch {};
+ }
+
+ if (addrs.len != 0) return;
+ if (ap[0].len < 4 or (ap[0][3] & 15) == 2) return error.TemporaryNameServerFailure;
+ if ((ap[0][3] & 15) == 0) return error.UnknownHostName;
+ if ((ap[0][3] & 15) == 3) return;
+ return error.NameServerFailure;
}
-fn testParseIp4Fail(buf: []const u8, expected_err: anyerror) void {
- if (parseIp4(buf)) |_| {
- @panic("expected error");
- } else |e| {
- assert(e == expected_err);
+const ResolvConf = struct {
+ attempts: u32,
+ ndots: u32,
+ timeout: u32,
+ search: std.Buffer,
+ ns: std.ArrayList(LookupAddr),
+
+ fn deinit(rc: *ResolvConf) void {
+ rc.ns.deinit();
+ rc.search.deinit();
+ rc.* = undefined;
+ }
+};
+
+/// Ignores lines longer than 512 bytes.
+/// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761
+fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void {
+ rc.* = ResolvConf{
+ .ns = std.ArrayList(LookupAddr).init(allocator),
+ .search = std.Buffer.initNull(allocator),
+ .ndots = 1,
+ .timeout = 5,
+ .attempts = 2,
+ };
+ errdefer rc.deinit();
+
+ const file = fs.File.openReadC(c"/etc/resolv.conf") catch |err| switch (err) {
+ error.FileNotFound,
+ error.NotDir,
+ error.AccessDenied,
+ => return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1", 53),
+ else => |e| return e,
+ };
+ defer file.close();
+
+ const stream = &std.io.BufferedInStream(fs.File.ReadError).init(&file.inStream().stream).stream;
+ var line_buf: [512]u8 = undefined;
+ while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) {
+ error.StreamTooLong => blk: {
+ // Skip to the delimiter in the stream, to fix parsing
+ try stream.skipUntilDelimiterOrEof('\n');
+ // Give an empty line to the while loop, which will be skipped.
+ break :blk line_buf[0..0];
+ },
+ else => |e| return e,
+ }) |line| {
+ const no_comment_line = mem.separate(line, "#").next().?;
+ var line_it = mem.tokenize(no_comment_line, " \t");
+
+ const token = line_it.next() orelse continue;
+ if (mem.eql(u8, token, "options")) {
+ while (line_it.next()) |sub_tok| {
+ var colon_it = mem.separate(sub_tok, ":");
+ const name = colon_it.next().?;
+ const value_txt = colon_it.next() orelse continue;
+ const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) {
+ error.Overflow => 255,
+ error.InvalidCharacter => continue,
+ };
+ if (mem.eql(u8, name, "ndots")) {
+ rc.ndots = std.math.min(value, 15);
+ } else if (mem.eql(u8, name, "attempts")) {
+ rc.attempts = std.math.min(value, 10);
+ } else if (mem.eql(u8, name, "timeout")) {
+ rc.timeout = std.math.min(value, 60);
+ }
+ }
+ } else if (mem.eql(u8, token, "nameserver")) {
+ const ip_txt = line_it.next() orelse continue;
+ try linuxLookupNameFromNumericUnspec(&rc.ns, ip_txt, 53);
+ } else if (mem.eql(u8, token, "domain") or mem.eql(u8, token, "search")) {
+ try rc.search.replaceContents(line_it.rest());
+ }
+ }
+
+ if (rc.ns.len == 0) {
+ return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1", 53);
}
}
-test "std.net.parseIp6" {
- const addr = try parseIp6("FF01:0:0:0:0:0:0:FB");
- assert(addr.addr[0] == 0xff);
- assert(addr.addr[1] == 0x01);
- assert(addr.addr[2] == 0x00);
+fn linuxLookupNameFromNumericUnspec(
+ addrs: *std.ArrayList(LookupAddr),
+ name: []const u8,
+ port: u16,
+) !void {
+ const addr = try IpAddress.parse(name, port);
+ (try addrs.addOne()).* = LookupAddr{ .addr = addr };
}
-pub fn connectUnixSocket(path: []const u8) !std.fs.File {
- const opt_non_block = if (std.event.Loop.instance != null) os.SOCK_NONBLOCK else 0;
- const sockfd = try os.socket(
- os.AF_UNIX,
- os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block,
- 0,
- );
- errdefer os.close(sockfd);
+fn resMSendRc(
+ queries: []const []const u8,
+ answers: [][]u8,
+ answer_bufs: []const []u8,
+ rc: ResolvConf,
+) !void {
+ const timeout = 1000 * rc.timeout;
+ const attempts = rc.attempts;
+
+ var sl: os.socklen_t = @sizeOf(os.sockaddr_in);
+ var family: os.sa_family_t = os.AF_INET;
+
+ var ns_list = std.ArrayList(IpAddress).init(rc.ns.allocator);
+ defer ns_list.deinit();
+
+ try ns_list.resize(rc.ns.len);
+ const ns = ns_list.toSlice();
+
+ for (rc.ns.toSliceConst()) |iplit, i| {
+ ns[i] = iplit.addr;
+ assert(ns[i].getPort() == 53);
+ if (iplit.addr.any.family != os.AF_INET) {
+ sl = @sizeOf(os.sockaddr_in6);
+ family = os.AF_INET6;
+ }
+ }
- var sock_addr = os.sockaddr{
- .un = os.sockaddr_un{
- .family = os.AF_UNIX,
- .path = undefined,
+ // Get local address and open/bind a socket
+ var sa: IpAddress = undefined;
+ @memset(@ptrCast([*]u8, &sa), 0, @sizeOf(IpAddress));
+ sa.any.family = family;
+ const flags = os.SOCK_DGRAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK;
+ const fd = os.socket(family, flags, 0) catch |err| switch (err) {
+ error.AddressFamilyNotSupported => blk: {
+ // Handle case where system lacks IPv6 support
+ if (family == os.AF_INET6) {
+ family = os.AF_INET;
+ break :blk try os.socket(os.AF_INET, flags, 0);
+ }
+ return err;
},
+ else => |e| return e,
};
+ defer os.close(fd);
+ try os.bind(fd, &sa.any, sl);
- if (path.len > @typeOf(sock_addr.un.path).len) return error.NameTooLong;
- mem.copy(u8, sock_addr.un.path[0..], path);
- const size = @intCast(u32, @sizeOf(os.sa_family_t) + path.len);
- if (std.event.Loop.instance) |loop| {
- try os.connect_async(sockfd, &sock_addr, size);
- try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
- try os.getsockoptError(sockfd);
- } else {
- try os.connect(sockfd, &sock_addr, size);
+ // Past this point, there are no errors. Each individual query will
+ // yield either no reply (indicated by zero length) or an answer
+ // packet which is up to the caller to interpret.
+
+ // Convert any IPv4 addresses in a mixed environment to v4-mapped
+ // TODO
+ //if (family == AF_INET6) {
+ // setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &(int){0}, sizeof 0);
+ // for (i=0; i<nns; i++) {
+ // if (ns[i].sin.sin_family != AF_INET) continue;
+ // memcpy(ns[i].sin6.sin6_addr.s6_addr+12,
+ // &ns[i].sin.sin_addr, 4);
+ // memcpy(ns[i].sin6.sin6_addr.s6_addr,
+ // "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12);
+ // ns[i].sin6.sin6_family = AF_INET6;
+ // ns[i].sin6.sin6_flowinfo = 0;
+ // ns[i].sin6.sin6_scope_id = 0;
+ // }
+ //}
+
+ var pfd = [1]os.pollfd{os.pollfd{
+ .fd = fd,
+ .events = os.POLLIN,
+ .revents = undefined,
+ }};
+ const retry_interval = timeout / attempts;
+ var next: u32 = 0;
+ var t2: u64 = std.time.milliTimestamp();
+ var t0 = t2;
+ var t1 = t2 - retry_interval;
+
+ var servfail_retry: usize = undefined;
+
+ outer: while (t2 - t0 < timeout) : (t2 = std.time.milliTimestamp()) {
+ if (t2 - t1 >= retry_interval) {
+ // Query all configured nameservers in parallel
+ var i: usize = 0;
+ while (i < queries.len) : (i += 1) {
+ if (answers[i].len == 0) {
+ var j: usize = 0;
+ while (j < ns.len) : (j += 1) {
+ _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined;
+ }
+ }
+ }
+ t1 = t2;
+ servfail_retry = 2 * queries.len;
+ }
+
+ // Wait for a response, or until time to retry
+ const clamped_timeout = std.math.min(u31(std.math.maxInt(u31)), t1 + retry_interval - t2);
+ const nevents = os.poll(&pfd, clamped_timeout) catch 0;
+ if (nevents == 0) continue;
+
+ while (true) {
+ var sl_copy = sl;
+ const rlen = os.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;
+
+ // Ignore non-identifiable packets
+ if (rlen < 4) continue;
+
+ // Ignore replies from addresses we didn't send to
+ var j: usize = 0;
+ while (j < ns.len and !ns[j].eql(sa)) : (j += 1) {}
+ if (j == ns.len) continue;
+
+ // Find which query this answer goes with, if any
+ var i: usize = next;
+ while (i < queries.len and (answer_bufs[next][0] != queries[i][0] or
+ answer_bufs[next][1] != queries[i][1])) : (i += 1)
+ {}
+
+ if (i == queries.len) continue;
+ if (answers[i].len != 0) continue;
+
+ // Only accept positive or negative responses;
+ // retry immediately on server failure, and ignore
+ // all other codes such as refusal.
+ switch (answer_bufs[next][3] & 15) {
+ 0, 3 => {},
+ 2 => if (servfail_retry != 0) {
+ servfail_retry -= 1;
+ _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j].any, sl) catch undefined;
+ },
+ else => continue,
+ }
+
+ // Store answer in the right slot, or update next
+ // available temp slot if it's already in place.
+ answers[i].len = rlen;
+ if (i == next) {
+ while (next < queries.len and answers[next].len != 0) : (next += 1) {}
+ } else {
+ mem.copy(u8, answer_bufs[i], answer_bufs[next][0..rlen]);
+ }
+
+ if (next == queries.len) break :outer;
+ }
}
+}
- return std.fs.File.openHandle(sockfd);
+fn dnsParse(
+ r: []const u8,
+ ctx: var,
+ comptime callback: var,
+) !void {
+ // This implementation is ported from musl libc.
+ // A more idiomatic "ziggy" implementation would be welcome.
+ if (r.len < 12) return error.InvalidDnsPacket;
+ if ((r[3] & 15) != 0) return;
+ var p = r.ptr + 12;
+ var qdcount = r[4] * usize(256) + r[5];
+ var ancount = r[6] * usize(256) + r[7];
+ if (qdcount + ancount > 64) return error.InvalidDnsPacket;
+ while (qdcount != 0) {
+ qdcount -= 1;
+ while (@ptrToInt(p) - @ptrToInt(r.ptr) < r.len and p[0] -% 1 < 127) p += 1;
+ if (p[0] > 193 or (p[0] == 193 and p[1] > 254) or @ptrToInt(p) > @ptrToInt(r.ptr) + r.len - 6)
+ return error.InvalidDnsPacket;
+ p += usize(5) + @boolToInt(p[0] != 0);
+ }
+ while (ancount != 0) {
+ ancount -= 1;
+ while (@ptrToInt(p) - @ptrToInt(r.ptr) < r.len and p[0] -% 1 < 127) p += 1;
+ if (p[0] > 193 or (p[0] == 193 and p[1] > 254) or @ptrToInt(p) > @ptrToInt(r.ptr) + r.len - 6)
+ return error.InvalidDnsPacket;
+ p += usize(1) + @boolToInt(p[0] != 0);
+ const len = p[8] * usize(256) + p[9];
+ if (@ptrToInt(p) + len > @ptrToInt(r.ptr) + r.len) return error.InvalidDnsPacket;
+ try callback(ctx, p[1], p[10 .. 10 + len], r);
+ p += 10 + len;
+ }
}
+
+fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void {
+ switch (rr) {
+ os.RR_A => {
+ if (data.len != 4) return error.InvalidDnsARecord;
+ const new_addr = try ctx.addrs.addOne();
+ new_addr.* = LookupAddr{
+ // TODO slice [0..4] to make this *[4]u8 without @ptrCast
+ .addr = IpAddress.initIp4(@ptrCast(*const [4]u8, data.ptr).*, ctx.port),
+ };
+ },
+ os.RR_AAAA => {
+ if (data.len != 16) return error.InvalidDnsAAAARecord;
+ const new_addr = try ctx.addrs.addOne();
+ new_addr.* = LookupAddr{
+ // TODO slice [0..16] to make this *[16]u8 without @ptrCast
+ .addr = IpAddress.initIp6(@ptrCast(*const [16]u8, data.ptr).*, ctx.port, 0, 0),
+ };
+ },
+ os.RR_CNAME => {
+ var tmp: [256]u8 = undefined;
+ // Returns len of compressed name. strlen to get canon name.
+ _ = try os.dn_expand(packet, data, &tmp);
+ const canon_name = mem.toSliceConst(u8, &tmp);
+ if (isValidHostName(canon_name)) {
+ try ctx.canon.replaceContents(canon_name);
+ }
+ },
+ else => return,
+ }
+}
+
+pub const TcpServer = struct {
+ /// Copied from `Options` on `init`.
+ kernel_backlog: u32,
+
+ /// `undefined` until `listen` returns successfully.
+ listen_address: IpAddress,
+
+ sockfd: ?os.fd_t,
+
+ pub const Options = struct {
+ /// How many connections the kernel will accept on the application's behalf.
+ /// If more than this many connections pool in the kernel, clients will start
+ /// seeing "Connection refused".
+ kernel_backlog: u32 = 128,
+ };
+
+ /// After this call succeeds, resources have been acquired and must
+ /// be released with `deinit`.
+ pub fn init(options: Options) TcpServer {
+ return TcpServer{
+ .sockfd = null,
+ .kernel_backlog = options.kernel_backlog,
+ .listen_address = undefined,
+ };
+ }
+
+ /// Release all resources. The `TcpServer` memory becomes `undefined`.
+ pub fn deinit(self: *TcpServer) void {
+ self.close();
+ self.* = undefined;
+ }
+
+ pub fn listen(self: *TcpServer, address: IpAddress) !void {
+ const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0;
+ const sock_flags = os.SOCK_STREAM | os.SOCK_CLOEXEC | nonblock;
+ const sockfd = try os.socket(os.AF_INET, sock_flags, os.PROTO_tcp);
+ self.sockfd = sockfd;
+ errdefer {
+ os.close(sockfd);
+ self.sockfd = null;
+ }
+
+ var socklen = address.getOsSockLen();
+ try os.bind(sockfd, &address.any, socklen);
+ try os.listen(sockfd, self.kernel_backlog);
+ try os.getsockname(sockfd, &self.listen_address.any, &socklen);
+ }
+
+ /// Stop listening. It is still necessary to call `deinit` after stopping listening.
+ /// Calling `deinit` will automatically call `close`. It is safe to call `close` when
+ /// not listening.
+ pub fn close(self: *TcpServer) void {
+ if (self.sockfd) |fd| {
+ os.close(fd);
+ self.sockfd = null;
+ self.listen_address = undefined;
+ }
+ }
+
+ pub const AcceptError = error{
+ ConnectionAborted,
+
+ /// The per-process limit on the number of open file descriptors has been reached.
+ ProcessFdQuotaExceeded,
+
+ /// The system-wide limit on the total number of open files has been reached.
+ SystemFdQuotaExceeded,
+
+ /// Not enough free memory. This often means that the memory allocation is limited
+ /// by the socket buffer limits, not by the system memory.
+ SystemResources,
+
+ ProtocolFailure,
+
+ /// Firewall rules forbid connection.
+ BlockedByFirewall,
+ } || os.UnexpectedError;
+
+ /// If this function succeeds, the returned `fs.File` is a caller-managed resource.
+ pub fn accept(self: *TcpServer) AcceptError!fs.File {
+ const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0;
+ const accept_flags = nonblock | os.SOCK_CLOEXEC;
+ var accepted_addr: IpAddress = undefined;
+ var adr_len: os.socklen_t = @sizeOf(IpAddress);
+ if (os.accept4(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| {
+ return fs.File.openHandle(fd);
+ } else |err| switch (err) {
+ // We only give SOCK_NONBLOCK when I/O mode is async, in which case this error
+ // is handled by os.accept4.
+ error.WouldBlock => unreachable,
+ else => |e| return e,
+ }
+ }
+};
diff --git a/lib/std/net/test.zig b/lib/std/net/test.zig
@@ -0,0 +1,91 @@
+const std = @import("../std.zig");
+const net = std.net;
+const mem = std.mem;
+const testing = std.testing;
+
+test "parse and render IPv6 addresses" {
+ const addr = try net.IpAddress.parseIp6("FF01:0:0:0:0:0:0:FB", 80);
+ var buf: [100]u8 = undefined;
+ const printed = try std.fmt.bufPrint(&buf, "{}", addr);
+ std.testing.expect(mem.eql(u8, "[ff01::fb]:80", printed));
+}
+
+test "parse and render IPv4 addresses" {
+ var buffer: [18]u8 = undefined;
+ for ([_][]const u8{
+ "0.0.0.0",
+ "255.255.255.255",
+ "1.2.3.4",
+ "123.255.0.91",
+ "127.0.0.1",
+ }) |ip| {
+ var addr = net.IpAddress.parseIp4(ip, 0);
+ var newIp = std.fmt.bufPrint(buffer[0..], "{}", addr) catch unreachable;
+ std.testing.expect(std.mem.eql(u8, ip, newIp[0 .. newIp.len - 2]));
+ }
+
+ testing.expectError(error.Overflow, net.IpAddress.parseIp4("256.0.0.1", 0));
+ testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp4("x.0.0.1", 0));
+ testing.expectError(error.InvalidEnd, net.IpAddress.parseIp4("127.0.0.1.1", 0));
+ testing.expectError(error.Incomplete, net.IpAddress.parseIp4("127.0.0.", 0));
+ testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp4("100..0.1", 0));
+}
+
+test "resolve DNS" {
+ if (std.builtin.os == .windows) {
+ // DNS resolution not implemented on Windows yet.
+ return error.SkipZigTest;
+ }
+ var buf: [1000 * 10]u8 = undefined;
+ const a = &std.heap.FixedBufferAllocator.init(&buf).allocator;
+
+ const address_list = net.getAddressList(a, "example.com", 80) catch |err| switch (err) {
+ // The tests are required to work even when there is no Internet connection,
+ // so some of these errors we must accept and skip the test.
+ error.UnknownHostName => return error.SkipZigTest,
+ error.TemporaryNameServerFailure => return error.SkipZigTest,
+ else => return err,
+ };
+ address_list.deinit();
+}
+
+test "listen on a port, send bytes, receive bytes" {
+ if (std.builtin.os != .linux) {
+ // TODO build abstractions for other operating systems
+ return error.SkipZigTest;
+ }
+ if (std.io.mode != .evented) {
+ // TODO add ability to run tests in non-blocking I/O mode
+ return error.SkipZigTest;
+ }
+
+ // TODO doing this at comptime crashed the compiler
+ const localhost = net.IpAddress.parse("127.0.0.1", 0);
+
+ var server = net.TcpServer.init(net.TcpServer.Options{});
+ defer server.deinit();
+ try server.listen(localhost);
+
+ var server_frame = async testServer(&server);
+ var client_frame = async testClient(server.listen_address);
+
+ try await server_frame;
+ try await client_frame;
+}
+
+fn testClient(addr: net.IpAddress) anyerror!void {
+ const socket_file = try net.tcpConnectToAddress(addr);
+ defer socket_file.close();
+
+ var buf: [100]u8 = undefined;
+ const len = try socket_file.read(&buf);
+ const msg = buf[0..len];
+ testing.expect(mem.eql(u8, msg, "hello from server\n"));
+}
+
+fn testServer(server: *net.TcpServer) anyerror!void {
+ var client_file = try server.accept();
+
+ const stream = &client_file.outStream().stream;
+ try stream.print("hello from server\n");
+}
diff --git a/lib/std/os.zig b/lib/std/os.zig
@@ -310,7 +310,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
EINVAL => unreachable,
EFAULT => unreachable,
EAGAIN => if (std.event.Loop.instance) |loop| {
- loop.waitUntilFdReadable(fd) catch return error.WouldBlock;
+ loop.waitUntilFdReadable(fd);
continue;
} else {
return error.WouldBlock;
@@ -327,7 +327,36 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
}
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
-/// This function is for blocking file descriptors only.
+/// If the application has a global event loop enabled, EAGAIN is handled
+/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
+pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
+ while (true) {
+ // TODO handle the case when iov_len is too large and get rid of this @intCast
+ const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len));
+ switch (errno(rc)) {
+ 0 => return @bitCast(usize, rc),
+ EINTR => continue,
+ EINVAL => unreachable,
+ EFAULT => unreachable,
+ EAGAIN => if (std.event.Loop.instance) |loop| {
+ loop.waitUntilFdReadable(fd);
+ continue;
+ } else {
+ return error.WouldBlock;
+ },
+ EBADF => unreachable, // always a race condition
+ EIO => return error.InputOutput,
+ EISDIR => return error.IsDir,
+ ENOBUFS => return error.SystemResources,
+ ENOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
+/// If the application has a global event loop enabled, EAGAIN is handled
+/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
if (comptime std.Target.current.isDarwin()) {
// Darwin does not have preadv but it does have pread.
@@ -357,7 +386,12 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
EINVAL => unreachable,
EFAULT => unreachable,
ESPIPE => unreachable, // fd is not seekable
- EAGAIN => unreachable, // This function is for blocking reads.
+ EAGAIN => if (std.event.Loop.instance) |loop| {
+ loop.waitUntilFdReadable(fd);
+ continue;
+ } else {
+ return error.WouldBlock;
+ },
EBADF => unreachable, // always a race condition
EIO => return error.InputOutput,
EISDIR => return error.IsDir,
@@ -375,7 +409,12 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
- EAGAIN => unreachable, // This function is for blocking reads.
+ EAGAIN => if (std.event.Loop.instance) |loop| {
+ loop.waitUntilFdReadable(fd);
+ continue;
+ } else {
+ return error.WouldBlock;
+ },
EBADF => unreachable, // always a race condition
EIO => return error.InputOutput,
EISDIR => return error.IsDir,
@@ -395,10 +434,17 @@ pub const WriteError = error{
BrokenPipe,
SystemResources,
OperationAborted,
+
+ /// This error occurs when no global event loop is configured,
+ /// and reading from the file descriptor would block.
+ WouldBlock,
} || UnexpectedError;
/// Write to a file descriptor. Keeps trying if it gets interrupted.
-/// This function is for blocking file descriptors only.
+/// If the application has a global event loop enabled, EAGAIN is handled
+/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
+/// TODO evented I/O integration is disabled until
+/// https://github.com/ziglang/zig/issues/3557 is solved.
pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
if (builtin.os == .windows) {
return windows.WriteFile(fd, bytes);
@@ -434,7 +480,14 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
- EAGAIN => unreachable, // This function is for blocking writes.
+ // TODO https://github.com/ziglang/zig/issues/3557
+ EAGAIN => return error.WouldBlock,
+ //EAGAIN => if (std.event.Loop.instance) |loop| {
+ // loop.waitUntilFdWritable(fd);
+ // continue;
+ //} else {
+ // return error.WouldBlock;
+ //},
EBADF => unreachable, // Always a race condition.
EDESTADDRREQ => unreachable, // `connect` was never called.
EDQUOT => return error.DiskQuota,
@@ -448,9 +501,9 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
}
}
-/// Write multiple buffers to a file descriptor. Keeps trying if it gets interrupted.
-/// This function is for blocking file descriptors only. For non-blocking, see
-/// `writevAsync`.
+/// Write multiple buffers to a file descriptor.
+/// If the application has a global event loop enabled, EAGAIN is handled
+/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
while (true) {
// TODO handle the case when iov_len is too large and get rid of this @intCast
@@ -460,7 +513,12 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
- EAGAIN => unreachable, // This function is for blocking writes.
+ EAGAIN => if (std.event.Loop.instance) |loop| {
+ loop.waitUntilFdWritable(fd);
+ continue;
+ } else {
+ return error.WouldBlock;
+ },
EBADF => unreachable, // Always a race condition.
EDESTADDRREQ => unreachable, // `connect` was never called.
EDQUOT => return error.DiskQuota,
@@ -476,8 +534,6 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
/// Write multiple buffers to a file descriptor, with a position offset.
/// Keeps trying if it gets interrupted.
-/// This function is for blocking file descriptors only. For non-blocking, see
-/// `pwritevAsync`.
pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {
if (comptime std.Target.current.isDarwin()) {
// Darwin does not have pwritev but it does have pwrite.
@@ -506,7 +562,12 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
ESPIPE => unreachable, // `fd` is not seekable.
EINVAL => unreachable,
EFAULT => unreachable,
- EAGAIN => unreachable, // This function is for blocking writes.
+ EAGAIN => if (std.event.Loop.instance) |loop| {
+ loop.waitUntilFdWritable(fd);
+ continue;
+ } else {
+ return error.WouldBlock;
+ },
EBADF => unreachable, // Always a race condition.
EDESTADDRREQ => unreachable, // `connect` was never called.
EDQUOT => return error.DiskQuota,
@@ -528,7 +589,12 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
- EAGAIN => unreachable, // This function is for blocking writes.
+ EAGAIN => if (std.event.Loop.instance) |loop| {
+ loop.waitUntilFdWritable(fd);
+ continue;
+ } else {
+ return error.WouldBlock;
+ },
EBADF => unreachable, // Always a race condition.
EDESTADDRREQ => unreachable, // `connect` was never called.
EDQUOT => return error.DiskQuota,
@@ -1510,16 +1576,17 @@ pub const SocketError = error{
ProtocolNotSupported,
} || UnexpectedError;
-pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!i32 {
+pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!fd_t {
const rc = system.socket(domain, socket_type, protocol);
switch (errno(rc)) {
- 0 => return @intCast(i32, rc),
+ 0 => return @intCast(fd_t, rc),
EACCES => return error.PermissionDenied,
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
EINVAL => return error.ProtocolFamilyNotAvailable,
EMFILE => return error.ProcessFdQuotaExceeded,
ENFILE => return error.SystemFdQuotaExceeded,
- ENOBUFS, ENOMEM => return error.SystemResources,
+ ENOBUFS => return error.SystemResources,
+ ENOMEM => return error.SystemResources,
EPROTONOSUPPORT => return error.ProtocolNotSupported,
else => |err| return unexpectedErrno(err),
}
@@ -1561,17 +1628,17 @@ pub const BindError = error{
} || UnexpectedError;
/// addr is `*const T` where T is one of the sockaddr
-pub fn bind(fd: i32, addr: *const sockaddr) BindError!void {
- const rc = system.bind(fd, addr, @sizeOf(sockaddr));
+pub fn bind(sockfd: fd_t, addr: *const sockaddr, len: socklen_t) BindError!void {
+ const rc = system.bind(sockfd, addr, len);
switch (errno(rc)) {
0 => return,
EACCES => return error.AccessDenied,
EADDRINUSE => return error.AddressInUse,
EBADF => unreachable, // always a race condition if this error is returned
- EINVAL => unreachable,
- ENOTSOCK => unreachable,
+ EINVAL => unreachable, // invalid parameters
+ ENOTSOCK => unreachable, // invalid `sockfd`
EADDRNOTAVAIL => return error.AddressNotAvailable,
- EFAULT => unreachable,
+ EFAULT => unreachable, // invalid `addr` pointer
ELOOP => return error.SymLinkLoop,
ENAMETOOLONG => return error.NameTooLong,
ENOENT => return error.FileNotFound,
@@ -1622,12 +1689,6 @@ pub const AcceptError = error{
/// by the socket buffer limits, not by the system memory.
SystemResources,
- /// The file descriptor sockfd does not refer to a socket.
- FileDescriptorNotASocket,
-
- /// The referenced socket is not of type SOCK_STREAM.
- OperationNotSupported,
-
ProtocolFailure,
/// Firewall rules forbid connection.
@@ -1644,7 +1705,7 @@ pub const AcceptError = error{
pub fn accept4(
/// This argument is a socket that has been created with `socket`, bound to a local address
/// with `bind`, and is listening for connections after a `listen`.
- sockfd: i32,
+ sockfd: fd_t,
/// This argument is a pointer to a sockaddr structure. This structure is filled in with the
/// address of the peer socket, as known to the communications layer. The exact format of the
/// address returned addr is determined by the socket's address family (see `socket` and the
@@ -1665,15 +1726,15 @@ pub fn accept4(
/// * `SOCK_CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. See the
/// description of the `O_CLOEXEC` flag in `open` for reasons why this may be useful.
flags: u32,
-) AcceptError!i32 {
+) AcceptError!fd_t {
while (true) {
const rc = system.accept4(sockfd, addr, addr_size, flags);
switch (errno(rc)) {
- 0 => return @intCast(i32, rc),
+ 0 => return @intCast(fd_t, rc),
EINTR => continue,
EAGAIN => if (std.event.Loop.instance) |loop| {
- loop.waitUntilFdReadable(sockfd) catch return error.WouldBlock;
+ loop.waitUntilFdReadable(sockfd);
continue;
} else {
return error.WouldBlock;
@@ -1682,12 +1743,12 @@ pub fn accept4(
ECONNABORTED => return error.ConnectionAborted,
EFAULT => unreachable,
EINVAL => unreachable,
+ ENOTSOCK => unreachable,
EMFILE => return error.ProcessFdQuotaExceeded,
ENFILE => return error.SystemFdQuotaExceeded,
ENOBUFS => return error.SystemResources,
ENOMEM => return error.SystemResources,
- ENOTSOCK => return error.FileDescriptorNotASocket,
- EOPNOTSUPP => return error.OperationNotSupported,
+ EOPNOTSUPP => unreachable,
EPROTO => return error.ProtocolFailure,
EPERM => return error.BlockedByFirewall,
@@ -1809,11 +1870,9 @@ pub const GetSockNameError = error{
SystemResources,
} || UnexpectedError;
-pub fn getsockname(sockfd: i32) GetSockNameError!sockaddr {
- var addr: sockaddr = undefined;
- var addrlen: socklen_t = @sizeOf(sockaddr);
- switch (errno(system.getsockname(sockfd, &addr, &addrlen))) {
- 0 => return addr,
+pub fn getsockname(sockfd: fd_t, addr: *sockaddr, addrlen: *socklen_t) GetSockNameError!void {
+ switch (errno(system.getsockname(sockfd, addr, addrlen))) {
+ 0 => return,
else => |err| return unexpectedErrno(err),
EBADF => unreachable, // always a race condition
@@ -1856,12 +1915,14 @@ pub const ConnectError = error{
/// Timeout while attempting connection. The server may be too busy to accept new connections. Note
/// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
ConnectionTimedOut,
+
+ /// This error occurs when no global event loop is configured,
+ /// and connecting to the socket would block.
+ WouldBlock,
} || UnexpectedError;
/// Initiate a connection on a socket.
-/// This is for blocking file descriptors only.
-/// For non-blocking, see `connect_async`.
-pub fn connect(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!void {
+pub fn connect(sockfd: fd_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void {
while (true) {
switch (errno(system.connect(sockfd, sock_addr, len))) {
0 => return,
@@ -1870,41 +1931,16 @@ pub fn connect(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!v
EADDRINUSE => return error.AddressInUse,
EADDRNOTAVAIL => return error.AddressNotAvailable,
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
- EAGAIN => return error.SystemResources,
+ EAGAIN, EINPROGRESS => {
+ const loop = std.event.Loop.instance orelse return error.WouldBlock;
+ loop.waitUntilFdWritableOrReadable(sockfd);
+ return getsockoptError(sockfd);
+ },
EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
EBADF => unreachable, // sockfd is not a valid open file descriptor.
ECONNREFUSED => return error.ConnectionRefused,
EFAULT => unreachable, // The socket structure address is outside the user's address space.
- EINPROGRESS => unreachable, // The socket is nonblocking and the connection cannot be completed immediately.
- EINTR => continue,
- EISCONN => unreachable, // The socket is already connected.
- ENETUNREACH => return error.NetworkUnreachable,
- ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
- EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
- ETIMEDOUT => return error.ConnectionTimedOut,
- else => |err| return unexpectedErrno(err),
- }
- }
-}
-
-/// Same as `connect` except it is for non-blocking socket file descriptors.
-/// It expects to receive EINPROGRESS`.
-pub fn connect_async(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!void {
- while (true) {
- switch (errno(system.connect(sockfd, sock_addr, len))) {
- EINVAL => unreachable,
EINTR => continue,
- 0, EINPROGRESS => return,
- EACCES => return error.PermissionDenied,
- EPERM => return error.PermissionDenied,
- EADDRINUSE => return error.AddressInUse,
- EADDRNOTAVAIL => return error.AddressNotAvailable,
- EAFNOSUPPORT => return error.AddressFamilyNotSupported,
- EAGAIN => return error.SystemResources,
- EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
- EBADF => unreachable, // sockfd is not a valid open file descriptor.
- ECONNREFUSED => return error.ConnectionRefused,
- EFAULT => unreachable, // The socket structure address is outside the user's address space.
EISCONN => unreachable, // The socket is already connected.
ENETUNREACH => return error.NetworkUnreachable,
ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
@@ -2835,3 +2871,297 @@ pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 {
@compileError("TODO implement gethostname for this OS");
}
+
+pub fn res_mkquery(
+ op: u4,
+ dname: []const u8,
+ class: u8,
+ ty: u8,
+ data: []const u8,
+ newrr: ?[*]const u8,
+ buf: []u8,
+) usize {
+ // This implementation is ported from musl libc.
+ // A more idiomatic "ziggy" implementation would be welcome.
+ var name = dname;
+ if (mem.endsWith(u8, name, ".")) name.len -= 1;
+ assert(name.len <= 253);
+ const n = 17 + name.len + @boolToInt(name.len != 0);
+
+ // Construct query template - ID will be filled later
+ var q: [280]u8 = undefined;
+ @memset(&q, 0, n);
+ q[2] = u8(op) * 8 + 1;
+ q[5] = 1;
+ mem.copy(u8, q[13..], name);
+ var i: usize = 13;
+ var j: usize = undefined;
+ while (q[i] != 0) : (i = j + 1) {
+ j = i;
+ while (q[j] != 0 and q[j] != '.') : (j += 1) {}
+ // TODO determine the circumstances for this and whether or
+ // not this should be an error.
+ if (j - i - 1 > 62) unreachable;
+ q[i - 1] = @intCast(u8, j - i);
+ }
+ q[i + 1] = ty;
+ q[i + 3] = class;
+
+ // Make a reasonably unpredictable id
+ var ts: timespec = undefined;
+ clock_gettime(CLOCK_REALTIME, &ts) catch {};
+ const UInt = @IntType(false, @typeOf(ts.tv_nsec).bit_count);
+ const unsec = @bitCast(UInt, ts.tv_nsec);
+ const id = @truncate(u32, unsec + unsec / 65536);
+ q[0] = @truncate(u8, id / 256);
+ q[1] = @truncate(u8, id);
+
+ mem.copy(u8, buf, q[0..n]);
+ return n;
+}
+
+pub const SendError = error{
+ /// (For UNIX domain sockets, which are identified by pathname) Write permission is denied
+ /// on the destination socket file, or search permission is denied for one of the
+ /// directories the path prefix. (See path_resolution(7).)
+ /// (For UDP sockets) An attempt was made to send to a network/broadcast address as though
+ /// it was a unicast address.
+ AccessDenied,
+
+ /// The socket is marked nonblocking and the requested operation would block, and
+ /// there is no global event loop configured.
+ /// It's also possible to get this error under the following condition:
+ /// (Internet domain datagram sockets) The socket referred to by sockfd had not previously
+ /// been bound to an address and, upon attempting to bind it to an ephemeral port, it was
+ /// determined that all port numbers in the ephemeral port range are currently in use. See
+ /// the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7).
+ WouldBlock,
+
+ /// Another Fast Open is already in progress.
+ FastOpenAlreadyInProgress,
+
+ /// Connection reset by peer.
+ ConnectionResetByPeer,
+
+ /// The socket type requires that message be sent atomically, and the size of the message
+ /// to be sent made this impossible. The message is not transmitted.
+ ///
+ MessageTooBig,
+
+ /// The output queue for a network interface was full. This generally indicates that the
+ /// interface has stopped sending, but may be caused by transient congestion. (Normally,
+ /// this does not occur in Linux. Packets are just silently dropped when a device queue
+ /// overflows.)
+ /// This is also caused when there is not enough kernel memory available.
+ SystemResources,
+
+ /// The local end has been shut down on a connection oriented socket. In this case, the
+ /// process will also receive a SIGPIPE unless MSG_NOSIGNAL is set.
+ BrokenPipe,
+} || UnexpectedError;
+
+/// Transmit a message to another socket.
+///
+/// The `sendto` call may be used only when the socket is in a connected state (so that the intended
+/// recipient is known). The following call
+///
+/// send(sockfd, buf, len, flags);
+///
+/// is equivalent to
+///
+/// sendto(sockfd, buf, len, flags, NULL, 0);
+///
+/// If sendto() is used on a connection-mode (`SOCK_STREAM`, `SOCK_SEQPACKET`) socket, the arguments
+/// `dest_addr` and `addrlen` are asserted to be `null` and `0` respectively, and asserted
+/// that the socket was actually connected.
+/// Otherwise, the address of the target is given by `dest_addr` with `addrlen` specifying its size.
+///
+/// If the message is too long to pass atomically through the underlying protocol,
+/// `SendError.MessageTooBig` is returned, and the message is not transmitted.
+///
+/// There is no indication of failure to deliver.
+///
+/// When the message does not fit into the send buffer of the socket, `sendto` normally blocks,
+/// unless the socket has been placed in nonblocking I/O mode. In nonblocking mode it would fail
+/// with `SendError.WouldBlock`. The `select` call may be used to determine when it is
+/// possible to send more data.
+pub fn sendto(
+ /// The file descriptor of the sending socket.
+ sockfd: fd_t,
+ /// Message to send.
+ buf: []const u8,
+ flags: u32,
+ dest_addr: ?*const sockaddr,
+ addrlen: socklen_t,
+) SendError!usize {
+ while (true) {
+ const rc = system.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen);
+ switch (errno(rc)) {
+ 0 => return rc,
+
+ EACCES => return error.AccessDenied,
+ EAGAIN => if (std.event.Loop.instance) |loop| {
+ loop.waitUntilFdWritable(sockfd);
+ continue;
+ } else {
+ return error.WouldBlock;
+ },
+ EALREADY => return error.FastOpenAlreadyInProgress,
+ EBADF => unreachable, // always a race condition
+ ECONNRESET => return error.ConnectionResetByPeer,
+ EDESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set.
+ EFAULT => unreachable, // An invalid user space address was specified for an argument.
+ EINTR => continue,
+ EINVAL => unreachable, // Invalid argument passed.
+ EISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
+ EMSGSIZE => return error.MessageTooBig,
+ ENOBUFS => return error.SystemResources,
+ ENOMEM => return error.SystemResources,
+ ENOTCONN => unreachable, // The socket is not connected, and no target has been given.
+ ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
+ EOPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
+ EPIPE => return error.BrokenPipe,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+/// Transmit a message to another socket.
+///
+/// The `send` call may be used only when the socket is in a connected state (so that the intended
+/// recipient is known). The only difference between `send` and `write` is the presence of
+/// flags. With a zero flags argument, `send` is equivalent to `write`. Also, the following
+/// call
+///
+/// send(sockfd, buf, len, flags);
+///
+/// is equivalent to
+///
+/// sendto(sockfd, buf, len, flags, NULL, 0);
+///
+/// There is no indication of failure to deliver.
+///
+/// When the message does not fit into the send buffer of the socket, `send` normally blocks,
+/// unless the socket has been placed in nonblocking I/O mode. In nonblocking mode it would fail
+/// with `SendError.WouldBlock`. The `select` call may be used to determine when it is
+/// possible to send more data.
+pub fn send(
+ /// The file descriptor of the sending socket.
+ sockfd: fd_t,
+ buf: []const u8,
+ flags: u32,
+) SendError!usize {
+ return sendto(sockfd, buf, flags, null, 0);
+}
+
+pub const PollError = error{
+ /// The kernel had no space to allocate file descriptor tables.
+ SystemResources,
+} || UnexpectedError;
+
+pub fn poll(fds: []pollfd, timeout: i32) PollError!usize {
+ while (true) {
+ const rc = system.poll(fds.ptr, fds.len, timeout);
+ switch (errno(rc)) {
+ 0 => return rc,
+ EFAULT => unreachable,
+ EINTR => continue,
+ EINVAL => unreachable,
+ ENOMEM => return error.SystemResources,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const RecvFromError = error{
+ /// The socket is marked nonblocking and the requested operation would block, and
+ /// there is no global event loop configured.
+ WouldBlock,
+
+ /// A remote host refused to allow the network connection, typically because it is not
+ /// running the requested service.
+ ConnectionRefused,
+
+ /// Could not allocate kernel memory.
+ SystemResources,
+} || UnexpectedError;
+
+pub fn recvfrom(
+ sockfd: fd_t,
+ buf: []u8,
+ flags: u32,
+ src_addr: ?*sockaddr,
+ addrlen: ?*socklen_t,
+) RecvFromError!usize {
+ while (true) {
+ const rc = system.recvfrom(sockfd, buf.ptr, buf.len, flags, src_addr, addrlen);
+ switch (errno(rc)) {
+ 0 => return rc,
+ EBADF => unreachable, // always a race condition
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ ENOTCONN => unreachable,
+ ENOTSOCK => unreachable,
+ EINTR => continue,
+ EAGAIN => if (std.event.Loop.instance) |loop| {
+ loop.waitUntilFdReadable(sockfd);
+ continue;
+ } else {
+ return error.WouldBlock;
+ },
+ ENOMEM => return error.SystemResources,
+ ECONNREFUSED => return error.ConnectionRefused,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
+pub const DnExpandError = error{InvalidDnsPacket};
+
+pub fn dn_expand(
+ msg: []const u8,
+ comp_dn: []const u8,
+ exp_dn: []u8,
+) DnExpandError!usize {
+ // This implementation is ported from musl libc.
+ // A more idiomatic "ziggy" implementation would be welcome.
+ var p = comp_dn.ptr;
+ var len: usize = std.math.maxInt(usize);
+ const end = msg.ptr + msg.len;
+ if (p == end or exp_dn.len == 0) return error.InvalidDnsPacket;
+ var dest = exp_dn.ptr;
+ const dend = dest + std.math.min(exp_dn.len, 254);
+ // detect reference loop using an iteration counter
+ var i: usize = 0;
+ while (i < msg.len) : (i += 2) {
+ // loop invariants: p<end, dest<dend
+ if ((p[0] & 0xc0) != 0) {
+ if (p + 1 == end) return error.InvalidDnsPacket;
+ var j = ((p[0] & usize(0x3f)) << 8) | p[1];
+ if (len == std.math.maxInt(usize)) len = @ptrToInt(p) + 2 - @ptrToInt(comp_dn.ptr);
+ if (j >= msg.len) return error.InvalidDnsPacket;
+ p = msg.ptr + j;
+ } else if (p[0] != 0) {
+ if (dest != exp_dn.ptr) {
+ dest.* = '.';
+ dest += 1;
+ }
+ var j = p[0];
+ p += 1;
+ if (j >= @ptrToInt(end) - @ptrToInt(p) or j >= @ptrToInt(dend) - @ptrToInt(dest)) {
+ return error.InvalidDnsPacket;
+ }
+ while (j != 0) {
+ j -= 1;
+ dest.* = p[0];
+ dest += 1;
+ p += 1;
+ }
+ } else {
+ dest.* = 0;
+ if (len == std.math.maxInt(usize)) len = @ptrToInt(p) + 1 - @ptrToInt(comp_dn.ptr);
+ return len;
+ }
+ }
+ return error.InvalidDnsPacket;
+}
diff --git a/lib/std/os/bits/darwin.zig b/lib/std/os/bits/darwin.zig
@@ -8,26 +8,34 @@ pub const pid_t = c_int;
pub const in_port_t = u16;
pub const sa_family_t = u8;
pub const socklen_t = u32;
-pub const sockaddr = extern union {
- in: sockaddr_in,
- in6: sockaddr_in6,
-};
-pub const sockaddr_in = extern struct {
+pub const sockaddr = extern struct {
len: u8,
family: sa_family_t,
+ data: [14]u8,
+};
+pub const sockaddr_in = extern struct {
+ len: u8 = @sizeOf(sockaddr_in),
+ family: sa_family_t = AF_INET,
port: in_port_t,
addr: u32,
- zero: [8]u8,
+ zero: [8]u8 = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 },
};
pub const sockaddr_in6 = extern struct {
- len: u8,
- family: sa_family_t,
+ len: u8 = @sizeOf(sockaddr_in6),
+ family: sa_family_t = AF_INET6,
port: in_port_t,
flowinfo: u32,
addr: [16]u8,
scope_id: u32,
};
+/// UNIX domain socket
+pub const sockaddr_un = extern struct {
+ len: u8 = @sizeOf(sockaddr_un),
+ family: sa_family_t = AF_UNIX,
+ path: [104]u8,
+};
+
pub const timeval = extern struct {
tv_sec: c_long,
tv_usec: i32,
@@ -1196,3 +1204,14 @@ pub const AT_SYMLINK_FOLLOW = 0x0040;
/// Path refers to directory
pub const AT_REMOVEDIR = 0x0080;
+
+pub const addrinfo = extern struct {
+ flags: i32,
+ family: i32,
+ socktype: i32,
+ protocol: i32,
+ addrlen: socklen_t,
+ canonname: ?[*]u8,
+ addr: ?*sockaddr,
+ next: ?*addrinfo,
+};
diff --git a/lib/std/os/bits/freebsd.zig b/lib/std/os/bits/freebsd.zig
@@ -141,28 +141,40 @@ pub const dirent = extern struct {
pub const in_port_t = u16;
pub const sa_family_t = u16;
-pub const sockaddr = extern union {
- in: sockaddr_in,
- in6: sockaddr_in6,
+pub const sockaddr = extern struct {
+ /// total length
+ len: u8,
+
+ /// address family
+ family: sa_family_t,
+
+ /// actually longer; address value
+ data: [14]u8,
};
pub const sockaddr_in = extern struct {
- len: u8,
- family: sa_family_t,
+ len: u8 = @sizeOf(sockaddr_in),
+ family: sa_family_t = AF_INET,
port: in_port_t,
- addr: [16]u8,
- zero: [8]u8,
+ addr: u32,
+ zero: [8]u8 = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 },
};
pub const sockaddr_in6 = extern struct {
- len: u8,
- family: sa_family_t,
+ len: u8 = @sizeOf(sockaddr_in6),
+ family: sa_family_t = AF_INET6,
port: in_port_t,
flowinfo: u32,
addr: [16]u8,
scope_id: u32,
};
+pub const sockaddr_un = extern struct {
+ len: u8 = @sizeOf(sockaddr_un),
+ family: sa_family_t = AF_UNIX,
+ path: [104]u8,
+};
+
pub const CTL_KERN = 1;
pub const CTL_DEBUG = 5;
@@ -336,43 +348,6 @@ pub const SOCK_SEQPACKET = 5;
pub const SOCK_CLOEXEC = 0x10000000;
pub const SOCK_NONBLOCK = 0x20000000;
-pub const PROTO_ip = 0o000;
-pub const PROTO_icmp = 0o001;
-pub const PROTO_igmp = 0o002;
-pub const PROTO_ggp = 0o003;
-pub const PROTO_ipencap = 0o004;
-pub const PROTO_st = 0o005;
-pub const PROTO_tcp = 0o006;
-pub const PROTO_egp = 0o010;
-pub const PROTO_pup = 0o014;
-pub const PROTO_udp = 0o021;
-pub const PROTO_hmp = 0o024;
-pub const PROTO_xns_idp = 0o026;
-pub const PROTO_rdp = 0o033;
-pub const PROTO_iso_tp4 = 0o035;
-pub const PROTO_xtp = 0o044;
-pub const PROTO_ddp = 0o045;
-pub const PROTO_idpr_cmtp = 0o046;
-pub const PROTO_ipv6 = 0o051;
-pub const PROTO_ipv6_route = 0o053;
-pub const PROTO_ipv6_frag = 0o054;
-pub const PROTO_idrp = 0o055;
-pub const PROTO_rsvp = 0o056;
-pub const PROTO_gre = 0o057;
-pub const PROTO_esp = 0o062;
-pub const PROTO_ah = 0o063;
-pub const PROTO_skip = 0o071;
-pub const PROTO_ipv6_icmp = 0o072;
-pub const PROTO_ipv6_nonxt = 0o073;
-pub const PROTO_ipv6_opts = 0o074;
-pub const PROTO_rspf = 0o111;
-pub const PROTO_vmtp = 0o121;
-pub const PROTO_ospf = 0o131;
-pub const PROTO_ipip = 0o136;
-pub const PROTO_encap = 0o142;
-pub const PROTO_pim = 0o147;
-pub const PROTO_raw = 0o377;
-
pub const PF_UNSPEC = 0;
pub const PF_LOCAL = 1;
pub const PF_UNIX = PF_LOCAL;
@@ -963,3 +938,351 @@ pub const AT_REMOVEDIR = 0x0800;
/// Fail if not under dirfd
pub const AT_BENEATH = 0x1000;
+
+/// dummy for IP
+pub const IPPROTO_IP = 0;
+
+/// control message protocol
+pub const IPPROTO_ICMP = 1;
+
+/// tcp
+pub const IPPROTO_TCP = 6;
+
+/// user datagram protocol
+pub const IPPROTO_UDP = 17;
+
+/// IP6 header
+pub const IPPROTO_IPV6 = 41;
+
+/// raw IP packet
+pub const IPPROTO_RAW = 255;
+
+/// IP6 hop-by-hop options
+pub const IPPROTO_HOPOPTS = 0;
+
+/// group mgmt protocol
+pub const IPPROTO_IGMP = 2;
+
+/// gateway^2 (deprecated)
+pub const IPPROTO_GGP = 3;
+
+/// IPv4 encapsulation
+pub const IPPROTO_IPV4 = 4;
+
+/// for compatibility
+pub const IPPROTO_IPIP = IPPROTO_IPV4;
+
+/// Stream protocol II
+pub const IPPROTO_ST = 7;
+
+/// exterior gateway protocol
+pub const IPPROTO_EGP = 8;
+
+/// private interior gateway
+pub const IPPROTO_PIGP = 9;
+
+/// BBN RCC Monitoring
+pub const IPPROTO_RCCMON = 10;
+
+/// network voice protocol
+pub const IPPROTO_NVPII = 11;
+
+/// pup
+pub const IPPROTO_PUP = 12;
+
+/// Argus
+pub const IPPROTO_ARGUS = 13;
+
+/// EMCON
+pub const IPPROTO_EMCON = 14;
+
+/// Cross Net Debugger
+pub const IPPROTO_XNET = 15;
+
+/// Chaos
+pub const IPPROTO_CHAOS = 16;
+
+/// Multiplexing
+pub const IPPROTO_MUX = 18;
+
+/// DCN Measurement Subsystems
+pub const IPPROTO_MEAS = 19;
+
+/// Host Monitoring
+pub const IPPROTO_HMP = 20;
+
+/// Packet Radio Measurement
+pub const IPPROTO_PRM = 21;
+
+/// xns idp
+pub const IPPROTO_IDP = 22;
+
+/// Trunk-1
+pub const IPPROTO_TRUNK1 = 23;
+
+/// Trunk-2
+pub const IPPROTO_TRUNK2 = 24;
+
+/// Leaf-1
+pub const IPPROTO_LEAF1 = 25;
+
+/// Leaf-2
+pub const IPPROTO_LEAF2 = 26;
+
+/// Reliable Data
+pub const IPPROTO_RDP = 27;
+
+/// Reliable Transaction
+pub const IPPROTO_IRTP = 28;
+
+/// tp-4 w/ class negotiation
+pub const IPPROTO_TP = 29;
+
+/// Bulk Data Transfer
+pub const IPPROTO_BLT = 30;
+
+/// Network Services
+pub const IPPROTO_NSP = 31;
+
+/// Merit Internodal
+pub const IPPROTO_INP = 32;
+
+/// Datagram Congestion Control Protocol
+pub const IPPROTO_DCCP = 33;
+
+/// Third Party Connect
+pub const IPPROTO_3PC = 34;
+
+/// InterDomain Policy Routing
+pub const IPPROTO_IDPR = 35;
+
+/// XTP
+pub const IPPROTO_XTP = 36;
+
+/// Datagram Delivery
+pub const IPPROTO_DDP = 37;
+
+/// Control Message Transport
+pub const IPPROTO_CMTP = 38;
+
+/// TP++ Transport
+pub const IPPROTO_TPXX = 39;
+
+/// IL transport protocol
+pub const IPPROTO_IL = 40;
+
+/// Source Demand Routing
+pub const IPPROTO_SDRP = 42;
+
+/// IP6 routing header
+pub const IPPROTO_ROUTING = 43;
+
+/// IP6 fragmentation header
+pub const IPPROTO_FRAGMENT = 44;
+
+/// InterDomain Routing
+pub const IPPROTO_IDRP = 45;
+
+/// resource reservation
+pub const IPPROTO_RSVP = 46;
+
+/// General Routing Encap.
+pub const IPPROTO_GRE = 47;
+
+/// Mobile Host Routing
+pub const IPPROTO_MHRP = 48;
+
+/// BHA
+pub const IPPROTO_BHA = 49;
+
+/// IP6 Encap Sec. Payload
+pub const IPPROTO_ESP = 50;
+
+/// IP6 Auth Header
+pub const IPPROTO_AH = 51;
+
+/// Integ. Net Layer Security
+pub const IPPROTO_INLSP = 52;
+
+/// IP with encryption
+pub const IPPROTO_SWIPE = 53;
+
+/// Next Hop Resolution
+pub const IPPROTO_NHRP = 54;
+
+/// IP Mobility
+pub const IPPROTO_MOBILE = 55;
+
+/// Transport Layer Security
+pub const IPPROTO_TLSP = 56;
+
+/// SKIP
+pub const IPPROTO_SKIP = 57;
+
+/// ICMP6
+pub const IPPROTO_ICMPV6 = 58;
+
+/// IP6 no next header
+pub const IPPROTO_NONE = 59;
+
+/// IP6 destination option
+pub const IPPROTO_DSTOPTS = 60;
+
+/// any host internal protocol
+pub const IPPROTO_AHIP = 61;
+
+/// CFTP
+pub const IPPROTO_CFTP = 62;
+
+/// "hello" routing protocol
+pub const IPPROTO_HELLO = 63;
+
+/// SATNET/Backroom EXPAK
+pub const IPPROTO_SATEXPAK = 64;
+
+/// Kryptolan
+pub const IPPROTO_KRYPTOLAN = 65;
+
+/// Remote Virtual Disk
+pub const IPPROTO_RVD = 66;
+
+/// Pluribus Packet Core
+pub const IPPROTO_IPPC = 67;
+
+/// Any distributed FS
+pub const IPPROTO_ADFS = 68;
+
+/// Satnet Monitoring
+pub const IPPROTO_SATMON = 69;
+
+/// VISA Protocol
+pub const IPPROTO_VISA = 70;
+
+/// Packet Core Utility
+pub const IPPROTO_IPCV = 71;
+
+/// Comp. Prot. Net. Executive
+pub const IPPROTO_CPNX = 72;
+
+/// Comp. Prot. HeartBeat
+pub const IPPROTO_CPHB = 73;
+
+/// Wang Span Network
+pub const IPPROTO_WSN = 74;
+
+/// Packet Video Protocol
+pub const IPPROTO_PVP = 75;
+
+/// BackRoom SATNET Monitoring
+pub const IPPROTO_BRSATMON = 76;
+
+/// Sun net disk proto (temp.)
+pub const IPPROTO_ND = 77;
+
+/// WIDEBAND Monitoring
+pub const IPPROTO_WBMON = 78;
+
+/// WIDEBAND EXPAK
+pub const IPPROTO_WBEXPAK = 79;
+
+/// ISO cnlp
+pub const IPPROTO_EON = 80;
+
+/// VMTP
+pub const IPPROTO_VMTP = 81;
+
+/// Secure VMTP
+pub const IPPROTO_SVMTP = 82;
+
+/// Banyon VINES
+pub const IPPROTO_VINES = 83;
+
+/// TTP
+pub const IPPROTO_TTP = 84;
+
+/// NSFNET-IGP
+pub const IPPROTO_IGP = 85;
+
+/// dissimilar gateway prot.
+pub const IPPROTO_DGP = 86;
+
+/// TCF
+pub const IPPROTO_TCF = 87;
+
+/// Cisco/GXS IGRP
+pub const IPPROTO_IGRP = 88;
+
+/// OSPFIGP
+pub const IPPROTO_OSPFIGP = 89;
+
+/// Strite RPC protocol
+pub const IPPROTO_SRPC = 90;
+
+/// Locus Address Resoloution
+pub const IPPROTO_LARP = 91;
+
+/// Multicast Transport
+pub const IPPROTO_MTP = 92;
+
+/// AX.25 Frames
+pub const IPPROTO_AX25 = 93;
+
+/// IP encapsulated in IP
+pub const IPPROTO_IPEIP = 94;
+
+/// Mobile Int.ing control
+pub const IPPROTO_MICP = 95;
+
+/// Semaphore Comm. security
+pub const IPPROTO_SCCSP = 96;
+
+/// Ethernet IP encapsulation
+pub const IPPROTO_ETHERIP = 97;
+
+/// encapsulation header
+pub const IPPROTO_ENCAP = 98;
+
+/// any private encr. scheme
+pub const IPPROTO_APES = 99;
+
+/// GMTP
+pub const IPPROTO_GMTP = 100;
+
+/// payload compression (IPComp)
+pub const IPPROTO_IPCOMP = 108;
+
+/// SCTP
+pub const IPPROTO_SCTP = 132;
+
+/// IPv6 Mobility Header
+pub const IPPROTO_MH = 135;
+
+/// UDP-Lite
+pub const IPPROTO_UDPLITE = 136;
+
+/// IP6 Host Identity Protocol
+pub const IPPROTO_HIP = 139;
+
+/// IP6 Shim6 Protocol
+pub const IPPROTO_SHIM6 = 140;
+
+/// Protocol Independent Mcast
+pub const IPPROTO_PIM = 103;
+
+/// CARP
+pub const IPPROTO_CARP = 112;
+
+/// PGM
+pub const IPPROTO_PGM = 113;
+
+/// MPLS-in-IP
+pub const IPPROTO_MPLS = 137;
+
+/// PFSYNC
+pub const IPPROTO_PFSYNC = 240;
+
+/// Reserved
+pub const IPPROTO_RESERVED_253 = 253;
+
+/// Reserved
+pub const IPPROTO_RESERVED_254 = 254;
diff --git a/lib/std/os/bits/linux.zig b/lib/std/os/bits/linux.zig
@@ -227,43 +227,6 @@ pub const SEEK_SET = 0;
pub const SEEK_CUR = 1;
pub const SEEK_END = 2;
-pub const PROTO_ip = 0o000;
-pub const PROTO_icmp = 0o001;
-pub const PROTO_igmp = 0o002;
-pub const PROTO_ggp = 0o003;
-pub const PROTO_ipencap = 0o004;
-pub const PROTO_st = 0o005;
-pub const PROTO_tcp = 0o006;
-pub const PROTO_egp = 0o010;
-pub const PROTO_pup = 0o014;
-pub const PROTO_udp = 0o021;
-pub const PROTO_hmp = 0o024;
-pub const PROTO_xns_idp = 0o026;
-pub const PROTO_rdp = 0o033;
-pub const PROTO_iso_tp4 = 0o035;
-pub const PROTO_xtp = 0o044;
-pub const PROTO_ddp = 0o045;
-pub const PROTO_idpr_cmtp = 0o046;
-pub const PROTO_ipv6 = 0o051;
-pub const PROTO_ipv6_route = 0o053;
-pub const PROTO_ipv6_frag = 0o054;
-pub const PROTO_idrp = 0o055;
-pub const PROTO_rsvp = 0o056;
-pub const PROTO_gre = 0o057;
-pub const PROTO_esp = 0o062;
-pub const PROTO_ah = 0o063;
-pub const PROTO_skip = 0o071;
-pub const PROTO_ipv6_icmp = 0o072;
-pub const PROTO_ipv6_nonxt = 0o073;
-pub const PROTO_ipv6_opts = 0o074;
-pub const PROTO_rspf = 0o111;
-pub const PROTO_vmtp = 0o121;
-pub const PROTO_ospf = 0o131;
-pub const PROTO_ipip = 0o136;
-pub const PROTO_encap = 0o142;
-pub const PROTO_pim = 0o147;
-pub const PROTO_raw = 0o377;
-
pub const SHUT_RD = 0;
pub const SHUT_WR = 1;
pub const SHUT_RDWR = 2;
@@ -846,30 +809,31 @@ pub const in_port_t = u16;
pub const sa_family_t = u16;
pub const socklen_t = u32;
-/// This intentionally only has ip4 and ip6
-pub const sockaddr = extern union {
- in: sockaddr_in,
- in6: sockaddr_in6,
- un: sockaddr_un,
+pub const sockaddr = extern struct {
+ family: sa_family_t,
+ data: [14]u8,
};
+/// IPv4 socket address
pub const sockaddr_in = extern struct {
- family: sa_family_t,
+ family: sa_family_t = AF_INET,
port: in_port_t,
addr: u32,
- zero: [8]u8,
+ zero: [8]u8 = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 },
};
+/// IPv6 socket address
pub const sockaddr_in6 = extern struct {
- family: sa_family_t,
+ family: sa_family_t = AF_INET6,
port: in_port_t,
flowinfo: u32,
addr: [16]u8,
scope_id: u32,
};
+/// UNIX domain socket address
pub const sockaddr_un = extern struct {
- family: sa_family_t,
+ family: sa_family_t = AF_UNIX,
path: [108]u8,
};
@@ -1388,3 +1352,70 @@ pub const Statx = extern struct {
__pad2: [14]u64,
};
+
+pub const addrinfo = extern struct {
+ flags: i32,
+ family: i32,
+ socktype: i32,
+ protocol: i32,
+ addrlen: socklen_t,
+ addr: ?*sockaddr,
+ canonname: ?[*]u8,
+ next: ?*addrinfo,
+};
+
+pub const IPPORT_RESERVED = 1024;
+
+pub const IPPROTO_IP = 0;
+pub const IPPROTO_HOPOPTS = 0;
+pub const IPPROTO_ICMP = 1;
+pub const IPPROTO_IGMP = 2;
+pub const IPPROTO_IPIP = 4;
+pub const IPPROTO_TCP = 6;
+pub const IPPROTO_EGP = 8;
+pub const IPPROTO_PUP = 12;
+pub const IPPROTO_UDP = 17;
+pub const IPPROTO_IDP = 22;
+pub const IPPROTO_TP = 29;
+pub const IPPROTO_DCCP = 33;
+pub const IPPROTO_IPV6 = 41;
+pub const IPPROTO_ROUTING = 43;
+pub const IPPROTO_FRAGMENT = 44;
+pub const IPPROTO_RSVP = 46;
+pub const IPPROTO_GRE = 47;
+pub const IPPROTO_ESP = 50;
+pub const IPPROTO_AH = 51;
+pub const IPPROTO_ICMPV6 = 58;
+pub const IPPROTO_NONE = 59;
+pub const IPPROTO_DSTOPTS = 60;
+pub const IPPROTO_MTP = 92;
+pub const IPPROTO_BEETPH = 94;
+pub const IPPROTO_ENCAP = 98;
+pub const IPPROTO_PIM = 103;
+pub const IPPROTO_COMP = 108;
+pub const IPPROTO_SCTP = 132;
+pub const IPPROTO_MH = 135;
+pub const IPPROTO_UDPLITE = 136;
+pub const IPPROTO_MPLS = 137;
+pub const IPPROTO_RAW = 255;
+pub const IPPROTO_MAX = 256;
+
+pub const RR_A = 1;
+pub const RR_CNAME = 5;
+pub const RR_AAAA = 28;
+
+pub const nfds_t = usize;
+pub const pollfd = extern struct {
+ fd: fd_t,
+ events: i16,
+ revents: i16,
+};
+
+pub const POLLIN = 0x001;
+pub const POLLPRI = 0x002;
+pub const POLLOUT = 0x004;
+pub const POLLERR = 0x008;
+pub const POLLHUP = 0x010;
+pub const POLLNVAL = 0x020;
+pub const POLLRDNORM = 0x040;
+pub const POLLRDBAND = 0x080;
diff --git a/lib/std/os/bits/netbsd.zig b/lib/std/os/bits/netbsd.zig
@@ -137,21 +137,27 @@ pub const dirent = extern struct {
pub const in_port_t = u16;
pub const sa_family_t = u8;
-pub const sockaddr = extern union {
- in: sockaddr_in,
- in6: sockaddr_in6,
+pub const sockaddr = extern struct {
+ /// total length
+ len: u8,
+
+ /// address family
+ family: sa_family_t,
+
+ /// actually longer; address value
+ data: [14]u8,
};
pub const sockaddr_in = extern struct {
- len: u8,
+ len: u8 = @sizeOf(sockaddr_in),
family: sa_family_t,
port: in_port_t,
addr: u32,
- zero: [8]u8,
+ zero: [8]u8 = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 },
};
pub const sockaddr_in6 = extern struct {
- len: u8,
+ len: u8 = @sizeOf(sockaddr_in6),
family: sa_family_t,
port: in_port_t,
flowinfo: u32,
@@ -159,6 +165,18 @@ pub const sockaddr_in6 = extern struct {
scope_id: u32,
};
+/// Definitions for UNIX IPC domain.
+pub const sockaddr_un = extern struct {
+ /// total sockaddr length
+ len: u8 = @sizeOf(sockaddr_un),
+
+ /// AF_LOCAL
+ family: sa_family_t,
+
+ /// path name
+ path: [104]u8,
+};
+
pub const CTL_KERN = 1;
pub const CTL_DEBUG = 5;
@@ -316,31 +334,6 @@ pub const SOCK_SEQPACKET = 5;
pub const SOCK_CLOEXEC = 0x10000000;
pub const SOCK_NONBLOCK = 0x20000000;
-pub const PROTO_ip = 0;
-pub const PROTO_icmp = 1;
-pub const PROTO_igmp = 2;
-pub const PROTO_ggp = 3;
-pub const PROTO_ipencap = 4;
-pub const PROTO_tcp = 6;
-pub const PROTO_egp = 8;
-pub const PROTO_pup = 12;
-pub const PROTO_udp = 17;
-pub const PROTO_xns_idp = 22;
-pub const PROTO_iso_tp4 = 29;
-pub const PROTO_ipv6 = 41;
-pub const PROTO_ipv6_route = 43;
-pub const PROTO_ipv6_frag = 44;
-pub const PROTO_rsvp = 46;
-pub const PROTO_gre = 47;
-pub const PROTO_esp = 50;
-pub const PROTO_ah = 51;
-pub const PROTO_ipv6_icmp = 58;
-pub const PROTO_ipv6_nonxt = 59;
-pub const PROTO_ipv6_opts = 60;
-pub const PROTO_encap = 98;
-pub const PROTO_pim = 103;
-pub const PROTO_raw = 255;
-
pub const PF_UNSPEC = 0;
pub const PF_LOCAL = 1;
pub const PF_UNIX = PF_LOCAL;
@@ -827,3 +820,114 @@ pub fn S_IWHT(m: u32) bool {
}
pub const HOST_NAME_MAX = 255;
+
+/// dummy for IP
+pub const IPPROTO_IP = 0;
+
+/// IP6 hop-by-hop options
+pub const IPPROTO_HOPOPTS = 0;
+
+/// control message protocol
+pub const IPPROTO_ICMP = 1;
+
+/// group mgmt protocol
+pub const IPPROTO_IGMP = 2;
+
+/// gateway^2 (deprecated)
+pub const IPPROTO_GGP = 3;
+
+/// IP header
+pub const IPPROTO_IPV4 = 4;
+
+/// IP inside IP
+pub const IPPROTO_IPIP = 4;
+
+/// tcp
+pub const IPPROTO_TCP = 6;
+
+/// exterior gateway protocol
+pub const IPPROTO_EGP = 8;
+
+/// pup
+pub const IPPROTO_PUP = 12;
+
+/// user datagram protocol
+pub const IPPROTO_UDP = 17;
+
+/// xns idp
+pub const IPPROTO_IDP = 22;
+
+/// tp-4 w/ class negotiation
+pub const IPPROTO_TP = 29;
+
+/// DCCP
+pub const IPPROTO_DCCP = 33;
+
+/// IP6 header
+pub const IPPROTO_IPV6 = 41;
+
+/// IP6 routing header
+pub const IPPROTO_ROUTING = 43;
+
+/// IP6 fragmentation header
+pub const IPPROTO_FRAGMENT = 44;
+
+/// resource reservation
+pub const IPPROTO_RSVP = 46;
+
+/// GRE encaps RFC 1701
+pub const IPPROTO_GRE = 47;
+
+/// encap. security payload
+pub const IPPROTO_ESP = 50;
+
+/// authentication header
+pub const IPPROTO_AH = 51;
+
+/// IP Mobility RFC 2004
+pub const IPPROTO_MOBILE = 55;
+
+/// IPv6 ICMP
+pub const IPPROTO_IPV6_ICMP = 58;
+
+/// ICMP6
+pub const IPPROTO_ICMPV6 = 58;
+
+/// IP6 no next header
+pub const IPPROTO_NONE = 59;
+
+/// IP6 destination option
+pub const IPPROTO_DSTOPTS = 60;
+
+/// ISO cnlp
+pub const IPPROTO_EON = 80;
+
+/// Ethernet-in-IP
+pub const IPPROTO_ETHERIP = 97;
+
+/// encapsulation header
+pub const IPPROTO_ENCAP = 98;
+
+/// Protocol indep. multicast
+pub const IPPROTO_PIM = 103;
+
+/// IP Payload Comp. Protocol
+pub const IPPROTO_IPCOMP = 108;
+
+/// VRRP RFC 2338
+pub const IPPROTO_VRRP = 112;
+
+/// Common Address Resolution Protocol
+pub const IPPROTO_CARP = 112;
+
+/// L2TPv3
+pub const IPPROTO_L2TP = 115;
+
+/// SCTP
+pub const IPPROTO_SCTP = 132;
+
+/// PFSYNC
+pub const IPPROTO_PFSYNC = 240;
+
+/// raw IP packet
+pub const IPPROTO_RAW = 255;
diff --git a/lib/std/os/bits/windows.zig b/lib/std/os/bits/windows.zig
@@ -161,3 +161,63 @@ pub const F_OK = 0;
/// Remove directory instead of unlinking file
pub const AT_REMOVEDIR = 0x200;
+
+pub const in_port_t = u16;
+pub const sa_family_t = u16;
+pub const socklen_t = u32;
+
+pub const sockaddr = extern struct {
+ family: sa_family_t,
+ data: [14]u8,
+};
+pub const sockaddr_in = extern struct {
+ family: sa_family_t = AF_INET,
+ port: in_port_t,
+ addr: in_addr,
+ zero: [8]u8 = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 },
+};
+pub const sockaddr_in6 = extern struct {
+ family: sa_family_t = AF_INET6,
+ port: in_port_t,
+ flowinfo: u32,
+ addr: in6_addr,
+ scope_id: u32,
+};
+pub const in6_addr = [16]u8;
+pub const in_addr = u32;
+
+pub const AF_UNSPEC = 0;
+pub const AF_UNIX = 1;
+pub const AF_INET = 2;
+pub const AF_IMPLINK = 3;
+pub const AF_PUP = 4;
+pub const AF_CHAOS = 5;
+pub const AF_NS = 6;
+pub const AF_IPX = AF_NS;
+pub const AF_ISO = 7;
+pub const AF_OSI = AF_ISO;
+pub const AF_ECMA = 8;
+pub const AF_DATAKIT = 9;
+pub const AF_CCITT = 10;
+pub const AF_SNA = 11;
+pub const AF_DECnet = 12;
+pub const AF_DLI = 13;
+pub const AF_LAT = 14;
+pub const AF_HYLINK = 15;
+pub const AF_APPLETALK = 16;
+pub const AF_NETBIOS = 17;
+pub const AF_VOICEVIEW = 18;
+pub const AF_FIREFOX = 19;
+pub const AF_UNKNOWN1 = 20;
+pub const AF_BAN = 21;
+pub const AF_ATM = 22;
+pub const AF_INET6 = 23;
+pub const AF_CLUSTER = 24;
+pub const AF_12844 = 25;
+pub const AF_IRDA = 26;
+pub const AF_NETDES = 28;
+pub const AF_TCNPROCESS = 29;
+pub const AF_TCNMESSAGE = 30;
+pub const AF_ICLFXBM = 31;
+pub const AF_BTH = 32;
+pub const AF_MAX = 33;
diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig
@@ -226,6 +226,28 @@ pub fn munmap(address: [*]const u8, length: usize) usize {
return syscall2(SYS_munmap, @ptrToInt(address), length);
}
+pub fn poll(fds: [*]pollfd, n: nfds_t, timeout: i32) usize {
+ if (@hasDecl(@This(), "SYS_poll")) {
+ return syscall3(SYS_poll, @ptrToInt(fds), n, @bitCast(u32, timeout));
+ } else {
+ return syscall6(
+ SYS_ppoll,
+ @ptrToInt(fds),
+ n,
+ @ptrToInt(if (timeout >= 0)
+ ×pec{
+ .tv_sec = @divTrunc(timeout, 1000),
+ .tv_nsec = @rem(timeout, 1000) * 1000000,
+ }
+ else
+ null),
+ 0,
+ 0,
+ NSIG / 8,
+ );
+ }
+}
+
pub fn read(fd: i32, buf: [*]u8, count: usize) usize {
return syscall3(SYS_read, @bitCast(usize, isize(fd)), @ptrToInt(buf), count);
}
diff --git a/lib/std/target.zig b/lib/std/target.zig
@@ -205,6 +205,8 @@ pub const Target = union(enum) {
},
};
+ pub const stack_align = 16;
+
pub fn zigTriple(self: Target, allocator: *mem.Allocator) ![]u8 {
return std.fmt.allocPrint(
allocator,
diff --git a/src-self-hosted/stage1.zig b/src-self-hosted/stage1.zig
@@ -128,6 +128,7 @@ export fn stage2_free_clang_errors(errors_ptr: [*]translate_c.ClangErrMsg, error
export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error {
const c_out_stream = &std.io.COutStream.init(output_file).stream;
_ = std.zig.render(std.heap.c_allocator, c_out_stream, tree) catch |e| switch (e) {
+ error.WouldBlock => unreachable, // stage1 opens stuff in exclusively blocking mode
error.SystemResources => return Error.SystemResources,
error.OperationAborted => return Error.OperationAborted,
error.BrokenPipe => return Error.BrokenPipe,
diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig
@@ -151,18 +151,22 @@ pub fn translate(
};
defer ZigClangASTUnit_delete(ast_unit);
- var tree_arena = std.heap.ArenaAllocator.init(backing_allocator);
- errdefer tree_arena.deinit();
-
- const tree = try tree_arena.allocator.create(ast.Tree);
- tree.* = ast.Tree{
- .source = undefined, // need to use Buffer.toOwnedSlice later
- .root_node = undefined,
- .arena_allocator = tree_arena,
- .tokens = undefined, // can't reference the allocator yet
- .errors = undefined, // can't reference the allocator yet
+ const tree = blk: {
+ var tree_arena = std.heap.ArenaAllocator.init(backing_allocator);
+ errdefer tree_arena.deinit();
+
+ const tree = try tree_arena.allocator.create(ast.Tree);
+ tree.* = ast.Tree{
+ .source = undefined, // need to use Buffer.toOwnedSlice later
+ .root_node = undefined,
+ .arena_allocator = tree_arena,
+ .tokens = undefined, // can't reference the allocator yet
+ .errors = undefined, // can't reference the allocator yet
+ };
+ break :blk tree;
};
const arena = &tree.arena_allocator.allocator; // now we can reference the allocator
+ errdefer tree.arena_allocator.deinit();
tree.tokens = ast.Tree.TokenList.init(arena);
tree.errors = ast.Tree.ErrorList.init(arena);
diff --git a/src/analyze.cpp b/src/analyze.cpp
@@ -4381,6 +4381,10 @@ static Error analyze_callee_async(CodeGen *g, ZigFn *fn, ZigFn *callee, AstNode
if (callee->anal_state == FnAnalStateComplete) {
analyze_fn_async(g, callee, true);
if (callee->anal_state == FnAnalStateInvalid) {
+ if (g->trace_err != nullptr) {
+ g->trace_err = add_error_note(g, g->trace_err, call_node,
+ buf_sprintf("while checking if '%s' is async", buf_ptr(&fn->symbol_name)));
+ }
return ErrorSemanticAnalyzeFail;
}
callee_is_async = fn_is_async(callee);
@@ -6128,6 +6132,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
param_name = buf_sprintf("@arg%" ZIG_PRI_usize, arg_i);
}
ZigType *param_type = param_info->type;
+ if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) {
+ return err;
+ }
fields.append({buf_ptr(param_name), param_type, 0});
}
@@ -7538,7 +7545,9 @@ bool type_is_c_abi_int(CodeGen *g, ZigType *ty) {
uint32_t get_host_int_bytes(CodeGen *g, ZigType *struct_type, TypeStructField *field) {
assert(struct_type->id == ZigTypeIdStruct);
- assert(type_is_resolved(struct_type, ResolveStatusSizeKnown));
+ if (struct_type->data.structure.layout != ContainerLayoutAuto) {
+ assert(type_is_resolved(struct_type, ResolveStatusSizeKnown));
+ }
if (struct_type->data.structure.host_int_bytes == nullptr)
return 0;
return struct_type->data.structure.host_int_bytes[field->gen_index];
diff --git a/src/ir.cpp b/src/ir.cpp
@@ -17692,7 +17692,8 @@ static IrInstruction *ir_analyze_instruction_elem_ptr(IrAnalyze *ira, IrInstruct
{
size_t offset = ptr_field->data.x_ptr.data.base_array.elem_index;
uint64_t new_index = offset + index;
- assert(new_index < ptr_field->data.x_ptr.data.base_array.array_val->type->data.array.len);
+ ir_assert(new_index < ptr_field->data.x_ptr.data.base_array.array_val->type->data.array.len,
+ &elem_ptr_instruction->base);
out_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
out_val->data.x_ptr.data.base_array.array_val =
ptr_field->data.x_ptr.data.base_array.array_val;
@@ -17854,7 +17855,10 @@ static IrInstruction *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInstruction
case OnePossibleValueNo:
break;
}
- if ((err = type_resolve(ira->codegen, struct_type, ResolveStatusAlignmentKnown)))
+ ResolveStatus needed_resolve_status =
+ (struct_type->data.structure.layout == ContainerLayoutAuto) ?
+ ResolveStatusZeroBitsKnown : ResolveStatusSizeKnown;
+ if ((err = type_resolve(ira->codegen, struct_type, needed_resolve_status)))
return ira->codegen->invalid_instruction;
assert(struct_ptr->value.type->id == ZigTypeIdPointer);
uint32_t ptr_bit_offset = struct_ptr->value.type->data.pointer.bit_offset_in_host;
@@ -17873,6 +17877,9 @@ static IrInstruction *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInstruction
return ira->codegen->invalid_instruction;
if (ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr) {
+ if ((err = type_resolve(ira->codegen, struct_type, ResolveStatusSizeKnown)))
+ return ira->codegen->invalid_instruction;
+
ConstExprValue *struct_val = const_ptr_pointee(ira, ira->codegen, ptr_val, source_instr->source_node);
if (struct_val == nullptr)
return ira->codegen->invalid_instruction;
@@ -17919,7 +17926,7 @@ static IrInstruction *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_
Error err;
ZigType *bare_type = container_ref_type(container_type);
- if ((err = type_resolve(ira->codegen, bare_type, ResolveStatusSizeKnown)))
+ if ((err = type_resolve(ira->codegen, bare_type, ResolveStatusZeroBitsKnown)))
return ira->codegen->invalid_instruction;
assert(container_ptr->value.type->id == ZigTypeIdPointer);
diff --git a/test/compile_errors.zig b/test/compile_errors.zig
@@ -162,9 +162,9 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
\\ const obj = AstObject{ .lhsExpr = lhsExpr };
\\}
,
- "tmp.zig:1:17: error: struct 'LhsExpr' depends on itself",
- "tmp.zig:5:5: note: while checking this field",
+ "tmp.zig:4:19: error: union 'AstObject' depends on itself",
"tmp.zig:2:5: note: while checking this field",
+ "tmp.zig:5:5: note: while checking this field",
);
cases.add(