[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