| //===----------------------------------------------------------------------===// | |
| // | |
| // 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 |