Commit c4c6c01cc5

Brandon Black <bblack@wikimedia.org>
2025-09-08 21:30:13
recvmsg: posix wrapper, void on windows
Also, added EPIPE to recvfrom() error set (it's a documented error for unix and tcp sockets, at least), which recvmsg() largely shares. Windows has an odd, callback-based form of recvmsg() that doesn't fit the normal interface here.
1 parent 79313d8
Changed files (2)
lib/std/c.zig
@@ -10779,7 +10779,15 @@ pub extern "c" fn recvfrom(
     noalias src_addr: ?*sockaddr,
     noalias addrlen: ?*socklen_t,
 ) if (native_os == .windows) c_int else isize;
-pub extern "c" fn recvmsg(sockfd: fd_t, msg: *msghdr, flags: u32) isize;
+
+pub const recvmsg = switch (native_os) {
+    // Windows: Technically, a form of recvmsg() exists for Windows, but the
+    // user has to install some kind of callback for it.  I'm not sure if/how
+    // we can map this to normal recvmsg() interface use.
+    // https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nc-mswsock-lpfn_wsarecvmsg
+    .windows => void,
+    else => private.recvmsg,
+};
 
 pub extern "c" fn kill(pid: pid_t, sig: c_int) c_int;
 
@@ -11424,6 +11432,7 @@ const private = struct {
     extern "c" fn pipe2(fds: *[2]fd_t, flags: O) c_int;
     extern "c" fn readdir(dir: *DIR) ?*dirent;
     extern "c" fn realpath(noalias file_name: [*:0]const u8, noalias resolved_name: [*]u8) ?[*:0]u8;
+    extern "c" fn recvmsg(sockfd: fd_t, msg: *msghdr, flags: u32) isize;
     extern "c" fn sched_yield() c_int;
     extern "c" fn sendfile(out_fd: fd_t, in_fd: fd_t, offset: ?*off_t, count: usize) isize;
     extern "c" fn sigaction(sig: c_int, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) c_int;
lib/std/posix.zig
@@ -6644,6 +6644,9 @@ pub const RecvFromError = error{
 
     /// The socket is not connected (connection-oriented sockets only).
     SocketNotConnected,
+
+    /// The other end closed the socket unexpectedly or a read is executed on a shut down socket
+    BrokenPipe,
 } || UnexpectedError;
 
 pub fn recv(sock: socket_t, buf: []u8, flags: u32) RecvFromError!usize {
@@ -6692,12 +6695,62 @@ pub fn recvfrom(
                 .CONNREFUSED => return error.ConnectionRefused,
                 .CONNRESET => return error.ConnectionResetByPeer,
                 .TIMEDOUT => return error.ConnectionTimedOut,
+                .PIPE => return error.BrokenPipe,
                 else => |err| return unexpectedErrno(err),
             }
         }
     }
 }
 
+pub const RecvMsgError = RecvFromError || error{
+    /// Reception of SCM_RIGHTS fds via ancillary data in msg.control would
+    /// exceed some system limit (generally this is retryable by trying to
+    /// receive fewer fds or closing some existing fds)
+    SystemFdQuotaExceeded,
+
+    /// Reception of SCM_RIGHTS fds via ancillary data in msg.control would
+    /// exceed some process limit (generally this is retryable by trying to
+    /// receive fewer fds, closing some existing fds, or changing the ulimit)
+    ProcessFdQuotaExceeded,
+};
+
+/// If `sockfd` is opened in non blocking mode, the function will
+/// return error.WouldBlock when EAGAIN is received.
+pub fn recvmsg(
+    /// The file descriptor of the sending socket.
+    sockfd: socket_t,
+    /// Message header and iovecs
+    msg: *msghdr,
+    flags: u32,
+) RecvMsgError!usize {
+    if (@TypeOf(system.recvmsg) == void)
+        @compileError("recvmsg() not supported on this OS");
+    while (true) {
+        const rc = system.recvmsg(sockfd, msg, flags);
+        switch (errno(rc)) {
+            .SUCCESS => return @intCast(rc),
+            .AGAIN => return error.WouldBlock,
+            .BADF => unreachable, // always a race condition
+            .NFILE => return error.SystemFdQuotaExceeded,
+            .MFILE => return error.ProcessFdQuotaExceeded,
+            .INTR => continue,
+            .FAULT => unreachable, // An invalid user space address was specified for an argument.
+            .INVAL => unreachable, // Invalid argument passed.
+            .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
+            .NOBUFS => return error.SystemResources,
+            .NOMEM => return error.SystemResources,
+            .NOTCONN => return error.SocketNotConnected,
+            .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
+            .MSGSIZE => return error.MessageTooBig,
+            .PIPE => return error.BrokenPipe,
+            .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
+            .CONNRESET => return error.ConnectionResetByPeer,
+            .NETDOWN => return error.NetworkSubsystemFailed,
+            else => |err| return unexpectedErrno(err),
+        }
+    }
+}
+
 pub const DnExpandError = error{InvalidDnsPacket};
 
 pub fn dn_expand(