[libc-commits] [libc] 9a91fc9 - [libc] Implement execlp in unistd (#232901)
via libc-commits
libc-commits at lists.llvm.org
Thu Oct 22 03:22:50 PDT 2026
Author: Aman Maurya
Date: 2026-10-22T15:52:44+05:30
New Revision: 9a91fc93564ff241da49c88ec7c427b48bebf30c
URL: https://github.com/llvm/llvm-project/commit/9a91fc93564ff241da49c88ec7c427b48bebf30c
DIFF: https://github.com/llvm/llvm-project/commit/9a91fc93564ff241da49c88ec7c427b48bebf30c.diff
LOG: [libc] Implement execlp in unistd (#232901)
Implement the standard POSIX function `execlp` in `<unistd.h>`.
Part of #232374. Builds on #232008.
Assisted by Antigravity and reviewed by Aman Maurya.
Added:
libc/src/unistd/execlp.h
libc/src/unistd/linux/execlp.cpp
libc/test/integration/src/unistd/execlp_test.cpp
libc/test/src/unistd/execlp_test.cpp
Modified:
libc/config/linux/aarch64/entrypoints.txt
libc/config/linux/riscv/entrypoints.txt
libc/config/linux/x86_64/entrypoints.txt
libc/include/unistd.yaml
libc/src/unistd/CMakeLists.txt
libc/src/unistd/linux/CMakeLists.txt
libc/test/integration/src/unistd/CMakeLists.txt
libc/test/src/unistd/CMakeLists.txt
Removed:
################################################################################
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 2fa1e8a107788d..be4d80db3afb37 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1457,6 +1457,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.unistd.confstr
libc.src.unistd.environ
libc.src.unistd.execl
+ libc.src.unistd.execlp
libc.src.unistd.execv
libc.src.unistd.execvp
libc.src.unistd.execvpe
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 3f47a8ae5b7c91..20e33295bfb206 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1656,6 +1656,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.unistd.confstr
libc.src.unistd.environ
libc.src.unistd.execl
+ libc.src.unistd.execlp
libc.src.unistd.execv
libc.src.unistd.execvp
libc.src.unistd.execvpe
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index a075e9197ff6bc..7d7c3ad1e1768d 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1682,6 +1682,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.unistd.confstr
libc.src.unistd.environ
libc.src.unistd.execl
+ libc.src.unistd.execlp
libc.src.unistd.execv
libc.src.unistd.execvp
libc.src.unistd.execvpe
diff --git a/libc/include/unistd.yaml b/libc/include/unistd.yaml
index 7756742ef2a179..82d43ad5045f00 100644
--- a/libc/include/unistd.yaml
+++ b/libc/include/unistd.yaml
@@ -222,6 +222,14 @@ functions:
- type: const char *
- type: const char *
- type: '...'
+ - name: execlp
+ standards:
+ - posix
+ return_type: int
+ arguments:
+ - type: const char *
+ - type: const char *
+ - type: '...'
- name: execv
standards:
- posix
diff --git a/libc/src/unistd/CMakeLists.txt b/libc/src/unistd/CMakeLists.txt
index 325bcbb8ee67c0..90690b725db17c 100644
--- a/libc/src/unistd/CMakeLists.txt
+++ b/libc/src/unistd/CMakeLists.txt
@@ -161,6 +161,13 @@ add_entrypoint_object(
.${LIBC_TARGET_OS}.execle
)
+add_entrypoint_object(
+ execlp
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.execlp
+)
+
add_entrypoint_object(
execv
ALIAS
diff --git a/libc/src/unistd/execlp.h b/libc/src/unistd/execlp.h
new file mode 100644
index 00000000000000..3b1c8800484818
--- /dev/null
+++ b/libc/src/unistd/execlp.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 execlp.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_UNISTD_EXECLP_H
+#define LLVM_LIBC_SRC_UNISTD_EXECLP_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int execlp(const char *file, const char *arg0, ...);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_UNISTD_EXECLP_H
diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt
index eafa1b8a413bb1..96ab1084ca2b43 100644
--- a/libc/src/unistd/linux/CMakeLists.txt
+++ b/libc/src/unistd/linux/CMakeLists.txt
@@ -235,6 +235,23 @@ add_entrypoint_object(
libc.src.errno.errno
)
+add_entrypoint_object(
+ execlp
+ SRCS
+ execlp.cpp
+ HDRS
+ ../execlp.h
+ DEPENDS
+ 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.errno.errno
+ libc.src.unistd.environ
+ libc.src.unistd.linux.exec_utils
+)
+
add_entrypoint_object(
execv
SRCS
diff --git a/libc/src/unistd/linux/execlp.cpp b/libc/src/unistd/linux/execlp.cpp
new file mode 100644
index 00000000000000..25882a4dabb292
--- /dev/null
+++ b/libc/src/unistd/linux/execlp.cpp
@@ -0,0 +1,67 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 execlp.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/unistd/execlp.h"
+
+#include "hdr/types/size_t.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 "src/unistd/linux/exec_utils.h"
+
+#include <stdarg.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, execlp, (const char *file, const char *arg0, ...)) {
+ LIBC_CRASH_ON_NULLPTR(file);
+ LIBC_CRASH_ON_NULLPTR(arg0);
+
+ va_list varargs, varargs_copy;
+ va_start(varargs, arg0);
+ va_copy(varargs_copy, varargs);
+
+ size_t argc = 1;
+ while (va_arg(varargs, const char *) != nullptr)
+ ++argc;
+ va_end(varargs);
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wvla"
+#if __has_warning("-Wvla-cxx-extension")
+#pragma GCC diagnostic ignored "-Wvla-cxx-extension"
+#endif
+ char *argv[argc + 1];
+#pragma GCC diagnostic pop
+ argv[0] = const_cast<char *>(arg0);
+
+ for (size_t i = 1; i < argc; ++i)
+ argv[i] = va_arg(varargs_copy, char *);
+ va_end(varargs_copy);
+ argv[argc] = nullptr;
+
+ auto ret = internal::execvpe(
+ file, argv, const_cast<char *const *>(LIBC_NAMESPACE::environ));
+ if (!ret) {
+ libc_errno = ret.error();
+ return -1;
+ }
+
+ // Control will not reach here on success but having a return statement will
+ // keep the compilers happy.
+ return *ret;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/integration/src/unistd/CMakeLists.txt b/libc/test/integration/src/unistd/CMakeLists.txt
index 8d2b6bb418e8dc..59f27331489513 100644
--- a/libc/test/integration/src/unistd/CMakeLists.txt
+++ b/libc/test/integration/src/unistd/CMakeLists.txt
@@ -270,3 +270,26 @@ add_integration_test(
ENV
EXECV_TEST=PASS
)
+
+add_integration_test(
+ execlp_test
+ SUITE
+ unistd-integration-tests
+ SRCS
+ execlp_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._exit
+ libc.src.unistd.execlp
+ libc.src.unistd.fork
+ ENV
+ EXECV_TEST=PASS
+)
diff --git a/libc/test/integration/src/unistd/execlp_test.cpp b/libc/test/integration/src/unistd/execlp_test.cpp
new file mode 100644
index 00000000000000..73aaddbe6b5340
--- /dev/null
+++ b/libc/test/integration/src/unistd/execlp_test.cpp
@@ -0,0 +1,143 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 execlp
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/setenv.h"
+#include "src/sys/wait/waitpid.h"
+#include "src/unistd/_exit.h"
+#include "src/unistd/execlp.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_execlp_slash_path() {
+ pid_t pid = LIBC_NAMESPACE::fork();
+ if (pid == 0) {
+ const char *path = "./libc_execv_test_normal_exit";
+ LIBC_NAMESPACE::execlp(path, "execv_test_normal_exit",
+ static_cast<char *>(nullptr));
+ LIBC_NAMESPACE::_exit(errno);
+ }
+ 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_execlp_path_search() {
+ pid_t pid = LIBC_NAMESPACE::fork();
+ if (pid == 0) {
+ ASSERT_EQ(LIBC_NAMESPACE::setenv(
+ "PATH", "/nonexistent_dir_for_execlp_test:testdata", 1),
+ 0);
+ const char *file = "libc_execvp_test_normal_exit";
+ LIBC_NAMESPACE::execlp(file, "execvp_test_normal_exit",
+ static_cast<char *>(nullptr));
+ LIBC_NAMESPACE::_exit(errno);
+ }
+ 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 execlp_missing_in_path() {
+ ASSERT_EQ(
+ LIBC_NAMESPACE::setenv("PATH", "/nonexistent_dir_for_execlp_test", 1), 0);
+ const char *file = "libc_execvp_test_normal_exit";
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::execlp(file, "execvp_test_normal_exit",
+ static_cast<char *>(nullptr)),
+ -1);
+ ASSERT_ERRNO_EQ(ENOENT);
+}
+
+void execlp_nonexistent_file() {
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("PATH", "testdata", 1), 0);
+ const char *file = "libc_execlp_nonexistent_file";
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::execlp(file, "execlp_nonexistent_file",
+ static_cast<char *>(nullptr)),
+ -1);
+ ASSERT_ERRNO_EQ(ENOENT);
+}
+
+// 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_execlp_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";
+ LIBC_NAMESPACE::execlp(file, "execvp_test_normal_exit",
+ static_cast<char *>(nullptr));
+ LIBC_NAMESPACE::_exit(errno);
+ }
+ 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 execlp_reports_eacces() {
+ ASSERT_EQ(LIBC_NAMESPACE::setenv(
+ "PATH", "testdata/noexec:/nonexistent_dir_for_execlp_test", 1),
+ 0);
+ const char *file = "libc_execvp_test_normal_exit";
+ errno = 0;
+ ASSERT_EQ(LIBC_NAMESPACE::execlp(file, "execvp_test_normal_exit",
+ static_cast<char *>(nullptr)),
+ -1);
+ ASSERT_ERRNO_EQ(EACCES);
+}
+
+void fork_and_execlp_signal_exit() {
+ pid_t pid = LIBC_NAMESPACE::fork();
+ if (pid == 0) {
+ const char *path = "./libc_exec_test_signal_exit";
+ LIBC_NAMESPACE::execlp(path, "exec_test_signal_exit",
+ static_cast<char *>(nullptr));
+ LIBC_NAMESPACE::_exit(errno);
+ }
+ 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_execlp_slash_path();
+ fork_and_execlp_path_search();
+ fork_and_execlp_skips_eacces();
+ execlp_missing_in_path();
+ execlp_nonexistent_file();
+ execlp_reports_eacces();
+ fork_and_execlp_signal_exit();
+ return 0;
+}
diff --git a/libc/test/src/unistd/CMakeLists.txt b/libc/test/src/unistd/CMakeLists.txt
index 900f63a3469e99..555a82b61874e4 100644
--- a/libc/test/src/unistd/CMakeLists.txt
+++ b/libc/test/src/unistd/CMakeLists.txt
@@ -246,6 +246,19 @@ add_libc_test(
libc.test.UnitTest.ErrnoSetterMatcher
)
+add_libc_test(
+ execlp_test
+ SUITE
+ libc_unistd_unittests
+ SRCS
+ execlp_test.cpp
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.src.unistd.execlp
+ libc.test.UnitTest.ErrnoCheckingTest
+ libc.test.UnitTest.ErrnoSetterMatcher
+)
+
add_libc_test(
fexecve_test
SUITE
diff --git a/libc/test/src/unistd/execlp_test.cpp b/libc/test/src/unistd/execlp_test.cpp
new file mode 100644
index 00000000000000..66c50f69e6fbb1
--- /dev/null
+++ b/libc/test/src/unistd/execlp_test.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 execlp.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "src/unistd/execlp.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcExeclpTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcExeclpTest, NonExistentFile) {
+ using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+ ASSERT_THAT(LIBC_NAMESPACE::execlp("nonexistent_binary_xyz_123",
+ "nonexistent_binary_xyz_123", nullptr),
+ Fails(ENOENT));
+}
+
+TEST_F(LlvmLibcExeclpTest, NonExistentPathWithSlash) {
+ using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+ ASSERT_THAT(LIBC_NAMESPACE::execlp("/nonexistent/path/xyz", "xyz", nullptr),
+ Fails(ENOENT));
+}
+
+TEST_F(LlvmLibcExeclpTest, EmptyFileName) {
+ using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+ ASSERT_THAT(LIBC_NAMESPACE::execlp("", "", nullptr), Fails(ENOENT));
+}
More information about the libc-commits
mailing list