Commit 8a8beefa36

Andrew Kelley <andrew@ziglang.org>
2020-05-02 23:36:28
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
1 parent 07bee9d
Changed files (3)
lib
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;
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,
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,
@@ -2284,7 +2295,7 @@ pub const AcceptError = error{
 /// 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(
+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,
@@ -2300,8 +2311,7 @@ pub fn accept4(
     /// 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:
+    /// 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.
@@ -2309,52 +2319,23 @@ 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");
-    }
+    const have_accept4 = comptime !std.Target.current.isDarwin();
+    assert(0 == (flags & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC))); // Unsupported flag(s)
 
-    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 = 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,