From 07bee9da42977420916ca0f1044b47fab4a694dc Mon Sep 17 00:00:00 2001 From: Cato Date: Sat, 2 May 2020 15:14:28 +0200 Subject: [PATCH 1/2] Fixed Darwin-incompatible socket flags and unavailable system calls --- lib/std/c.zig | 1 + lib/std/net.zig | 76 +++++++++++++++++++++++++++++++++++++++---------- lib/std/os.zig | 46 ++++++++++++++++++++++++++++-- 3 files changed, 105 insertions(+), 18 deletions(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index e6516fa8ab..9f1114b9c1 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -131,6 +131,7 @@ pub extern "c" fn socketpair(domain: c_uint, sock_type: c_uint, protocol: c_uint pub extern "c" fn listen(sockfd: fd_t, backlog: c_uint) c_int; pub extern "c" fn getsockname(sockfd: fd_t, noalias addr: *sockaddr, noalias addrlen: *socklen_t) c_int; pub extern "c" fn connect(sockfd: fd_t, sock_addr: *const sockaddr, addrlen: socklen_t) c_int; +pub extern "c" fn accept(sockfd: fd_t, addr: *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: u32, optname: u32, optval: ?*c_void, optlen: *socklen_t) c_int; pub extern "c" fn setsockopt(sockfd: fd_t, level: u32, optname: u32, optval: ?*const c_void, optlen: socklen_t) c_int; diff --git a/lib/std/net.zig b/lib/std/net.zig index 1c7355bcb2..cd1b1f3088 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -358,13 +358,44 @@ pub const Address = extern union { } }; +fn getUnixSocketInitFlags() u16 { + comptime { + var flags = 0; + switch (builtin.os.tag) { + .linux, .freebsd, .netbsd, .dragonfly => { + flags |= os.SOCK_CLOEXEC; + flags |= if (std.io.is_async) os.SOCK_NONBLOCK else 0; + }, + else => {}, + } + + return flags; + } +} + +// These are primarily needed for UNIX-based platforms without +// SOCK_CLOEXEC and SOCK_NONBLOCK flags when creating sockets +// or accepting connections +fn setUnixSocketFlags(sock: os.fd_t) os.FcntlError!void { + var fdflags = try os.fcntl(sock, os.F_GETFD, 0); + fdflags |= os.FD_CLOEXEC; + _ = try os.fcntl(sock, os.F_SETFD, fdflags); + + if (std.io.is_async) { + var flflags = try os.fcntl(sock, os.F_GETFL, 0); + flflags |= os.O_NONBLOCK; + _ = try os.fcntl(sock, os.F_SETFL, fdflags); + } +} + pub fn connectUnixSocket(path: []const u8) !fs.File { - const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0; - const sockfd = try os.socket( - os.AF_UNIX, - os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block, - 0, - ); + const flags = os.SOCK_STREAM | getUnixSocketInitFlags(); + const sockfd = try os.socket(os.AF_UNIX, flags, 0); + + if (comptime builtin.os.tag.isDarwin()) { + try setUnixSocketFlags(sockfd); + } + errdefer os.close(sockfd); var addr = try std.net.Address.initUnix(path); @@ -406,9 +437,13 @@ pub fn tcpConnectToHost(allocator: *mem.Allocator, name: []const u8, port: u16) } pub fn tcpConnectToAddress(address: Address) !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 sock_flags = os.SOCK_STREAM | getUnixSocketInitFlags(); const sockfd = try os.socket(address.any.family, sock_flags, os.IPPROTO_TCP); + + if (comptime builtin.os.tag.isDarwin()) { + try setUnixSocketFlags(sockfd); + } + errdefer os.close(sockfd); try os.connect(sockfd, &address.any, address.getOsSockLen()); @@ -1312,11 +1347,14 @@ pub const StreamServer = struct { } pub fn listen(self: *StreamServer, address: Address) !void { - const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0; - const sock_flags = os.SOCK_STREAM | os.SOCK_CLOEXEC | nonblock; + const flags = os.SOCK_STREAM | getUnixSocketInitFlags(); const proto = if (address.any.family == os.AF_UNIX) @as(u32, 0) else os.IPPROTO_TCP; + const sockfd = try os.socket(address.any.family, flags, proto); + + if (comptime builtin.os.tag.isDarwin()) { + try setUnixSocketFlags(sockfd); + } - const sockfd = try os.socket(address.any.family, sock_flags, proto); self.sockfd = sockfd; errdefer { os.close(sockfd); @@ -1374,12 +1412,20 @@ pub const StreamServer = struct { }; /// If this function succeeds, the returned `Connection` is a caller-managed resource. - pub fn accept(self: *StreamServer) AcceptError!Connection { - const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0; - const accept_flags = nonblock | os.SOCK_CLOEXEC; + pub fn accept(self: *StreamServer) !Connection { var accepted_addr: Address = undefined; var adr_len: os.socklen_t = @sizeOf(Address); - if (os.accept4(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| { + var _accept: os.AcceptError!os.fd_t = undefined; + + if (comptime builtin.os.tag.isDarwin()) { + try setUnixSocketFlags(self.sockfd.?); + _accept = os.accept(self.sockfd.?, &accepted_addr.any, &adr_len); + } else { + const flags = getUnixSocketInitFlags(); + _accept = os.accept4(self.sockfd.?, &accepted_addr.any, &adr_len, flags); + } + + if (_accept) |fd| { return Connection{ .file = fs.File{ .handle = fd }, .address = accepted_addr, diff --git a/lib/std/os.zig b/lib/std/os.zig index 90c74738b8..99dee13dd0 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2309,12 +2309,53 @@ pub fn accept4( /// description of the `O_CLOEXEC` flag in `open` for reasons why this may be useful. flags: u32, ) AcceptError!fd_t { + if (comptime builtin.os.tag.isDarwin()) { + @compileError("accept4 not available for target Darwin, use accept"); + } + + return try _accept(sockfd, addr, addr_size, flags); +} + +/// Accept a connection on a socket. +/// If the application has a global event loop enabled, EAGAIN is handled +/// via the event loop. Otherwise EAGAIN results in error.WouldBlock. +pub fn accept( + /// 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: 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 + /// respective protocol man pages). + addr: *sockaddr, + /// This argument is a value-result argument: the caller must initialize it to contain the + /// size (in bytes) of the structure pointed to by addr; on return it will contain the actual size + /// of the peer address. + /// + /// The returned address is truncated if the buffer provided is too small; in this case, `addr_size` + /// will return a value greater than was supplied to the call. + addr_size: *socklen_t, +) AcceptError!fd_t { + return try _accept(sockfd, addr, addr_size, 0); +} + +fn _accept(sockfd: fd_t, addr: *sockaddr, addr_size: *socklen_t, flags: u32) AcceptError!fd_t { while (true) { - const rc = system.accept4(sockfd, addr, addr_size, flags); + const rc = func: { + switch (comptime builtin.os.tag) { + .linux, .freebsd, .netbsd, .dragonfly => + break :func system.accept4(sockfd, addr, addr_size, flags), + .ios, .macosx, .watchos, .tvos => { + assert(flags == 0); + break :func system.accept(sockfd, addr, addr_size); + }, + else => @compileError("accept not available for target"), + } + }; + switch (errno(rc)) { 0 => return @intCast(fd_t, rc), EINTR => continue, - EAGAIN => if (std.event.Loop.instance) |loop| { loop.waitUntilFdReadable(sockfd); continue; @@ -2333,7 +2374,6 @@ pub fn accept4( EOPNOTSUPP => unreachable, EPROTO => return error.ProtocolFailure, EPERM => return error.BlockedByFirewall, - else => |err| return unexpectedErrno(err), } } From 8a8beefa36da8257a328a86340d0d60e1bd569a6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 2 May 2020 17:36:28 -0400 Subject: [PATCH 2/2] solve the problem with Darwin shims in std.os instead * implement SOCK_NONBLOCK and SOCK_CLOEXEC Darwin shims in std.os * revert changes to std.net * remove os.accept and rename os.accept4 to os.accept --- lib/std/net.zig | 76 +++++-------------------- lib/std/os.zig | 112 ++++++++++++++++++++----------------- lib/std/os/bits/darwin.zig | 9 +++ 3 files changed, 85 insertions(+), 112 deletions(-) diff --git a/lib/std/net.zig b/lib/std/net.zig index cd1b1f3088..b9681bc618 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -358,44 +358,13 @@ pub const Address = extern union { } }; -fn getUnixSocketInitFlags() u16 { - comptime { - var flags = 0; - switch (builtin.os.tag) { - .linux, .freebsd, .netbsd, .dragonfly => { - flags |= os.SOCK_CLOEXEC; - flags |= if (std.io.is_async) os.SOCK_NONBLOCK else 0; - }, - else => {}, - } - - return flags; - } -} - -// These are primarily needed for UNIX-based platforms without -// SOCK_CLOEXEC and SOCK_NONBLOCK flags when creating sockets -// or accepting connections -fn setUnixSocketFlags(sock: os.fd_t) os.FcntlError!void { - var fdflags = try os.fcntl(sock, os.F_GETFD, 0); - fdflags |= os.FD_CLOEXEC; - _ = try os.fcntl(sock, os.F_SETFD, fdflags); - - if (std.io.is_async) { - var flflags = try os.fcntl(sock, os.F_GETFL, 0); - flflags |= os.O_NONBLOCK; - _ = try os.fcntl(sock, os.F_SETFL, fdflags); - } -} - pub fn connectUnixSocket(path: []const u8) !fs.File { - const flags = os.SOCK_STREAM | getUnixSocketInitFlags(); - const sockfd = try os.socket(os.AF_UNIX, flags, 0); - - if (comptime builtin.os.tag.isDarwin()) { - try setUnixSocketFlags(sockfd); - } - + const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0; + const sockfd = try os.socket( + os.AF_UNIX, + os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block, + 0, + ); errdefer os.close(sockfd); var addr = try std.net.Address.initUnix(path); @@ -437,13 +406,9 @@ pub fn tcpConnectToHost(allocator: *mem.Allocator, name: []const u8, port: u16) } pub fn tcpConnectToAddress(address: Address) !fs.File { - const sock_flags = os.SOCK_STREAM | getUnixSocketInitFlags(); + 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); - - if (comptime builtin.os.tag.isDarwin()) { - try setUnixSocketFlags(sockfd); - } - errdefer os.close(sockfd); try os.connect(sockfd, &address.any, address.getOsSockLen()); @@ -1347,14 +1312,11 @@ pub const StreamServer = struct { } pub fn listen(self: *StreamServer, address: Address) !void { - const flags = os.SOCK_STREAM | getUnixSocketInitFlags(); + const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0; + const sock_flags = os.SOCK_STREAM | os.SOCK_CLOEXEC | nonblock; const proto = if (address.any.family == os.AF_UNIX) @as(u32, 0) else os.IPPROTO_TCP; - const sockfd = try os.socket(address.any.family, flags, proto); - - if (comptime builtin.os.tag.isDarwin()) { - try setUnixSocketFlags(sockfd); - } + const sockfd = try os.socket(address.any.family, sock_flags, proto); self.sockfd = sockfd; errdefer { os.close(sockfd); @@ -1412,20 +1374,12 @@ pub const StreamServer = struct { }; /// If this function succeeds, the returned `Connection` is a caller-managed resource. - pub fn accept(self: *StreamServer) !Connection { + pub fn accept(self: *StreamServer) AcceptError!Connection { + const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0; + const accept_flags = nonblock | os.SOCK_CLOEXEC; var accepted_addr: Address = undefined; var adr_len: os.socklen_t = @sizeOf(Address); - var _accept: os.AcceptError!os.fd_t = undefined; - - if (comptime builtin.os.tag.isDarwin()) { - try setUnixSocketFlags(self.sockfd.?); - _accept = os.accept(self.sockfd.?, &accepted_addr.any, &adr_len); - } else { - const flags = getUnixSocketInitFlags(); - _accept = os.accept4(self.sockfd.?, &accepted_addr.any, &adr_len, flags); - } - - if (_accept) |fd| { + if (os.accept(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| { return Connection{ .file = fs.File{ .handle = fd }, .address = accepted_addr, diff --git a/lib/std/os.zig b/lib/std/os.zig index 99dee13dd0..ff7089ceb0 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2159,9 +2159,20 @@ pub const SocketError = error{ } || UnexpectedError; pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!fd_t { + const have_sock_flags = comptime !std.Target.current.isDarwin(); + const filtered_sock_type = if (!have_sock_flags) + socket_type & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC) + else + socket_type; const rc = system.socket(domain, socket_type, protocol); switch (errno(rc)) { - 0 => return @intCast(fd_t, rc), + 0 => { + const fd = @intCast(fd_t, rc); + if (!have_sock_flags and filtered_sock_type != socket_type) { + try setSockFlags(fd, socket_type); + } + return fd; + }, EACCES => return error.PermissionDenied, EAFNOSUPPORT => return error.AddressFamilyNotSupported, EINVAL => return error.ProtocolFamilyNotAvailable, @@ -2281,41 +2292,6 @@ pub const AcceptError = error{ WouldBlock, } || UnexpectedError; -/// Accept a connection on a socket. -/// If the application has a global event loop enabled, EAGAIN is handled -/// via the event loop. Otherwise EAGAIN results in error.WouldBlock. -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: 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 - /// respective protocol man pages). - addr: *sockaddr, - /// This argument is a value-result argument: the caller must initialize it to contain the - /// size (in bytes) of the structure pointed to by addr; on return it will contain the actual size - /// of the peer address. - /// - /// The returned address is truncated if the buffer provided is too small; in this case, `addr_size` - /// will return a value greater than was supplied to the call. - addr_size: *socklen_t, - /// If flags is 0, then `accept4` is the same as `accept`. The following values can be bitwise - /// ORed in flags to obtain different behavior: - /// * `SOCK_NONBLOCK` - Set the `O_NONBLOCK` file status flag on the open file description (see `open`) - /// referred to by the new file descriptor. Using this flag saves extra calls to `fcntl` to achieve - /// the same result. - /// * `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!fd_t { - if (comptime builtin.os.tag.isDarwin()) { - @compileError("accept4 not available for target Darwin, use accept"); - } - - return try _accept(sockfd, addr, addr_size, flags); -} - /// Accept a connection on a socket. /// If the application has a global event loop enabled, EAGAIN is handled /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. @@ -2335,26 +2311,31 @@ pub fn accept( /// The returned address is truncated if the buffer provided is too small; in this case, `addr_size` /// will return a value greater than was supplied to the call. addr_size: *socklen_t, + /// The following values can be bitwise ORed in flags to obtain different behavior: + /// * `SOCK_NONBLOCK` - Set the `O_NONBLOCK` file status flag on the open file description (see `open`) + /// referred to by the new file descriptor. Using this flag saves extra calls to `fcntl` to achieve + /// the same result. + /// * `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!fd_t { - return try _accept(sockfd, addr, addr_size, 0); -} + const have_accept4 = comptime !std.Target.current.isDarwin(); + assert(0 == (flags & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC))); // Unsupported flag(s) -fn _accept(sockfd: fd_t, addr: *sockaddr, addr_size: *socklen_t, flags: u32) AcceptError!fd_t { while (true) { - const rc = func: { - switch (comptime builtin.os.tag) { - .linux, .freebsd, .netbsd, .dragonfly => - break :func system.accept4(sockfd, addr, addr_size, flags), - .ios, .macosx, .watchos, .tvos => { - assert(flags == 0); - break :func system.accept(sockfd, addr, addr_size); - }, - else => @compileError("accept not available for target"), - } - }; + const rc = if (have_accept4) + system.accept4(sockfd, addr, addr_size, flags) + else + system.accept(sockfd, addr, addr_size); switch (errno(rc)) { - 0 => return @intCast(fd_t, rc), + 0 => { + const fd = @intCast(fd_t, rc); + if (!have_accept4 and flags != 0) { + try setSockFlags(fd, flags); + } + return fd; + }, EINTR => continue, EAGAIN => if (std.event.Loop.instance) |loop| { loop.waitUntilFdReadable(sockfd); @@ -3285,6 +3266,35 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize { } } +fn setSockFlags(fd: fd_t, flags: u32) !void { + { + var fd_flags = fcntl(fd, F_GETFD, 0) catch |err| switch (err) { + error.FileBusy => unreachable, + error.Locked => unreachable, + else => |e| return e, + }; + if ((flags & SOCK_NONBLOCK) != 0) fd_flags |= FD_CLOEXEC; + _ = fcntl(fd, F_SETFD, fd_flags) catch |err| switch (err) { + error.FileBusy => unreachable, + error.Locked => unreachable, + else => |e| return e, + }; + } + { + var fl_flags = fcntl(fd, F_GETFL, 0) catch |err| switch (err) { + error.FileBusy => unreachable, + error.Locked => unreachable, + else => |e| return e, + }; + if ((flags & SOCK_CLOEXEC) != 0) fl_flags |= O_NONBLOCK; + _ = fcntl(fd, F_SETFL, fl_flags) catch |err| switch (err) { + error.FileBusy => unreachable, + error.Locked => unreachable, + else => |e| return e, + }; + } +} + pub const FlockError = error{ WouldBlock, diff --git a/lib/std/os/bits/darwin.zig b/lib/std/os/bits/darwin.zig index 2ea9ae2cfa..d116d6157a 100644 --- a/lib/std/os/bits/darwin.zig +++ b/lib/std/os/bits/darwin.zig @@ -764,6 +764,15 @@ pub const SOCK_RDM = 4; pub const SOCK_SEQPACKET = 5; pub const SOCK_MAXADDRLEN = 255; +/// Not actually supported by Darwin, but Zig supplies a shim. +/// This numerical value is not ABI-stable. It need only not conflict +/// with any other "SOCK_" bits. +pub const SOCK_CLOEXEC = 1 << 15; +/// Not actually supported by Darwin, but Zig supplies a shim. +/// This numerical value is not ABI-stable. It need only not conflict +/// with any other "SOCK_" bits. +pub const SOCK_NONBLOCK = 1 << 16; + pub const IPPROTO_ICMP = 1; pub const IPPROTO_ICMPV6 = 58; pub const IPPROTO_TCP = 6;