Commit 07bee9da42

Cato <cato@cato.ninja>
2020-05-02 15:14:28
Fixed Darwin-incompatible socket flags and unavailable system calls
1 parent 03a7124
Changed files (3)
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;
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,
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),
         }
     }