diff --git a/lib/std/buffer.zig b/lib/std/buffer.zig index bc6aa254da..24bd23fa74 100644 --- 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 index 2c4a224408..71e111a61d 100644 --- 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 index ebd12a0d86..391939aba7 100644 --- 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 index 8936d2ab5a..a1db162e20 100644 --- 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 index 56c5223ba3..2c72c22588 100644 --- 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 index f8539d5259..88edc90f16 100644 --- 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 index 22013edba6..ae8d76676d 100644 --- 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 deleted file mode 100644 index bed665dcdc..0000000000 --- a/lib/std/event/net.zig +++ /dev/null @@ -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 index db30fa916d..d3d795bf9d 100644 --- 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 index a447267cf0..460789366a 100644 --- 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 index 06a26ed481..431b89bebf 100644 --- 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 index 39c3a7cf9b..95280b888f 100644 --- 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 index 44c74fcca4..9854b90794 100644 --- 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 new file mode 100644 index 0000000000..42c40337a8 --- /dev/null +++ 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 index 6443c64815..bf8f7b8be7 100644 --- 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 index be9d18056c..eca313a84c 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -4,220 +4,326 @@ 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, -}; - -pub const OsAddress = switch (builtin.os) { - builtin.Os.windows => TmpWinAddr, - else => os.sockaddr, -}; - -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, - }, - }, - }; - } - - 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 port(self: Address) u16 { - return mem.bigToNative(u16, self.os_addr.in.port); - } - - pub fn initPosix(addr: os.sockaddr) Address { - return Address{ .os_addr = addr }; - } - - pub fn format(self: *const Address, out_stream: var) !void { - switch (self.os_addr.in.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); - }, - 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); - }, - else => try out_stream.write("(unrecognized address family)"), - } - } -}; - -pub fn parseIp4(buf: []const u8) !u32 { - var result: u32 = undefined; - const out_ptr = @sliceToBytes((*[1]u32)(&result)[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; - const digit = c - '0'; - if (@mulWithOverflow(u8, x, 10, &x)) { - return error.Overflow; - } - if (@addWithOverflow(u8, x, digit, &x)) { - return error.Overflow; - } - } else { - return error.InvalidCharacter; - } - } - if (index == 3 and saw_any_digits) { - out_ptr[index] = x; - return result; - } - - return error.Incomplete; +test "" { + _ = @import("net/test.zig"); } -pub const Ip6Addr = struct { - scope_id: u32, - addr: [16]u8, -}; +pub const IpAddress = extern union { + any: os.sockaddr, + in: os.sockaddr_in, + in6: os.sockaddr_in6, -pub fn parseIp6(buf: []const u8) !Ip6Addr { - var result: Ip6Addr = undefined; - result.scope_id = 0; - const ip_slice = result.addr[0..]; + // TODO this crashed the compiler + //pub const localhost = initIp4(parseIp4("127.0.0.1") catch unreachable, 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; + 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; } - if (@addWithOverflow(u32, result.scope_id, digit, &result.scope_id)) { - return error.Overflow; + } else if (c == ':') { + if (!saw_any_digits) { + return error.InvalidCharacter; + } + if (index == 14) { + return error.InvalidEnd; } - } 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) { + 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; } - if (scope_id) { - return result; + 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; } - if (index == 14) { - ip_slice[14] = @truncate(u8, x >> 8); - ip_slice[15] = @truncate(u8, x); - return result; + 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).*, + }, + }; } - return error.Incomplete; -} - -test "std.net.parseIp4" { - assert((try parseIp4("127.0.0.1")) == mem.bigToNative(u32, 0x7f000001)); - - 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 testParseIp4Fail(buf: []const u8, expected_err: anyerror) void { - if (parseIp4(buf)) |_| { - @panic("expected error"); - } else |e| { - assert(e == expected_err); + 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, + }, + }; } -} -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); -} + /// 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); + } -pub fn connectUnixSocket(path: []const u8) !std.fs.File { - const opt_non_block = if (std.event.Loop.instance != null) os.SOCK_NONBLOCK else 0; + /// `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); + } + + /// 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: 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 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 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 => 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 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, @@ -225,23 +331,1002 @@ pub fn connectUnixSocket(path: []const u8) !std.fs.File { ); errdefer os.close(sockfd); - var sock_addr = os.sockaddr{ - .un = os.sockaddr_un{ - .family = os.AF_UNIX, - .path = undefined, - }, + var sock_addr = os.sockaddr_un{ + .family = os.AF_UNIX, + .path = undefined, }; - 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); + 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; + } + } + 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)); + } + } + i += 1; + } + + 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(); + } + + 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"); +} + +const LookupAddr = struct { + addr: IpAddress, + sortkey: i32 = 0, +}; + +const DAS_USABLE = 0x40000000; +const DAS_MATCHINGSCOPE = 0x20000000; +const DAS_MATCHINGLABEL = 0x10000000; +const DAS_PREC_SHIFT = 20; +const DAS_SCOPE_SHIFT = 16; +const DAS_PREFIX_SHIFT = 8; +const DAS_ORDER_SHIFT = 0; + +fn linuxLookupName( + 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); + } + } } else { - try os.connect(sockfd, &sock_addr, size); + 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 { + 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 (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; + } + } 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); + } + } +} + +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; +} + +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; } - return std.fs.File.openHandle(sockfd); + 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; + } + + canon.shrink(canon_name.len); + return linuxLookupNameFromDns(addrs, canon, name, family, rc, port); } + +const dpc_ctx = struct { + addrs: *std.ArrayList(LookupAddr), + canon: *std.Buffer, + port: u16, +}; + +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; +} + +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); + } +} + +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 }; +} + +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; + } + } + + // 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); + + // 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= 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; + } + } +} + +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 new file mode 100644 index 0000000000..54fd40f23e --- /dev/null +++ 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 index 376beb1280..988ab873d8 100644 --- 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,12 +1931,15 @@ 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, @@ -1887,34 +1951,6 @@ pub fn connect(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!v } } -/// 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. - EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. - ETIMEDOUT => return error.ConnectionTimedOut, - else => |err| return unexpectedErrno(err), - } - } -} - pub fn getsockoptError(sockfd: i32) ConnectError!void { var err_code: u32 = undefined; var size: u32 = @sizeOf(u32); @@ -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= 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 index dce3ed4410..0a417a587a 100644 --- 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 = extern struct { + len: u8, + family: sa_family_t, + 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: 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 index dbe556154f..9be070010b 100644 --- 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 index a1898a5151..46c47bf339 100644 --- 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 index 50d6883a58..ff6f8d2f80 100644 --- 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 index c261b52bed..178811bb1e 100644 --- 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 index 2c8500574d..d9f1f4e311 100644 --- 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 index 095ba3c8e1..d297312b4e 100644 --- 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 index 619fd32dc1..3524c4f5b5 100644 --- 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 index a5c4959d08..b4f69753b1 100644 --- 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 = 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 + 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 index e9e03466c5..e38470f7f9 100644 --- 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 index 3b3814b3ad..a53367a324 100644 --- 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 index 8f12ec2e57..440545de8f 100644 --- 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(