[libc-commits] [libc] [libc] Implement execvp in unistd (PR #232427)
Aman Maurya via libc-commits
libc-commits at lists.llvm.org
Fri Oct 16 10:57:13 PDT 2026
https://github.com/amanmaurya92 updated https://github.com/llvm/llvm-project/pull/232427
>From f916d493709e3519e7b09abd36096b9c4667e9c7 Mon Sep 17 00:00:00 2001
From: amanmaurya92 <amanmaurya9209 at gmail.com>
Date: Thu, 15 Oct 2026 22:33:22 +0530
Subject: [PATCH 1/3] [libc][unistd] Implement execvp
Part of #232374.
---
libc/config/linux/aarch64/entrypoints.txt | 1 +
libc/config/linux/riscv/entrypoints.txt | 1 +
libc/config/linux/x86_64/entrypoints.txt | 1 +
libc/include/unistd.yaml | 7 +
libc/src/unistd/CMakeLists.txt | 7 +
libc/src/unistd/execvp.h | 25 +++
libc/src/unistd/linux/CMakeLists.txt | 20 +++
libc/src/unistd/linux/execvp.cpp | 162 ++++++++++++++++++
.../integration/src/unistd/CMakeLists.txt | 21 +++
.../integration/src/unistd/execvp_test.cpp | 80 +++++++++
libc/test/src/unistd/CMakeLists.txt | 13 ++
libc/test/src/unistd/execvp_test.cpp | 41 +++++
12 files changed, 379 insertions(+)
create mode 100644 libc/src/unistd/execvp.h
create mode 100644 libc/src/unistd/linux/execvp.cpp
create mode 100644 libc/test/integration/src/unistd/execvp_test.cpp
create mode 100644 libc/test/src/unistd/execvp_test.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index cee039a405c8d..d692541e65f59 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1454,6 +1454,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.unistd.environ
libc.src.unistd.execl
libc.src.unistd.execv
+ libc.src.unistd.execvp
libc.src.unistd.fork
libc.src.unistd.getopt
libc.src.unistd.optarg
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index f91a8939ac61d..465f36dcbf6b2 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1653,6 +1653,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.unistd.environ
libc.src.unistd.execl
libc.src.unistd.execv
+ libc.src.unistd.execvp
libc.src.unistd.fork
libc.src.unistd.getopt
libc.src.unistd.optarg
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 1aaafc9c08467..f3462fe4bf81e 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1679,6 +1679,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.unistd.environ
libc.src.unistd.execl
libc.src.unistd.execv
+ libc.src.unistd.execvp
libc.src.unistd.fork
libc.src.unistd.getopt
libc.src.unistd.optarg
diff --git a/libc/include/unistd.yaml b/libc/include/unistd.yaml
index 5964e35b6ce5d..a94bf195a4eef 100644
--- a/libc/include/unistd.yaml
+++ b/libc/include/unistd.yaml
@@ -237,6 +237,13 @@ functions:
- type: const char *
- type: __exec_argv_t
- type: __exec_envp_t
+ - name: execvp
+ standards:
+ - posix
+ return_type: int
+ arguments:
+ - type: const char *
+ - type: __exec_argv_t
- name: faccessat
standards:
- posix
diff --git a/libc/src/unistd/CMakeLists.txt b/libc/src/unistd/CMakeLists.txt
index 27b74b8ca2de3..aee321f599588 100644
--- a/libc/src/unistd/CMakeLists.txt
+++ b/libc/src/unistd/CMakeLists.txt
@@ -175,6 +175,13 @@ add_entrypoint_object(
.${LIBC_TARGET_OS}.execve
)
+add_entrypoint_object(
+ execvp
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.execvp
+)
+
add_entrypoint_object(
fexecve
ALIAS
diff --git a/libc/src/unistd/execvp.h b/libc/src/unistd/execvp.h
new file mode 100644
index 0000000000000..3d3db0d822ed1
--- /dev/null
+++ b/libc/src/unistd/execvp.h
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 execvp.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_UNISTD_EXECVP_H
+#define LLVM_LIBC_SRC_UNISTD_EXECVP_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int execvp(const char *file, char *const argv[]);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_UNISTD_EXECVP_H
diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt
index 61030ec7e1228..9a293479c6039 100644
--- a/libc/src/unistd/linux/CMakeLists.txt
+++ b/libc/src/unistd/linux/CMakeLists.txt
@@ -264,6 +264,26 @@ add_entrypoint_object(
libc.src.errno.errno
)
+add_entrypoint_object(
+ execvp
+ SRCS
+ execvp.cpp
+ HDRS
+ ../execvp.h
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.types.size_t
+ libc.src.__support.common
+ libc.src.__support.libc_errno
+ libc.src.__support.macros.config
+ libc.src.__support.macros.null_check
+ libc.src.__support.CPP.string_view
+ libc.src.__support.CPP.stringstream
+ libc.src.__support.OSUtil.linux.syscall_wrappers.execve
+ libc.src.errno.errno
+ libc.src.unistd.environ
+)
+
add_entrypoint_object(
fexecve
SRCS
diff --git a/libc/src/unistd/linux/execvp.cpp b/libc/src/unistd/linux/execvp.cpp
new file mode 100644
index 0000000000000..92cec0e0a0a1a
--- /dev/null
+++ b/libc/src/unistd/linux/execvp.cpp
@@ -0,0 +1,162 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 execvp.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/unistd/execvp.h"
+
+#include "hdr/errno_macros.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/CPP/stringstream.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/execve.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/unistd/environ.h"
+
+#include <linux/limits.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+namespace {
+
+LIBC_INLINE const char *get_path() {
+ if (LIBC_NAMESPACE::environ == nullptr)
+ return nullptr;
+
+ for (char **env = LIBC_NAMESPACE::environ; *env != nullptr; ++env) {
+ cpp::string_view entry(*env);
+ if (entry.starts_with("PATH="))
+ return *env + 5;
+ }
+ return nullptr;
+}
+
+LIBC_INLINE int execve_with_fallback(const char *path, char *const argv[],
+ char *const envp[]) {
+ auto ret = linux_syscalls::execve(path, argv, envp);
+ if (!ret && ret.error() == ENOEXEC) {
+ size_t argc = 0;
+ while (argv[argc] != nullptr)
+ ++argc;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wvla"
+#if __has_warning("-Wvla-cxx-extension")
+#pragma GCC diagnostic ignored "-Wvla-cxx-extension"
+#endif
+ char *new_argv[argc + 2];
+#pragma GCC diagnostic pop
+ new_argv[0] = const_cast<char *>("sh");
+ new_argv[1] = const_cast<char *>(path);
+ for (size_t i = 1; i <= argc; ++i)
+ new_argv[i + 1] = argv[i];
+
+ ret = linux_syscalls::execve("/bin/sh", new_argv, envp);
+ }
+ if (!ret) {
+ libc_errno = ret.error();
+ return -1;
+ }
+ return *ret;
+}
+
+} // namespace
+
+LLVM_LIBC_FUNCTION(int, execvp, (const char *file, char *const argv[])) {
+ LIBC_CRASH_ON_NULLPTR(file);
+
+ if (file[0] == '\0') {
+ libc_errno = ENOENT;
+ return -1;
+ }
+
+ char *const *envp = const_cast<char *const *>(LIBC_NAMESPACE::environ);
+
+ cpp::string_view file_view(file);
+ if (file_view.contains('/'))
+ return execve_with_fallback(file, argv, envp);
+
+ const char *path = get_path();
+ // POSIX standard default path if PATH is not set.
+ if (path == nullptr)
+ path = ":/bin:/usr/bin";
+
+ cpp::string_view path_view(path);
+ bool seen_eacces = false;
+
+ size_t start = 0;
+ while (start <= path_view.size()) {
+ size_t end = path_view.find_first_of(':', start);
+ if (end == cpp::string_view::npos)
+ end = path_view.size();
+
+ cpp::string_view dir = path_view.substr(start, end - start);
+ start = end + 1;
+
+ char path_buf[PATH_MAX];
+ cpp::StringStream ss(path_buf);
+
+ if (dir.empty()) {
+ ss << file << cpp::StringStream::ENDS;
+ } else {
+ ss << dir;
+ if (!dir.ends_with('/'))
+ ss << '/';
+ ss << file << cpp::StringStream::ENDS;
+ }
+
+ if (ss.overflow())
+ continue;
+
+ auto ret = linux_syscalls::execve(path_buf, argv, envp);
+ if (!ret && ret.error() == ENOEXEC) {
+ size_t argc = 0;
+ while (argv[argc] != nullptr)
+ ++argc;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wvla"
+#if __has_warning("-Wvla-cxx-extension")
+#pragma GCC diagnostic ignored "-Wvla-cxx-extension"
+#endif
+ char *new_argv[argc + 2];
+#pragma GCC diagnostic pop
+ new_argv[0] = const_cast<char *>("sh");
+ new_argv[1] = path_buf;
+ for (size_t i = 1; i <= argc; ++i)
+ new_argv[i + 1] = argv[i];
+
+ ret = linux_syscalls::execve("/bin/sh", new_argv, envp);
+ }
+
+ if (ret)
+ return *ret;
+
+ int err = ret.error();
+ if (err == EACCES)
+ seen_eacces = true;
+ else if (err != ENOENT && err != ENOTDIR) {
+ libc_errno = err;
+ return -1;
+ }
+
+ if (end == path_view.size())
+ break;
+ }
+
+ libc_errno = seen_eacces ? EACCES : ENOENT;
+ return -1;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/integration/src/unistd/CMakeLists.txt b/libc/test/integration/src/unistd/CMakeLists.txt
index cc04b374a8378..a401f6c466c4e 100644
--- a/libc/test/integration/src/unistd/CMakeLists.txt
+++ b/libc/test/integration/src/unistd/CMakeLists.txt
@@ -225,3 +225,24 @@ add_integration_test(
ENV
EXECV_TEST=PASS
)
+
+add_integration_test(
+ execvp_test
+ SUITE
+ unistd-integration-tests
+ SRCS
+ execvp_test.cpp
+ DEPENDS
+ libc_execv_test_normal_exit
+ libc_exec_test_signal_exit
+ libc.hdr.signal_macros
+ libc.hdr.sys_wait_macros
+ libc.src.errno.errno
+ libc.src.sys.wait.waitpid
+ libc.src.unistd.environ
+ libc.src.unistd.execvp
+ libc.src.unistd.fork
+ ENV
+ EXECV_TEST=PASS
+ PATH=.
+)
diff --git a/libc/test/integration/src/unistd/execvp_test.cpp b/libc/test/integration/src/unistd/execvp_test.cpp
new file mode 100644
index 0000000000000..7db853880b6fe
--- /dev/null
+++ b/libc/test/integration/src/unistd/execvp_test.cpp
@@ -0,0 +1,80 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Integration test for execvp.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/sys/wait/waitpid.h"
+#include "src/unistd/execvp.h"
+#include "src/unistd/fork.h"
+
+#include "hdr/signal_macros.h"
+#include "hdr/sys_wait_macros.h"
+#include "test/IntegrationTest/test.h"
+
+void fork_and_execvp_path_search() {
+ pid_t pid = LIBC_NAMESPACE::fork();
+ if (pid == 0) {
+ char *const argv[] = {
+ const_cast<char *>("libc_execv_test_normal_exit"),
+ nullptr,
+ };
+ LIBC_NAMESPACE::execvp("libc_execv_test_normal_exit", argv);
+ }
+ ASSERT_TRUE(pid > 0);
+ int status;
+ pid_t cpid = LIBC_NAMESPACE::waitpid(pid, &status, 0);
+ ASSERT_TRUE(cpid > 0);
+ ASSERT_EQ(cpid, pid);
+ ASSERT_TRUE(WIFEXITED(status));
+}
+
+void fork_and_execvp_slash_path() {
+ pid_t pid = LIBC_NAMESPACE::fork();
+ if (pid == 0) {
+ char *const argv[] = {
+ const_cast<char *>("libc_execv_test_normal_exit"),
+ nullptr,
+ };
+ LIBC_NAMESPACE::execvp("./libc_execv_test_normal_exit", argv);
+ }
+ ASSERT_TRUE(pid > 0);
+ int status;
+ pid_t cpid = LIBC_NAMESPACE::waitpid(pid, &status, 0);
+ ASSERT_TRUE(cpid > 0);
+ ASSERT_EQ(cpid, pid);
+ ASSERT_TRUE(WIFEXITED(status));
+}
+
+void fork_and_execvp_signal_exit() {
+ pid_t pid = LIBC_NAMESPACE::fork();
+ if (pid == 0) {
+ char *const argv[] = {
+ const_cast<char *>("libc_exec_test_signal_exit"),
+ nullptr,
+ };
+ LIBC_NAMESPACE::execvp("libc_exec_test_signal_exit", argv);
+ }
+ ASSERT_TRUE(pid > 0);
+ int status;
+ pid_t cpid = LIBC_NAMESPACE::waitpid(pid, &status, 0);
+ ASSERT_TRUE(cpid > 0);
+ ASSERT_EQ(cpid, pid);
+ ASSERT_FALSE(WIFEXITED(status));
+ ASSERT_TRUE(WTERMSIG(status) == SIGUSR1);
+}
+
+TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
+ [[maybe_unused]] char **envp) {
+ fork_and_execvp_path_search();
+ fork_and_execvp_slash_path();
+ fork_and_execvp_signal_exit();
+ return 0;
+}
diff --git a/libc/test/src/unistd/CMakeLists.txt b/libc/test/src/unistd/CMakeLists.txt
index 2e8d9bb8e2509..cd7e60ebccead 100644
--- a/libc/test/src/unistd/CMakeLists.txt
+++ b/libc/test/src/unistd/CMakeLists.txt
@@ -245,6 +245,19 @@ add_libc_test(
libc.test.UnitTest.ErrnoSetterMatcher
)
+add_libc_test(
+ execvp_test
+ SUITE
+ libc_unistd_unittests
+ SRCS
+ execvp_test.cpp
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.src.unistd.execvp
+ libc.test.UnitTest.ErrnoCheckingTest
+ libc.test.UnitTest.ErrnoSetterMatcher
+)
+
add_libc_test(
fexecve_test
SUITE
diff --git a/libc/test/src/unistd/execvp_test.cpp b/libc/test/src/unistd/execvp_test.cpp
new file mode 100644
index 0000000000000..570c88f30e66d
--- /dev/null
+++ b/libc/test/src/unistd/execvp_test.cpp
@@ -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
+/// Unittests for execvp.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "src/unistd/execvp.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcExecvpTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcExecvpTest, NonExistentFile) {
+ using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+ char *const argv[] = {const_cast<char *>("nonexistent_binary_xyz_123"),
+ nullptr};
+ ASSERT_THAT(LIBC_NAMESPACE::execvp("nonexistent_binary_xyz_123", argv),
+ Fails(ENOENT));
+}
+
+TEST_F(LlvmLibcExecvpTest, NonExistentPathWithSlash) {
+ using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+ char *const argv[] = {const_cast<char *>("nonexistent"), nullptr};
+ ASSERT_THAT(LIBC_NAMESPACE::execvp("/nonexistent/path/xyz", argv),
+ Fails(ENOENT));
+}
+
+TEST_F(LlvmLibcExecvpTest, EmptyFileName) {
+ using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+ char *const argv[] = {const_cast<char *>(""), nullptr};
+ ASSERT_THAT(LIBC_NAMESPACE::execvp("", argv), Fails(ENOENT));
+}
>From b8d827badc9f4f4227e9bed03425585f40a7880e Mon Sep 17 00:00:00 2001
From: amanmaurya92 <amanmaurya9209 at gmail.com>
Date: Fri, 16 Oct 2026 21:38:37 +0530
Subject: [PATCH 2/3] [libc][unistd] Refactor execvp and add tests for EACCES
and ENOEXEC
- Use EnvironmentManager and inline_memcpy following #232008
- Consolidate shell fallback into unified execve_with_fallback helper
- Add integration tests for EACCES and ENOEXEC shell fallback
---
libc/src/unistd/linux/CMakeLists.txt | 7 +-
libc/src/unistd/linux/execvp.cpp | 126 +++++++-----------
.../integration/src/unistd/CMakeLists.txt | 8 ++
.../integration/src/unistd/execvp_test.cpp | 80 +++++++++++
4 files changed, 141 insertions(+), 80 deletions(-)
diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt
index 9a293479c6039..629051e36191a 100644
--- a/libc/src/unistd/linux/CMakeLists.txt
+++ b/libc/src/unistd/linux/CMakeLists.txt
@@ -272,15 +272,18 @@ add_entrypoint_object(
../execvp.h
DEPENDS
libc.hdr.errno_macros
+ libc.hdr.limits_macros
libc.hdr.types.size_t
libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.CPP.string_view
libc.src.__support.libc_errno
libc.src.__support.macros.config
libc.src.__support.macros.null_check
- libc.src.__support.CPP.string_view
- libc.src.__support.CPP.stringstream
libc.src.__support.OSUtil.linux.syscall_wrappers.execve
libc.src.errno.errno
+ libc.src.stdlib.environ_internal
+ libc.src.string.memory_utils.inline_memcpy
libc.src.unistd.environ
)
diff --git a/libc/src/unistd/linux/execvp.cpp b/libc/src/unistd/linux/execvp.cpp
index 92cec0e0a0a1a..06f763a2d00f6 100644
--- a/libc/src/unistd/linux/execvp.cpp
+++ b/libc/src/unistd/linux/execvp.cpp
@@ -14,36 +14,26 @@
#include "src/unistd/execvp.h"
#include "hdr/errno_macros.h"
+#include "hdr/limits_macros.h"
#include "hdr/types/size_t.h"
#include "src/__support/CPP/string_view.h"
-#include "src/__support/CPP/stringstream.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/execve.h"
#include "src/__support/common.h"
+#include "src/__support/error_or.h"
#include "src/__support/libc_errno.h"
#include "src/__support/macros/config.h"
#include "src/__support/macros/null_check.h"
+#include "src/stdlib/environ_internal.h"
+#include "src/string/memory_utils/inline_memcpy.h"
#include "src/unistd/environ.h"
-#include <linux/limits.h>
-
namespace LIBC_NAMESPACE_DECL {
namespace {
-LIBC_INLINE const char *get_path() {
- if (LIBC_NAMESPACE::environ == nullptr)
- return nullptr;
-
- for (char **env = LIBC_NAMESPACE::environ; *env != nullptr; ++env) {
- cpp::string_view entry(*env);
- if (entry.starts_with("PATH="))
- return *env + 5;
- }
- return nullptr;
-}
-
-LIBC_INLINE int execve_with_fallback(const char *path, char *const argv[],
- char *const envp[]) {
+LIBC_INLINE ErrorOr<int> execve_with_fallback(const char *path,
+ char *const argv[],
+ char *const envp[]) {
auto ret = linux_syscalls::execve(path, argv, envp);
if (!ret && ret.error() == ENOEXEC) {
size_t argc = 0;
@@ -64,98 +54,78 @@ LIBC_INLINE int execve_with_fallback(const char *path, char *const argv[],
ret = linux_syscalls::execve("/bin/sh", new_argv, envp);
}
- if (!ret) {
- libc_errno = ret.error();
- return -1;
- }
- return *ret;
+ return ret;
}
} // namespace
LLVM_LIBC_FUNCTION(int, execvp, (const char *file, char *const argv[])) {
LIBC_CRASH_ON_NULLPTR(file);
+ LIBC_CRASH_ON_NULLPTR(argv);
- if (file[0] == '\0') {
+ cpp::string_view file_sv(file);
+ if (file_sv.empty()) {
libc_errno = ENOENT;
return -1;
}
char *const *envp = const_cast<char *const *>(LIBC_NAMESPACE::environ);
- cpp::string_view file_view(file);
- if (file_view.contains('/'))
- return execve_with_fallback(file, argv, envp);
+ // If there's a / in file, treat it as a path.
+ if (file_sv.find_first_of('/') != cpp::string_view::npos) {
+ auto ret = execve_with_fallback(file, argv, envp);
+ if (!ret) {
+ libc_errno = ret.error();
+ return -1;
+ }
+ return *ret;
+ }
- const char *path = get_path();
- // POSIX standard default path if PATH is not set.
- if (path == nullptr)
- path = ":/bin:/usr/bin";
+ // Otherwise treat file as the name of a file in a directory, search the
+ // directories listed in PATH.
+ constexpr cpp::string_view DEFAULT_PATH = "/bin:/usr/bin";
+ const char *env_path =
+ internal::EnvironmentManager::get_instance().get("PATH");
+ cpp::string_view path_env =
+ env_path != nullptr ? cpp::string_view(env_path) : DEFAULT_PATH;
- cpp::string_view path_view(path);
+ char buf[PATH_MAX];
bool seen_eacces = false;
+ int last_error = ENOENT;
- size_t start = 0;
- while (start <= path_view.size()) {
- size_t end = path_view.find_first_of(':', start);
- if (end == cpp::string_view::npos)
- end = path_view.size();
-
- cpp::string_view dir = path_view.substr(start, end - start);
- start = end + 1;
+ for (size_t colon_pos = 0; colon_pos != cpp::string_view::npos;
+ path_env = path_env.substr(colon_pos + 1)) {
+ colon_pos = path_env.find_first_of(':');
+ cpp::string_view dir = path_env.substr(0, colon_pos);
+ if (dir.empty())
+ dir = ".";
- char path_buf[PATH_MAX];
- cpp::StringStream ss(path_buf);
-
- if (dir.empty()) {
- ss << file << cpp::StringStream::ENDS;
- } else {
- ss << dir;
- if (!dir.ends_with('/'))
- ss << '/';
- ss << file << cpp::StringStream::ENDS;
- }
-
- if (ss.overflow())
+ if (dir.size() + 1 + file_sv.size() >= PATH_MAX) {
+ last_error = ENAMETOOLONG;
continue;
-
- auto ret = linux_syscalls::execve(path_buf, argv, envp);
- if (!ret && ret.error() == ENOEXEC) {
- size_t argc = 0;
- while (argv[argc] != nullptr)
- ++argc;
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wvla"
-#if __has_warning("-Wvla-cxx-extension")
-#pragma GCC diagnostic ignored "-Wvla-cxx-extension"
-#endif
- char *new_argv[argc + 2];
-#pragma GCC diagnostic pop
- new_argv[0] = const_cast<char *>("sh");
- new_argv[1] = path_buf;
- for (size_t i = 1; i <= argc; ++i)
- new_argv[i + 1] = argv[i];
-
- ret = linux_syscalls::execve("/bin/sh", new_argv, envp);
}
+ inline_memcpy(buf, dir.data(), dir.size());
+ buf[dir.size()] = '/';
+ inline_memcpy(buf + dir.size() + 1, file_sv.data(), file_sv.size());
+ buf[dir.size() + 1 + file_sv.size()] = '\0';
+
+ auto ret = execve_with_fallback(buf, argv, envp);
if (ret)
return *ret;
int err = ret.error();
- if (err == EACCES)
+ if (err == EACCES) {
seen_eacces = true;
- else if (err != ENOENT && err != ENOTDIR) {
+ } else if (err == ENOENT || err == ENOTDIR) {
+ last_error = err;
+ } else {
libc_errno = err;
return -1;
}
-
- if (end == path_view.size())
- break;
}
- libc_errno = seen_eacces ? EACCES : ENOENT;
+ libc_errno = seen_eacces ? EACCES : last_error;
return -1;
}
diff --git a/libc/test/integration/src/unistd/CMakeLists.txt b/libc/test/integration/src/unistd/CMakeLists.txt
index a401f6c466c4e..8b81886f769d7 100644
--- a/libc/test/integration/src/unistd/CMakeLists.txt
+++ b/libc/test/integration/src/unistd/CMakeLists.txt
@@ -235,13 +235,21 @@ add_integration_test(
DEPENDS
libc_execv_test_normal_exit
libc_exec_test_signal_exit
+ libc.hdr.errno_macros
+ libc.hdr.fcntl_macros
libc.hdr.signal_macros
libc.hdr.sys_wait_macros
+ libc.hdr.types.ssize_t
libc.src.errno.errno
+ libc.src.fcntl.open
+ libc.src.stdlib.getenv
libc.src.sys.wait.waitpid
+ libc.src.unistd.close
libc.src.unistd.environ
libc.src.unistd.execvp
libc.src.unistd.fork
+ libc.src.unistd.unlink
+ libc.src.unistd.write
ENV
EXECV_TEST=PASS
PATH=.
diff --git a/libc/test/integration/src/unistd/execvp_test.cpp b/libc/test/integration/src/unistd/execvp_test.cpp
index 7db853880b6fe..8c36effb47825 100644
--- a/libc/test/integration/src/unistd/execvp_test.cpp
+++ b/libc/test/integration/src/unistd/execvp_test.cpp
@@ -11,14 +11,23 @@
///
//===----------------------------------------------------------------------===//
+#include "src/fcntl/open.h"
#include "src/sys/wait/waitpid.h"
+#include "src/unistd/close.h"
#include "src/unistd/execvp.h"
#include "src/unistd/fork.h"
+#include "src/unistd/unlink.h"
+#include "src/unistd/write.h"
+#include "hdr/errno_macros.h"
+#include "hdr/fcntl_macros.h"
#include "hdr/signal_macros.h"
#include "hdr/sys_wait_macros.h"
+#include "hdr/types/ssize_t.h"
#include "test/IntegrationTest/test.h"
+#include <errno.h>
+
void fork_and_execvp_path_search() {
pid_t pid = LIBC_NAMESPACE::fork();
if (pid == 0) {
@@ -71,10 +80,81 @@ void fork_and_execvp_signal_exit() {
ASSERT_TRUE(WTERMSIG(status) == SIGUSR1);
}
+void execvp_eacces() {
+ const char *no_exec_file = "libc_execvp_test_no_exec.test";
+ int fd =
+ LIBC_NAMESPACE::open(no_exec_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
+ ASSERT_TRUE(fd > 0);
+ LIBC_NAMESPACE::close(fd);
+
+ char *const argv[] = {
+ const_cast<char *>(no_exec_file),
+ nullptr,
+ };
+
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::execvp("./libc_execvp_test_no_exec.test", argv), -1);
+ ASSERT_ERRNO_EQ(EACCES);
+
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::execvp(no_exec_file, argv), -1);
+ ASSERT_ERRNO_EQ(EACCES);
+
+ LIBC_NAMESPACE::unlink(no_exec_file);
+}
+
+void fork_and_execvp_enoexec_fallback() {
+ const char *script_file = "libc_execvp_test_script.test";
+ int fd =
+ LIBC_NAMESPACE::open(script_file, O_CREAT | O_WRONLY | O_TRUNC, 0777);
+ ASSERT_TRUE(fd > 0);
+ const char content[] = "exit 0\n";
+ ASSERT_EQ(LIBC_NAMESPACE::write(fd, content, sizeof(content) - 1),
+ static_cast<ssize_t>(sizeof(content) - 1));
+ LIBC_NAMESPACE::close(fd);
+
+ // Test slash path with fallback to shell.
+ pid_t pid = LIBC_NAMESPACE::fork();
+ if (pid == 0) {
+ char *const argv[] = {
+ const_cast<char *>(script_file),
+ nullptr,
+ };
+ LIBC_NAMESPACE::execvp("./libc_execvp_test_script.test", argv);
+ }
+ ASSERT_TRUE(pid > 0);
+ int status;
+ pid_t cpid = LIBC_NAMESPACE::waitpid(pid, &status, 0);
+ ASSERT_TRUE(cpid > 0);
+ ASSERT_EQ(cpid, pid);
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
+
+ // Test PATH search with fallback to shell.
+ pid = LIBC_NAMESPACE::fork();
+ if (pid == 0) {
+ char *const argv[] = {
+ const_cast<char *>(script_file),
+ nullptr,
+ };
+ LIBC_NAMESPACE::execvp(script_file, argv);
+ }
+ ASSERT_TRUE(pid > 0);
+ cpid = LIBC_NAMESPACE::waitpid(pid, &status, 0);
+ ASSERT_TRUE(cpid > 0);
+ ASSERT_EQ(cpid, pid);
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
+
+ LIBC_NAMESPACE::unlink(script_file);
+}
+
TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
[[maybe_unused]] char **envp) {
fork_and_execvp_path_search();
fork_and_execvp_slash_path();
fork_and_execvp_signal_exit();
+ execvp_eacces();
+ fork_and_execvp_enoexec_fallback();
return 0;
}
>From 9d2d46c6470b369772c38ae75bb1379a9546824d Mon Sep 17 00:00:00 2001
From: amanmaurya92 <amanmaurya9209 at gmail.com>
Date: Fri, 16 Oct 2026 23:26:44 +0530
Subject: [PATCH 3/3] [libc][unistd] Fix clang-format warnings in execvp
---
libc/src/unistd/linux/execvp.cpp | 5 ++---
libc/test/integration/src/unistd/execvp_test.cpp | 3 ++-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/libc/src/unistd/linux/execvp.cpp b/libc/src/unistd/linux/execvp.cpp
index 06f763a2d00f6..36c75605c3af5 100644
--- a/libc/src/unistd/linux/execvp.cpp
+++ b/libc/src/unistd/linux/execvp.cpp
@@ -31,9 +31,8 @@ namespace LIBC_NAMESPACE_DECL {
namespace {
-LIBC_INLINE ErrorOr<int> execve_with_fallback(const char *path,
- char *const argv[],
- char *const envp[]) {
+LIBC_INLINE ErrorOr<int>
+execve_with_fallback(const char *path, char *const argv[], char *const envp[]) {
auto ret = linux_syscalls::execve(path, argv, envp);
if (!ret && ret.error() == ENOEXEC) {
size_t argc = 0;
diff --git a/libc/test/integration/src/unistd/execvp_test.cpp b/libc/test/integration/src/unistd/execvp_test.cpp
index 8c36effb47825..dc7af459b2b9a 100644
--- a/libc/test/integration/src/unistd/execvp_test.cpp
+++ b/libc/test/integration/src/unistd/execvp_test.cpp
@@ -93,7 +93,8 @@ void execvp_eacces() {
};
errno = 0;
- ASSERT_EQ(LIBC_NAMESPACE::execvp("./libc_execvp_test_no_exec.test", argv), -1);
+ ASSERT_EQ(LIBC_NAMESPACE::execvp("./libc_execvp_test_no_exec.test", argv),
+ -1);
ASSERT_ERRNO_EQ(EACCES);
errno = 0;
More information about the libc-commits
mailing list