| //===----------------------------------------------------------------------===// |
| // |
| // 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/fcntl_macros.h" |
| #include "hdr/limits_macros.h" |
| #include "hdr/sys_stat_macros.h" |
| #include "hdr/types/mode_t.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/linux/stat/kernel_statx_types.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/getcwd.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/readlink.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/statx.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 { |
| |
| #ifdef SYMLOOP_MAX |
| constexpr size_t MAX_SYMLINK_FOLLOWS = SYMLOOP_MAX; |
| #else |
| // Maximum number of symlinks that may be followed during path resolution. |
| // This is a large, arbitrary value consistent with other libc implementations. |
| // Must be at least _POSIX_SYMLOOP_MAX (8). |
| // |
| // TODO: This value should ideally be read from sysconf, so that it follows |
| // limits set by the system libc when LLVM-libc us used in overlay mode. |
| constexpr size_t MAX_SYMLINK_FOLLOWS = 40; |
| #endif |
| |
| // Container for a fully resolved, canonical path. |
| // |
| // The contained path is always in its canonical form. It is: |
| // - Absolute |
| // - 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() { |
| 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() { |
| 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(); } |
| |
| const char *c_str() const { return path.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"); |
| // |
| // p.prepend("b/c"); |
| // assert(p.advance_component() == "b"); |
| // assert(p.advance_component() == "c"); |
| // assert(p.advance_component() == ".."); |
| // assert(p.empty()); |
| // ``` |
| class PendingPath { |
| public: |
| PendingPath() {} |
| |
| // Whether all path components have been consumed. |
| bool empty() const { return consumed_size >= path.size(); } |
| |
| // Takes the next path component, |
| // starting with the component closest to the root. |
| cpp::string_view advance_component() { |
| cpp::string_view view = path; |
| |
| const size_t component_start = |
| view.find_first_not_of(path::SEPARATOR, /* From= */ consumed_size); |
| if (component_start == cpp::string_view::npos) { |
| consumed_size = path.size(); |
| return ""; |
| } |
| |
| size_t component_end = |
| view.find_first_of(path::SEPARATOR, /* From = */ component_start); |
| if (component_end == cpp::string_view::npos) |
| component_end = path.size(); |
| |
| consumed_size = component_end; |
| return view.substr(component_start, component_end - component_start); |
| } |
| |
| // Prepends other_path to this path. |
| cpp::optional<Error> prepend(cpp::string_view other_path) { |
| size_t new_size = other_path.size() + path.size() - consumed_size; |
| if (new_size >= PATH_MAX) |
| return Error(ENAMETOOLONG); |
| |
| if (other_path.size() <= consumed_size) { |
| // If the string to prepend fits in the unused prefix of path, |
| // just slot it in directly and decrease consumed_size. |
| size_t start = consumed_size - other_path.size(); |
| path.replace(start, other_path.size(), other_path); |
| consumed_size = start; |
| } else { |
| path.replace(0, consumed_size, other_path); |
| consumed_size = 0; |
| } |
| return cpp::nullopt; |
| } |
| |
| private: |
| // The pending path. The unprocessed section is [consumed_size, path.size()). |
| cpp::string path; |
| |
| // The prefix of path that has already been advanced through. |
| size_t consumed_size = 0; |
| }; |
| |
| // Reads the target of a symlink. Returns a view into buf. |
| ErrorOr<cpp::string_view> readlink(const char *path, char (&buf)[PATH_MAX]) { |
| ErrorOr<ssize_t> bytes_written = |
| linux_syscalls::readlink(path, buf, sizeof(buf)); |
| if (!bytes_written) |
| return Error(bytes_written.error()); |
| if (*bytes_written <= 0) |
| return Error(EIO); // Should not be possible, but check to guard underflow |
| |
| cpp::string_view target(buf, static_cast<size_t>(*bytes_written)); |
| if (target.size() >= sizeof(buf)) |
| return Error(ENAMETOOLONG); |
| |
| return target; |
| } |
| |
| ErrorOr<mode_t> read_file_type(const char *path) { |
| internal::kernel_statx_buf buf; |
| ErrorOr<int> ret = |
| linux_syscalls::statx(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, |
| internal::KERNEL_STATX_TYPE_MASK, &buf); |
| if (!ret) |
| return Error(ret.error()); |
| |
| if (!(buf.stx_mask & internal::KERNEL_STATX_TYPE_MASK)) |
| return Error(EIO); |
| |
| return static_cast<mode_t>(buf.stx_mode); |
| } |
| |
| cpp::optional<Error> resolve_path(PendingPath &pending_path, |
| ResolvedPath &resolved_path) { |
| size_t symlinks_followed = 0; |
| |
| 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; |
| |
| ErrorOr<mode_t> mode = read_file_type(resolved_path.c_str()); |
| if (!mode) |
| return Error(mode.error()); |
| |
| if (S_ISLNK(*mode)) { |
| if (symlinks_followed >= MAX_SYMLINK_FOLLOWS) |
| return Error(ELOOP); |
| symlinks_followed += 1; |
| |
| char buf[PATH_MAX]; |
| ErrorOr<cpp::string_view> target = readlink(resolved_path.c_str(), buf); |
| if (!target) |
| return Error(target.error()); |
| |
| if (cpp::optional<Error> err = pending_path.prepend(*target); err) |
| return err; |
| |
| if (path::is_absolute(*target)) |
| resolved_path.set_to_root(); |
| |
| // Since the last component of resolved_path was a link, remove it. |
| resolved_path.set_to_parent(); |
| continue; |
| } |
| |
| // If the path is not a directory, but there are more directory traversals, |
| // then error. e.g. realpath("/path/to/file.txt/") should give ENOTDIR. |
| if (!S_ISDIR(*mode) && !pending_path.empty()) |
| return Error(ENOTDIR); |
| } |
| |
| 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; |
| if (cpp::optional<Error> err = pending_path.prepend(path); err) |
| return Error(*err); |
| |
| 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 |