[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