[libc-commits] [libc] [libc] Implement execvp in unistd (PR #232427)
Aman Maurya via libc-commits
libc-commits at lists.llvm.org
Sat Oct 17 00:46:09 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/4] [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 cee039a405c8d3..d692541e65f591 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 f91a8939ac61d6..465f36dcbf6b28 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 1aaafc9c084679..f3462fe4bf81ec 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 5964e35b6ce5d2..a94bf195a4eef3 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 27b74b8ca2de3c..aee321f599588f 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 00000000000000..3d3db0d822ed17
--- /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 61030ec7e12283..9a293479c6039a 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 00000000000000..92cec0e0a0a1a0
--- /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 cc04b374a83789..a401f6c466c4ef 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 00000000000000..7db853880b6fe3
--- /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 2e8d9bb8e2509f..cd7e60ebccead8 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 00000000000000..570c88f30e66d6
--- /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/4] [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 9a293479c6039a..629051e36191a0 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 92cec0e0a0a1a0..06f763a2d00f69 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 a401f6c466c4ef..8b81886f769d71 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 7db853880b6fe3..8c36effb478259 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/4] [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 06f763a2d00f69..36c75605c3af5f 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 8c36effb478259..dc7af459b2b9a7 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;
>From dc2b127bf5242af0d9175d9f3f9799ec2800364c Mon Sep 17 00:00:00 2001
From: amanmaurya92 <amanmaurya9209 at gmail.com>
Date: Sat, 17 Oct 2026 13:15:18 +0530
Subject: [PATCH 4/4] [libc] Simplify execvp and adopt test suite from #232008
- Remove ENOEXEC shell fallback and VLA pragmas per maintainer request
- Adopt comprehensive integration test suite with noexec fixture
---
libc/src/unistd/linux/CMakeLists.txt | 1 -
libc/src/unistd/linux/execvp.cpp | 42 +-----
.../integration/src/unistd/CMakeLists.txt | 14 +-
.../integration/src/unistd/execvp_test.cpp | 139 +++++++-----------
.../src/unistd/testdata/CMakeLists.txt | 20 +++
.../testdata/execvp_test_normal_exit.cpp | 23 +++
6 files changed, 113 insertions(+), 126 deletions(-)
create mode 100644 libc/test/integration/src/unistd/testdata/CMakeLists.txt
create mode 100644 libc/test/integration/src/unistd/testdata/execvp_test_normal_exit.cpp
diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt
index 629051e36191a0..4a24077f4f4c7f 100644
--- a/libc/src/unistd/linux/CMakeLists.txt
+++ b/libc/src/unistd/linux/CMakeLists.txt
@@ -275,7 +275,6 @@ add_entrypoint_object(
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
diff --git a/libc/src/unistd/linux/execvp.cpp b/libc/src/unistd/linux/execvp.cpp
index 36c75605c3af5f..508f21ed6444f2 100644
--- a/libc/src/unistd/linux/execvp.cpp
+++ b/libc/src/unistd/linux/execvp.cpp
@@ -19,7 +19,6 @@
#include "src/__support/CPP/string_view.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"
@@ -29,35 +28,10 @@
namespace LIBC_NAMESPACE_DECL {
-namespace {
-
-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;
- 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);
- }
- return ret;
-}
-
-} // namespace
-
+// TODO: POSIX requires that if execve fails with ENOEXEC, execvp executes the
+// file using the system shell (as if by `sh -- file args...`). The kernel only
+// handles ELF, "#!" scripts, and binfmt_misc formats, so shebang-less scripts
+// currently fail with ENOEXEC instead of being run by /bin/sh.
LLVM_LIBC_FUNCTION(int, execvp, (const char *file, char *const argv[])) {
LIBC_CRASH_ON_NULLPTR(file);
LIBC_CRASH_ON_NULLPTR(argv);
@@ -68,11 +42,10 @@ LLVM_LIBC_FUNCTION(int, execvp, (const char *file, char *const argv[])) {
return -1;
}
- char *const *envp = const_cast<char *const *>(LIBC_NAMESPACE::environ);
-
// 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);
+ auto ret = linux_syscalls::execve(
+ file, argv, const_cast<char *const *>(LIBC_NAMESPACE::environ));
if (!ret) {
libc_errno = ret.error();
return -1;
@@ -109,7 +82,8 @@ LLVM_LIBC_FUNCTION(int, execvp, (const char *file, char *const argv[])) {
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);
+ auto ret = linux_syscalls::execve(
+ buf, argv, const_cast<char *const *>(LIBC_NAMESPACE::environ));
if (ret)
return *ret;
diff --git a/libc/test/integration/src/unistd/CMakeLists.txt b/libc/test/integration/src/unistd/CMakeLists.txt
index 8b81886f769d71..0676764aed7551 100644
--- a/libc/test/integration/src/unistd/CMakeLists.txt
+++ b/libc/test/integration/src/unistd/CMakeLists.txt
@@ -1,6 +1,8 @@
add_custom_target(unistd-integration-tests)
add_dependencies(libc-integration-tests unistd-integration-tests)
+add_subdirectory(testdata)
+
add_integration_test(
getcwd_test
SUITE
@@ -234,23 +236,17 @@ add_integration_test(
execvp_test.cpp
DEPENDS
libc_execv_test_normal_exit
+ libc_execvp_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.stdlib.setenv
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 dc7af459b2b9a7..d1ec6080b0a976 100644
--- a/libc/test/integration/src/unistd/execvp_test.cpp
+++ b/libc/test/integration/src/unistd/execvp_test.cpp
@@ -7,52 +7,30 @@
//===----------------------------------------------------------------------===//
///
/// \file
-/// Integration test for execvp.
+/// Integration tests for execvp.
///
//===----------------------------------------------------------------------===//
-#include "src/fcntl/open.h"
+#include "src/stdlib/setenv.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) {
- 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) {
+ const char *path = "./libc_execv_test_normal_exit";
char *const argv[] = {
- const_cast<char *>("libc_execv_test_normal_exit"),
+ const_cast<char *>("execv_test_normal_exit"),
nullptr,
};
- LIBC_NAMESPACE::execvp("./libc_execv_test_normal_exit", argv);
+ LIBC_NAMESPACE::execvp(path, argv);
}
ASSERT_TRUE(pid > 0);
int status;
@@ -60,68 +38,68 @@ void fork_and_execvp_slash_path() {
ASSERT_TRUE(cpid > 0);
ASSERT_EQ(cpid, pid);
ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
}
-void fork_and_execvp_signal_exit() {
+void fork_and_execvp_path_search() {
pid_t pid = LIBC_NAMESPACE::fork();
if (pid == 0) {
+ ASSERT_EQ(LIBC_NAMESPACE::setenv(
+ "PATH", "/nonexistent_dir_for_execvp_test:testdata", 1),
+ 0);
+ const char *file = "libc_execvp_test_normal_exit";
char *const argv[] = {
- const_cast<char *>("libc_exec_test_signal_exit"),
+ const_cast<char *>("execvp_test_normal_exit"),
nullptr,
};
- LIBC_NAMESPACE::execvp("libc_exec_test_signal_exit", argv);
+ LIBC_NAMESPACE::execvp(file, 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);
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
}
-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);
-
+void execvp_missing_in_path() {
+ ASSERT_EQ(
+ LIBC_NAMESPACE::setenv("PATH", "/nonexistent_dir_for_execvp_test", 1), 0);
+ const char *file = "libc_execvp_test_normal_exit";
char *const argv[] = {
- const_cast<char *>(no_exec_file),
+ const_cast<char *>("execvp_test_normal_exit"),
nullptr,
};
-
errno = 0;
- ASSERT_EQ(LIBC_NAMESPACE::execvp("./libc_execvp_test_no_exec.test", argv),
- -1);
- ASSERT_ERRNO_EQ(EACCES);
+ ASSERT_EQ(LIBC_NAMESPACE::execvp(file, argv), -1);
+ ASSERT_ERRNO_EQ(ENOENT);
+}
+void execvp_nonexistent_file() {
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("PATH", "testdata", 1), 0);
+ const char *file = "libc_execvp_nonexistent_file";
+ char *const argv[] = {
+ const_cast<char *>("execvp_nonexistent_file"),
+ nullptr,
+ };
errno = 0;
- ASSERT_EQ(LIBC_NAMESPACE::execvp(no_exec_file, argv), -1);
- ASSERT_ERRNO_EQ(EACCES);
-
- LIBC_NAMESPACE::unlink(no_exec_file);
+ ASSERT_EQ(LIBC_NAMESPACE::execvp(file, argv), -1);
+ ASSERT_ERRNO_EQ(ENOENT);
}
-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.
+// testdata/noexec contains a file with the same name as the helper binary but
+// with no execute permission bits, so execve on it fails with EACCES.
+void fork_and_execvp_skips_eacces() {
pid_t pid = LIBC_NAMESPACE::fork();
if (pid == 0) {
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("PATH", "testdata/noexec:testdata", 1), 0);
+ const char *file = "libc_execvp_test_normal_exit";
char *const argv[] = {
- const_cast<char *>(script_file),
+ const_cast<char *>("execvp_test_normal_exit"),
nullptr,
};
- LIBC_NAMESPACE::execvp("./libc_execvp_test_script.test", argv);
+ LIBC_NAMESPACE::execvp(file, argv);
}
ASSERT_TRUE(pid > 0);
int status;
@@ -130,32 +108,29 @@ void fork_and_execvp_enoexec_fallback() {
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);
+void execvp_reports_eacces() {
+ ASSERT_EQ(LIBC_NAMESPACE::setenv(
+ "PATH", "testdata/noexec:/nonexistent_dir_for_execvp_test", 1),
+ 0);
+ const char *file = "libc_execvp_test_normal_exit";
+ char *const argv[] = {
+ const_cast<char *>("execvp_test_normal_exit"),
+ nullptr,
+ };
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::execvp(file, argv), -1);
+ ASSERT_ERRNO_EQ(EACCES);
}
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();
+ fork_and_execvp_path_search();
+ fork_and_execvp_skips_eacces();
+ execvp_missing_in_path();
+ execvp_nonexistent_file();
+ execvp_reports_eacces();
return 0;
}
diff --git a/libc/test/integration/src/unistd/testdata/CMakeLists.txt b/libc/test/integration/src/unistd/testdata/CMakeLists.txt
new file mode 100644
index 00000000000000..3fcce1d8856692
--- /dev/null
+++ b/libc/test/integration/src/unistd/testdata/CMakeLists.txt
@@ -0,0 +1,20 @@
+add_executable(
+ libc_execvp_test_normal_exit
+ EXCLUDE_FROM_ALL
+ execvp_test_normal_exit.cpp
+)
+set_target_properties(
+ libc_execvp_test_normal_exit
+ PROPERTIES
+ OUTPUT_NAME libc_execvp_test_normal_exit
+ RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+# A file with the same name as the helper binary but without any execute
+# permission bits. Used to test that execvp continues searching PATH after
+# EACCES and reports EACCES if no later PATH entry succeeds.
+set(execvp_noexec_file
+ ${CMAKE_CURRENT_BINARY_DIR}/noexec/libc_execvp_test_normal_exit)
+file(WRITE ${execvp_noexec_file} "#!/bin/sh\nexit 1\n")
+file(CHMOD ${execvp_noexec_file}
+ PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
diff --git a/libc/test/integration/src/unistd/testdata/execvp_test_normal_exit.cpp b/libc/test/integration/src/unistd/testdata/execvp_test_normal_exit.cpp
new file mode 100644
index 00000000000000..8b162485a36b5a
--- /dev/null
+++ b/libc/test/integration/src/unistd/testdata/execvp_test_normal_exit.cpp
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Helper test program for execvp integration tests
+///
+//===----------------------------------------------------------------------===//
+
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int main() {
+ char *env = getenv("EXECV_TEST");
+ if (env == nullptr)
+ raise(SIGUSR1);
+ return 0;
+}
More information about the libc-commits
mailing list