[libc-commits] [libc] [libc][realpath] Implement symbolic path resolution (PR #204467)

Jackson Stogel via libc-commits libc-commits at lists.llvm.org
Wed Jun 17 20:01:02 PDT 2026


https://github.com/jtstogel updated https://github.com/llvm/llvm-project/pull/204467

>From 5a867eb371bfc52da10103ff436191d9a96314d4 Mon Sep 17 00:00:00 2001
From: jtstogel <jtstogel at google.com>
Date: Tue, 16 Jun 2026 19:43:30 -0700
Subject: [PATCH 1/4] [libc][realpath] Implement symbolic path resolution

This is an incremental implementation of realpath. It performs no syscalls. A real implementation would perform getcwd/statx/readlinkat to actually resolve symlinks / valid that paths exist / etc.

For testing purposes, I've added the entrypoint when `LLVM_LIBC_ENABLE_EXPERIMENTAL_ENTRYPOINTS` is enabled.
---
 libc/config/linux/x86_64/entrypoints.txt |   4 +
 libc/include/stdlib.yaml                 |   7 +
 libc/src/stdlib/CMakeLists.txt           |  15 +-
 libc/src/stdlib/linux/CMakeLists.txt     |  18 ++
 libc/src/stdlib/linux/realpath.cpp       | 217 +++++++++++++++++++++++
 libc/src/stdlib/realpath.h               |  25 +++
 libc/test/src/stdlib/CMakeLists.txt      |  15 ++
 libc/test/src/stdlib/realpath_test.cpp   | 109 ++++++++++++
 8 files changed, 406 insertions(+), 4 deletions(-)
 create mode 100644 libc/src/stdlib/linux/realpath.cpp
 create mode 100644 libc/src/stdlib/realpath.h
 create mode 100644 libc/test/src/stdlib/realpath_test.cpp

diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index ce88a6749d9dc..78e88bf1a352f 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1512,6 +1512,10 @@ if(LLVM_LIBC_FULL_BUILD)
 endif()
 
 if(LLVM_LIBC_ENABLE_EXPERIMENTAL_ENTRYPOINTS)
+  list(APPEND TARGET_LIBC_ENTRYPOINTS
+    libc.src.stdlib.realpath
+  )
+
   if(LLVM_LIBC_FULL_BUILD)
     list(APPEND TARGET_LIBC_ENTRYPOINTS
       # regex.h entrypoints
diff --git a/libc/include/stdlib.yaml b/libc/include/stdlib.yaml
index bf77d5d850ea0..a9b7f2f73ad57 100644
--- a/libc/include/stdlib.yaml
+++ b/libc/include/stdlib.yaml
@@ -216,6 +216,13 @@ functions:
     return_type: int
     arguments:
       - type: void
+  - name: realpath
+    standards:
+      - posix
+    return_type: char *
+    arguments:
+      - type: const char *__restrict
+      - type: char *__restrict
   - name: srand
     standards:
       - stdc
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 4593288881c7d..2bb515bbf6c24 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -585,6 +585,17 @@ if(NOT LIBC_TARGET_OS_IS_BAREMETAL AND NOT LIBC_TARGET_OS_IS_GPU)
   endif()
 endif()
 
+if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
+  add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
+endif()
+
+add_entrypoint_object(
+  realpath
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_OS}.realpath
+)
+
 if(NOT LLVM_LIBC_FULL_BUILD)
   return()
 endif()
@@ -666,10 +677,6 @@ add_entrypoint_object(
     libc.src.__support.str_to_integer
 )
 
-if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
-  add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
-endif()
-
 add_entrypoint_object(
   setenv
   ALIAS
diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index aedcd3f11bf38..2ea3cbeb06cb8 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -9,6 +9,24 @@ add_header_library(
     libc.src.signal.linux.signal_utils
 )
 
+add_entrypoint_object(
+  realpath
+  SRCS
+    realpath.cpp
+  HDRS
+    ../realpath.h
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.limits_macros
+    libc.src.__support.alloc_checker
+    libc.src.__support.common
+    libc.src.__support.CPP.string_view
+    libc.src.__support.error_or
+    libc.src.__support.libc_errno
+    libc.src.__support.macros.config
+    libc.src.string.memory_utils.inline_memcpy
+)
+
 add_entrypoint_object(
   setenv
   SRCS
diff --git a/libc/src/stdlib/linux/realpath.cpp b/libc/src/stdlib/linux/realpath.cpp
new file mode 100644
index 0000000000000..e635b72dacca6
--- /dev/null
+++ b/libc/src/stdlib/linux/realpath.cpp
@@ -0,0 +1,217 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 of POSIX realpath.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/realpath.h"
+#include "hdr/errno_macros.h"
+#include "hdr/limits_macros.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/alloc-checker.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/string/memory_utils/inline_memcpy.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace {
+
+// Separator character for POSIX paths.
+constexpr char PATH_SEP = '/';
+
+// Dummy struct to represent success in `ErrorOr` when no value is needed.
+struct Ok {};
+
+// Whether a path is absolute.
+bool is_absolute(cpp::string_view path) { return path.starts_with(PATH_SEP); }
+
+// Container for a fully resolved, canonical path.
+//
+// The contained path is always in its canonical form. It is:
+// - Absolute
+// - Symlink-free
+// - Without a trailing separator
+// - Devoid of path traversals like "." or ".."
+class ResolvedPath {
+public:
+  ResolvedPath() { set_to_root(); }
+
+  void set_to_root() {
+    buf_[0] = PATH_SEP;
+    size_ = 1;
+  }
+
+  bool is_root() const { return size_ == 1; }
+
+  ErrorOr<Ok> set_to_cwd() { return Error(ENOSYS); }
+
+  void set_to_parent() {
+    size_t sep_index = view().find_last_of(PATH_SEP);
+
+    // Ensure we maintain the root separator.
+    size_ = sep_index == 0 ? 1 : sep_index;
+  }
+
+  // Adds a single component to the end of this path.
+  ErrorOr<Ok> push_component(cpp::string_view component) {
+    if (component.size() > NAME_MAX)
+      return Error(ENAMETOOLONG);
+
+    if (!is_root()) {
+      if (ErrorOr<Ok> res = push_raw(PATH_SEP); !res)
+        return res;
+    }
+
+    return push_raw(component);
+  }
+
+  cpp::string_view view() const { return cpp::string_view(buf_, size_); }
+
+private:
+  ErrorOr<Ok> push_raw(cpp::string_view value) {
+    if (value.size() > sizeof(buf_) - size_)
+      return Error(ENAMETOOLONG);
+
+    inline_memcpy(buf_ + size_, value.data(), value.size());
+    size_ += value.size();
+    return Ok{};
+  }
+
+  ErrorOr<Ok> push_raw(char value) {
+    return push_raw(cpp::string_view(&value, 1));
+  }
+
+  // Current size of the path stored in `buf_`.
+  size_t size_;
+
+  // `PATH_MAX` includes a null-terminator in its count,
+  // so use `PATH_MAX - 1` here as `ResolvedPath` is not null-terminated.
+  char buf_[PATH_MAX - 1];
+};
+
+// A view over path components yet to be processed by realpath.
+//
+// When `realpath("./a/../b")` is called, the input path can be viewed as
+// a stack of components, where components closest to the root are at the top.
+// For example:
+//
+//   ```
+//   PendingPath p("./a/..");
+//   assert(p.advance_component() == ".");
+//   assert(p.advance_component() == "a");
+//   assert(p.advance_component() == "..");
+//   assert(p.empty());
+//   ```
+class PendingPath {
+public:
+  explicit PendingPath(cpp::string_view path) : view_(path) {}
+
+  // Whether all path components have been consumed.
+  bool empty() const { return view_.empty(); }
+
+  // Takes the next path component,
+  // starting with the component closest to the root.
+  cpp::string_view advance_component() {
+    const cpp::string_view path = view_;
+
+    const size_t component_start = path.find_first_not_of(PATH_SEP);
+    if (component_start == cpp::string_view::npos) {
+      view_ = "";
+      return "";
+    }
+
+    const size_t component_end =
+        path.find_first_of(PATH_SEP, /* From = */ component_start);
+    if (component_end == cpp::string_view::npos) {
+      view_ = "";
+      return path.substr(component_start);
+    }
+
+    view_ = view_.substr(component_end);
+    return path.substr(component_start, component_end - component_start);
+  }
+
+private:
+  cpp::string_view view_;
+};
+
+ErrorOr<char *> copy_or_allocate_cstr(char *dst, cpp::string_view src) {
+  if (dst == nullptr) {
+    AllocChecker ac;
+    dst = new (ac) char[src.size() + 1];
+    if (!ac)
+      return Error(ENOMEM);
+  }
+  inline_memcpy(dst, src.data(), src.size());
+  dst[src.size()] = '\0';
+  return dst;
+}
+
+ErrorOr<Ok> resolve_path(PendingPath &pending_path,
+                         ResolvedPath &resolved_path) {
+  while (!pending_path.empty()) {
+    cpp::string_view component = pending_path.advance_component();
+    if (component.empty() || component == ".")
+      continue;
+
+    if (component == "..") {
+      resolved_path.set_to_parent();
+      continue;
+    }
+
+    if (ErrorOr<Ok> res = resolved_path.push_component(component); !res)
+      return res;
+  }
+
+  return Ok{};
+}
+
+ErrorOr<char *> realpath_impl(const char *__restrict path_cstr,
+                              char *__restrict resolved_path_buf) {
+  if (path_cstr == nullptr)
+    return Error(EINVAL);
+
+  cpp::string_view path(path_cstr);
+  if (path.size() == 0)
+    return Error(ENOENT);
+
+  if (path.size() >= PATH_MAX)
+    return Error(ENAMETOOLONG);
+
+  PendingPath pending_path(path);
+
+  ResolvedPath resolved_path;
+  if (!is_absolute(path)) {
+    if (ErrorOr<Ok> res = resolved_path.set_to_cwd(); !res)
+      return Error(res.error());
+  }
+
+  if (ErrorOr<Ok> res = resolve_path(pending_path, resolved_path); !res)
+    return Error(res.error());
+
+  return copy_or_allocate_cstr(resolved_path_buf, resolved_path.view());
+}
+
+} // namespace
+
+LLVM_LIBC_FUNCTION(char *, realpath,
+                   (const char *__restrict path,
+                    char *__restrict resolved_path)) {
+  ErrorOr<char *> res = realpath_impl(path, resolved_path);
+  if (!res) {
+    libc_errno = res.error();
+    return nullptr;
+  }
+  return *res;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/realpath.h b/libc/src/stdlib/realpath.h
new file mode 100644
index 0000000000000..ddc8700ffc197
--- /dev/null
+++ b/libc/src/stdlib/realpath.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
+/// Declaration of the POSIX realpath function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDLIB_REALPATH_H
+#define LLVM_LIBC_SRC_STDLIB_REALPATH_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+char *realpath(const char *__restrict path, char *__restrict resolved_path);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDLIB_REALPATH_H
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index 95e5a3ccc2bb5..6b48d86d6ae50 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -376,6 +376,21 @@ add_libc_test(
     libc.src.stdlib.srand
 )
 
+add_libc_test(
+  realpath_test
+  SUITE
+    libc-stdlib-tests
+  SRCS
+    realpath_test.cpp
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.limits_macros
+    libc.src.__support.CPP.string
+    libc.src.__support.macros.config
+    libc.src.stdlib.realpath
+    libc.test.UnitTest.ErrnoCheckingTest
+)
+
 add_libc_test(
   memalignment_test
   SUITE
diff --git a/libc/test/src/stdlib/realpath_test.cpp b/libc/test/src/stdlib/realpath_test.cpp
new file mode 100644
index 0000000000000..604121e321dd0
--- /dev/null
+++ b/libc/test/src/stdlib/realpath_test.cpp
@@ -0,0 +1,109 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Unit tests for realpath.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/limits_macros.h"
+#include "src/__support/CPP/string.h"
+#include "src/__support/macros/config.h"
+#include "src/stdlib/realpath.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+class LlvmLibcRealpathTest : public testing::ErrnoCheckingTest {
+public:
+  char *realpath_buffered(const char *path) { return realpath(path, buf_); }
+
+  char *realpath_buffered(const cpp::string &path) {
+    return realpath_buffered(path.c_str());
+  }
+
+  char buf_[PATH_MAX];
+};
+
+TEST_F(LlvmLibcRealpathTest, ErrorsWithInvalidArgIfNullPath) {
+  ASSERT_EQ(realpath_buffered(nullptr), nullptr);
+  ASSERT_ERRNO_EQ(EINVAL);
+}
+
+TEST_F(LlvmLibcRealpathTest, ErrorsWithNoEntryIfEmptyPath) {
+  ASSERT_EQ(realpath_buffered(""), nullptr);
+  ASSERT_ERRNO_EQ(ENOENT);
+}
+
+TEST_F(LlvmLibcRealpathTest, OkIfPathIsExactlyMaxSize) {
+  // PATH_MAX counts null terminator, so construct a path of size PATH_MAX-1.
+  cpp::string s(PATH_MAX - 1, 'a');
+  for (size_t i = 0; i < s.size(); i += NAME_MAX + 1)
+    s[i] = '/';
+
+  ASSERT_STREQ(realpath_buffered(s), s.c_str());
+}
+
+TEST_F(LlvmLibcRealpathTest, ErrorsWithNameTooLongIfPathExceedsMaxSize) {
+  // PATH_MAX counts null terminator, so construct a path of size PATH_MAX.
+  cpp::string s(PATH_MAX, 'a');
+  for (size_t i = 0; i < s.size(); i += NAME_MAX + 1)
+    s[i] = '/';
+
+  ASSERT_EQ(realpath_buffered(s), nullptr);
+  ASSERT_ERRNO_EQ(ENAMETOOLONG);
+}
+
+TEST_F(LlvmLibcRealpathTest, OkWhenComponentLengthIsExactlyNameMax) {
+  cpp::string s(NAME_MAX + 1, 'a'); // +1 for leading "/"
+  s[0] = '/';
+
+  ASSERT_STREQ(realpath_buffered(s), s.c_str());
+}
+
+TEST_F(LlvmLibcRealpathTest, ErrorsIfComponentExceedsNameMax) {
+  constexpr size_t COMPONENT_SIZE = NAME_MAX + 1;
+  cpp::string s(COMPONENT_SIZE + 1, 'a'); // +1 for leading "/"
+  s[0] = '/';
+
+  ASSERT_EQ(realpath_buffered(s), nullptr);
+  ASSERT_ERRNO_EQ(ENAMETOOLONG);
+}
+
+TEST_F(LlvmLibcRealpathTest, RootResolvesToRoot) {
+  ASSERT_STREQ(realpath_buffered("/"), "/");
+}
+
+TEST_F(LlvmLibcRealpathTest, RootDotDotTraversalStaysAtRoot) {
+  ASSERT_STREQ(realpath_buffered("/.."), "/");
+}
+
+TEST_F(LlvmLibcRealpathTest, SimpleAbsolutePath) {
+  ASSERT_STREQ(realpath_buffered("/a/b/c"), "/a/b/c");
+}
+
+TEST_F(LlvmLibcRealpathTest, DotDotTraversesParent) {
+  ASSERT_STREQ(realpath_buffered("/a/b/.."), "/a");
+}
+
+TEST_F(LlvmLibcRealpathTest, DotTraversalIsNop) {
+  ASSERT_STREQ(realpath_buffered("/a/b/./"), "/a/b");
+}
+
+TEST_F(LlvmLibcRealpathTest, ConsecutiveSeparatorsIgnored) {
+  ASSERT_STREQ(realpath_buffered("////a///..///a//"), "/a");
+}
+
+TEST_F(LlvmLibcRealpathTest, AllocatesResultWhenBufferIsNull) {
+  char *result = LIBC_NAMESPACE::realpath("/a", nullptr);
+  ASSERT_STREQ(result, "/a");
+  ::free(result);
+}
+
+} // namespace LIBC_NAMESPACE_DECL

>From 38e3a1e3cfda0b984046b04e1b97098491811598 Mon Sep 17 00:00:00 2001
From: jtstogel <jtstogel at gmail.com>
Date: Wed, 17 Jun 2026 19:50:25 -0700
Subject: [PATCH 2/4] Prefer direct malloc to AllocChecker for simple char*
 allocation

---
 libc/src/stdlib/linux/CMakeLists.txt | 2 +-
 libc/src/stdlib/linux/realpath.cpp   | 7 +++----
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index 2ea3cbeb06cb8..b9aa72cace80a 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -17,8 +17,8 @@ add_entrypoint_object(
     ../realpath.h
   DEPENDS
     libc.hdr.errno_macros
+    libc.hdr.func.malloc
     libc.hdr.limits_macros
-    libc.src.__support.alloc_checker
     libc.src.__support.common
     libc.src.__support.CPP.string_view
     libc.src.__support.error_or
diff --git a/libc/src/stdlib/linux/realpath.cpp b/libc/src/stdlib/linux/realpath.cpp
index e635b72dacca6..46910158e51f5 100644
--- a/libc/src/stdlib/linux/realpath.cpp
+++ b/libc/src/stdlib/linux/realpath.cpp
@@ -13,9 +13,9 @@
 
 #include "src/stdlib/realpath.h"
 #include "hdr/errno_macros.h"
+#include "hdr/func/malloc.h"
 #include "hdr/limits_macros.h"
 #include "src/__support/CPP/string_view.h"
-#include "src/__support/alloc-checker.h"
 #include "src/__support/common.h"
 #include "src/__support/error_or.h"
 #include "src/__support/libc_errno.h"
@@ -146,9 +146,8 @@ class PendingPath {
 
 ErrorOr<char *> copy_or_allocate_cstr(char *dst, cpp::string_view src) {
   if (dst == nullptr) {
-    AllocChecker ac;
-    dst = new (ac) char[src.size() + 1];
-    if (!ac)
+    dst = reinterpret_cast<char *>(::malloc(src.size() + 1));
+    if (dst == nullptr)
       return Error(ENOMEM);
   }
   inline_memcpy(dst, src.data(), src.size());

>From e98233f2bdd588daece7dee4111ee0dd0a884e1d Mon Sep 17 00:00:00 2001
From: jtstogel <jtstogel at gmail.com>
Date: Wed, 17 Jun 2026 19:53:54 -0700
Subject: [PATCH 3/4] Include size_t

---
 libc/src/stdlib/linux/CMakeLists.txt   | 1 +
 libc/src/stdlib/linux/realpath.cpp     | 1 +
 libc/test/src/stdlib/CMakeLists.txt    | 1 +
 libc/test/src/stdlib/realpath_test.cpp | 1 +
 4 files changed, 4 insertions(+)

diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index b9aa72cace80a..b7cf1578c1be4 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -19,6 +19,7 @@ add_entrypoint_object(
     libc.hdr.errno_macros
     libc.hdr.func.malloc
     libc.hdr.limits_macros
+    libc.hdr.types.size_t
     libc.src.__support.common
     libc.src.__support.CPP.string_view
     libc.src.__support.error_or
diff --git a/libc/src/stdlib/linux/realpath.cpp b/libc/src/stdlib/linux/realpath.cpp
index 46910158e51f5..941b1f4420f21 100644
--- a/libc/src/stdlib/linux/realpath.cpp
+++ b/libc/src/stdlib/linux/realpath.cpp
@@ -15,6 +15,7 @@
 #include "hdr/errno_macros.h"
 #include "hdr/func/malloc.h"
 #include "hdr/limits_macros.h"
+#include "hdr/types/size_t.h"
 #include "src/__support/CPP/string_view.h"
 #include "src/__support/common.h"
 #include "src/__support/error_or.h"
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index 6b48d86d6ae50..224e9c4ac3bad 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -385,6 +385,7 @@ add_libc_test(
   DEPENDS
     libc.hdr.errno_macros
     libc.hdr.limits_macros
+    libc.hdr.types.size_t
     libc.src.__support.CPP.string
     libc.src.__support.macros.config
     libc.src.stdlib.realpath
diff --git a/libc/test/src/stdlib/realpath_test.cpp b/libc/test/src/stdlib/realpath_test.cpp
index 604121e321dd0..62d7f14c53c8d 100644
--- a/libc/test/src/stdlib/realpath_test.cpp
+++ b/libc/test/src/stdlib/realpath_test.cpp
@@ -13,6 +13,7 @@
 
 #include "hdr/errno_macros.h"
 #include "hdr/limits_macros.h"
+#include "hdr/types/size_t.h"
 #include "src/__support/CPP/string.h"
 #include "src/__support/macros/config.h"
 #include "src/stdlib/realpath.h"

>From 73f0d2dd75aaace6139c5ea183a146a27dca690d Mon Sep 17 00:00:00 2001
From: jtstogel <jtstogel at gmail.com>
Date: Wed, 17 Jun 2026 19:55:33 -0700
Subject: [PATCH 4/4] Don't check against NAME_MAX, we can let the kernel do so
 in statx

---
 libc/src/stdlib/linux/realpath.cpp     |  3 ---
 libc/test/src/stdlib/realpath_test.cpp | 35 +++++++++++---------------
 2 files changed, 14 insertions(+), 24 deletions(-)

diff --git a/libc/src/stdlib/linux/realpath.cpp b/libc/src/stdlib/linux/realpath.cpp
index 941b1f4420f21..22e83f7c79ef3 100644
--- a/libc/src/stdlib/linux/realpath.cpp
+++ b/libc/src/stdlib/linux/realpath.cpp
@@ -64,9 +64,6 @@ class ResolvedPath {
 
   // Adds a single component to the end of this path.
   ErrorOr<Ok> push_component(cpp::string_view component) {
-    if (component.size() > NAME_MAX)
-      return Error(ENAMETOOLONG);
-
     if (!is_root()) {
       if (ErrorOr<Ok> res = push_raw(PATH_SEP); !res)
         return res;
diff --git a/libc/test/src/stdlib/realpath_test.cpp b/libc/test/src/stdlib/realpath_test.cpp
index 62d7f14c53c8d..515ef37be410d 100644
--- a/libc/test/src/stdlib/realpath_test.cpp
+++ b/libc/test/src/stdlib/realpath_test.cpp
@@ -42,36 +42,29 @@ TEST_F(LlvmLibcRealpathTest, ErrorsWithNoEntryIfEmptyPath) {
   ASSERT_ERRNO_EQ(ENOENT);
 }
 
-TEST_F(LlvmLibcRealpathTest, OkIfPathIsExactlyMaxSize) {
+TEST_F(LlvmLibcRealpathTest, OkIfPathArgIsExactlyMaxSize) {
   // PATH_MAX counts null terminator, so construct a path of size PATH_MAX-1.
-  cpp::string s(PATH_MAX - 1, 'a');
-  for (size_t i = 0; i < s.size(); i += NAME_MAX + 1)
-    s[i] = '/';
+  cpp::string s(PATH_MAX - 1, '/');
+  for (size_t i = 1; i < s.size(); i += 2)
+    s[i] = '.';
 
-  ASSERT_STREQ(realpath_buffered(s), s.c_str());
+  ASSERT_STREQ(realpath_buffered(s), "/");
 }
 
-TEST_F(LlvmLibcRealpathTest, ErrorsWithNameTooLongIfPathExceedsMaxSize) {
-  // PATH_MAX counts null terminator, so construct a path of size PATH_MAX.
-  cpp::string s(PATH_MAX, 'a');
-  for (size_t i = 0; i < s.size(); i += NAME_MAX + 1)
+TEST_F(LlvmLibcRealpathTest, OkIfResolvedPathIsExactlyMaxSize) {
+  // PATH_MAX counts null terminator, so construct a path of size PATH_MAX-1.
+  cpp::string s(PATH_MAX - 1, 'a');
+  for (size_t i = 0; i < s.size() - 1; i += 8)
     s[i] = '/';
 
-  ASSERT_EQ(realpath_buffered(s), nullptr);
-  ASSERT_ERRNO_EQ(ENAMETOOLONG);
-}
-
-TEST_F(LlvmLibcRealpathTest, OkWhenComponentLengthIsExactlyNameMax) {
-  cpp::string s(NAME_MAX + 1, 'a'); // +1 for leading "/"
-  s[0] = '/';
-
   ASSERT_STREQ(realpath_buffered(s), s.c_str());
 }
 
-TEST_F(LlvmLibcRealpathTest, ErrorsIfComponentExceedsNameMax) {
-  constexpr size_t COMPONENT_SIZE = NAME_MAX + 1;
-  cpp::string s(COMPONENT_SIZE + 1, 'a'); // +1 for leading "/"
-  s[0] = '/';
+TEST_F(LlvmLibcRealpathTest, ErrorsWithNameTooLongIfPathArgExceedsMaxSize) {
+  // PATH_MAX counts null terminator, so construct a path of size PATH_MAX.
+  cpp::string s(PATH_MAX, '/');
+  for (size_t i = 1; i < s.size(); i += 2)
+    s[i] = '.';
 
   ASSERT_EQ(realpath_buffered(s), nullptr);
   ASSERT_ERRNO_EQ(ENAMETOOLONG);



More information about the libc-commits mailing list