[libc-commits] [libc] [libc] Implement execvpe in unistd (PR #234071)
Aman Maurya via libc-commits
libc-commits at lists.llvm.org
Wed Oct 21 04:27:00 PDT 2026
https://github.com/amanmaurya92 updated https://github.com/llvm/llvm-project/pull/234071
>From e20b894455d998497253263f2aa8da1f03324f8d Mon Sep 17 00:00:00 2001
From: amanmaurya92 <amanmaurya9209 at gmail.com>
Date: Wed, 21 Oct 2026 09:47:43 +0530
Subject: [PATCH 1/2] [libc] Implement execvpe in unistd (#234065)
Implement the standard POSIX.1-2024 function `execvpe` in `<unistd.h>`.
Fixes #234065.
Like `execvp`, `execvpe` duplicates the actions of the shell in searching
for an executable file if the specified filename does not contain a slash (`/`),
resolving against the directories in the caller's `PATH`. Unlike `execvp`,
`execvpe` allows passing an explicit custom environment (`envp`) to the
executed process image rather than using the global `environ`.
Per POSIX.1-2024 and Linux specifications, `execvpe` searches for the program
using the value of `PATH` from the caller's environment, not from the `envp`
argument.
- Add `execvpe.h` and `linux/execvpe.cpp` entrypoint.
- Register `execvpe` in `unistd.yaml` and docgen.
- Add entrypoints for x86_64, aarch64, and riscv.
- Add integration tests under `libc/test/integration/src/unistd/execvpe_test.cpp`,
including verification that caller `PATH` is prioritized over `envp`.
---
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 | 8 +
libc/src/unistd/CMakeLists.txt | 7 +
libc/src/unistd/execvpe.h | 25 +++
libc/src/unistd/linux/CMakeLists.txt | 21 ++
libc/src/unistd/linux/execvpe.cpp | 104 ++++++++++
.../integration/src/unistd/CMakeLists.txt | 20 ++
.../integration/src/unistd/execvpe_test.cpp | 186 ++++++++++++++++++
libc/utils/docgen/unistd.yaml | 2 +
11 files changed, 376 insertions(+)
create mode 100644 libc/src/unistd/execvpe.h
create mode 100644 libc/src/unistd/linux/execvpe.cpp
create mode 100644 libc/test/integration/src/unistd/execvpe_test.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index dc806d854c6167..4be30207812136 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1452,6 +1452,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.unistd.execl
libc.src.unistd.execv
libc.src.unistd.execvp
+ libc.src.unistd.execvpe
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 1f6df7ce168d3e..cd5b5adb2dbad8 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1651,6 +1651,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.unistd.execl
libc.src.unistd.execv
libc.src.unistd.execvp
+ libc.src.unistd.execvpe
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 33b7ec6d9a2813..d4114a0e6c2288 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1677,6 +1677,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.unistd.execl
libc.src.unistd.execv
libc.src.unistd.execvp
+ libc.src.unistd.execvpe
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 ee59a2dc0fe2e4..bde0422e800461 100644
--- a/libc/include/unistd.yaml
+++ b/libc/include/unistd.yaml
@@ -244,6 +244,14 @@ functions:
arguments:
- type: const char *
- type: __exec_argv_t
+ - name: execvpe
+ standards:
+ - posix
+ return_type: int
+ arguments:
+ - type: const char *
+ - type: __exec_argv_t
+ - type: __exec_envp_t
- name: faccessat
standards:
- posix
diff --git a/libc/src/unistd/CMakeLists.txt b/libc/src/unistd/CMakeLists.txt
index 4cbf4d9f26128b..325bcbb8ee67c0 100644
--- a/libc/src/unistd/CMakeLists.txt
+++ b/libc/src/unistd/CMakeLists.txt
@@ -182,6 +182,13 @@ add_entrypoint_object(
.${LIBC_TARGET_OS}.execvp
)
+add_entrypoint_object(
+ execvpe
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.execvpe
+)
+
add_entrypoint_object(
fexecve
ALIAS
diff --git a/libc/src/unistd/execvpe.h b/libc/src/unistd/execvpe.h
new file mode 100644
index 00000000000000..f8f2a514cfe808
--- /dev/null
+++ b/libc/src/unistd/execvpe.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 execvpe
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_UNISTD_EXECVPE_H
+#define LLVM_LIBC_SRC_UNISTD_EXECVPE_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int execvpe(const char *file, char *const argv[], char *const envp[]);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_UNISTD_EXECVPE_H
diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt
index 833fc796054c7c..9304c95ca404cc 100644
--- a/libc/src/unistd/linux/CMakeLists.txt
+++ b/libc/src/unistd/linux/CMakeLists.txt
@@ -286,6 +286,27 @@ add_entrypoint_object(
libc.src.unistd.environ
)
+add_entrypoint_object(
+ execvpe
+ SRCS
+ execvpe.cpp
+ HDRS
+ ../execvpe.h
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.limits_macros
+ libc.hdr.types.size_t
+ libc.src.__support.common
+ 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.OSUtil.linux.syscall_wrappers.execve
+ libc.src.errno.errno
+ libc.src.stdlib.environ_internal
+ libc.src.string.memory_utils.inline_memcpy
+)
+
add_entrypoint_object(
fexecve
SRCS
diff --git a/libc/src/unistd/linux/execvpe.cpp b/libc/src/unistd/linux/execvpe.cpp
new file mode 100644
index 00000000000000..30d4d93b031e66
--- /dev/null
+++ b/libc/src/unistd/linux/execvpe.cpp
@@ -0,0 +1,104 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 execvpe
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/unistd/execvpe.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/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/stdlib/environ_internal.h"
+#include "src/string/memory_utils/inline_memcpy.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+// TODO: POSIX requires that if execve fails with ENOEXEC, execvpe 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, execvpe,
+ (const char *file, char *const argv[], char *const envp[])) {
+ LIBC_CRASH_ON_NULLPTR(file);
+ LIBC_CRASH_ON_NULLPTR(argv);
+
+ cpp::string_view file_sv(file);
+ if (file_sv.empty()) {
+ libc_errno = ENOENT;
+ return -1;
+ }
+
+ // If there's a / in file, treat it as a path.
+ if (file_sv.find_first_of('/') != cpp::string_view::npos) {
+ auto ret = linux_syscalls::execve(file, argv, envp);
+ if (!ret) {
+ libc_errno = ret.error();
+ return -1;
+ }
+ return *ret;
+ }
+
+ // POSIX.1-2024 / Linux man pages:
+ // execvpe searches for the program using the value of PATH from the caller's
+ // environment, not from the envp argument.
+ 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;
+
+ char buf[PATH_MAX];
+ bool seen_eacces = false;
+ int last_error = ENOENT;
+
+ 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 = ".";
+
+ if (dir.size() + 1 + file_sv.size() >= PATH_MAX) {
+ last_error = ENAMETOOLONG;
+ continue;
+ }
+
+ 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 = linux_syscalls::execve(buf, argv, envp);
+ if (ret)
+ return *ret;
+
+ int err = ret.error();
+ if (err == EACCES) {
+ seen_eacces = true;
+ } else if (err == ENOENT || err == ENOTDIR) {
+ last_error = err;
+ } else {
+ libc_errno = err;
+ return -1;
+ }
+ }
+
+ libc_errno = seen_eacces ? EACCES : last_error;
+ 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 fa035b86267bc6..15a7b0b1a6e847 100644
--- a/libc/test/integration/src/unistd/CMakeLists.txt
+++ b/libc/test/integration/src/unistd/CMakeLists.txt
@@ -228,6 +228,26 @@ add_integration_test(
EXECV_TEST=PASS
)
+add_integration_test(
+ execvpe_test
+ SUITE
+ unistd-integration-tests
+ SRCS
+ execvpe_test.cpp
+ DEPENDS
+ libc_execv_test_normal_exit
+ libc_execvp_test_normal_exit
+ libc_exec_test_signal_exit
+ libc.hdr.errno_macros
+ libc.hdr.signal_macros
+ libc.hdr.sys_wait_macros
+ libc.src.errno.errno
+ libc.src.stdlib.setenv
+ libc.src.sys.wait.waitpid
+ libc.src.unistd.execvpe
+ libc.src.unistd.fork
+)
+
add_integration_test(
fexecve_test
SUITE
diff --git a/libc/test/integration/src/unistd/execvpe_test.cpp b/libc/test/integration/src/unistd/execvpe_test.cpp
new file mode 100644
index 00000000000000..0e0d8abd1a0e05
--- /dev/null
+++ b/libc/test/integration/src/unistd/execvpe_test.cpp
@@ -0,0 +1,186 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 tests for execvpe
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/setenv.h"
+#include "src/sys/wait/waitpid.h"
+#include "src/unistd/execvpe.h"
+#include "src/unistd/fork.h"
+
+#include "hdr/signal_macros.h"
+#include "hdr/sys_wait_macros.h"
+#include "test/IntegrationTest/test.h"
+
+#include <errno.h>
+
+void fork_and_execvpe_slash_path() {
+ pid_t pid = LIBC_NAMESPACE::fork();
+ if (pid == 0) {
+ const char *path = "./libc_execvp_test_normal_exit";
+ char *const argv[] = {
+ const_cast<char *>("execvp_test_normal_exit"),
+ nullptr,
+ };
+ char *const envp[] = {
+ const_cast<char *>("EXECV_TEST=PASS"),
+ nullptr,
+ };
+ LIBC_NAMESPACE::execvpe(path, argv, envp);
+ }
+ 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);
+}
+
+void fork_and_execvpe_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 *>("execvp_test_normal_exit"),
+ nullptr,
+ };
+ char *const envp[] = {
+ const_cast<char *>("EXECV_TEST=PASS"),
+ nullptr,
+ };
+ LIBC_NAMESPACE::execvpe(file, argv, envp);
+ }
+ 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);
+}
+
+// POSIX.1-2024 mandates that execvpe() searches for the program using the
+// value of PATH from the caller's environment, not from the envp argument.
+void fork_and_execvpe_caller_path_not_envp() {
+ pid_t pid = LIBC_NAMESPACE::fork();
+ if (pid == 0) {
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("PATH", "testdata", 1), 0);
+ const char *file = "libc_execvp_test_normal_exit";
+ char *const argv[] = {
+ const_cast<char *>("execvp_test_normal_exit"),
+ nullptr,
+ };
+ // envp has a bogus PATH, but execvpe must ignore it and use caller's PATH.
+ char *const envp[] = {
+ const_cast<char *>("PATH=/nonexistent_dir_for_execvpe_test"),
+ const_cast<char *>("EXECV_TEST=PASS"),
+ nullptr,
+ };
+ LIBC_NAMESPACE::execvpe(file, argv, envp);
+ }
+ 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);
+}
+
+// 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_execvpe_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 *>("execvp_test_normal_exit"),
+ nullptr,
+ };
+ char *const envp[] = {
+ const_cast<char *>("EXECV_TEST=PASS"),
+ nullptr,
+ };
+ LIBC_NAMESPACE::execvpe(file, argv, envp);
+ }
+ 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);
+}
+
+void execvpe_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 *>("execvp_test_normal_exit"),
+ nullptr,
+ };
+ char *const envp[] = {
+ nullptr,
+ };
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::execvpe(file, argv, envp), -1);
+ ASSERT_ERRNO_EQ(ENOENT);
+}
+
+void execvpe_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,
+ };
+ char *const envp[] = {
+ nullptr,
+ };
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::execvpe(file, argv, envp), -1);
+ ASSERT_ERRNO_EQ(ENOENT);
+}
+
+void execvpe_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,
+ };
+ char *const envp[] = {
+ nullptr,
+ };
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::execvpe(file, argv, envp), -1);
+ ASSERT_ERRNO_EQ(EACCES);
+}
+
+TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
+ [[maybe_unused]] char **envp) {
+ fork_and_execvpe_slash_path();
+ fork_and_execvpe_path_search();
+ fork_and_execvpe_caller_path_not_envp();
+ fork_and_execvpe_skips_eacces();
+ execvpe_missing_in_path();
+ execvpe_nonexistent_file();
+ execvpe_reports_eacces();
+ return 0;
+}
diff --git a/libc/utils/docgen/unistd.yaml b/libc/utils/docgen/unistd.yaml
index d7a976263aa4e0..ec72577de0f6f2 100644
--- a/libc/utils/docgen/unistd.yaml
+++ b/libc/utils/docgen/unistd.yaml
@@ -541,6 +541,8 @@ functions:
in-latest-posix: ""
execvp:
in-latest-posix: ""
+ execvpe:
+ in-latest-posix: ""
faccessat:
in-latest-posix: ""
fchdir:
>From 531eaa3a4b73652f2203f36aef06d003c9e0bca0 Mon Sep 17 00:00:00 2001
From: amanmaurya92 <amanmaurya9209 at gmail.com>
Date: Wed, 21 Oct 2026 16:56:18 +0530
Subject: [PATCH 2/2] [libc] Refactor execvp/execvpe to shared helper and fix
integration test
- Extract common PATH search and execution logic into internal::execvpe in
linux/exec_utils.h and reuse it in both execvp and execvpe.
- Fix fork_and_execvpe_slash_path integration test to execute
libc_execv_test_normal_exit rather than the missing local binary.
- Add _exit(libc_errno) after execvpe in integration test child branches
to avoid falling through on unexpected exec failures.
- Mark execvpe as a GNU extension with `standards: [gnu]` in unistd.yaml
and `c-definition: ''` in docgen/unistd.yaml.
---
libc/include/unistd.yaml | 2 +-
libc/src/unistd/linux/CMakeLists.txt | 32 +++---
libc/src/unistd/linux/exec_utils.h | 99 +++++++++++++++++++
libc/src/unistd/linux/execvp.cpp | 79 +--------------
libc/src/unistd/linux/execvpe.cpp | 78 +--------------
.../integration/src/unistd/CMakeLists.txt | 1 +
.../integration/src/unistd/execvpe_test.cpp | 9 +-
libc/utils/docgen/unistd.yaml | 2 +-
8 files changed, 132 insertions(+), 170 deletions(-)
create mode 100644 libc/src/unistd/linux/exec_utils.h
diff --git a/libc/include/unistd.yaml b/libc/include/unistd.yaml
index bde0422e800461..7756742ef2a179 100644
--- a/libc/include/unistd.yaml
+++ b/libc/include/unistd.yaml
@@ -246,7 +246,7 @@ functions:
- type: __exec_argv_t
- name: execvpe
standards:
- - posix
+ - gnu
return_type: int
arguments:
- type: const char *
diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt
index 9304c95ca404cc..c4401658e22dc9 100644
--- a/libc/src/unistd/linux/CMakeLists.txt
+++ b/libc/src/unistd/linux/CMakeLists.txt
@@ -264,12 +264,10 @@ add_entrypoint_object(
libc.src.errno.errno
)
-add_entrypoint_object(
- execvp
- SRCS
- execvp.cpp
+add_header_library(
+ exec_utils
HDRS
- ../execvp.h
+ exec_utils.h
DEPENDS
libc.hdr.errno_macros
libc.hdr.limits_macros
@@ -278,11 +276,23 @@ add_entrypoint_object(
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.OSUtil.linux.syscall_wrappers.execve
libc.src.errno.errno
libc.src.stdlib.environ_internal
libc.src.string.memory_utils.inline_memcpy
+)
+
+add_entrypoint_object(
+ execvp
+ SRCS
+ execvp.cpp
+ HDRS
+ ../execvp.h
+ DEPENDS
+ libc.src.unistd.linux.exec_utils
+ libc.src.__support.common
+ libc.src.__support.macros.config
+ libc.src.__support.macros.null_check
libc.src.unistd.environ
)
@@ -293,18 +303,10 @@ add_entrypoint_object(
HDRS
../execvpe.h
DEPENDS
- libc.hdr.errno_macros
- libc.hdr.limits_macros
- libc.hdr.types.size_t
+ libc.src.unistd.linux.exec_utils
libc.src.__support.common
- 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.OSUtil.linux.syscall_wrappers.execve
- libc.src.errno.errno
- libc.src.stdlib.environ_internal
- libc.src.string.memory_utils.inline_memcpy
)
add_entrypoint_object(
diff --git a/libc/src/unistd/linux/exec_utils.h b/libc/src/unistd/linux/exec_utils.h
new file mode 100644
index 00000000000000..c0cfaf9340551d
--- /dev/null
+++ b/libc/src/unistd/linux/exec_utils.h
@@ -0,0 +1,99 @@
+//===-- Linux implementation helpers for exec -------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_UNISTD_LINUX_EXEC_UTILS_H
+#define LLVM_LIBC_SRC_UNISTD_LINUX_EXEC_UTILS_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/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/stdlib/environ_internal.h"
+#include "src/string/memory_utils/inline_memcpy.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+// TODO: POSIX requires that if execve fails with ENOEXEC, the file is executed
+// 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.
+LIBC_INLINE int execvpe(const char *file, char *const argv[],
+ char *const envp[]) {
+ cpp::string_view file_sv(file);
+ if (file_sv.empty()) {
+ libc_errno = ENOENT;
+ return -1;
+ }
+
+ // If there's a / in file, treat it as a path.
+ if (file_sv.find_first_of('/') != cpp::string_view::npos) {
+ auto ret = linux_syscalls::execve(file, argv, envp);
+ if (!ret) {
+ libc_errno = ret.error();
+ return -1;
+ }
+ return *ret;
+ }
+
+ // 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;
+
+ char buf[PATH_MAX];
+ bool seen_eacces = false;
+ int last_error = ENOENT;
+
+ 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 = ".";
+
+ if (dir.size() + 1 + file_sv.size() >= PATH_MAX) {
+ last_error = ENAMETOOLONG;
+ continue;
+ }
+
+ 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 = linux_syscalls::execve(buf, argv, envp);
+ if (ret)
+ return *ret;
+
+ int err = ret.error();
+ if (err == EACCES) {
+ seen_eacces = true;
+ } else if (err == ENOENT || err == ENOTDIR) {
+ last_error = err;
+ } else {
+ libc_errno = err;
+ return -1;
+ }
+ }
+
+ libc_errno = seen_eacces ? EACCES : last_error;
+ return -1;
+}
+
+} // namespace internal
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_UNISTD_LINUX_EXEC_UTILS_H
diff --git a/libc/src/unistd/linux/execvp.cpp b/libc/src/unistd/linux/execvp.cpp
index 9b64e7c9a4becd..628dd636ea9e0e 100644
--- a/libc/src/unistd/linux/execvp.cpp
+++ b/libc/src/unistd/linux/execvp.cpp
@@ -13,93 +13,20 @@
#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/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/stdlib/environ_internal.h"
-#include "src/string/memory_utils/inline_memcpy.h"
#include "src/unistd/environ.h"
+#include "src/unistd/linux/exec_utils.h"
namespace LIBC_NAMESPACE_DECL {
-// 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);
- cpp::string_view file_sv(file);
- if (file_sv.empty()) {
- libc_errno = ENOENT;
- return -1;
- }
-
- // if there's a / in file, treat it as a path.
- if (file_sv.find_first_of('/') != cpp::string_view::npos) {
- auto ret = linux_syscalls::execve(
- file, argv, const_cast<char *const *>(LIBC_NAMESPACE::environ));
- if (!ret) {
- libc_errno = ret.error();
- return -1;
- }
- return *ret;
- }
-
- // else 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;
-
- char buf[PATH_MAX];
- bool seen_eacces = false;
- int last_error = ENOENT;
-
- 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 = ".";
-
- if (dir.size() + 1 + file_sv.size() >= PATH_MAX) {
- last_error = ENAMETOOLONG;
- continue;
- }
-
- 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 = linux_syscalls::execve(
- buf, argv, const_cast<char *const *>(LIBC_NAMESPACE::environ));
- if (ret)
- return *ret;
-
- int err = ret.error();
- if (err == EACCES) {
- seen_eacces = true;
- } else if (err == ENOENT || err == ENOTDIR) {
- last_error = err;
- } else {
- libc_errno = err;
- return -1;
- }
- }
-
- libc_errno = seen_eacces ? EACCES : last_error;
- return -1;
+ return internal::execvpe(file, argv,
+ const_cast<char *const *>(LIBC_NAMESPACE::environ));
}
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/unistd/linux/execvpe.cpp b/libc/src/unistd/linux/execvpe.cpp
index 30d4d93b031e66..4b790fe1521013 100644
--- a/libc/src/unistd/linux/execvpe.cpp
+++ b/libc/src/unistd/linux/execvpe.cpp
@@ -13,92 +13,20 @@
#include "src/unistd/execvpe.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/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/stdlib/environ_internal.h"
-#include "src/string/memory_utils/inline_memcpy.h"
+#include "src/unistd/linux/exec_utils.h"
namespace LIBC_NAMESPACE_DECL {
-// TODO: POSIX requires that if execve fails with ENOEXEC, execvpe 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, execvpe,
(const char *file, char *const argv[], char *const envp[])) {
LIBC_CRASH_ON_NULLPTR(file);
LIBC_CRASH_ON_NULLPTR(argv);
+ LIBC_CRASH_ON_NULLPTR(envp);
- cpp::string_view file_sv(file);
- if (file_sv.empty()) {
- libc_errno = ENOENT;
- return -1;
- }
-
- // If there's a / in file, treat it as a path.
- if (file_sv.find_first_of('/') != cpp::string_view::npos) {
- auto ret = linux_syscalls::execve(file, argv, envp);
- if (!ret) {
- libc_errno = ret.error();
- return -1;
- }
- return *ret;
- }
-
- // POSIX.1-2024 / Linux man pages:
- // execvpe searches for the program using the value of PATH from the caller's
- // environment, not from the envp argument.
- 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;
-
- char buf[PATH_MAX];
- bool seen_eacces = false;
- int last_error = ENOENT;
-
- 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 = ".";
-
- if (dir.size() + 1 + file_sv.size() >= PATH_MAX) {
- last_error = ENAMETOOLONG;
- continue;
- }
-
- 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 = linux_syscalls::execve(buf, argv, envp);
- if (ret)
- return *ret;
-
- int err = ret.error();
- if (err == EACCES) {
- seen_eacces = true;
- } else if (err == ENOENT || err == ENOTDIR) {
- last_error = err;
- } else {
- libc_errno = err;
- return -1;
- }
- }
-
- libc_errno = seen_eacces ? EACCES : last_error;
- return -1;
+ return internal::execvpe(file, argv, envp);
}
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/integration/src/unistd/CMakeLists.txt b/libc/test/integration/src/unistd/CMakeLists.txt
index 15a7b0b1a6e847..8d2b6bb418e8dc 100644
--- a/libc/test/integration/src/unistd/CMakeLists.txt
+++ b/libc/test/integration/src/unistd/CMakeLists.txt
@@ -244,6 +244,7 @@ add_integration_test(
libc.src.errno.errno
libc.src.stdlib.setenv
libc.src.sys.wait.waitpid
+ libc.src.unistd._exit
libc.src.unistd.execvpe
libc.src.unistd.fork
)
diff --git a/libc/test/integration/src/unistd/execvpe_test.cpp b/libc/test/integration/src/unistd/execvpe_test.cpp
index 0e0d8abd1a0e05..6853821b76bd8d 100644
--- a/libc/test/integration/src/unistd/execvpe_test.cpp
+++ b/libc/test/integration/src/unistd/execvpe_test.cpp
@@ -13,6 +13,7 @@
#include "src/stdlib/setenv.h"
#include "src/sys/wait/waitpid.h"
+#include "src/unistd/_exit.h"
#include "src/unistd/execvpe.h"
#include "src/unistd/fork.h"
@@ -25,9 +26,9 @@
void fork_and_execvpe_slash_path() {
pid_t pid = LIBC_NAMESPACE::fork();
if (pid == 0) {
- const char *path = "./libc_execvp_test_normal_exit";
+ const char *path = "./libc_execv_test_normal_exit";
char *const argv[] = {
- const_cast<char *>("execvp_test_normal_exit"),
+ const_cast<char *>("execv_test_normal_exit"),
nullptr,
};
char *const envp[] = {
@@ -35,6 +36,7 @@ void fork_and_execvpe_slash_path() {
nullptr,
};
LIBC_NAMESPACE::execvpe(path, argv, envp);
+ LIBC_NAMESPACE::_exit(libc_errno);
}
ASSERT_TRUE(pid > 0);
int status;
@@ -61,6 +63,7 @@ void fork_and_execvpe_path_search() {
nullptr,
};
LIBC_NAMESPACE::execvpe(file, argv, envp);
+ LIBC_NAMESPACE::_exit(libc_errno);
}
ASSERT_TRUE(pid > 0);
int status;
@@ -89,6 +92,7 @@ void fork_and_execvpe_caller_path_not_envp() {
nullptr,
};
LIBC_NAMESPACE::execvpe(file, argv, envp);
+ LIBC_NAMESPACE::_exit(libc_errno);
}
ASSERT_TRUE(pid > 0);
int status;
@@ -115,6 +119,7 @@ void fork_and_execvpe_skips_eacces() {
nullptr,
};
LIBC_NAMESPACE::execvpe(file, argv, envp);
+ LIBC_NAMESPACE::_exit(libc_errno);
}
ASSERT_TRUE(pid > 0);
int status;
diff --git a/libc/utils/docgen/unistd.yaml b/libc/utils/docgen/unistd.yaml
index ec72577de0f6f2..688a21dd7cc319 100644
--- a/libc/utils/docgen/unistd.yaml
+++ b/libc/utils/docgen/unistd.yaml
@@ -542,7 +542,7 @@ functions:
execvp:
in-latest-posix: ""
execvpe:
- in-latest-posix: ""
+ c-definition: ''
faccessat:
in-latest-posix: ""
fchdir:
More information about the libc-commits
mailing list