//===----------------------------------------------------------------------===// // // 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 // //===----------------------------------------------------------------------===// #include <__config> #include #include #include "error.h" #include "path_parser.h" _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM using detail::ErrorHandler; using parser::createView; using parser::PathParser; using parser::string_view_t; /////////////////////////////////////////////////////////////////////////////// // path definitions /////////////////////////////////////////////////////////////////////////////// constexpr path::value_type path::preferred_separator; path& path::replace_extension(path const& replacement) { path p = extension(); if (not p.empty()) { __pn_.erase(__pn_.size() - p.native().size()); } if (!replacement.empty()) { if (replacement.native()[0] != '.') { __pn_ += PATHSTR("."); } __pn_.append(replacement.__pn_); } return *this; } /////////////////////////////////////////////////////////////////////////////// // path.decompose string_view_t path::__root_name() const { auto PP = PathParser::CreateBegin(__pn_); if (PP.State == PathParser::PS_InRootName) return *PP; return {}; } string_view_t path::__root_directory() const { auto PP = PathParser::CreateBegin(__pn_); if (PP.State == PathParser::PS_InRootName) ++PP; if (PP.State == PathParser::PS_InRootDir) return *PP; return {}; } string_view_t path::__root_path_raw() const { auto PP = PathParser::CreateBegin(__pn_); if (PP.State == PathParser::PS_InRootName) { auto NextCh = PP.peek(); if (NextCh && isSeparator(*NextCh)) { ++PP; return createView(__pn_.data(), &PP.RawEntry.back()); } return PP.RawEntry; } if (PP.State == PathParser::PS_InRootDir) return *PP; return {}; } static bool ConsumeRootName(PathParser *PP) { static_assert(PathParser::PS_BeforeBegin == 1 && PathParser::PS_InRootName == 2, "Values for enums are incorrect"); while (PP->State <= PathParser::PS_InRootName) ++(*PP); return PP->State == PathParser::PS_AtEnd; } static bool ConsumeRootDir(PathParser* PP) { static_assert(PathParser::PS_BeforeBegin == 1 && PathParser::PS_InRootName == 2 && PathParser::PS_InRootDir == 3, "Values for enums are incorrect"); while (PP->State <= PathParser::PS_InRootDir) ++(*PP); return PP->State == PathParser::PS_AtEnd; } string_view_t path::__relative_path() const { auto PP = PathParser::CreateBegin(__pn_); if (ConsumeRootDir(&PP)) return {}; return createView(PP.RawEntry.data(), &__pn_.back()); } string_view_t path::__parent_path() const { if (empty()) return {}; // Determine if we have a root path but not a relative path. In that case // return *this. { auto PP = PathParser::CreateBegin(__pn_); if (ConsumeRootDir(&PP)) return __pn_; } // Otherwise remove a single element from the end of the path, and return // a string representing that path { auto PP = PathParser::CreateEnd(__pn_); --PP; if (PP.RawEntry.data() == __pn_.data()) return {}; --PP; return createView(__pn_.data(), &PP.RawEntry.back()); } } string_view_t path::__filename() const { if (empty()) return {}; { PathParser PP = PathParser::CreateBegin(__pn_); if (ConsumeRootDir(&PP)) return {}; } return *(--PathParser::CreateEnd(__pn_)); } string_view_t path::__stem() const { return parser::separate_filename(__filename()).first; } string_view_t path::__extension() const { return parser::separate_filename(__filename()).second; } //////////////////////////////////////////////////////////////////////////// // path.gen enum PathPartKind : unsigned char { PK_None, PK_RootSep, PK_Filename, PK_Dot, PK_DotDot, PK_TrailingSep }; static PathPartKind ClassifyPathPart(string_view_t Part) { if (Part.empty()) return PK_TrailingSep; if (Part == PATHSTR(".")) return PK_Dot; if (Part == PATHSTR("..")) return PK_DotDot; if (Part == PATHSTR("/")) return PK_RootSep; #if defined(_LIBCPP_WIN32API) if (Part == PATHSTR("\\")) return PK_RootSep; #endif return PK_Filename; } path path::lexically_normal() const { if (__pn_.empty()) return *this; using PartKindPair = pair; vector Parts; // Guess as to how many elements the path has to avoid reallocating. Parts.reserve(32); // Track the total size of the parts as we collect them. This allows the // resulting path to reserve the correct amount of memory. size_t NewPathSize = 0; auto AddPart = [&](PathPartKind K, string_view_t P) { NewPathSize += P.size(); Parts.emplace_back(P, K); }; auto LastPartKind = [&]() { if (Parts.empty()) return PK_None; return Parts.back().second; }; bool MaybeNeedTrailingSep = false; // Build a stack containing the remaining elements of the path, popping off // elements which occur before a '..' entry. for (auto PP = PathParser::CreateBegin(__pn_); PP; ++PP) { auto Part = *PP; PathPartKind Kind = ClassifyPathPart(Part); switch (Kind) { case PK_Filename: case PK_RootSep: { // Add all non-dot and non-dot-dot elements to the stack of elements. AddPart(Kind, Part); MaybeNeedTrailingSep = false; break; } case PK_DotDot: { // Only push a ".." element if there are no elements preceding the "..", // or if the preceding element is itself "..". auto LastKind = LastPartKind(); if (LastKind == PK_Filename) { NewPathSize -= Parts.back().first.size(); Parts.pop_back(); } else if (LastKind != PK_RootSep) AddPart(PK_DotDot, PATHSTR("..")); MaybeNeedTrailingSep = LastKind == PK_Filename; break; } case PK_Dot: case PK_TrailingSep: { MaybeNeedTrailingSep = true; break; } case PK_None: __libcpp_unreachable(); } } // [fs.path.generic]p6.8: If the path is empty, add a dot. if (Parts.empty()) return PATHSTR("."); // [fs.path.generic]p6.7: If the last filename is dot-dot, remove any // trailing directory-separator. bool NeedTrailingSep = MaybeNeedTrailingSep && LastPartKind() == PK_Filename; path Result; Result.__pn_.reserve(Parts.size() + NewPathSize + NeedTrailingSep); for (auto& PK : Parts) Result /= PK.first; if (NeedTrailingSep) Result /= PATHSTR(""); Result.make_preferred(); return Result; } static int DetermineLexicalElementCount(PathParser PP) { int Count = 0; for (; PP; ++PP) { auto Elem = *PP; if (Elem == PATHSTR("..")) --Count; else if (Elem != PATHSTR(".") && Elem != PATHSTR("")) ++Count; } return Count; } path path::lexically_relative(const path& base) const { { // perform root-name/root-directory mismatch checks auto PP = PathParser::CreateBegin(__pn_); auto PPBase = PathParser::CreateBegin(base.__pn_); auto CheckIterMismatchAtBase = [&]() { return PP.State != PPBase.State && (PP.inRootPath() || PPBase.inRootPath()); }; if (PP.inRootName() && PPBase.inRootName()) { if (*PP != *PPBase) return {}; } else if (CheckIterMismatchAtBase()) return {}; if (PP.inRootPath()) ++PP; if (PPBase.inRootPath()) ++PPBase; if (CheckIterMismatchAtBase()) return {}; } // Find the first mismatching element auto PP = PathParser::CreateBegin(__pn_); auto PPBase = PathParser::CreateBegin(base.__pn_); while (PP && PPBase && PP.State == PPBase.State && *PP == *PPBase) { ++PP; ++PPBase; } // If there is no mismatch, return ".". if (!PP && !PPBase) return "."; // Otherwise, determine the number of elements, 'n', which are not dot or // dot-dot minus the number of dot-dot elements. int ElemCount = DetermineLexicalElementCount(PPBase); if (ElemCount < 0) return {}; // if n == 0 and (a == end() || a->empty()), returns path("."); otherwise if (ElemCount == 0 && (PP.atEnd() || *PP == PATHSTR(""))) return PATHSTR("."); // return a path constructed with 'n' dot-dot elements, followed by the // elements of '*this' after the mismatch. path Result; // FIXME: Reserve enough room in Result that it won't have to re-allocate. while (ElemCount--) Result /= PATHSTR(".."); for (; PP; ++PP) Result /= *PP; return Result; } //////////////////////////////////////////////////////////////////////////// // path.comparisons static int CompareRootName(PathParser *LHS, PathParser *RHS) { if (!LHS->inRootName() && !RHS->inRootName()) return 0; auto GetRootName = [](PathParser *Parser) -> string_view_t { return Parser->inRootName() ? **Parser : PATHSTR(""); }; int res = GetRootName(LHS).compare(GetRootName(RHS)); ConsumeRootName(LHS); ConsumeRootName(RHS); return res; } static int CompareRootDir(PathParser *LHS, PathParser *RHS) { if (!LHS->inRootDir() && RHS->inRootDir()) return -1; else if (LHS->inRootDir() && !RHS->inRootDir()) return 1; else { ConsumeRootDir(LHS); ConsumeRootDir(RHS); return 0; } } static int CompareRelative(PathParser *LHSPtr, PathParser *RHSPtr) { auto &LHS = *LHSPtr; auto &RHS = *RHSPtr; int res; while (LHS && RHS) { if ((res = (*LHS).compare(*RHS)) != 0) return res; ++LHS; ++RHS; } return 0; } static int CompareEndState(PathParser *LHS, PathParser *RHS) { if (LHS->atEnd() && !RHS->atEnd()) return -1; else if (!LHS->atEnd() && RHS->atEnd()) return 1; return 0; } int path::__compare(string_view_t __s) const { auto LHS = PathParser::CreateBegin(__pn_); auto RHS = PathParser::CreateBegin(__s); int res; if ((res = CompareRootName(&LHS, &RHS)) != 0) return res; if ((res = CompareRootDir(&LHS, &RHS)) != 0) return res; if ((res = CompareRelative(&LHS, &RHS)) != 0) return res; return CompareEndState(&LHS, &RHS); } //////////////////////////////////////////////////////////////////////////// // path.nonmembers size_t hash_value(const path& __p) noexcept { auto PP = PathParser::CreateBegin(__p.native()); size_t hash_value = 0; hash hasher; while (PP) { hash_value = __hash_combine(hash_value, hasher(*PP)); ++PP; } return hash_value; } //////////////////////////////////////////////////////////////////////////// // path.itr path::iterator path::begin() const { auto PP = PathParser::CreateBegin(__pn_); iterator it; it.__path_ptr_ = this; it.__state_ = static_cast(PP.State); it.__entry_ = PP.RawEntry; it.__stashed_elem_.__assign_view(*PP); return it; } path::iterator path::end() const { iterator it{}; it.__state_ = path::iterator::_AtEnd; it.__path_ptr_ = this; return it; } path::iterator& path::iterator::__increment() { PathParser PP(__path_ptr_->native(), __entry_, __state_); ++PP; __state_ = static_cast<_ParserState>(PP.State); __entry_ = PP.RawEntry; __stashed_elem_.__assign_view(*PP); return *this; } path::iterator& path::iterator::__decrement() { PathParser PP(__path_ptr_->native(), __entry_, __state_); --PP; __state_ = static_cast<_ParserState>(PP.State); __entry_ = PP.RawEntry; __stashed_elem_.__assign_view(*PP); return *this; } #if defined(_LIBCPP_WIN32API) //////////////////////////////////////////////////////////////////////////// // Windows path conversions size_t __wide_to_char(const wstring &str, char *out, size_t outlen) { if (str.empty()) return 0; ErrorHandler err("__wide_to_char", nullptr); UINT codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP; BOOL used_default = FALSE; int ret = WideCharToMultiByte(codepage, 0, str.data(), str.size(), out, outlen, nullptr, &used_default); if (ret <= 0 || used_default) return err.report(errc::illegal_byte_sequence); return ret; } size_t __char_to_wide(const string &str, wchar_t *out, size_t outlen) { if (str.empty()) return 0; ErrorHandler err("__char_to_wide", nullptr); UINT codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP; int ret = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, str.data(), str.size(), out, outlen); if (ret <= 0) return err.report(errc::illegal_byte_sequence); return ret; } #endif _LIBCPP_END_NAMESPACE_FILESYSTEM