[libc-commits] [libc] [libc] Implement recvmmsg (on linux) (PR #202328)

Pavel Labath via libc-commits libc-commits at lists.llvm.org
Tue Jun 9 02:06:07 PDT 2026


https://github.com/labath updated https://github.com/llvm/llvm-project/pull/202328

>From 77b13f939b94182ba26ee90b58704fc4bf328453 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Mon, 8 Jun 2026 08:03:38 +0000
Subject: [PATCH 1/2] [libc] Implement recvmmsg (on linux)

I've implemented the recvmmsg system call wrapper and entrypoint for Linux.
The function is declared in sys/socket.h and conforms to the Linux specification.

While in there:
- renamed the sendmmsg_test.cpp unit test to sendrecvmmsg_test.cpp
  to test both functions using a socketpair
- updated the yaml config and docgen files to include the new function
  and the struct_timespec type dependency

Assisted by Gemini.
---
 libc/config/linux/aarch64/entrypoints.txt     |  1 +
 libc/config/linux/riscv/entrypoints.txt       |  1 +
 libc/config/linux/x86_64/entrypoints.txt      |  1 +
 libc/include/sys/socket.yaml                  | 11 +++++
 .../linux/syscall_wrappers/CMakeLists.txt     | 14 ++++++
 .../OSUtil/linux/syscall_wrappers/recvmmsg.h  | 46 +++++++++++++++++
 libc/src/sys/socket/CMakeLists.txt            |  8 +++
 libc/src/sys/socket/linux/CMakeLists.txt      | 14 ++++++
 libc/src/sys/socket/linux/recvmmsg.cpp        | 32 ++++++++++++
 libc/src/sys/socket/recvmmsg.h                | 28 +++++++++++
 libc/test/src/sys/socket/linux/CMakeLists.txt |  7 +--
 ...endmmsg_test.cpp => sendrecvmmsg_test.cpp} | 49 ++++++++++++-------
 libc/utils/docgen/sys/socket.yaml             |  2 +
 13 files changed, 194 insertions(+), 20 deletions(-)
 create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/recvmmsg.h
 create mode 100644 libc/src/sys/socket/linux/recvmmsg.cpp
 create mode 100644 libc/src/sys/socket/recvmmsg.h
 rename libc/test/src/sys/socket/linux/{sendmmsg_test.cpp => sendrecvmmsg_test.cpp} (60%)

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 590b8c19bf1ef..92e260e475b51 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -302,6 +302,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.sys.socket.socketpair
     # TODO: These functions are not compatible with 64-bit musl. They need to
     # be excluded if overlaying musl.
+    libc.src.sys.socket.recvmmsg
     libc.src.sys.socket.recvmsg
     libc.src.sys.socket.sendmmsg
     libc.src.sys.socket.sendmsg
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 5cea007a6c3ae..12466a10d7f52 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -302,6 +302,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.sys.socket.socketpair
     # TODO: These functions are not compatible with 64-bit musl. They need to
     # be excluded if overlaying musl.
+    libc.src.sys.socket.recvmmsg
     libc.src.sys.socket.recvmsg
     libc.src.sys.socket.sendmmsg
     libc.src.sys.socket.sendmsg
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 0bf88f504cd3c..559988dc2b3da 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -328,6 +328,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.sys.socket.socketpair
     # TODO: These functions are not compatible with 64-bit musl. They need to
     # be excluded if overlaying musl.
+    libc.src.sys.socket.recvmmsg
     libc.src.sys.socket.recvmsg
     libc.src.sys.socket.sendmmsg
     libc.src.sys.socket.sendmsg
diff --git a/libc/include/sys/socket.yaml b/libc/include/sys/socket.yaml
index c4f4b0fc58fce..18b8b9e750aa7 100644
--- a/libc/include/sys/socket.yaml
+++ b/libc/include/sys/socket.yaml
@@ -11,6 +11,7 @@ types:
   - type_name: sa_family_t
   - type_name: struct_cmsghdr
   - type_name: struct_mmsghdr
