[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