[libc-commits] [libc] [libc] Implement system function (PR #211672)
Michael Jones via libc-commits
libc-commits at lists.llvm.org
Sat Aug 8 00:15:48 PDT 2026
https://github.com/michaelrj-google updated https://github.com/llvm/llvm-project/pull/211672
>From 4b03e674bfce2766a65455157907f5480ed4268d Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Thu, 23 Jul 2026 21:42:23 +0000
Subject: [PATCH 1/4] [libc] Implement system function
This Pr implements the system function, as well as adding syscall
wrappers for several syscalls that system needs.
---
libc/config/linux/x86_64/entrypoints.txt | 1 +
.../linux/syscall_wrappers/CMakeLists.txt | 78 ++++++++++++
.../OSUtil/linux/syscall_wrappers/execle.h | 49 ++++++++
.../OSUtil/linux/syscall_wrappers/fork.h | 41 +++++++
.../linux/syscall_wrappers/rt_sigaction.h | 36 ++++++
.../OSUtil/linux/syscall_wrappers/wait4.h | 36 ++++++
libc/src/stdlib/linux/CMakeLists.txt | 28 +++++
libc/src/stdlib/linux/system.cpp | 112 ++++++++++++++++++
libc/test/src/stdlib/CMakeLists.txt | 12 ++
libc/test/src/stdlib/system_test.cpp | 48 ++++++++
10 files changed, 441 insertions(+)
create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h
create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/fork.h
create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/rt_sigaction.h
create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/wait4.h
create mode 100644 libc/src/stdlib/linux/system.cpp
create mode 100644 libc/test/src/stdlib/system_test.cpp
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 7a9e741310c6a..739f88a256754 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1426,6 +1426,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.mbtowc
libc.src.stdlib.mblen
libc.src.stdlib.quick_exit
+ libc.src.stdlib.system
libc.src.stdlib.wcstombs
libc.src.stdlib.wctomb
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index 2b22bb8e733de..5dee62f7722e3 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -940,3 +940,81 @@ add_header_library(
libc.src.__support.macros.config
libc.include.sys_syscall
)
+
+add_header_library(
+ rt_sigaction
+ HDRS
+ rt_sigaction.h
+ DEPENDS
+ libc.hdr.types.sigset_t
+ libc.src.__support.OSUtil.osutil
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.macros.config
+ libc.include.sys_syscall
+)
+
+add_header_library(
+ fork
+ HDRS
+ fork.h
+ DEPENDS
+ libc.hdr.signal_macros
+ libc.hdr.types.pid_t
+ libc.src.__support.OSUtil.osutil
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.macros.config
+ libc.include.sys_syscall
+)
+
+add_header_library(
+ execve
+ HDRS
+ execve.h
+ DEPENDS
+ libc.src.__support.OSUtil.osutil
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.macros.config
+ libc.include.sys_syscall
+)
+
+add_header_library(
+ execl
+ HDRS
+ execl.h
+ DEPENDS
+ libc.src.__support.OSUtil.osutil
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.macros.config
+ libc.include.sys_syscall
+)
+
+add_header_library(
+ execle
+ HDRS
+ execle.h
+ DEPENDS
+ libc.src.__support.OSUtil.osutil
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.macros.config
+ libc.include.sys_syscall
+)
+
+add_header_library(
+ wait4
+ HDRS
+ wait4.h
+ DEPENDS
+ libc.hdr.types.pid_t
+ libc.hdr.types.struct_rusage
+ libc.src.__support.OSUtil.osutil
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.macros.config
+ libc.include.sys_syscall
+)
+
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h
new file mode 100644
index 0000000000000..f6b87d04f5f60
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h
@@ -0,0 +1,49 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 execle.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_EXECLE_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_EXECLE_H
+
+#include "src/__support/OSUtil/linux/syscall.h" // For syscall_checked
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+#include <sys/syscall.h> // For syscall numbers
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+template <typename... Args>
+LIBC_INLINE ErrorOr<int> execle(const char *path, Args... args) {
+ // All this logic because the standard says the environment pointer goes at
+ // the end. It's annoying but it's all compile time so it's not actually a
+ // problem.
+ const void *all[] = {static_cast<const void *>(args)...};
+ constexpr size_t total = sizeof...(Args);
+ static_assert(total >= 2,
+ "execle requires at least (arg0, ..., nullptr, envp)");
+ char *const *envp =
+ reinterpret_cast<char *const *>(const_cast<void *>(all[total - 1]));
+
+ const char *argv[total];
+ for (size_t i = 0; i < total - 1; ++i)
+ argv[i] = reinterpret_cast<const char *>(all[i]);
+ argv[total - 1] = nullptr;
+
+ return syscall_checked<int>(SYS_execve, path, argv, envp);
+}
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_EXECLE_H
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/fork.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/fork.h
new file mode 100644
index 0000000000000..f570d54339b7a
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/fork.h
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 fork.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_FORK_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_FORK_H
+
+#include "hdr/signal_macros.h"
+#include "hdr/types/pid_t.h"
+#include "src/__support/OSUtil/linux/syscall.h" // For syscall_checked
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+#include <sys/syscall.h> // For syscall numbers
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+LIBC_INLINE ErrorOr<pid_t> fork() {
+#ifdef SYS_fork
+ return syscall_checked<pid_t>(SYS_fork);
+#elif defined(SYS_clone)
+ return syscall_checked<pid_t>(SYS_clone, SIGCHLD, 0);
+#else
+#error "fork and clone syscalls not available."
+#endif
+}
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_FORK_H
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/rt_sigaction.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/rt_sigaction.h
new file mode 100644
index 0000000000000..72f9e74930587
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/rt_sigaction.h
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 rt_sigaction.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_RT_SIGACTION_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_RT_SIGACTION_H
+
+#include "hdr/types/sigset_t.h"
+#include "src/__support/OSUtil/linux/syscall.h" // For syscall_checked
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+#include <sys/syscall.h> // For syscall numbers
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+LIBC_INLINE ErrorOr<int> rt_sigaction(int signum, const void *act,
+ void *oldact) {
+ return syscall_checked<int>(SYS_rt_sigaction, signum, act, oldact,
+ sizeof(sigset_t));
+}
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_RT_SIGACTION_H
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/wait4.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/wait4.h
new file mode 100644
index 0000000000000..30d19ebae65f2
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/wait4.h
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 wait4.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_WAIT4_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_WAIT4_H
+
+#include "hdr/types/pid_t.h"
+#include "hdr/types/struct_rusage.h"
+#include "src/__support/OSUtil/linux/syscall.h" // For syscall_checked
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+#include <sys/syscall.h> // For syscall numbers
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+LIBC_INLINE ErrorOr<pid_t> wait4(pid_t pid, int *wstatus, int options,
+ struct rusage *rusage) {
+ return syscall_checked<pid_t>(SYS_wait4, pid, wstatus, options, rusage);
+}
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_WAIT4_H
diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index a02c2441ec148..27b0b1f34b916 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -80,3 +80,31 @@ add_entrypoint_object(
libc.src.__support.macros.null_check
libc.src.stdlib.environ_internal
)
+
+add_entrypoint_object(
+ system
+ SRCS
+ system.cpp
+ HDRS
+ ../system.h
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.signal_macros
+ libc.hdr.types.pid_t
+ libc.hdr.types.sigset_t
+ libc.src.__support.common
+ libc.src.__support.libc_errno
+ libc.src.__support.macros.config
+ libc.src.__support.OSUtil.osutil
+ libc.src.__support.OSUtil.linux.syscall_wrappers.execle
+ libc.src.__support.OSUtil.linux.syscall_wrappers.fork
+ libc.src.__support.OSUtil.linux.syscall_wrappers.rt_sigaction
+ libc.src.__support.OSUtil.linux.syscall_wrappers.rt_sigprocmask
+ libc.src.__support.OSUtil.linux.syscall_wrappers.wait4
+ libc.src.signal.linux.signal_utils
+ libc.src.unistd.environ
+)
+
+
+
+
diff --git a/libc/src/stdlib/linux/system.cpp b/libc/src/stdlib/linux/system.cpp
new file mode 100644
index 0000000000000..7e9bb8334efe3
--- /dev/null
+++ b/libc/src/stdlib/linux/system.cpp
@@ -0,0 +1,112 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 of system for Linux.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/system.h"
+#include "hdr/errno_macros.h"
+#include "hdr/signal_macros.h"
+#include "hdr/types/pid_t.h"
+#include "hdr/types/sigset_t.h"
+#include "src/__support/OSUtil/exit.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/execle.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/fork.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/rt_sigaction.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/rt_sigprocmask.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/wait4.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/signal/linux/signal_utils.h"
+#include "src/unistd/environ.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, system, (const char *command)) {
+ if (command == nullptr)
+ return 1;
+
+ KernelSigaction sa_ign{};
+ sa_ign.sa_handler = SIG_IGN;
+
+ KernelSigaction orig_int{};
+ KernelSigaction orig_quit{};
+
+ if (auto res = linux_syscalls::rt_sigaction(SIGINT, &sa_ign, &orig_int);
+ !res.has_value()) {
+ libc_errno = res.error();
+ return -1;
+ }
+
+ if (auto res = linux_syscalls::rt_sigaction(SIGQUIT, &sa_ign, &orig_quit);
+ !res.has_value()) {
+ linux_syscalls::rt_sigaction(SIGINT, &orig_int, nullptr);
+ libc_errno = res.error();
+ return -1;
+ }
+
+ sigset_t block_mask{};
+ add_signal(block_mask, SIGCHLD);
+ sigset_t orig_mask{};
+ if (auto res =
+ linux_syscalls::rt_sigprocmask(SIG_BLOCK, &block_mask, &orig_mask);
+ !res.has_value()) {
+ linux_syscalls::rt_sigaction(SIGINT, &orig_int, nullptr);
+ linux_syscalls::rt_sigaction(SIGQUIT, &orig_quit, nullptr);
+ libc_errno = res.error();
+ return -1;
+ }
+
+ auto fork_res = linux_syscalls::fork();
+ if (!fork_res.has_value()) {
+ linux_syscalls::rt_sigaction(SIGINT, &orig_int, nullptr);
+ linux_syscalls::rt_sigaction(SIGQUIT, &orig_quit, nullptr);
+ linux_syscalls::rt_sigprocmask(SIG_SETMASK, &orig_mask, nullptr);
+ libc_errno = fork_res.error();
+ return -1;
+ }
+
+ pid_t pid = fork_res.value();
+ if (pid == 0) {
+ linux_syscalls::rt_sigaction(SIGINT, &orig_int, nullptr);
+ linux_syscalls::rt_sigaction(SIGQUIT, &orig_quit, nullptr);
+ linux_syscalls::rt_sigprocmask(SIG_SETMASK, &orig_mask, nullptr);
+
+ // Error checking isn't helpful since this is the forked process, so we
+ // can't set errno. All we can meaningfully do is exit with status 127.
+ linux_syscalls::execle("/bin/sh", "sh", "-c", command, nullptr, environ);
+
+ internal::exit(127);
+ }
+
+ int status = 0;
+ int wait_ret = 0;
+ do {
+ if (auto wait_res = linux_syscalls::wait4(pid, &status, 0, nullptr);
+ !wait_res.has_value()) {
+ if (wait_res.error() == EINTR)
+ continue;
+ wait_ret = -1;
+ libc_errno = wait_res.error();
+ break;
+ }
+ wait_ret = status;
+ break;
+ } while (true);
+
+ linux_syscalls::rt_sigaction(SIGINT, &orig_int, nullptr);
+ linux_syscalls::rt_sigaction(SIGQUIT, &orig_quit, nullptr);
+ linux_syscalls::rt_sigprocmask(SIG_SETMASK, &orig_mask, nullptr);
+
+ return wait_ret;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index 8a1f44ba632bf..9b79952d870fe 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -578,6 +578,18 @@ if(LLVM_LIBC_FULL_BUILD AND LIBC_TEST_SUBPROCESS_TESTS)
libc.include.stdlib
libc.src.stdlib.quick_exit
)
+
+ add_libc_test(
+ system_test
+ SUITE
+ libc-stdlib-tests
+ SRCS
+ system_test.cpp
+ DEPENDS
+ libc.hdr.sys_wait_macros
+ libc.src.stdlib.system
+ libc.test.UnitTest.ErrnoCheckingTest
+ )
endif()
# Only baremetal and GPU has an in-tree 'malloc' implementation.
diff --git a/libc/test/src/stdlib/system_test.cpp b/libc/test/src/stdlib/system_test.cpp
new file mode 100644
index 0000000000000..7d4387e4ebd90
--- /dev/null
+++ b/libc/test/src/stdlib/system_test.cpp
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Unit tests for system.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/sys_wait_macros.h"
+#include "src/stdlib/system.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcSystemTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcSystemTest, NullCommand) {
+ int status = LIBC_NAMESPACE::system(nullptr);
+ EXPECT_NE(status, 0);
+}
+
+TEST_F(LlvmLibcSystemTest, ValidCommandExitZero) {
+ int status = LIBC_NAMESPACE::system("exit 0");
+ EXPECT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(WEXITSTATUS(status), 0);
+}
+
+TEST_F(LlvmLibcSystemTest, ValidCommandExitNonZero) {
+ int status = LIBC_NAMESPACE::system("exit 42");
+ EXPECT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(WEXITSTATUS(status), 42);
+}
+
+TEST_F(LlvmLibcSystemTest, EmptyCommand) {
+ int status = LIBC_NAMESPACE::system("");
+ EXPECT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(WEXITSTATUS(status), 0);
+}
+
+TEST_F(LlvmLibcSystemTest, NonExistentCommand) {
+ int status = LIBC_NAMESPACE::system("definitely_nonexistent_command_xyz123");
+ EXPECT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(WEXITSTATUS(status), 127);
+}
>From c5b11dd6d99150554e3cfbbbc6f453748c0f3679 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Fri, 24 Jul 2026 21:33:00 +0000
Subject: [PATCH 2/4] address comments
---
libc/config/linux/aarch64/entrypoints.txt | 1 +
libc/config/linux/riscv/entrypoints.txt | 1 +
libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h | 2 +-
libc/src/stdlib/linux/system.cpp | 6 +++---
4 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 1d5244b9c443e..f60423f69eabe 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1219,6 +1219,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.getenv
libc.src.stdlib.putenv
libc.src.stdlib.setenv
+ libc.src.stdlib.system
libc.src.stdlib.unsetenv
libc.src.stdlib.quick_exit
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 0f7822b36a761..4646be9fcbb97 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1412,6 +1412,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.getenv
libc.src.stdlib.putenv
libc.src.stdlib.setenv
+ libc.src.stdlib.system
libc.src.stdlib.unsetenv
libc.src.stdlib.mbstowcs
libc.src.stdlib.mbtowc
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h
index f6b87d04f5f60..080e8f75df556 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h
@@ -28,7 +28,7 @@ LIBC_INLINE ErrorOr<int> execle(const char *path, Args... args) {
// All this logic because the standard says the environment pointer goes at
// the end. It's annoying but it's all compile time so it's not actually a
// problem.
- const void *all[] = {static_cast<const void *>(args)...};
+ const void *all[] = {reinterpret_cast<const void *>(args)...};
constexpr size_t total = sizeof...(Args);
static_assert(total >= 2,
"execle requires at least (arg0, ..., nullptr, envp)");
diff --git a/libc/src/stdlib/linux/system.cpp b/libc/src/stdlib/linux/system.cpp
index 7e9bb8334efe3..8298730a07353 100644
--- a/libc/src/stdlib/linux/system.cpp
+++ b/libc/src/stdlib/linux/system.cpp
@@ -82,14 +82,14 @@ LLVM_LIBC_FUNCTION(int, system, (const char *command)) {
// Error checking isn't helpful since this is the forked process, so we
// can't set errno. All we can meaningfully do is exit with status 127.
- linux_syscalls::execle("/bin/sh", "sh", "-c", command, nullptr, environ);
+ linux_syscalls::execle("/bin/sh", "sh", "-c", command, 0, environ);
internal::exit(127);
}
int status = 0;
int wait_ret = 0;
- do {
+ while (true) {
if (auto wait_res = linux_syscalls::wait4(pid, &status, 0, nullptr);
!wait_res.has_value()) {
if (wait_res.error() == EINTR)
@@ -100,7 +100,7 @@ LLVM_LIBC_FUNCTION(int, system, (const char *command)) {
}
wait_ret = status;
break;
- } while (true);
+ }
linux_syscalls::rt_sigaction(SIGINT, &orig_int, nullptr);
linux_syscalls::rt_sigaction(SIGQUIT, &orig_quit, nullptr);
>From fb977f798493b06237160f791f0a359084f66694 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Tue, 4 Aug 2026 19:45:00 +0000
Subject: [PATCH 3/4] address comments, move to clone3
---
libc/include/sys/syscall.h.def | 4 ++++
.../linux/syscall_wrappers/CMakeLists.txt | 5 ++---
.../syscall_wrappers/{fork.h => clone3.h} | 21 +++++++------------
.../OSUtil/linux/syscall_wrappers/execle.h | 6 +++---
libc/src/stdlib/linux/CMakeLists.txt | 6 +-----
libc/src/stdlib/linux/system.cpp | 10 ++++++---
6 files changed, 25 insertions(+), 27 deletions(-)
rename libc/src/__support/OSUtil/linux/syscall_wrappers/{fork.h => clone3.h} (63%)
diff --git a/libc/include/sys/syscall.h.def b/libc/include/sys/syscall.h.def
index 486947709b572..d197e7fed2337 100644
--- a/libc/include/sys/syscall.h.def
+++ b/libc/include/sys/syscall.h.def
@@ -197,6 +197,10 @@
#define SYS_clone2 __NR_clone2
#endif
+#ifdef __NR_clone3
+#define SYS_clone3 __NR_clone3
+#endif
+
#ifdef __NR_close
#define SYS_close __NR_close
#endif
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index 5dee62f7722e3..95d58e473fde3 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -955,11 +955,10 @@ add_header_library(
)
add_header_library(
- fork
+ clone3
HDRS
- fork.h
+ clone3.h
DEPENDS
- libc.hdr.signal_macros
libc.hdr.types.pid_t
libc.src.__support.OSUtil.osutil
libc.src.__support.common
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/fork.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/clone3.h
similarity index 63%
rename from libc/src/__support/OSUtil/linux/syscall_wrappers/fork.h
rename to libc/src/__support/OSUtil/linux/syscall_wrappers/clone3.h
index f570d54339b7a..a5bf42d759a7f 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/fork.h
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/clone3.h
@@ -7,35 +7,30 @@
//===----------------------------------------------------------------------===//
///
/// \file
-/// Implementation header for fork.
+/// Implementation header for clone3.
///
//===----------------------------------------------------------------------===//
-#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_FORK_H
-#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_FORK_H
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_CLONE3_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_CLONE3_H
-#include "hdr/signal_macros.h"
#include "hdr/types/pid_t.h"
#include "src/__support/OSUtil/linux/syscall.h" // For syscall_checked
#include "src/__support/common.h"
#include "src/__support/error_or.h"
#include "src/__support/macros/config.h"
+
+#include <linux/sched.h> // For struct clone_args
#include <sys/syscall.h> // For syscall numbers
namespace LIBC_NAMESPACE_DECL {
namespace linux_syscalls {
-LIBC_INLINE ErrorOr<pid_t> fork() {
-#ifdef SYS_fork
- return syscall_checked<pid_t>(SYS_fork);
-#elif defined(SYS_clone)
- return syscall_checked<pid_t>(SYS_clone, SIGCHLD, 0);
-#else
-#error "fork and clone syscalls not available."
-#endif
+LIBC_INLINE ErrorOr<pid_t> clone3(struct clone_args *cl_args, size_t size) {
+ return syscall_checked<pid_t>(SYS_clone3, cl_args, size);
}
} // namespace linux_syscalls
} // namespace LIBC_NAMESPACE_DECL
-#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_FORK_H
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_CLONE3_H
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h
index 080e8f75df556..fcc7f40de0abf 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h
@@ -28,16 +28,16 @@ LIBC_INLINE ErrorOr<int> execle(const char *path, Args... args) {
// All this logic because the standard says the environment pointer goes at
// the end. It's annoying but it's all compile time so it's not actually a
// problem.
- const void *all[] = {reinterpret_cast<const void *>(args)...};
+ const void *all[] = {static_cast<const void *>(args)...};
constexpr size_t total = sizeof...(Args);
static_assert(total >= 2,
"execle requires at least (arg0, ..., nullptr, envp)");
char *const *envp =
- reinterpret_cast<char *const *>(const_cast<void *>(all[total - 1]));
+ static_cast<char *const *>(const_cast<void *>(all[total - 1]));
const char *argv[total];
for (size_t i = 0; i < total - 1; ++i)
- argv[i] = reinterpret_cast<const char *>(all[i]);
+ argv[i] = static_cast<const char *>(all[i]);
argv[total - 1] = nullptr;
return syscall_checked<int>(SYS_execve, path, argv, envp);
diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index 27b0b1f34b916..c4bc634d262a2 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -97,14 +97,10 @@ add_entrypoint_object(
libc.src.__support.macros.config
libc.src.__support.OSUtil.osutil
libc.src.__support.OSUtil.linux.syscall_wrappers.execle
- libc.src.__support.OSUtil.linux.syscall_wrappers.fork
+ libc.src.__support.OSUtil.linux.syscall_wrappers.clone3
libc.src.__support.OSUtil.linux.syscall_wrappers.rt_sigaction
libc.src.__support.OSUtil.linux.syscall_wrappers.rt_sigprocmask
libc.src.__support.OSUtil.linux.syscall_wrappers.wait4
libc.src.signal.linux.signal_utils
libc.src.unistd.environ
)
-
-
-
-
diff --git a/libc/src/stdlib/linux/system.cpp b/libc/src/stdlib/linux/system.cpp
index 8298730a07353..b26a90017059e 100644
--- a/libc/src/stdlib/linux/system.cpp
+++ b/libc/src/stdlib/linux/system.cpp
@@ -17,8 +17,8 @@
#include "hdr/types/pid_t.h"
#include "hdr/types/sigset_t.h"
#include "src/__support/OSUtil/exit.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/clone3.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/execle.h"
-#include "src/__support/OSUtil/linux/syscall_wrappers/fork.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/rt_sigaction.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/rt_sigprocmask.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/wait4.h"
@@ -28,6 +28,8 @@
#include "src/signal/linux/signal_utils.h"
#include "src/unistd/environ.h"
+#include <linux/sched.h> // For struct clone_args
+
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(int, system, (const char *command)) {
@@ -65,7 +67,9 @@ LLVM_LIBC_FUNCTION(int, system, (const char *command)) {
return -1;
}
- auto fork_res = linux_syscalls::fork();
+ struct clone_args cl_args{};
+ cl_args.exit_signal = SIGCHLD;
+ auto fork_res = linux_syscalls::clone3(&cl_args, sizeof(cl_args));
if (!fork_res.has_value()) {
linux_syscalls::rt_sigaction(SIGINT, &orig_int, nullptr);
linux_syscalls::rt_sigaction(SIGQUIT, &orig_quit, nullptr);
@@ -82,7 +86,7 @@ LLVM_LIBC_FUNCTION(int, system, (const char *command)) {
// Error checking isn't helpful since this is the forked process, so we
// can't set errno. All we can meaningfully do is exit with status 127.
- linux_syscalls::execle("/bin/sh", "sh", "-c", command, 0, environ);
+ linux_syscalls::execle("/bin/sh", "sh", "-c", command, nullptr, environ);
internal::exit(127);
}
>From 99113a08c17d9689d5c878b600c4cffd5c5e55bc Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Sat, 8 Aug 2026 07:15:28 +0000
Subject: [PATCH 4/4] address comments
---
.../linux/syscall_wrappers/CMakeLists.txt | 24 -------------------
.../OSUtil/linux/syscall_wrappers/execle.h | 4 ++--
libc/src/stdlib/linux/system.cpp | 4 +++-
3 files changed, 5 insertions(+), 27 deletions(-)
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index 95d58e473fde3..997e199460ab7 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -967,30 +967,6 @@ add_header_library(
libc.include.sys_syscall
)
-add_header_library(
- execve
- HDRS
- execve.h
- DEPENDS
- libc.src.__support.OSUtil.osutil
- libc.src.__support.common
- libc.src.__support.error_or
- libc.src.__support.macros.config
- libc.include.sys_syscall
-)
-
-add_header_library(
- execl
- HDRS
- execl.h
- DEPENDS
- libc.src.__support.OSUtil.osutil
- libc.src.__support.common
- libc.src.__support.error_or
- libc.src.__support.macros.config
- libc.include.sys_syscall
-)
-
add_header_library(
execle
HDRS
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h
index fcc7f40de0abf..3b0d2a87917e8 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/execle.h
@@ -28,7 +28,7 @@ LIBC_INLINE ErrorOr<int> execle(const char *path, Args... args) {
// All this logic because the standard says the environment pointer goes at
// the end. It's annoying but it's all compile time so it's not actually a
// problem.
- const void *all[] = {static_cast<const void *>(args)...};
+ const void *all[] = {reinterpret_cast<const void *>(args)...};
constexpr size_t total = sizeof...(Args);
static_assert(total >= 2,
"execle requires at least (arg0, ..., nullptr, envp)");
@@ -37,7 +37,7 @@ LIBC_INLINE ErrorOr<int> execle(const char *path, Args... args) {
const char *argv[total];
for (size_t i = 0; i < total - 1; ++i)
- argv[i] = static_cast<const char *>(all[i]);
+ argv[i] = reinterpret_cast<const char *>(all[i]);
argv[total - 1] = nullptr;
return syscall_checked<int>(SYS_execve, path, argv, envp);
diff --git a/libc/src/stdlib/linux/system.cpp b/libc/src/stdlib/linux/system.cpp
index b26a90017059e..f40e1f8756b15 100644
--- a/libc/src/stdlib/linux/system.cpp
+++ b/libc/src/stdlib/linux/system.cpp
@@ -32,6 +32,8 @@
namespace LIBC_NAMESPACE_DECL {
+const char *SHELL_PATH = "/bin/sh";
+
LLVM_LIBC_FUNCTION(int, system, (const char *command)) {
if (command == nullptr)
return 1;
@@ -86,7 +88,7 @@ LLVM_LIBC_FUNCTION(int, system, (const char *command)) {
// Error checking isn't helpful since this is the forked process, so we
// can't set errno. All we can meaningfully do is exit with status 127.
- linux_syscalls::execle("/bin/sh", "sh", "-c", command, nullptr, environ);
+ linux_syscalls::execle(SHELL_PATH, "sh", "-c", command, 0, environ);
internal::exit(127);
}
More information about the libc-commits
mailing list