[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