[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