[libc][realpath] Implement symbolic path resolution (#204467)
This PR contains an incremental implementation of `realpath` that just simplifies path traversals (e.g. `"/a/./../b"` -> `"/b"`). The complete implementation would perform `getcwd`/`statx`/`readlinkat` to actually resolve symlinks, validate paths, etc. This PR aims to add as much functionality/error handling as possible without making any syscalls. This implementation lives in `libc/src/stdlib/linux/`, since follow-on PRs will rely on linux-specific syscalls. For testing purposes, I've added the entrypoint for x86-64 linux when `LLVM_LIBC_ENABLE_EXPERIMENTAL_ENTRYPOINTS` is enabled. Follow-on PRs to add: path validation (e.g. paths must exist), support for cwd-relative paths, and symlink resolution.
This commit is contained in:
1 parent
4ce874e05a
commit
88a3488c8a
17 files changed
+561
-24
No files matched your search
@@ -1526,6 +1526,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -90,6 +90,7 @@ add_header_library(
|
||||
libc.hdr.func.realloc
|
||||
libc.src.__support.common
|
||||
libc.src.__support.integer_to_string
|
||||
libc.src.__support.macros.null_check
|
||||
libc.src.string.memory_utils.inline_memcpy
|
||||
libc.src.string.memory_utils.inline_memset
|
||||
libc.src.string.string_utils
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "src/__support/CPP/string_view.h"
|
||||
#include "src/__support/integer_to_string.h" // IntegerToString
|
||||
#include "src/__support/macros/config.h"
|
||||
#include "src/__support/macros/null_check.h"
|
||||
#include "src/string/memory_utils/inline_memcpy.h"
|
||||
#include "src/string/memory_utils/inline_memset.h"
|
||||
#include "src/string/string_utils.h" // string_length
|
||||
@@ -23,6 +24,16 @@
|
||||
|
||||
namespace LIBC_NAMESPACE_DECL {
|
||||
namespace cpp {
|
||||
namespace {
|
||||
|
||||
char *realloc_or_die(char *ptr, size_t size) {
|
||||
void *new_ptr = ::realloc(ptr, size);
|
||||
// Out of memory: this is not handled in current implementation, so crash.
|
||||
LIBC_CRASH_ON_NULLPTR(new_ptr);
|
||||
return reinterpret_cast<char *>(new_ptr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// This class mimics std::string but does not intend to be a full fledged
|
||||
// implementation. Most notably it does not provide support for character traits
|
||||
@@ -76,6 +87,11 @@ public:
|
||||
return (*this) += other;
|
||||
}
|
||||
|
||||
LIBC_INLINE string &operator=(char other) {
|
||||
resize(0);
|
||||
return (*this) += other;
|
||||
}
|
||||
|
||||
LIBC_INLINE string &operator=(string &&other) {
|
||||
buffer_ = other.buffer_;
|
||||
size_ = other.size_;
|
||||
@@ -133,15 +149,9 @@ public:
|
||||
if (new_capacity < SIZE_MAX / 11)
|
||||
new_capacity = new_capacity * 11 / 8;
|
||||
|
||||
if (void *Ptr = ::realloc(buffer_ == get_empty_string() ? nullptr : buffer_,
|
||||
new_capacity)) {
|
||||
buffer_ = static_cast<char *>(Ptr);
|
||||
capacity_ = new_capacity;
|
||||
return;
|
||||
}
|
||||
// Out of memory: this is not handled in current implementation,
|
||||
// We trap the program and exits.
|
||||
__builtin_trap();
|
||||
buffer_ = realloc_or_die(buffer_ == get_empty_string() ? nullptr : buffer_,
|
||||
new_capacity);
|
||||
capacity_ = new_capacity;
|
||||
}
|
||||
|
||||
LIBC_INLINE void resize(size_t size) {
|
||||
@@ -153,6 +163,24 @@ public:
|
||||
set_size_and_add_null_character(size);
|
||||
}
|
||||
|
||||
// Releases the backing C-string.
|
||||
//
|
||||
// The returned pointer must be free'd by the caller.
|
||||
// This is a non-standard extension to std::string.
|
||||
LIBC_INLINE char *release_c_str() {
|
||||
if (buffer_ == get_empty_string()) {
|
||||
// Ensure the buffer is heap allocated,
|
||||
// so that it may later be passed to `free`.
|
||||
char *res = realloc_or_die(nullptr, 1);
|
||||
res[0] = '\0';
|
||||
return res;
|
||||
}
|
||||
|
||||
char *res = buffer_;
|
||||
reset_no_deallocate();
|
||||
return res;
|
||||
}
|
||||
|
||||
LIBC_INLINE string &operator+=(const string &rhs) {
|
||||
const size_t new_size = size_ + rhs.size();
|
||||
reserve(new_size);
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_subdirectory(${LIBC_TARGET_OS})
|
||||
|
||||
set(target_os_util libc.src.__support.OSUtil.${LIBC_TARGET_OS}.${LIBC_TARGET_OS}_util)
|
||||
if(NOT TARGET ${target_os_util})
|
||||
return()
|
||||
if(TARGET ${target_os_util})
|
||||
add_object_library(
|
||||
osutil
|
||||
ALIAS
|
||||
${target_os_util}
|
||||
DEPENDS
|
||||
${target_os_util}
|
||||
)
|
||||
endif()
|
||||
|
||||
add_object_library(
|
||||
osutil
|
||||
ALIAS
|
||||
${target_os_util}
|
||||
DEPENDS
|
||||
${target_os_util}
|
||||
)
|
||||
set(target_path libc.src.__support.OSUtil.${LIBC_TARGET_OS}.path)
|
||||
if(TARGET ${target_path})
|
||||
add_header_library(
|
||||
path
|
||||
HDRS
|
||||
path.h
|
||||
DEPENDS
|
||||
${target_path}
|
||||
)
|
||||
endif()
|
||||
@@ -44,6 +44,15 @@ add_header_library(
|
||||
libc.src.__support.threads.callonce
|
||||
)
|
||||
|
||||
add_header_library(
|
||||
path
|
||||
HDRS
|
||||
path.h
|
||||
DEPENDS
|
||||
libc.src.__support.common
|
||||
libc.src.__support.CPP.string_view
|
||||
)
|
||||
|
||||
add_header_library(
|
||||
sysinfo
|
||||
HDRS
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
/// Internal linux path helpers.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_PATH_H
|
||||
#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_PATH_H
|
||||
|
||||
#include "src/__support/CPP/string_view.h"
|
||||
#include "src/__support/macros/config.h"
|
||||
|
||||
namespace LIBC_NAMESPACE_DECL {
|
||||
namespace path {
|
||||
|
||||
// Separator character for linux paths.
|
||||
constexpr char SEPARATOR = '/';
|
||||
|
||||
// Component pointing to the current directory.
|
||||
constexpr cpp::string_view CURRENT_DIR_COMPONENT = ".";
|
||||
|
||||
// Component pointing to the parent directory.
|
||||
constexpr cpp::string_view PARENT_DIR_COMPONENT = "..";
|
||||
|
||||
// Whether `path` is absolute.
|
||||
LIBC_INLINE constexpr bool is_absolute(cpp::string_view path) {
|
||||
return path.starts_with(SEPARATOR);
|
||||
}
|
||||
|
||||
// Whether `path` is absolute.
|
||||
LIBC_INLINE constexpr bool is_root(cpp::string_view path) {
|
||||
return path.size() == 1 && path[0] == SEPARATOR;
|
||||
}
|
||||
|
||||
} // namespace path
|
||||
} // namespace LIBC_NAMESPACE_DECL
|
||||
|
||||
#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_PATH_H
|
||||
@@ -0,0 +1,23 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
/// Internal path helpers.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_PATH_H
|
||||
#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_PATH_H
|
||||
|
||||
#if defined(__linux__)
|
||||
#include "src/__support/OSUtil/linux/path.h"
|
||||
#else
|
||||
#error path.h unavailable on this platfrom
|
||||
#endif
|
||||
|
||||
#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_PATH_H
|
||||
@@ -596,6 +596,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()
|
||||
@@ -677,10 +688,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
|
||||
|
||||
@@ -9,6 +9,27 @@ 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.hdr.types.size_t
|
||||
libc.src.__support.common
|
||||
libc.src.__support.CPP.optional
|
||||
libc.src.__support.CPP.string
|
||||
libc.src.__support.CPP.string_view
|
||||
libc.src.__support.error_or
|
||||
libc.src.__support.libc_errno
|
||||
libc.src.__support.macros.config
|
||||
libc.src.__support.OSUtil.path
|
||||
libc.src.string.memory_utils.inline_memcpy
|
||||
)
|
||||
|
||||
add_entrypoint_object(
|
||||
setenv
|
||||
SRCS
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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 "hdr/types/size_t.h"
|
||||
#include "src/__support/CPP/optional.h"
|
||||
#include "src/__support/CPP/string.h"
|
||||
#include "src/__support/CPP/string_view.h"
|
||||
#include "src/__support/OSUtil/path.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 {
|
||||
|
||||
// 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() { path_ = path::SEPARATOR; }
|
||||
|
||||
cpp::optional<Error> set_to_cwd() { return Error(ENOSYS); }
|
||||
|
||||
// Removes the trailing path component.
|
||||
void set_to_parent() {
|
||||
size_t sep_index = cpp::string_view(path_).find_last_of(path::SEPARATOR);
|
||||
|
||||
// Never move past the root separator. For example,
|
||||
// ensures that set_to_parent on "/hello" only resizes to "/".
|
||||
path_.resize(sep_index >= 1 ? sep_index : 1);
|
||||
}
|
||||
|
||||
// Adds a single component to the end of this path.
|
||||
cpp::optional<Error> push_component(cpp::string_view component) {
|
||||
if (!path::is_root(path_)) {
|
||||
if (cpp::optional<Error> err = push_raw(path::SEPARATOR); err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return push_raw(component);
|
||||
}
|
||||
|
||||
// Releases ownership of the underlying C-string and resets this path.
|
||||
//
|
||||
// Must be free'd by the caller.
|
||||
char *release() { return path_.release_c_str(); }
|
||||
|
||||
// Copies the content of this path to `dst`.
|
||||
void copy_to(char *dst) {
|
||||
inline_memcpy(dst, path_.c_str(), path_.size() + 1);
|
||||
}
|
||||
|
||||
private:
|
||||
cpp::optional<Error> push_raw(cpp::string_view value) {
|
||||
// -1 because PATH_MAX includes a null-terminator.
|
||||
size_t remaining_bytes = (PATH_MAX - 1) - path_.size();
|
||||
if (value.size() > remaining_bytes)
|
||||
return Error(ENAMETOOLONG);
|
||||
|
||||
path_ += value;
|
||||
return cpp::nullopt;
|
||||
}
|
||||
|
||||
cpp::optional<Error> push_raw(char c) {
|
||||
return push_raw(cpp::string_view(&c, 1));
|
||||
}
|
||||
|
||||
cpp::string path_;
|
||||
};
|
||||
|
||||
// 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::SEPARATOR);
|
||||
if (component_start == cpp::string_view::npos) {
|
||||
view_ = "";
|
||||
return "";
|
||||
}
|
||||
|
||||
const size_t component_end =
|
||||
path.find_first_of(path::SEPARATOR, /* 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_;
|
||||
};
|
||||
|
||||
cpp::optional<Error> resolve_path(PendingPath &pending_path,
|
||||
ResolvedPath &resolved_path) {
|
||||
while (!pending_path.empty()) {
|
||||
cpp::string_view component = pending_path.advance_component();
|
||||
if (component.empty() || component == path::CURRENT_DIR_COMPONENT)
|
||||
continue;
|
||||
|
||||
if (component == path::PARENT_DIR_COMPONENT) {
|
||||
resolved_path.set_to_parent();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cpp::optional<Error> err = resolved_path.push_component(component); err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return cpp::nullopt;
|
||||
}
|
||||
|
||||
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 (!path::is_absolute(path)) {
|
||||
if (cpp::optional<Error> err = resolved_path.set_to_cwd(); err)
|
||||
return *err;
|
||||
}
|
||||
|
||||
if (cpp::optional<Error> err = resolve_path(pending_path, resolved_path); err)
|
||||
return *err;
|
||||
|
||||
if (resolved_path_buf != nullptr) {
|
||||
resolved_path.copy_to(resolved_path_buf);
|
||||
return resolved_path_buf;
|
||||
}
|
||||
return resolved_path.release();
|
||||
}
|
||||
|
||||
} // 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
|
||||
@@ -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
|
||||
@@ -169,6 +169,7 @@ add_libc_test(
|
||||
SRCS
|
||||
string_test.cpp
|
||||
DEPENDS
|
||||
libc.hdr.func.free
|
||||
libc.src.__support.CPP.string
|
||||
libc.src.__support.CPP.string_view
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "hdr/func/free.h"
|
||||
#include "src/__support/CPP/string.h"
|
||||
#include "test/UnitTest/Test.h"
|
||||
|
||||
@@ -77,7 +78,7 @@ TEST(LlvmLibcStringTest, InitializeRepeatedChar) {
|
||||
ASSERT_EQ(string_view(s), string_view("1111"));
|
||||
}
|
||||
|
||||
TEST(LlvmLibcStringTest, InitializeZeorChar) {
|
||||
TEST(LlvmLibcStringTest, InitializeZeroChar) {
|
||||
const string s(0, '1');
|
||||
ASSERT_TRUE(s.empty());
|
||||
}
|
||||
@@ -135,6 +136,13 @@ TEST(LlvmLibcStringTest, StringViewAssign) {
|
||||
ASSERT_EQ(s.back(), 'b');
|
||||
}
|
||||
|
||||
TEST(LlvmLibcStringTest, CharacterAssign) {
|
||||
string s("abc");
|
||||
s = 'a';
|
||||
ASSERT_EQ(s.size(), size_t(1));
|
||||
ASSERT_STREQ(s.c_str(), "a");
|
||||
}
|
||||
|
||||
TEST(LlvmLibcStringTest, Concat) {
|
||||
const char *const str = "abc";
|
||||
string a(str);
|
||||
@@ -186,6 +194,25 @@ TEST(LlvmLibcStringTest, ConcatWithCString) {
|
||||
ASSERT_STREQ(("a" + string("b")).c_str(), "ab");
|
||||
}
|
||||
|
||||
TEST(LlvmLibcStringTest, ReleaseCStrResetsString) {
|
||||
string s("abc");
|
||||
|
||||
char *cstr = s.release_c_str();
|
||||
ASSERT_STREQ(cstr, "abc");
|
||||
ASSERT_STREQ(s.c_str(), "");
|
||||
free(cstr);
|
||||
}
|
||||
|
||||
TEST(LlvmLibcStringTest, ReleaseCStrOnDefaultString) {
|
||||
string s;
|
||||
|
||||
char *cstr = s.release_c_str();
|
||||
ASSERT_STREQ(cstr, "");
|
||||
ASSERT_STREQ(s.c_str(), "");
|
||||
ASSERT_NE(cstr, s.data()); // Different pointers.
|
||||
free(cstr);
|
||||
}
|
||||
|
||||
TEST(LlvmLibcStringTest, Comparison) {
|
||||
// Here we simply check that comparison of string and string_view have the
|
||||
// same semantic.
|
||||
|
||||
@@ -377,6 +377,23 @@ 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.hdr.types.size_t
|
||||
libc.src.__support.CPP.string
|
||||
libc.src.__support.macros.config
|
||||
libc.src.__support.OSUtil.path
|
||||
libc.src.stdlib.realpath
|
||||
libc.test.UnitTest.ErrnoCheckingTest
|
||||
)
|
||||
|
||||
add_libc_test(
|
||||
memalignment_test
|
||||
SUITE
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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 "hdr/types/size_t.h"
|
||||
#include "src/__support/CPP/string.h"
|
||||
#include "src/__support/OSUtil/path.h"
|
||||
#include "src/__support/macros/config.h"
|
||||
#include "src/stdlib/realpath.h"
|
||||
#include "test/UnitTest/ErrnoCheckingTest.h"
|
||||
|
||||
namespace cpp = LIBC_NAMESPACE::cpp;
|
||||
namespace path = LIBC_NAMESPACE::path;
|
||||
using LIBC_NAMESPACE::testing::ErrnoCheckingTest;
|
||||
|
||||
// This test assumes the following values, so fail early if they mismatch.
|
||||
static_assert(path::SEPARATOR == '/');
|
||||
static_assert(path::CURRENT_DIR_COMPONENT == ".");
|
||||
static_assert(path::PARENT_DIR_COMPONENT == "..");
|
||||
|
||||
class LlvmLibcRealpathTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
|
||||
public:
|
||||
char *realpath_buffered(const char *path) {
|
||||
return LIBC_NAMESPACE::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, OkIfPathArgIsExactlyMaxSize) {
|
||||
// PATH_MAX counts null terminator, so construct a path of size PATH_MAX-1.
|
||||
cpp::string s(PATH_MAX - 1, '/');
|
||||
for (size_t i = 1; i < s.size(); i += 2)
|
||||
s[i] = '.';
|
||||
|
||||
ASSERT_STREQ(realpath_buffered(s), "/");
|
||||
}
|
||||
|
||||
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_STREQ(realpath_buffered(s), s.c_str());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1129,6 +1129,7 @@ libc_support_library(
|
||||
":__support_cpp_string_view",
|
||||
":__support_integer_to_string",
|
||||
":__support_macros_config",
|
||||
":__support_macros_null_check",
|
||||
":func_free",
|
||||
":func_malloc",
|
||||
":func_realloc",
|
||||
|
||||
Reference in new issue
Block a user