[llvm-branch-commits] [libc] [libc][realpath] Support relative paths (PR #209374)
Jackson Stogel via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Jul 13 21:54:33 PDT 2026
https://github.com/jtstogel created https://github.com/llvm/llvm-project/pull/209374
Updates realpath to call the `getcwd` linux syscall wrapper to resolve cwd-relative queries.
This is a stacked PR on top of https://github.com/llvm/llvm-project/pull/209369.
>From bd14bd75626386e906c1f016549b3a0e5c5d9e5e Mon Sep 17 00:00:00 2001
From: jtstogel <jtstogel at gmail.com>
Date: Fri, 3 Jul 2026 18:52:42 -0700
Subject: [PATCH] [libc][realpath] Support relative paths
---
libc/src/stdlib/linux/CMakeLists.txt | 1 +
libc/src/stdlib/linux/realpath.cpp | 17 ++++-
libc/test/src/stdlib/CMakeLists.txt | 2 +
libc/test/src/stdlib/realpath_test.cpp | 87 +++++++++++++++++++++++++-
4 files changed, 103 insertions(+), 4 deletions(-)
diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index 357073f61962b..e71e3bf3e4ddf 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -30,6 +30,7 @@ add_entrypoint_object(
libc.src.__support.libc_errno
libc.src.__support.macros.config
libc.src.__support.OSUtil.linux.stat.kernel_statx_types
+ libc.src.__support.OSUtil.linux.syscall_wrappers.getcwd
libc.src.__support.OSUtil.linux.syscall_wrappers.statx
libc.src.__support.OSUtil.path
libc.src.string.memory_utils.inline_memcpy
diff --git a/libc/src/stdlib/linux/realpath.cpp b/libc/src/stdlib/linux/realpath.cpp
index bebfc14f3d256..4c6a1e14e8e47 100644
--- a/libc/src/stdlib/linux/realpath.cpp
+++ b/libc/src/stdlib/linux/realpath.cpp
@@ -22,6 +22,7 @@
#include "src/__support/CPP/string.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/OSUtil/linux/stat/kernel_statx_types.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/getcwd.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/statx.h"
#include "src/__support/OSUtil/path.h"
#include "src/__support/common.h"
@@ -46,7 +47,21 @@ class ResolvedPath {
void set_to_root() { path_ = path::SEPARATOR; }
- cpp::optional<Error> set_to_cwd() { return Error(ENOSYS); }
+ cpp::optional<Error> set_to_cwd() {
+ char buf[PATH_MAX];
+ ErrorOr<ssize_t> ret = linux_syscalls::getcwd(buf, PATH_MAX);
+ if (!ret) {
+ if (ret.error() == ERANGE)
+ return Error(ENAMETOOLONG);
+ return Error(ret.error());
+ }
+
+ if (*ret <= 0)
+ return Error(EIO);
+
+ path_ = cpp::string_view(buf, *ret - 1);
+ return cpp::nullopt;
+ }
// Removes the trailing path component.
void set_to_parent() {
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index 31b710229ce95..2d03a3d2e4702 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -398,7 +398,9 @@ add_libc_test(
libc.src.fcntl.openat
libc.src.stdlib.realpath
libc.src.sys.stat.mkdirat
+ libc.src.unistd.chdir
libc.src.unistd.close
+ libc.src.unistd.getcwd
libc.src.unistd.unlinkat
libc.test.UnitTest.ErrnoCheckingTest
libc.test.UnitTest.ErrnoSetterMatcher
diff --git a/libc/test/src/stdlib/realpath_test.cpp b/libc/test/src/stdlib/realpath_test.cpp
index 0a7f0032d292c..20a12cd3358d8 100644
--- a/libc/test/src/stdlib/realpath_test.cpp
+++ b/libc/test/src/stdlib/realpath_test.cpp
@@ -25,7 +25,9 @@
#include "src/fcntl/openat.h"
#include "src/stdlib/realpath.h"
#include "src/sys/stat/mkdirat.h"
+#include "src/unistd/chdir.h"
#include "src/unistd/close.h"
+#include "src/unistd/getcwd.h"
#include "src/unistd/unlinkat.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
#include "test/UnitTest/ErrnoSetterMatcher.h"
@@ -33,6 +35,7 @@
namespace cpp = LIBC_NAMESPACE::cpp;
namespace path = LIBC_NAMESPACE::path;
using LIBC_NAMESPACE::FixedVector;
+using LIBC_NAMESPACE::testing::ErrnoCheckingTest;
using LIBC_NAMESPACE::testing::tlog;
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
@@ -153,10 +156,23 @@ class TestDir {
}
};
-class LlvmLibcRealpathTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
+class LlvmLibcRealpathTest : public ErrnoCheckingTest {
public:
+ void SetUp() override {
+ ErrnoCheckingTest::SetUp();
+
+ (void)LIBC_NAMESPACE::getcwd(start_dir_, sizeof(start_dir_));
+ ASSERT_ERRNO_SUCCESS();
+ }
+
+ void TearDown() override {
+ ASSERT_THAT(LIBC_NAMESPACE::chdir(start_dir_), Succeeds());
+
+ ErrnoCheckingTest::TearDown();
+ }
+
char *realpath_buffered(const char *path) {
- return LIBC_NAMESPACE::realpath(path, buf_);
+ return LIBC_NAMESPACE::realpath(path, realpath_buf_);
}
char *realpath_buffered(const cpp::string &path) {
@@ -184,7 +200,11 @@ class LlvmLibcRealpathTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
}
private:
- char buf_[PATH_MAX];
+ // Buffer for storing realpath output.
+ char realpath_buf_[PATH_MAX];
+
+ // Current working dir at test SetUp.
+ char start_dir_[PATH_MAX];
};
TEST_F(LlvmLibcRealpathTest, ErrorsWithInvalidArgIfNullPath) {
@@ -378,3 +398,64 @@ TEST_F(LlvmLibcRealpathTest, ErrorsWithNoAccesWhenDirectoryNotSearchable) {
ASSERT_STREQ(realpath_buffered(test_dir.abspath("a/b")), nullptr);
ASSERT_ERRNO_EQ(EACCES);
}
+
+TEST_F(LlvmLibcRealpathTest, RelativePathResolvesToCurrentWorkingDir) {
+ TestDir test_dir;
+ ASSERT_TRUE(
+ create_test_dir("RelativePathResolvesToCurrentWorkingDir", test_dir));
+
+ ASSERT_THAT(LIBC_NAMESPACE::chdir(test_dir.c_str()), Succeeds());
+ ASSERT_STREQ(realpath_buffered("."), test_dir.c_str());
+
+ ASSERT_THAT(test_dir.mkdir("a"), Succeeds());
+ ASSERT_STREQ(realpath_buffered("a"), test_dir.abspath("a").c_str());
+}
+
+// Creates a directory with the desired_size and then chdir's into it.
+// Returns true on success.
+[[nodiscard]] bool chdir_to_abspath_with_size(TestDir &test_dir,
+ size_t desired_size,
+ cpp::string &out) {
+ if (!create_abspath_with_size(test_dir, desired_size, out))
+ return false;
+
+ // Convert the directory to be relative to test_dir.
+ const char *test_dir_relative_path =
+ out.c_str() + test_dir.view().size() + PATH_SEP_SIZE;
+
+ // Change directories into the path iteratively.
+ // This allows us to chdir into paths longer than PATH_MAX, as long
+ // as each of test_dir and relpath are shorter than PATH_MAX.
+ if (LIBC_NAMESPACE::chdir(test_dir.c_str()))
+ return false;
+ if (LIBC_NAMESPACE::chdir(test_dir_relative_path))
+ return false;
+ return true;
+}
+
+TEST_F(LlvmLibcRealpathTest, RelativeRealpathAcceptsPathExactlyMaxSize) {
+ TestDir test_dir;
+ ASSERT_TRUE(
+ create_test_dir("RelativeRealpathAcceptsPathExactlyMaxSize", test_dir));
+
+ cpp::string path;
+ ASSERT_TRUE(chdir_to_abspath_with_size(test_dir, PATH_MAX - 1, path));
+
+ ASSERT_STREQ(realpath_buffered("."), path.c_str());
+}
+
+TEST_F(LlvmLibcRealpathTest, RelativeRealpathRejectsPathExceedingMaxSize) {
+ TestDir test_dir;
+ ASSERT_TRUE(
+ create_test_dir("RelativeRealpathRejectsPathExceedingMaxSize", test_dir));
+
+ cpp::string path;
+ if (!chdir_to_abspath_with_size(test_dir, PATH_MAX, path)) {
+ // Skip the test if the system didn't allow creating the path.
+ ASSERT_ERRNO_EQ(ENAMETOOLONG);
+ return;
+ }
+
+ ASSERT_EQ(realpath_buffered("."), nullptr);
+ ASSERT_ERRNO_EQ(ENAMETOOLONG);
+}
More information about the llvm-branch-commits
mailing list