+  - type_name: struct_timespec
   - type_name: struct_msghdr
   - type_name: struct_iovec
   - type_name: struct_linger
@@ -105,6 +106,16 @@ functions:
       - type: int
       - type: struct sockaddr *__restrict
       - type: socklen_t *__restrict
+  - name: recvmmsg
+    standards:
+      - linux
+    return_type: int
+    arguments:
+      - type: int
+      - type: struct mmsghdr *
+      - type: unsigned int
+      - type: int
+      - type: struct timespec *
   - name: recvmsg
     standards:
       - posix
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index 4fc8c4eb3b67a..97848b1848660 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -175,6 +175,20 @@ add_header_library(
     libc.include.sys_syscall
 )
 
+add_header_library(
+  recvmmsg
+  HDRS
+    recvmmsg.h
+  DEPENDS
+    libc.src.__support.OSUtil.osutil
+    libc.src.__support.common
+    libc.src.__support.error_or
+    libc.src.__support.macros.config
+    libc.hdr.types.struct_mmsghdr
+    libc.hdr.types.struct_timespec
+    libc.include.sys_syscall
+)
+
 add_header_library(
   recvmsg
   HDRS
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/recvmmsg.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/recvmmsg.h
new file mode 100644
index 0000000000000..4faebc99f90b5
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/recvmmsg.h
@@ -0,0 +1,46 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Syscall wrapper for recvmmsg.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_RECVMMSG_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_RECVMMSG_H
+
+#include "hdr/types/struct_mmsghdr.h"
+#include "hdr/types/struct_timespec.h"
+#include "src/__support/OSUtil/linux/syscall.h" // For syscall_checked
+#include "src/__support/macros/config.h"
+#include <sys/syscall.h> // For syscall numbers
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+LIBC_INLINE ErrorOr<int> recvmmsg(int sockfd, struct mmsghdr *msgvec,
+                                  unsigned int vlen, int flags,
+                                  struct timespec *timeout) {
+#ifdef SYS_recvmmsg_time64
+  return syscall_checked<int>(SYS_recvmmsg_time64, sockfd, msgvec, vlen, flags,
+                              timeout);
+#else
+  static_assert(
+      sizeof(timespec::tv_nsec) == sizeof(long),
+      "This legacy syscall fallback is only safe on platforms where tv_nsec "
+      "matches the register size (long). It is unsafe on 32-bit platforms "
+      "with 64-bit tv_nsec.");
+  return syscall_checked<int>(SYS_recvmmsg, sockfd, msgvec, vlen, flags,
+                              timeout);
+#endif
+}
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_RECVMMSG_H
diff --git a/libc/src/sys/socket/CMakeLists.txt b/libc/src/sys/socket/CMakeLists.txt
index 22b665392a4d6..45c0aee6f6f3b 100644
--- a/libc/src/sys/socket/CMakeLists.txt
+++ b/libc/src/sys/socket/CMakeLists.txt
@@ -128,6 +128,14 @@ add_entrypoint_object(
     .${LIBC_TARGET_OS}.recvmsg
 )
 
+add_entrypoint_object(
+  recvmmsg
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_OS}.recvmmsg
+)
+
+
 add_entrypoint_object(
   shutdown
   ALIAS
diff --git a/libc/src/sys/socket/linux/CMakeLists.txt b/libc/src/sys/socket/linux/CMakeLists.txt
index b8ff28f94dd2c..82a7554f80107 100644
--- a/libc/src/sys/socket/linux/CMakeLists.txt
+++ b/libc/src/sys/socket/linux/CMakeLists.txt
@@ -236,6 +236,20 @@ add_entrypoint_object(
     libc.src.errno.errno
 )
 
+add_entrypoint_object(
+  recvmmsg
+  SRCS
+    recvmmsg.cpp
+  HDRS
+    ../recvmmsg.h
+  DEPENDS
+    libc.hdr.types.struct_mmsghdr
+    libc.hdr.types.struct_timespec
+    libc.src.__support.common
+    libc.src.__support.OSUtil.linux.syscall_wrappers.recvmmsg
+    libc.src.errno.errno
+)
+
 add_entrypoint_object(
   recvmsg
   SRCS
diff --git a/libc/src/sys/socket/linux/recvmmsg.cpp b/libc/src/sys/socket/linux/recvmmsg.cpp
new file mode 100644
index 0000000000000..509c521e921a2
--- /dev/null
+++ b/libc/src/sys/socket/linux/recvmmsg.cpp
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Linux implementation of recvmmsg.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/sys/socket/recvmmsg.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/recvmmsg.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, recvmmsg,
+                   (int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
+                    int flags, struct timespec *timeout)) {
+  auto result = linux_syscalls::recvmmsg(sockfd, msgvec, vlen, flags, timeout);
+  if (!result.has_value()) {
+    libc_errno = result.error();
+    return -1;
+  }
+  return result.value();
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/sys/socket/recvmmsg.h b/libc/src/sys/socket/recvmmsg.h
new file mode 100644
index 0000000000000..7dc12b25a1d27
--- /dev/null
+++ b/libc/src/sys/socket/recvmmsg.h
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Declaration of the recvmmsg function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_SYS_SOCKET_RECVMMSG_H
+#define LLVM_LIBC_SRC_SYS_SOCKET_RECVMMSG_H
+
+#include "hdr/types/struct_mmsghdr.h"
+#include "hdr/types/struct_timespec.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, int flags,
+             struct timespec *timeout);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SYS_SOCKET_RECVMMSG_H
diff --git a/libc/test/src/sys/socket/linux/CMakeLists.txt b/libc/test/src/sys/socket/linux/CMakeLists.txt
index bcda617c4b28d..c14a9f3fbe353 100644
--- a/libc/test/src/sys/socket/linux/CMakeLists.txt
+++ b/libc/test/src/sys/socket/linux/CMakeLists.txt
@@ -205,19 +205,20 @@ add_libc_unittest(
 )
 
 add_libc_unittest(
-  sendmmsg_test
+  sendrecvmmsg_test
   SUITE
     libc_sys_socket_unittests
   SRCS
-    sendmmsg_test.cpp
+    sendrecvmmsg_test.cpp
   DEPENDS
     libc.hdr.sys_socket_macros
     libc.hdr.types.struct_mmsghdr
+    libc.hdr.types.struct_timespec
     libc.src.errno.errno
     libc.src.string.strlen
     libc.src.sys.socket.socketpair
     libc.src.sys.socket.sendmmsg
-    libc.src.sys.socket.recvmsg
+    libc.src.sys.socket.recvmmsg
     libc.src.unistd.close
     libc.src.__support.CPP.scope
     libc.test.UnitTest.ErrnoCheckingTest
diff --git a/libc/test/src/sys/socket/linux/sendmmsg_test.cpp b/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp
similarity index 60%
rename from libc/test/src/sys/socket/linux/sendmmsg_test.cpp
rename to libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp
index adbfe20205b8f..d932b27cd61d7 100644
--- a/libc/test/src/sys/socket/linux/sendmmsg_test.cpp
+++ b/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp
@@ -7,15 +7,16 @@
 //===----------------------------------------------------------------------===//
 ///
 /// \file
-/// Unit tests for sendmmsg.
+/// Unit tests for sendmmsg and recvmmsg.
 ///
 //===----------------------------------------------------------------------===//
 
 #include "hdr/sys_socket_macros.h"
 #include "hdr/types/struct_mmsghdr.h"
+#include "hdr/types/struct_timespec.h"
 #include "src/__support/CPP/scope.h"
 #include "src/string/strlen.h"
-#include "src/sys/socket/recvmsg.h"
+#include "src/sys/socket/recvmmsg.h"
 #include "src/sys/socket/sendmmsg.h"
 #include "src/sys/socket/socketpair.h"
 #include "src/unistd/close.h"
@@ -26,9 +27,9 @@
 
 using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
 using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
-using LlvmLibcSendMmsgTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+using LlvmLibcSendRecvMmsgTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
 
-TEST_F(LlvmLibcSendMmsgTest, SendMmsgSucceedsWithSocketPair) {
+TEST_F(LlvmLibcSendRecvMmsgTest, SendRecvMmsgSucceedsWithSocketPair) {
   const char *const TEST_MESSAGES[] = {"message one", "message two"};
   const size_t MESSAGES_COUNT = 2;
 
@@ -60,25 +61,39 @@ TEST_F(LlvmLibcSendMmsgTest, SendMmsgSucceedsWithSocketPair) {
               LIBC_NAMESPACE::strlen(TEST_MESSAGES[i]) + 1);
   }
 
+  char recv_buffers[MESSAGES_COUNT][256] = {};
+  struct iovec recv_msg_vec[MESSAGES_COUNT] = {};
+  struct mmsghdr recv_msg_hdr[MESSAGES_COUNT] = {};
   for (size_t i = 0; i < MESSAGES_COUNT; ++i) {
-    char recv_buffer[256] = {};
-    struct iovec recv_msg_vec;
-    recv_msg_vec.iov_base = reinterpret_cast<void *>(recv_buffer);
-    recv_msg_vec.iov_len = sizeof(recv_buffer);
+    recv_msg_vec[i].iov_base = reinterpret_cast<void *>(recv_buffers[i]);
+    recv_msg_vec[i].iov_len = sizeof(recv_buffers[i]);
+    recv_msg_hdr[i].msg_hdr.msg_iov = &recv_msg_vec[i];
+    recv_msg_hdr[i].msg_hdr.msg_iovlen = 1;
+  }
+
+  struct timespec invalid_timeout = {-1, 0};
+  ASSERT_THAT(LIBC_NAMESPACE::recvmmsg(sockpair[1], recv_msg_hdr,
+                                       MESSAGES_COUNT, 0, &invalid_timeout),
+              Fails<int>(EINVAL));
 
-    struct msghdr recv_msg_hdr = {};
-    recv_msg_hdr.msg_iov = &recv_msg_vec;
-    recv_msg_hdr.msg_iovlen = 1;
+  ASSERT_THAT(LIBC_NAMESPACE::recvmmsg(sockpair[1], recv_msg_hdr,
+                                       MESSAGES_COUNT, 0, nullptr),
+              Succeeds(static_cast<int>(MESSAGES_COUNT)));
 
-    ASSERT_THAT(LIBC_NAMESPACE::recvmsg(sockpair[1], &recv_msg_hdr, 0),
-                Succeeds(static_cast<ssize_t>(
-                    LIBC_NAMESPACE::strlen(TEST_MESSAGES[i]) + 1)));
-    ASSERT_STREQ(recv_buffer, TEST_MESSAGES[i]);
+  for (size_t i = 0; i < MESSAGES_COUNT; ++i) {
+    ASSERT_EQ(static_cast<size_t>(recv_msg_hdr[i].msg_len),
+              LIBC_NAMESPACE::strlen(TEST_MESSAGES[i]) + 1);
+    ASSERT_STREQ(recv_buffers[i], TEST_MESSAGES[i]);
   }
 }
 
-TEST_F(LlvmLibcSendMmsgTest, SendMmsgFails) {
+TEST_F(LlvmLibcSendRecvMmsgTest, SendMmsgFails) {
   struct mmsghdr msg_hdrs = {};
-
   ASSERT_THAT(LIBC_NAMESPACE::sendmmsg(-1, &msg_hdrs, 1, 0), Fails(EBADF, -1));
 }
+
+TEST_F(LlvmLibcSendRecvMmsgTest, RecvmmsgFails) {
+  struct mmsghdr msg_hdrs = {};
+  ASSERT_THAT(LIBC_NAMESPACE::recvmmsg(-1, &msg_hdrs, 1, 0, nullptr),
+              Fails(EBADF, -1));
+}
diff --git a/libc/utils/docgen/sys/socket.yaml b/libc/utils/docgen/sys/socket.yaml
index 1c9af64a76331..e8a250453df4a 100644
--- a/libc/utils/docgen/sys/socket.yaml
+++ b/libc/utils/docgen/sys/socket.yaml
@@ -122,6 +122,8 @@ functions:
     in-latest-posix: ''
   recvfrom:
     in-latest-posix: ''
+  recvmmsg:
+    c-definition: ''
   recvmsg:
     in-latest-posix: ''
   send:

>From 032ea09fa51d8ad95d35e976402f9bea57e3b673 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Tue, 9 Jun 2026 09:05:41 +0000
Subject: [PATCH 2/2] less casts

---
 libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp b/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp
index d932b27cd61d7..834713dde07b1 100644
--- a/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp
+++ b/libc/test/src/sys/socket/linux/sendrecvmmsg_test.cpp
@@ -45,8 +45,7 @@ TEST_F(LlvmLibcSendRecvMmsgTest, SendRecvMmsgSucceedsWithSocketPair) {
   struct iovec send_msg_vec[MESSAGES_COUNT] = {};
   struct mmsghdr send_msg_hdr[MESSAGES_COUNT] = {};
   for (size_t i = 0; i < MESSAGES_COUNT; ++i) {
-    send_msg_vec[i].iov_base =
-        reinterpret_cast<void *>(const_cast<char *>(TEST_MESSAGES[i]));
+    send_msg_vec[i].iov_base = const_cast<char *>(TEST_MESSAGES[i]);
     send_msg_vec[i].iov_len = LIBC_NAMESPACE::strlen(TEST_MESSAGES[i]) + 1;
     send_msg_hdr[i].msg_hdr.msg_iov = &send_msg_vec[i];
     send_msg_hdr[i].msg_hdr.msg_iovlen = 1;
@@ -54,7 +53,7 @@ TEST_F(LlvmLibcSendRecvMmsgTest, SendRecvMmsgSucceedsWithSocketPair) {
 
   ASSERT_THAT(
       LIBC_NAMESPACE::sendmmsg(sockpair[0], send_msg_hdr, MESSAGES_COUNT, 0),
-      Succeeds(static_cast<int>(MESSAGES_COUNT)));
+      Succeeds<int>(MESSAGES_COUNT));
 
   for (size_t i = 0; i < MESSAGES_COUNT; ++i) {
     ASSERT_EQ(static_cast<size_t>(send_msg_hdr[i].msg_len),
@@ -65,7 +64,7 @@ TEST_F(LlvmLibcSendRecvMmsgTest, SendRecvMmsgSucceedsWithSocketPair) {
   struct iovec recv_msg_vec[MESSAGES_COUNT] = {};
   struct mmsghdr recv_msg_hdr[MESSAGES_COUNT] = {};
   for (size_t i = 0; i < MESSAGES_COUNT; ++i) {
-    recv_msg_vec[i].iov_base = reinterpret_cast<void *>(recv_buffers[i]);
+    recv_msg_vec[i].iov_base = recv_buffers[i];
     recv_msg_vec[i].iov_len = sizeof(recv_buffers[i]);
     recv_msg_hdr[i].msg_hdr.msg_iov = &recv_msg_vec[i];
     recv_msg_hdr[i].msg_hdr.msg_iovlen = 1;
@@ -78,7 +77,7 @@ TEST_F(LlvmLibcSendRecvMmsgTest, SendRecvMmsgSucceedsWithSocketPair) {
 
   ASSERT_THAT(LIBC_NAMESPACE::recvmmsg(sockpair[1], recv_msg_hdr,
                                        MESSAGES_COUNT, 0, nullptr),
-              Succeeds(static_cast<int>(MESSAGES_COUNT)));
+              Succeeds<int>(MESSAGES_COUNT));
 
   for (size_t i = 0; i < MESSAGES_COUNT; ++i) {
     ASSERT_EQ(static_cast<size_t>(recv_msg_hdr[i].msg_len),



More information about the libc-commits mailing list