[libc-commits] [libc] [libc] Add if_nameindex and if_freenameindex as an experimental entrypoint (PR #208438)
Pavel Labath via libc-commits
libc-commits at lists.llvm.org
Thu Jul 9 06:23:36 PDT 2026
https://github.com/labath updated https://github.com/llvm/llvm-project/pull/208438
>From 03edc668e2f1d491ab63b6c6535b49f054efcc3c Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Tue, 7 Jul 2026 14:21:52 +0000
Subject: [PATCH 1/2] [libc] Add if_nameindex and if_freenameindex as an
experimental entry point
This is a fairly complex function, so this patch implements bare minimum
needed to demonstrate the overall direction. The functionality is
implemented by communicating with the linux kernel over a netlink
socket, and the complexity comes from parsing the messages. In this
patch, parsing stops at the first interface, and we don't handle
multiple messages or deduplication resulting from restarted dumps.
Some notable implementation choices are:
- using a single allocation (including both the interface vector and the
strings it points to). This makes deallocation fast, reduces heap
fragmentation, and doesn't make the final code much more complex since
we still need auxiliary data structures for deduplication.
- using linux kernel headers for all the netlink structures. I think
this is fine as we're not exposing that to the user.
- while this information is available via /sys, that would mean we
require /sys to be mounted, and it wouldn't make the code much
simpler. This also matches other libc implementations.
Testing is a completely separate story, and the main source of
complexity of this patch. To make the code testable without requiring
root or live interfaces, the core function
(`net::if_nameindex<Policy>()`) is templated on a syscall policy class
(`NetworkSyscallPolicy`). The production entrypoint uses
`DefaultNetworkSyscallPolicy` forwarding directly to syscall wrappers
(`socket`, `sendto`, `recvfrom`, `close`), while the unit tests use
`FakeNetworkSyscallPolicy` to feed crafted netlink buffers from memory
and verify `sendto` request formation.
The fake policy is set up such that one can program the mock to return
certain values, and validate arguments. It does not resemble the
googletest functionality (mocks) very closely, but I've tried to put it
in a form where it is possible to morph it into that -- if that's the
direction we desire to go.
While doing this, I ran into the limitations of various container
classes (mainly the inability to handle non-trivial types). To avoid
growing the scope of this even further, I worked around those
limitations locally, and left TODOs which I intend to resolve in
follow-ups. The only thing I couldn't avoid is the addition of
cpp::expected default constructor (which is actually a consequence of
how FixedVector constructs elements). However, this is a simple addition
and it is consistent with the STL class it is emulating.
---
libc/config/linux/aarch64/entrypoints.txt | 4 +
libc/config/linux/riscv/entrypoints.txt | 4 +
libc/config/linux/x86_64/entrypoints.txt | 4 +
libc/hdr/types/CMakeLists.txt | 9 +
libc/hdr/types/struct_if_nameindex.h | 26 +
libc/include/CMakeLists.txt | 1 +
.../linux/sys-socket-macros.h | 12 +-
libc/include/llvm-libc-types/CMakeLists.txt | 1 +
.../llvm-libc-types/struct_if_nameindex.h | 22 +
libc/include/net/if.yaml | 12 +
libc/src/__support/CPP/expected.h | 1 +
.../src/__support/OSUtil/linux/CMakeLists.txt | 16 +
.../OSUtil/linux/network_syscall_policy.h | 58 ++
libc/src/net/CMakeLists.txt | 14 +
libc/src/net/if_freenameindex.h | 26 +
libc/src/net/if_nameindex.h | 26 +
libc/src/net/linux/CMakeLists.txt | 46 ++
libc/src/net/linux/if_freenameindex.cpp | 26 +
libc/src/net/linux/if_nameindex.cpp | 32 ++
libc/src/net/linux/if_nameindex.h | 142 +++++
libc/test/src/net/linux/CMakeLists.txt | 27 +
libc/test/src/net/linux/if_nameindex_test.cpp | 514 ++++++++++++++++++
22 files changed, 1018 insertions(+), 5 deletions(-)
create mode 100644 libc/hdr/types/struct_if_nameindex.h
create mode 100644 libc/include/llvm-libc-types/struct_if_nameindex.h
create mode 100644 libc/src/__support/OSUtil/linux/network_syscall_policy.h
create mode 100644 libc/src/net/if_freenameindex.h
create mode 100644 libc/src/net/if_nameindex.h
create mode 100644 libc/src/net/linux/if_freenameindex.cpp
create mode 100644 libc/src/net/linux/if_nameindex.cpp
create mode 100644 libc/src/net/linux/if_nameindex.h
create mode 100644 libc/test/src/net/linux/if_nameindex_test.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index af89add35f01e..8387e7f08b993 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1311,6 +1311,10 @@ endif()
if(LLVM_LIBC_ENABLE_EXPERIMENTAL_ENTRYPOINTS)
if(LLVM_LIBC_FULL_BUILD)
list(APPEND TARGET_LIBC_ENTRYPOINTS
+ # net/if.h entrypoints
+ libc.src.net.if_freenameindex
+ libc.src.net.if_nameindex
+
# regex.h entrypoints
libc.src.regex.regcomp
libc.src.regex.regexec
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 15e774fb453b7..9a8fbd7344ec8 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1445,6 +1445,10 @@ endif()
if(LLVM_LIBC_ENABLE_EXPERIMENTAL_ENTRYPOINTS)
if(LLVM_LIBC_FULL_BUILD)
list(APPEND TARGET_LIBC_ENTRYPOINTS
+ # net/if.h entrypoints
+ libc.src.net.if_freenameindex
+ libc.src.net.if_nameindex
+
# regex.h entrypoints
libc.src.regex.regcomp
libc.src.regex.regexec
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index ae457e1cb79e0..290653a1f01e8 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1541,6 +1541,10 @@ if(LLVM_LIBC_ENABLE_EXPERIMENTAL_ENTRYPOINTS)
if(LLVM_LIBC_FULL_BUILD)
list(APPEND TARGET_LIBC_ENTRYPOINTS
+ # net/if.h entrypoints
+ libc.src.net.if_freenameindex
+ libc.src.net.if_nameindex
+
# regex.h entrypoints
libc.src.regex.regcomp
libc.src.regex.regexec
diff --git a/libc/hdr/types/CMakeLists.txt b/libc/hdr/types/CMakeLists.txt
index 5a6b17ef67f2d..8f61cca3364ae 100644
--- a/libc/hdr/types/CMakeLists.txt
+++ b/libc/hdr/types/CMakeLists.txt
@@ -547,6 +547,15 @@ add_proxy_header_library(
libc.include.net_if
)
+add_proxy_header_library(
+ struct_if_nameindex
+ HDRS
+ struct_if_nameindex.h
+ FULL_BUILD_DEPENDS
+ libc.include.llvm-libc-types.struct_if_nameindex
+ libc.include.net_if
+)
+
add_proxy_header_library(
struct_sockaddr
HDRS
diff --git a/libc/hdr/types/struct_if_nameindex.h b/libc/hdr/types/struct_if_nameindex.h
new file mode 100644
index 0000000000000..569e33a7c53ed
--- /dev/null
+++ b/libc/hdr/types/struct_if_nameindex.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Proxy for struct if_nameindex.
+///
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_LIBC_HDR_TYPES_STRUCT_IF_NAMEINDEX_H
+#define LLVM_LIBC_HDR_TYPES_STRUCT_IF_NAMEINDEX_H
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/struct_if_nameindex.h"
+
+#else
+
+#include <net/if.h>
+
+#endif // LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_STRUCT_IF_NAMEINDEX_H
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index 57310e6c6291b..053df1b63f256 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -905,6 +905,7 @@ add_header_macro(
DEPENDS
.llvm_libc_common_h
.llvm-libc-macros.net_if_macros
+ .llvm-libc-types.struct_if_nameindex
.llvm-libc-types.struct_ifreq
)
diff --git a/libc/include/llvm-libc-macros/linux/sys-socket-macros.h b/libc/include/llvm-libc-macros/linux/sys-socket-macros.h
index 5478521be02b6..2c7d55c114328 100644
--- a/libc/include/llvm-libc-macros/linux/sys-socket-macros.h
+++ b/libc/include/llvm-libc-macros/linux/sys-socket-macros.h
@@ -12,11 +12,12 @@
// IEEE Std 1003.1-2017 - basedefs/sys_socket.h.html
// Macro values come from the Linux syscall interface.
-#define AF_UNSPEC 0 // Unspecified
-#define AF_UNIX 1 // Unix domain sockets
-#define AF_LOCAL 1 // POSIX name for AF_UNIX
-#define AF_INET 2 // Internet IPv4 Protocol
-#define AF_INET6 10 // IP version 6
+#define AF_UNSPEC 0 // Unspecified
+#define AF_UNIX 1 // Unix domain sockets
+#define AF_LOCAL 1 // POSIX name for AF_UNIX
+#define AF_INET 2 // Internet IPv4 Protocol
+#define AF_INET6 10 // IP version 6
+#define AF_NETLINK 16 // Netlink sockets
// 4.2BSD protocol families
#define PF_UNSPEC AF_UNSPEC
@@ -24,6 +25,7 @@
#define PF_LOCAL AF_LOCAL
#define PF_INET AF_INET
#define PF_INET6 AF_INET6
+#define PF_NETLINK AF_NETLINK
#define SOCK_STREAM 1
#define SOCK_DGRAM 2
diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index 5ea8ca0634834..855af87d35429 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -227,6 +227,7 @@ add_header(
add_header(struct_sockaddr_storage HDR struct_sockaddr_storage.h DEPENDS .sa_family_t)
add_header(struct_sockaddr_un HDR struct_sockaddr_un.h DEPENDS .sa_family_t)
add_header(struct_ifreq HDR struct_ifreq.h DEPENDS libc.include.llvm-libc-macros.net_if_macros .struct_sockaddr)
+add_header(struct_if_nameindex HDR struct_if_nameindex.h)
add_header(struct_iovec HDR struct_iovec.h DEPENDS .size_t)
add_header(struct_linger HDR struct_linger.h)
add_header(struct_cmsghdr HDR struct_cmsghdr.h DEPENDS .size_t)
diff --git a/libc/include/llvm-libc-types/struct_if_nameindex.h b/libc/include/llvm-libc-types/struct_if_nameindex.h
new file mode 100644
index 0000000000000..c876a0ece541b
--- /dev/null
+++ b/libc/include/llvm-libc-types/struct_if_nameindex.h
@@ -0,0 +1,22 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Definition of struct if_nameindex.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TYPES_STRUCT_IF_NAMEINDEX_H
+#define LLVM_LIBC_TYPES_STRUCT_IF_NAMEINDEX_H
+
+struct if_nameindex {
+ unsigned int if_index;
+ char *if_name;
+};
+
+#endif // LLVM_LIBC_TYPES_STRUCT_IF_NAMEINDEX_H
diff --git a/libc/include/net/if.yaml b/libc/include/net/if.yaml
index 51034d84e4428..3b6d807e2fceb 100644
--- a/libc/include/net/if.yaml
+++ b/libc/include/net/if.yaml
@@ -6,9 +6,16 @@ macros:
macro_header: net-if-macros.h
types:
- type_name: struct_ifreq
+ - type_name: struct_if_nameindex
enums: []
objects: []
functions:
+ - name: if_freenameindex
+ standards:
+ - posix
+ return_type: void
+ arguments:
+ - type: struct if_nameindex *
- name: if_indextoname
standards:
- posix
@@ -16,6 +23,11 @@ functions:
arguments:
- type: unsigned int
- type: char *
+ - name: if_nameindex
+ standards:
+ - posix
+ return_type: struct if_nameindex *
+ arguments: []
- name: if_nametoindex
standards:
- posix
diff --git a/libc/src/__support/CPP/expected.h b/libc/src/__support/CPP/expected.h
index 8a93091f0ebfb..a8fac975b7478 100644
--- a/libc/src/__support/CPP/expected.h
+++ b/libc/src/__support/CPP/expected.h
@@ -35,6 +35,7 @@ template <class T, class E> class expected {
bool is_expected;
public:
+ LIBC_INLINE constexpr expected() : exp(), is_expected(true) {}
LIBC_INLINE constexpr expected(T exp) : exp(exp), is_expected(true) {}
LIBC_INLINE constexpr expected(unexpected<E> unexp)
: unexp(unexp.error()), is_expected(false) {}
diff --git a/libc/src/__support/OSUtil/linux/CMakeLists.txt b/libc/src/__support/OSUtil/linux/CMakeLists.txt
index 1f11067f0154f..de9d8f4f477b7 100644
--- a/libc/src/__support/OSUtil/linux/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/CMakeLists.txt
@@ -44,6 +44,22 @@ add_header_library(
libc.src.__support.threads.callonce
)
+add_header_library(
+ network_syscall_policy
+ HDRS
+ network_syscall_policy.h
+ DEPENDS
+ libc.hdr.types.socklen_t
+ libc.hdr.types.ssize_t
+ libc.hdr.types.struct_sockaddr
+ libc.src.__support.OSUtil.linux.syscall_wrappers.close
+ libc.src.__support.OSUtil.linux.syscall_wrappers.recvfrom
+ libc.src.__support.OSUtil.linux.syscall_wrappers.sendto
+ libc.src.__support.OSUtil.linux.syscall_wrappers.socket
+ libc.src.__support.common
+ libc.src.__support.error_or
+)
+
add_header_library(
path
HDRS
diff --git a/libc/src/__support/OSUtil/linux/network_syscall_policy.h b/libc/src/__support/OSUtil/linux/network_syscall_policy.h
new file mode 100644
index 0000000000000..f02111800db3a
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/network_syscall_policy.h
@@ -0,0 +1,58 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Policy class wrapper for network-related system calls.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_NETWORK_SYSCALL_POLICY_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_NETWORK_SYSCALL_POLICY_H
+
+#include "hdr/types/socklen_t.h"
+#include "hdr/types/ssize_t.h"
+#include "hdr/types/struct_sockaddr.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/recvfrom.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/sendto.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/socket.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace net {
+
+struct DefaultNetworkSyscallPolicy {
+ LIBC_INLINE static ErrorOr<int> socket(int domain, int type, int protocol) {
+ return linux_syscalls::socket(domain, type, protocol);
+ }
+
+ LIBC_INLINE static ErrorOr<ssize_t> sendto(int fd, const void *buf,
+ size_t len, int flags,
+ const struct sockaddr *dest_addr,
+ socklen_t addrlen) {
+ return linux_syscalls::sendto(fd, buf, len, flags, dest_addr, addrlen);
+ }
+
+ LIBC_INLINE static ErrorOr<ssize_t> recvfrom(int fd, void *buf, size_t len,
+ int flags,
+ struct sockaddr *src_addr,
+ socklen_t *addrlen) {
+ return linux_syscalls::recvfrom(fd, buf, len, flags, src_addr, addrlen);
+ }
+
+ LIBC_INLINE static ErrorOr<int> close(int fd) {
+ return linux_syscalls::close(fd);
+ }
+};
+
+} // namespace net
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_NETWORK_SYSCALL_POLICY_H
diff --git a/libc/src/net/CMakeLists.txt b/libc/src/net/CMakeLists.txt
index 2173a111cff4f..2860ba2be02cb 100644
--- a/libc/src/net/CMakeLists.txt
+++ b/libc/src/net/CMakeLists.txt
@@ -15,3 +15,17 @@ add_entrypoint_object(
DEPENDS
.${LIBC_TARGET_OS}.if_nametoindex
)
+
+add_entrypoint_object(
+ if_nameindex
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.if_nameindex
+)
+
+add_entrypoint_object(
+ if_freenameindex
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.if_freenameindex
+)
diff --git a/libc/src/net/if_freenameindex.h b/libc/src/net/if_freenameindex.h
new file mode 100644
index 0000000000000..eb0ac7eb2ddd4
--- /dev/null
+++ b/libc/src/net/if_freenameindex.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Implementation header for if_freenameindex.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_NET_IF_FREENAMEINDEX_H
+#define LLVM_LIBC_SRC_NET_IF_FREENAMEINDEX_H
+
+#include "hdr/types/struct_if_nameindex.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+void if_freenameindex(struct if_nameindex *ptr);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_NET_IF_FREENAMEINDEX_H
diff --git a/libc/src/net/if_nameindex.h b/libc/src/net/if_nameindex.h
new file mode 100644
index 0000000000000..bf6cf034e64c1
--- /dev/null
+++ b/libc/src/net/if_nameindex.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Implementation header for if_nameindex.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_NET_IF_NAMEINDEX_H
+#define LLVM_LIBC_SRC_NET_IF_NAMEINDEX_H
+
+#include "hdr/types/struct_if_nameindex.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct if_nameindex *if_nameindex();
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_NET_IF_NAMEINDEX_H
diff --git a/libc/src/net/linux/CMakeLists.txt b/libc/src/net/linux/CMakeLists.txt
index 9c82636d1c54c..8dd040ef3908f 100644
--- a/libc/src/net/linux/CMakeLists.txt
+++ b/libc/src/net/linux/CMakeLists.txt
@@ -42,3 +42,49 @@ add_entrypoint_object(
libc.src.errno.errno
libc.src.string.memory_utils.inline_memcpy
)
+
+add_header_library(
+ if_nameindex_internal
+ HDRS
+ if_nameindex.h
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.net_if_macros
+ libc.hdr.sys_socket_macros
+ libc.hdr.types.socklen_t
+ libc.hdr.types.ssize_t
+ libc.hdr.types.struct_if_nameindex
+ libc.src.__support.CPP.new
+ libc.src.__support.CPP.scope
+ libc.src.__support.alloc_checker
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.string.memory_utils.inline_memcpy
+ libc.src.string.memory_utils.inline_memset
+ libc.src.string.string_utils
+)
+
+add_entrypoint_object(
+ if_nameindex
+ SRCS
+ if_nameindex.cpp
+ HDRS
+ ../if_nameindex.h
+ DEPENDS
+ .if_nameindex_internal
+ libc.hdr.types.struct_if_nameindex
+ libc.src.__support.OSUtil.linux.network_syscall_policy
+ libc.src.__support.error_or
+ libc.src.errno.errno
+)
+
+add_entrypoint_object(
+ if_freenameindex
+ SRCS
+ if_freenameindex.cpp
+ HDRS
+ ../if_freenameindex.h
+ DEPENDS
+ libc.hdr.types.struct_if_nameindex
+ libc.src.__support.CPP.new
+)
diff --git a/libc/src/net/linux/if_freenameindex.cpp b/libc/src/net/linux/if_freenameindex.cpp
new file mode 100644
index 0000000000000..0090f4a7430da
--- /dev/null
+++ b/libc/src/net/linux/if_freenameindex.cpp
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 if_freenameindex.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/net/if_freenameindex.h"
+#include "hdr/stdint_proxy.h"
+#include "hdr/types/struct_if_nameindex.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/common.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(void, if_freenameindex, (struct if_nameindex * ptr)) {
+ delete[] reinterpret_cast<uint8_t *>(ptr);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/net/linux/if_nameindex.cpp b/libc/src/net/linux/if_nameindex.cpp
new file mode 100644
index 0000000000000..2a9ffcd8047f2
--- /dev/null
+++ b/libc/src/net/linux/if_nameindex.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 if_nameindex.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/net/if_nameindex.h"
+#include "src/__support/OSUtil/linux/network_syscall_policy.h"
+#include "src/__support/error_or.h"
+#include "src/__support/libc_errno.h"
+#include "src/net/linux/if_nameindex.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(struct if_nameindex *, if_nameindex, ()) {
+ ErrorOr<struct if_nameindex *> result =
+ net::if_nameindex<net::DefaultNetworkSyscallPolicy>();
+ if (!result.has_value()) {
+ libc_errno = result.error();
+ return nullptr;
+ }
+ return *result;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/net/linux/if_nameindex.h b/libc/src/net/linux/if_nameindex.h
new file mode 100644
index 0000000000000..4b9c43ebbfbed
--- /dev/null
+++ b/libc/src/net/linux/if_nameindex.h
@@ -0,0 +1,142 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Internal templatized implementation of if_nameindex for Linux.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_NET_LINUX_IF_NAMEINDEX_H
+#define LLVM_LIBC_SRC_NET_LINUX_IF_NAMEINDEX_H
+
+#include "hdr/errno_macros.h"
+#include "hdr/net_if_macros.h"
+#include "hdr/sys_socket_macros.h"
+#include "hdr/types/socklen_t.h"
+#include "hdr/types/ssize_t.h"
+#include "hdr/types/struct_if_nameindex.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/CPP/scope.h"
+#include "src/__support/alloc-checker.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/string/memory_utils/inline_memcpy.h"
+#include "src/string/memory_utils/inline_memset.h"
+#include "src/string/string_utils.h"
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+namespace LIBC_NAMESPACE_DECL {
+namespace net {
+
+template <typename Policy> ErrorOr<struct if_nameindex *> if_nameindex() {
+ ErrorOr<int> fd_or_err =
+ Policy::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
+ if (!fd_or_err.has_value())
+ return Error(fd_or_err.error());
+ int fd = *fd_or_err;
+ cpp::scope_exit close_fd([fd]() { Policy::close(fd); });
+
+ struct {
+ struct nlmsghdr nlh;
+ struct ifinfomsg ifm;
+ } req = {};
+ static_assert(sizeof(req) >= NLMSG_LENGTH(sizeof(req.ifm)));
+ req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifm));
+ req.nlh.nlmsg_type = RTM_GETLINK;
+ req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ req.ifm.ifi_family = AF_UNSPEC;
+
+ ErrorOr<ssize_t> send_res =
+ Policy::sendto(fd, &req, req.nlh.nlmsg_len, 0, nullptr, 0);
+ if (!send_res.has_value())
+ return Error(send_res.error());
+
+ alignas(struct nlmsghdr) uint8_t buf[4096];
+ ErrorOr<ssize_t> recv_res =
+ Policy::recvfrom(fd, buf, sizeof(buf), 0, nullptr, nullptr);
+ if (!recv_res.has_value())
+ return Error(recv_res.error());
+
+ close_fd.release();
+ if (ErrorOr<int> close_res = Policy::close(fd); !close_res.has_value())
+ return Error(close_res.error());
+
+ // TODO: Read more than one message.
+ // TODO: Read more than one interface per message.
+ // TODO: Deduplicate interfaces to handle restarts.
+ auto len = static_cast<size_t>(*recv_res);
+ for (auto *nh = reinterpret_cast<struct nlmsghdr *>(buf); NLMSG_OK(nh, len);
+ nh = NLMSG_NEXT(nh, len)) {
+ if (nh->nlmsg_type == NLMSG_DONE)
+ break;
+ if (nh->nlmsg_type == NLMSG_ERROR) {
+ if (nh->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr)))
+ return Error(EINVAL);
+ auto *err = reinterpret_cast<struct nlmsgerr *>(NLMSG_DATA(nh));
+ if (err->error == 0) {
+ // Zero means an ACK, which we shouldn't get because we didn't ask for
+ // it...
+ continue;
+ }
+ return Error(-err->error);
+ }
+ if (nh->nlmsg_type != RTM_NEWLINK)
+ continue;
+ if (nh->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg)))
+ continue;
+
+ auto *ifm = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
+ size_t attrlen = nh->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg));
+
+ auto *rta = reinterpret_cast<struct rtattr *>(
+ reinterpret_cast<uint8_t *>(ifm) +
+ NLMSG_ALIGN(sizeof(struct ifinfomsg)));
+ for (; RTA_OK(rta, attrlen); rta = RTA_NEXT(rta, attrlen)) {
+ if (rta->rta_type == IFLA_IFNAME) {
+ size_t rta_payload_len = RTA_PAYLOAD(rta);
+ auto index = static_cast<unsigned int>(ifm->ifi_index);
+ const char *name_data = reinterpret_cast<const char *>(RTA_DATA(rta));
+ size_t name_len = internal::strnlen(name_data, rta_payload_len);
+
+ size_t total_size = 2 * sizeof(struct if_nameindex) + name_len + 1;
+ AllocChecker ac;
+ uint8_t *buffer = new (ac) uint8_t[total_size];
+ if (!ac)
+ return Error(ENOMEM);
+
+ auto *result = reinterpret_cast<struct if_nameindex *>(buffer);
+ char *string_ptr = reinterpret_cast<char *>(result + 2);
+
+ result[0].if_index = index;
+ result[0].if_name = string_ptr;
+ inline_memcpy(string_ptr, name_data, name_len);
+ string_ptr[name_len] = '\0';
+
+ result[1].if_index = 0;
+ result[1].if_name = nullptr;
+
+ return result;
+ }
+ }
+ }
+
+ AllocChecker ac;
+ uint8_t *buffer = new (ac) uint8_t[sizeof(struct if_nameindex)];
+ if (!ac)
+ return Error(ENOMEM);
+ auto *result = reinterpret_cast<struct if_nameindex *>(buffer);
+ result[0] = {};
+ return result;
+}
+
+} // namespace net
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_NET_LINUX_IF_NAMEINDEX_H
diff --git a/libc/test/src/net/linux/CMakeLists.txt b/libc/test/src/net/linux/CMakeLists.txt
index c9119a954ad9c..016ce1d407a2c 100644
--- a/libc/test/src/net/linux/CMakeLists.txt
+++ b/libc/test/src/net/linux/CMakeLists.txt
@@ -25,3 +25,30 @@ add_libc_unittest(
libc.src.net.if_nametoindex
libc.test.UnitTest.ErrnoCheckingTest
)
+
+add_libc_unittest(
+ if_nameindex_test
+ SUITE
+ libc_net_unittests
+ SRCS
+ if_nameindex_test.cpp
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.net_if_macros
+ libc.hdr.types.socklen_t
+ libc.hdr.types.ssize_t
+ libc.hdr.types.struct_if_nameindex
+ libc.src.__support.CPP.span
+ libc.src.__support.CPP.string
+ libc.src.__support.CPP.string_view
+ libc.src.__support.CPP.tuple
+ libc.src.__support.CPP.utility
+ libc.src.__support.error_or
+ libc.src.__support.fixedvector
+ libc.src.net.if_freenameindex
+ libc.src.net.if_nameindex
+ libc.src.net.linux.if_nameindex_internal
+ libc.src.string.memory_utils.inline_memcpy
+ libc.src.string.memory_utils.inline_memset
+ libc.test.UnitTest.ErrnoCheckingTest
+)
diff --git a/libc/test/src/net/linux/if_nameindex_test.cpp b/libc/test/src/net/linux/if_nameindex_test.cpp
new file mode 100644
index 0000000000000..86bf0618f8a67
--- /dev/null
+++ b/libc/test/src/net/linux/if_nameindex_test.cpp
@@ -0,0 +1,514 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Unittests for if_nameindex and if_freenameindex.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/net_if_macros.h"
+#include "hdr/types/socklen_t.h"
+#include "hdr/types/ssize_t.h"
+#include "hdr/types/struct_if_nameindex.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/CPP/string.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/CPP/tuple.h"
+#include "src/__support/CPP/type_traits/type_identity.h"
+#include "src/__support/error_or.h"
+#include "src/__support/fixedvector.h"
+#include "src/net/if_freenameindex.h"
+#include "src/net/if_nameindex.h"
+#include "src/net/linux/if_nameindex.h"
+#include "src/string/memory_utils/inline_memcpy.h"
+#include "src/string/memory_utils/inline_memset.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+using LIBC_NAMESPACE::Error;
+using LIBC_NAMESPACE::ErrorOr;
+using LIBC_NAMESPACE::FixedVector;
+using LIBC_NAMESPACE::cpp::get;
+using LIBC_NAMESPACE::cpp::span;
+using LIBC_NAMESPACE::cpp::string;
+using LIBC_NAMESPACE::cpp::string_view;
+using LIBC_NAMESPACE::cpp::tuple;
+
+template <typename T, size_t CAPACITY>
+static T
+pop_front_or(FixedVector<T, CAPACITY> &vec,
+ typename LIBC_NAMESPACE::cpp::type_identity<T>::type default_val) {
+ if (vec.empty())
+ return default_val;
+ // TODO: Add front() and erase() to FixedVector, then clean this up.
+ T first = vec[0];
+ for (size_t i = 1; i < vec.size(); ++i)
+ vec[i - 1] = vec[i];
+ vec.pop_back();
+ return first;
+}
+
+// TODO: Add string::operator+=(string_view), then remove this helper.
+static void append_bytes(string &str, const void *data, size_t len) {
+ size_t old_size = str.size();
+ str.resize(old_size + len);
+ LIBC_NAMESPACE::inline_memcpy(str.data() + old_size, data, len);
+}
+
+namespace {
+
+struct FakeNetworkSyscallPolicyData {
+ FixedVector<tuple<int, int, int>, 10> socket_calls;
+ FixedVector<ErrorOr<int>, 10> socket_results;
+ FixedVector<tuple<int, size_t, int>, 10> sendto_calls;
+ FixedVector<ErrorOr<ssize_t>, 10> sendto_results;
+ // TODO: Move this into sendto_calls when FixedVector and tuple support
+ // non-trivial types.
+ string sendto_data;
+ FixedVector<int, 10> close_calls;
+ FixedVector<ErrorOr<int>, 10> close_results;
+ FixedVector<tuple<int, size_t, int>, 10> recv_calls;
+ // TODO: Make this ErrorOr<string> when ErrorOr and FixedVector support
+ // non-trivial types.
+ FixedVector<ErrorOr<span<const uint8_t>>, 10> recv_results;
+};
+
+template <FakeNetworkSyscallPolicyData *DATA> struct FakeNetworkSyscallPolicy {
+ static ErrorOr<int> socket(int domain, int type, int protocol) {
+ DATA->socket_calls.push_back(tuple<int, int, int>(domain, type, protocol));
+ return pop_front_or(DATA->socket_results, Error(ENFILE));
+ }
+
+ static ErrorOr<ssize_t> sendto(int fd, const void *buf, size_t len, int flags,
+ const struct sockaddr *, socklen_t) {
+ DATA->sendto_calls.push_back(tuple<int, size_t, int>(fd, len, flags));
+ append_bytes(DATA->sendto_data, buf, len);
+ return pop_front_or(DATA->sendto_results, static_cast<ssize_t>(len));
+ }
+
+ static ErrorOr<ssize_t> recvfrom(int fd, void *buf, size_t len, int flags,
+ struct sockaddr *, socklen_t *) {
+ DATA->recv_calls.push_back(tuple<int, size_t, int>(fd, len, flags));
+ ErrorOr<span<const uint8_t>> chunk =
+ pop_front_or(DATA->recv_results, span<const uint8_t>());
+ if (!chunk.has_value())
+ return Error(chunk.error());
+ if (chunk->size() > len)
+ return Error(EINVAL);
+ LIBC_NAMESPACE::inline_memcpy(buf, chunk->data(), chunk->size());
+ return static_cast<ssize_t>(chunk->size());
+ }
+
+ static ErrorOr<int> close(int fd) {
+ DATA->close_calls.push_back(fd);
+ return pop_front_or(DATA->close_results, 0);
+ }
+};
+
+struct LlvmLibcIfNameIndexTest
+ : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
+ static constexpr int FAKE_SOCKET = 47;
+ static FakeNetworkSyscallPolicyData policy_data;
+ using Policy = FakeNetworkSyscallPolicy<&policy_data>;
+
+ void SetUp() override {
+ ErrnoCheckingTest::SetUp();
+ policy_data = {};
+ }
+ void validate_dump_request() {
+ ASSERT_EQ(policy_data.sendto_calls.size(), size_t(1));
+ ASSERT_EQ(get<0>(policy_data.sendto_calls[0]), FAKE_SOCKET);
+ ASSERT_EQ(get<2>(policy_data.sendto_calls[0]), 0);
+ ASSERT_GE(policy_data.sendto_data.size(), sizeof(nlmsghdr));
+ const nlmsghdr *nlh =
+ reinterpret_cast<const nlmsghdr *>(policy_data.sendto_data.data());
+ ASSERT_EQ(nlh->nlmsg_type, static_cast<uint16_t>(RTM_GETLINK));
+ ASSERT_EQ(nlh->nlmsg_flags,
+ static_cast<uint16_t>(NLM_F_REQUEST | NLM_F_DUMP));
+ }
+};
+
+struct LlvmLibcIfNameIndexSocketTest : public LlvmLibcIfNameIndexTest {
+ void SetUp() override {
+ LlvmLibcIfNameIndexTest::SetUp();
+ policy_data.socket_results.push_back(FAKE_SOCKET);
+ }
+
+ void TearDown() override {
+ ASSERT_EQ(policy_data.socket_calls.size(), size_t(1));
+ ASSERT_EQ(get<0>(policy_data.socket_calls[0]), AF_NETLINK);
+ ASSERT_EQ(get<1>(policy_data.socket_calls[0]), SOCK_RAW | SOCK_CLOEXEC);
+ ASSERT_EQ(get<2>(policy_data.socket_calls[0]), NETLINK_ROUTE);
+
+ ASSERT_EQ(policy_data.close_calls.size(), size_t(1));
+ ASSERT_EQ(policy_data.close_calls[0], FAKE_SOCKET);
+ LlvmLibcIfNameIndexTest::TearDown();
+ }
+};
+
+} // namespace
+
+FakeNetworkSyscallPolicyData LlvmLibcIfNameIndexTest::policy_data;
+
+struct AttrName {
+ string_view name;
+ bool null_terminate = true;
+ size_t payload_len() const { return name.size() + (null_terminate ? 1 : 0); }
+ void write(uint8_t *dest) const {
+ LIBC_NAMESPACE::inline_memcpy(dest, name.data(), name.size());
+ if (null_terminate)
+ dest[name.size()] = '\0';
+ }
+ static constexpr uint16_t TYPE = IFLA_IFNAME;
+};
+
+template <uint16_t ATTR_TYPE> struct AttrInt {
+ unsigned int value;
+ size_t payload_len() const { return sizeof(unsigned int); }
+ void write(uint8_t *dest) const {
+ LIBC_NAMESPACE::inline_memcpy(dest, &value, sizeof(unsigned int));
+ }
+ static constexpr uint16_t TYPE = ATTR_TYPE;
+};
+
+using AttrMtu = AttrInt<IFLA_MTU>;
+using AttrTxqlen = AttrInt<IFLA_TXQLEN>;
+
+template <typename... Attrs>
+static size_t build_ifinfomsg_packet(uint8_t *buf, unsigned int index,
+ const Attrs &...attrs) {
+ size_t total_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)) +
+ (0 + ... + RTA_ALIGN(RTA_LENGTH(attrs.payload_len())));
+
+ LIBC_NAMESPACE::inline_memset(buf, 0, total_len);
+
+ struct nlmsghdr *nh = reinterpret_cast<struct nlmsghdr *>(buf);
+ nh->nlmsg_len = static_cast<uint32_t>(total_len);
+ nh->nlmsg_type = RTM_NEWLINK;
+
+ struct ifinfomsg *ifm = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
+ ifm->ifi_family = AF_UNSPEC;
+ ifm->ifi_index = static_cast<int>(index);
+
+ uint8_t *attr_ptr =
+ reinterpret_cast<uint8_t *>(ifm) + NLMSG_ALIGN(sizeof(struct ifinfomsg));
+
+ auto write_attr = [&attr_ptr](const auto &attr) {
+ size_t rta_len = RTA_LENGTH(attr.payload_len());
+ struct rtattr *rta = reinterpret_cast<struct rtattr *>(attr_ptr);
+ rta->rta_len = static_cast<unsigned short>(rta_len);
+ rta->rta_type = attr.TYPE;
+ attr.write(reinterpret_cast<uint8_t *>(RTA_DATA(rta)));
+ attr_ptr += RTA_ALIGN(rta_len);
+ };
+
+ if constexpr (sizeof...(Attrs) > 0)
+ (..., write_attr(attrs));
+
+ return total_len;
+}
+
+static size_t build_nlmsg_done_packet(uint8_t *buf) {
+ size_t total_len = NLMSG_LENGTH(sizeof(int));
+ LIBC_NAMESPACE::inline_memset(buf, 0, total_len);
+ struct nlmsghdr *nh = reinterpret_cast<struct nlmsghdr *>(buf);
+ nh->nlmsg_len = static_cast<uint32_t>(total_len);
+ nh->nlmsg_type = NLMSG_DONE;
+ return total_len;
+}
+
+static size_t build_nlmsg_error_packet(uint8_t *buf, int errcode) {
+ size_t total_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ LIBC_NAMESPACE::inline_memset(buf, 0, total_len);
+ struct nlmsghdr *nh = reinterpret_cast<struct nlmsghdr *>(buf);
+ nh->nlmsg_len = static_cast<uint32_t>(total_len);
+ nh->nlmsg_type = NLMSG_ERROR;
+ struct nlmsgerr *err = reinterpret_cast<struct nlmsgerr *>(NLMSG_DATA(nh));
+ err->error = errcode;
+ return total_len;
+}
+
+static size_t build_generic_nlmsg_packet(uint8_t *buf, uint16_t type) {
+ size_t total_len = NLMSG_LENGTH(0);
+ LIBC_NAMESPACE::inline_memset(buf, 0, total_len);
+ struct nlmsghdr *nh = reinterpret_cast<struct nlmsghdr *>(buf);
+ nh->nlmsg_len = static_cast<uint32_t>(total_len);
+ nh->nlmsg_type = type;
+ return total_len;
+}
+
+TEST_F(LlvmLibcIfNameIndexTest, SocketCreationFailure) {
+ policy_data.socket_results.push_back(Error(EAFNOSUPPORT));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_FALSE(res.has_value());
+ ASSERT_EQ(res.error(), EAFNOSUPPORT);
+ ASSERT_EQ(policy_data.socket_calls.size(), size_t(1));
+ ASSERT_EQ(get<0>(policy_data.socket_calls[0]), AF_NETLINK);
+ ASSERT_EQ(get<1>(policy_data.socket_calls[0]), SOCK_RAW | SOCK_CLOEXEC);
+ ASSERT_EQ(get<2>(policy_data.socket_calls[0]), NETLINK_ROUTE);
+ ASSERT_EQ(policy_data.close_calls.size(), size_t(0));
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, SendFailure) {
+ policy_data.sendto_results.push_back(Error(ENETDOWN));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_FALSE(res.has_value());
+ ASSERT_EQ(res.error(), ENETDOWN);
+ validate_dump_request();
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, ZeroInterfaces) {
+ uint8_t pkt_buf[128];
+ size_t len = build_nlmsg_done_packet(pkt_buf);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 0u);
+ ASSERT_EQ(list[0].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, SingleInterface) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_ifinfomsg_packet(pkt_buf, 1, AttrName{"lo"});
+ size_t len2 = build_nlmsg_done_packet(pkt_buf + len1);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len1 + len2));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 1u);
+ ASSERT_STREQ(list[0].if_name, "lo");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+
+ ASSERT_EQ(policy_data.recv_calls.size(), size_t(1));
+ ASSERT_EQ(get<0>(policy_data.recv_calls[0]), FAKE_SOCKET);
+
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, RecvFailure) {
+ policy_data.recv_results.push_back(Error(ETIMEDOUT));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_FALSE(res.has_value());
+ ASSERT_EQ(res.error(), ETIMEDOUT);
+ validate_dump_request();
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, NetlinkErrorPacket) {
+ uint8_t pkt_buf[128];
+ size_t len = build_nlmsg_error_packet(pkt_buf, -EINVAL);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_FALSE(res.has_value());
+ ASSERT_EQ(res.error(), EINVAL);
+ validate_dump_request();
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, TruncatedNetlinkErrorPacket) {
+ uint8_t pkt_buf[128];
+ size_t len = build_generic_nlmsg_packet(pkt_buf, NLMSG_ERROR);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_FALSE(res.has_value());
+ ASSERT_EQ(res.error(), EINVAL);
+ validate_dump_request();
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, TruncatedIfInfoMsgPacket) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_generic_nlmsg_packet(pkt_buf, RTM_NEWLINK);
+ size_t len2 = build_ifinfomsg_packet(pkt_buf + len1, 2, AttrName{"eth0"});
+ size_t len3 = build_nlmsg_done_packet(pkt_buf + len1 + len2);
+
+ policy_data.recv_results.push_back(
+ span<const uint8_t>(pkt_buf, len1 + len2 + len3));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 2u);
+ ASSERT_STREQ(list[0].if_name, "eth0");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, CloseFailure) {
+ uint8_t pkt_buf[128];
+ size_t len = build_nlmsg_done_packet(pkt_buf);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len));
+ policy_data.close_results.push_back(Error(EIO));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_FALSE(res.has_value());
+ ASSERT_EQ(res.error(), EIO);
+ validate_dump_request();
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, NetlinkAckPacket) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_nlmsg_error_packet(pkt_buf, 0);
+ size_t len2 = build_ifinfomsg_packet(pkt_buf + len1, 1, AttrName{"lo"});
+ size_t len3 = build_nlmsg_done_packet(pkt_buf + len1 + len2);
+
+ policy_data.recv_results.push_back(
+ span<const uint8_t>(pkt_buf, len1 + len2 + len3));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 1u);
+ ASSERT_STREQ(list[0].if_name, "lo");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, IgnoredNetlinkMessageTypes) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_generic_nlmsg_packet(pkt_buf, NLMSG_NOOP);
+ size_t len2 = build_ifinfomsg_packet(pkt_buf + len1, 2, AttrName{"eth0"});
+ size_t len3 = build_nlmsg_done_packet(pkt_buf + len1 + len2);
+
+ policy_data.recv_results.push_back(
+ span<const uint8_t>(pkt_buf, len1 + len2 + len3));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 2u);
+ ASSERT_STREQ(list[0].if_name, "eth0");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, InterfaceWithMultipleAttributes) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_ifinfomsg_packet(pkt_buf, 3, AttrMtu{1500},
+ AttrName{"wlan0"}, AttrTxqlen{1000});
+ size_t len2 = build_nlmsg_done_packet(pkt_buf + len1);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len1 + len2));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 3u);
+ ASSERT_STREQ(list[0].if_name, "wlan0");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, InterfaceMissingNameAttribute) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_ifinfomsg_packet(pkt_buf, 1, AttrMtu{1500});
+ size_t len2 = build_ifinfomsg_packet(pkt_buf + len1, 2, AttrName{"eth0"});
+ size_t len3 = build_nlmsg_done_packet(pkt_buf + len1 + len2);
+
+ policy_data.recv_results.push_back(
+ span<const uint8_t>(pkt_buf, len1 + len2 + len3));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 2u);
+ ASSERT_STREQ(list[0].if_name, "eth0");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, InterfaceNameWithoutNullTerminator) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_ifinfomsg_packet(pkt_buf, 4, AttrName{"docker0", false});
+ size_t len2 = build_nlmsg_done_packet(pkt_buf + len1);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len1 + len2));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 4u);
+ ASSERT_STREQ(list[0].if_name, "docker0");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+using LlvmLibcIfNameIndexLiveTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcIfNameIndexLiveTest, LiveOSIntegration) {
+ struct if_nameindex *list = LIBC_NAMESPACE::if_nameindex();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+ ASSERT_ERRNO_SUCCESS();
+
+ bool found_valid = false;
+ for (struct if_nameindex *cur = list;
+ cur->if_index != 0 || cur->if_name != nullptr; ++cur) {
+ ASSERT_GT(cur->if_index, 0u);
+ ASSERT_NE(cur->if_name, static_cast<char *>(nullptr));
+ if (LIBC_NAMESPACE::cpp::string_view(cur->if_name) == "lo" ||
+ cur->if_index > 0)
+ found_valid = true;
+ }
+ ASSERT_TRUE(found_valid);
+
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
>From 0a1a0675cfc590b7bc6e6a3ac6c827d602704033 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Thu, 9 Jul 2026 12:34:16 +0000
Subject: [PATCH 2/2] Assorted fixes
- Add more comments
- fix deps
- use cpp::span in a few places
- more tests
---
.../llvm-libc-types/struct_if_nameindex.h | 1 +
libc/src/net/if_freenameindex.h | 4 +++
libc/src/net/if_nameindex.h | 6 ++++
libc/src/net/linux/CMakeLists.txt | 5 ++-
libc/src/net/linux/if_freenameindex.cpp | 3 ++
libc/src/net/linux/if_nameindex.cpp | 2 ++
libc/src/net/linux/if_nameindex.h | 32 ++++++++++--------
libc/test/src/net/linux/CMakeLists.txt | 2 +-
libc/test/src/net/linux/if_nameindex_test.cpp | 33 +++++++++++++++++--
9 files changed, 70 insertions(+), 18 deletions(-)
diff --git a/libc/include/llvm-libc-types/struct_if_nameindex.h b/libc/include/llvm-libc-types/struct_if_nameindex.h
index c876a0ece541b..12bd79f7b6106 100644
--- a/libc/include/llvm-libc-types/struct_if_nameindex.h
+++ b/libc/include/llvm-libc-types/struct_if_nameindex.h
@@ -14,6 +14,7 @@
#ifndef LLVM_LIBC_TYPES_STRUCT_IF_NAMEINDEX_H
#define LLVM_LIBC_TYPES_STRUCT_IF_NAMEINDEX_H
+/// Structure storing a network interface index and its corresponding name.
struct if_nameindex {
unsigned int if_index;
char *if_name;
diff --git a/libc/src/net/if_freenameindex.h b/libc/src/net/if_freenameindex.h
index eb0ac7eb2ddd4..52aea1eb55ba4 100644
--- a/libc/src/net/if_freenameindex.h
+++ b/libc/src/net/if_freenameindex.h
@@ -19,6 +19,10 @@
namespace LIBC_NAMESPACE_DECL {
+/// Frees the memory allocated by if_nameindex().
+///
+/// \param ptr Pointer to the array of if_nameindex structures returned by
+/// if_nameindex(). If ptr is nullptr, this function does nothing.
void if_freenameindex(struct if_nameindex *ptr);
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/net/if_nameindex.h b/libc/src/net/if_nameindex.h
index bf6cf034e64c1..966cb440b5408 100644
--- a/libc/src/net/if_nameindex.h
+++ b/libc/src/net/if_nameindex.h
@@ -19,6 +19,12 @@
namespace LIBC_NAMESPACE_DECL {
+/// Returns an array of if_nameindex structures containing the interface index
+/// and name of all network interfaces on the system.
+///
+/// The array is terminated by an element with if_index == 0 and if_name ==
+/// nullptr. On failure, returns nullptr and sets errno. The returned pointer
+/// must be freed by passing it to if_freenameindex().
struct if_nameindex *if_nameindex();
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/net/linux/CMakeLists.txt b/libc/src/net/linux/CMakeLists.txt
index 8dd040ef3908f..ec170a02eea46 100644
--- a/libc/src/net/linux/CMakeLists.txt
+++ b/libc/src/net/linux/CMakeLists.txt
@@ -56,11 +56,11 @@ add_header_library(
libc.hdr.types.struct_if_nameindex
libc.src.__support.CPP.new
libc.src.__support.CPP.scope
+ libc.src.__support.CPP.span
libc.src.__support.alloc_checker
libc.src.__support.common
libc.src.__support.error_or
libc.src.string.memory_utils.inline_memcpy
- libc.src.string.memory_utils.inline_memset
libc.src.string.string_utils
)
@@ -74,6 +74,7 @@ add_entrypoint_object(
.if_nameindex_internal
libc.hdr.types.struct_if_nameindex
libc.src.__support.OSUtil.linux.network_syscall_policy
+ libc.src.__support.common
libc.src.__support.error_or
libc.src.errno.errno
)
@@ -85,6 +86,8 @@ add_entrypoint_object(
HDRS
../if_freenameindex.h
DEPENDS
+ libc.hdr.stdint_proxy
libc.hdr.types.struct_if_nameindex
libc.src.__support.CPP.new
+ libc.src.__support.common
)
diff --git a/libc/src/net/linux/if_freenameindex.cpp b/libc/src/net/linux/if_freenameindex.cpp
index 0090f4a7430da..229d679797e26 100644
--- a/libc/src/net/linux/if_freenameindex.cpp
+++ b/libc/src/net/linux/if_freenameindex.cpp
@@ -20,6 +20,9 @@
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(void, if_freenameindex, (struct if_nameindex * ptr)) {
+ // ptr was allocated in net::if_nameindex() as a single contiguous uint8_t[]
+ // buffer holding both the if_nameindex struct array and the interface name
+ // strings.
delete[] reinterpret_cast<uint8_t *>(ptr);
}
diff --git a/libc/src/net/linux/if_nameindex.cpp b/libc/src/net/linux/if_nameindex.cpp
index 2a9ffcd8047f2..7e17455a1ea8b 100644
--- a/libc/src/net/linux/if_nameindex.cpp
+++ b/libc/src/net/linux/if_nameindex.cpp
@@ -12,7 +12,9 @@
//===----------------------------------------------------------------------===//
#include "src/net/if_nameindex.h"
+#include "hdr/types/struct_if_nameindex.h"
#include "src/__support/OSUtil/linux/network_syscall_policy.h"
+#include "src/__support/common.h"
#include "src/__support/error_or.h"
#include "src/__support/libc_errno.h"
#include "src/net/linux/if_nameindex.h"
diff --git a/libc/src/net/linux/if_nameindex.h b/libc/src/net/linux/if_nameindex.h
index 4b9c43ebbfbed..c7ba770dd5fb8 100644
--- a/libc/src/net/linux/if_nameindex.h
+++ b/libc/src/net/linux/if_nameindex.h
@@ -22,11 +22,11 @@
#include "hdr/types/struct_if_nameindex.h"
#include "src/__support/CPP/new.h"
#include "src/__support/CPP/scope.h"
+#include "src/__support/CPP/span.h"
#include "src/__support/alloc-checker.h"
#include "src/__support/common.h"
#include "src/__support/error_or.h"
#include "src/string/memory_utils/inline_memcpy.h"
-#include "src/string/memory_utils/inline_memset.h"
#include "src/string/string_utils.h"
#include <linux/netlink.h>
@@ -95,9 +95,10 @@ template <typename Policy> ErrorOr<struct if_nameindex *> if_nameindex() {
auto *ifm = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
size_t attrlen = nh->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg));
+ cpp::span<uint8_t> ifm_payload(reinterpret_cast<uint8_t *>(ifm),
+ nh->nlmsg_len - NLMSG_LENGTH(0));
auto *rta = reinterpret_cast<struct rtattr *>(
- reinterpret_cast<uint8_t *>(ifm) +
- NLMSG_ALIGN(sizeof(struct ifinfomsg)));
+ ifm_payload.subspan(NLMSG_ALIGN(sizeof(struct ifinfomsg))).data());
for (; RTA_OK(rta, attrlen); rta = RTA_NEXT(rta, attrlen)) {
if (rta->rta_type == IFLA_IFNAME) {
size_t rta_payload_len = RTA_PAYLOAD(rta);
@@ -109,20 +110,24 @@ template <typename Policy> ErrorOr<struct if_nameindex *> if_nameindex() {
AllocChecker ac;
uint8_t *buffer = new (ac) uint8_t[total_size];
if (!ac)
- return Error(ENOMEM);
+ return Error(ENOBUFS);
- auto *result = reinterpret_cast<struct if_nameindex *>(buffer);
- char *string_ptr = reinterpret_cast<char *>(result + 2);
+ cpp::span<uint8_t> buffer_span(buffer, total_size);
+ cpp::span<struct if_nameindex> result(
+ reinterpret_cast<struct if_nameindex *>(buffer_span.data()), 2);
+ cpp::span<char> string_span(
+ reinterpret_cast<char *>(result.end()),
+ reinterpret_cast<char *>(buffer_span.end()));
result[0].if_index = index;
- result[0].if_name = string_ptr;
- inline_memcpy(string_ptr, name_data, name_len);
- string_ptr[name_len] = '\0';
+ result[0].if_name = string_span.data();
+ inline_memcpy(string_span.data(), name_data, name_len);
+ string_span[name_len] = '\0';
result[1].if_index = 0;
result[1].if_name = nullptr;
- return result;
+ return result.data();
}
}
}
@@ -130,10 +135,11 @@ template <typename Policy> ErrorOr<struct if_nameindex *> if_nameindex() {
AllocChecker ac;
uint8_t *buffer = new (ac) uint8_t[sizeof(struct if_nameindex)];
if (!ac)
- return Error(ENOMEM);
- auto *result = reinterpret_cast<struct if_nameindex *>(buffer);
+ return Error(ENOBUFS);
+ cpp::span<struct if_nameindex> result(
+ reinterpret_cast<struct if_nameindex *>(buffer), 1);
result[0] = {};
- return result;
+ return result.data();
}
} // namespace net
diff --git a/libc/test/src/net/linux/CMakeLists.txt b/libc/test/src/net/linux/CMakeLists.txt
index 016ce1d407a2c..cd521dba14511 100644
--- a/libc/test/src/net/linux/CMakeLists.txt
+++ b/libc/test/src/net/linux/CMakeLists.txt
@@ -42,7 +42,7 @@ add_libc_unittest(
libc.src.__support.CPP.string
libc.src.__support.CPP.string_view
libc.src.__support.CPP.tuple
- libc.src.__support.CPP.utility
+ libc.src.__support.CPP.type_traits
libc.src.__support.error_or
libc.src.__support.fixedvector
libc.src.net.if_freenameindex
diff --git a/libc/test/src/net/linux/if_nameindex_test.cpp b/libc/test/src/net/linux/if_nameindex_test.cpp
index 86bf0618f8a67..47ff12376b463 100644
--- a/libc/test/src/net/linux/if_nameindex_test.cpp
+++ b/libc/test/src/net/linux/if_nameindex_test.cpp
@@ -128,12 +128,17 @@ struct LlvmLibcIfNameIndexTest
ASSERT_EQ(policy_data.sendto_calls.size(), size_t(1));
ASSERT_EQ(get<0>(policy_data.sendto_calls[0]), FAKE_SOCKET);
ASSERT_EQ(get<2>(policy_data.sendto_calls[0]), 0);
- ASSERT_GE(policy_data.sendto_data.size(), sizeof(nlmsghdr));
- const nlmsghdr *nlh =
- reinterpret_cast<const nlmsghdr *>(policy_data.sendto_data.data());
+ ASSERT_EQ(policy_data.sendto_data.size(),
+ static_cast<size_t>(NLMSG_LENGTH(sizeof(ifinfomsg))));
+ auto *nlh =
+ reinterpret_cast<struct nlmsghdr *>(policy_data.sendto_data.data());
+ ASSERT_EQ(nlh->nlmsg_len,
+ static_cast<uint32_t>(NLMSG_LENGTH(sizeof(ifinfomsg))));
ASSERT_EQ(nlh->nlmsg_type, static_cast<uint16_t>(RTM_GETLINK));
ASSERT_EQ(nlh->nlmsg_flags,
static_cast<uint16_t>(NLM_F_REQUEST | NLM_F_DUMP));
+ auto *ifm = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(nlh));
+ ASSERT_EQ(ifm->ifi_family, static_cast<unsigned char>(AF_UNSPEC));
}
};
@@ -151,6 +156,8 @@ struct LlvmLibcIfNameIndexSocketTest : public LlvmLibcIfNameIndexTest {
ASSERT_EQ(policy_data.close_calls.size(), size_t(1));
ASSERT_EQ(policy_data.close_calls[0], FAKE_SOCKET);
+ ASSERT_TRUE(policy_data.sendto_results.empty());
+ ASSERT_TRUE(policy_data.recv_results.empty());
LlvmLibcIfNameIndexTest::TearDown();
}
};
@@ -367,6 +374,22 @@ TEST_F(LlvmLibcIfNameIndexSocketTest, TruncatedIfInfoMsgPacket) {
LIBC_NAMESPACE::if_freenameindex(list);
}
+TEST_F(LlvmLibcIfNameIndexSocketTest, TruncatedNetlinkHeaderPacket) {
+ uint8_t pkt_buf[4] = {1, 2, 3, 4};
+ policy_data.recv_results.push_back(
+ span<const uint8_t>(pkt_buf, sizeof(pkt_buf)));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+ ASSERT_EQ(list[0].if_index, 0u);
+ ASSERT_EQ(list[0].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
TEST_F(LlvmLibcIfNameIndexSocketTest, CloseFailure) {
uint8_t pkt_buf[128];
size_t len = build_nlmsg_done_packet(pkt_buf);
@@ -512,3 +535,7 @@ TEST_F(LlvmLibcIfNameIndexLiveTest, LiveOSIntegration) {
LIBC_NAMESPACE::if_freenameindex(list);
}
+
+TEST_F(LlvmLibcIfNameIndexLiveTest, FreeNullptr) {
+ LIBC_NAMESPACE::if_freenameindex(nullptr);
+}
More information about the libc-commits
mailing list