Path: blob/main/system/lib/libcxx/src/filesystem/path.cpp
6175 views
//===----------------------------------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include <__config>9#include <filesystem>10#include <vector>1112#include "error.h"13#include "path_parser.h"1415_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM1617using detail::ErrorHandler;18using parser::createView;19using parser::PathParser;20using parser::string_view_t;2122///////////////////////////////////////////////////////////////////////////////23// path definitions24///////////////////////////////////////////////////////////////////////////////2526_LIBCPP_DIAGNOSTIC_PUSH27_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wdeprecated")28constexpr path::value_type path::preferred_separator;29_LIBCPP_DIAGNOSTIC_POP3031path& path::replace_extension(path const& replacement) {32path p = extension();33if (not p.empty()) {34__pn_.erase(__pn_.size() - p.native().size());35}36if (!replacement.empty()) {37if (replacement.native()[0] != '.') {38__pn_ += PATHSTR(".");39}40__pn_.append(replacement.__pn_);41}42return *this;43}4445///////////////////////////////////////////////////////////////////////////////46// path.decompose4748string_view_t path::__root_name() const {49auto PP = PathParser::CreateBegin(__pn_);50if (PP.State_ == PathParser::PS_InRootName)51return *PP;52return {};53}5455string_view_t path::__root_directory() const {56auto PP = PathParser::CreateBegin(__pn_);57if (PP.State_ == PathParser::PS_InRootName)58++PP;59if (PP.State_ == PathParser::PS_InRootDir)60return *PP;61return {};62}6364string_view_t path::__root_path_raw() const {65auto PP = PathParser::CreateBegin(__pn_);66if (PP.State_ == PathParser::PS_InRootName) {67auto NextCh = PP.peek();68if (NextCh && isSeparator(*NextCh)) {69++PP;70return createView(__pn_.data(), &PP.RawEntry.back());71}72return PP.RawEntry;73}74if (PP.State_ == PathParser::PS_InRootDir)75return *PP;76return {};77}7879static bool ConsumeRootName(PathParser* PP) {80static_assert(PathParser::PS_BeforeBegin == 1 && PathParser::PS_InRootName == 2, "Values for enums are incorrect");81while (PP->State_ <= PathParser::PS_InRootName)82++(*PP);83return PP->State_ == PathParser::PS_AtEnd;84}8586static bool ConsumeRootDir(PathParser* PP) {87static_assert(PathParser::PS_BeforeBegin == 1 && PathParser::PS_InRootName == 2 && PathParser::PS_InRootDir == 3,88"Values for enums are incorrect");89while (PP->State_ <= PathParser::PS_InRootDir)90++(*PP);91return PP->State_ == PathParser::PS_AtEnd;92}9394string_view_t path::__relative_path() const {95auto PP = PathParser::CreateBegin(__pn_);96if (ConsumeRootDir(&PP))97return {};98return createView(PP.RawEntry.data(), &__pn_.back());99}100101string_view_t path::__parent_path() const {102if (empty())103return {};104// Determine if we have a root path but not a relative path. In that case105// return *this.106{107auto PP = PathParser::CreateBegin(__pn_);108if (ConsumeRootDir(&PP))109return __pn_;110}111// Otherwise remove a single element from the end of the path, and return112// a string representing that path113{114auto PP = PathParser::CreateEnd(__pn_);115--PP;116if (PP.RawEntry.data() == __pn_.data())117return {};118--PP;119return createView(__pn_.data(), &PP.RawEntry.back());120}121}122123string_view_t path::__filename() const {124if (empty())125return {};126{127PathParser PP = PathParser::CreateBegin(__pn_);128if (ConsumeRootDir(&PP))129return {};130}131return *(--PathParser::CreateEnd(__pn_));132}133134string_view_t path::__stem() const { return parser::separate_filename(__filename()).first; }135136string_view_t path::__extension() const { return parser::separate_filename(__filename()).second; }137138////////////////////////////////////////////////////////////////////////////139// path.gen140141enum PathPartKind : unsigned char { PK_None, PK_RootSep, PK_Filename, PK_Dot, PK_DotDot, PK_TrailingSep };142143static PathPartKind ClassifyPathPart(string_view_t Part) {144if (Part.empty())145return PK_TrailingSep;146if (Part == PATHSTR("."))147return PK_Dot;148if (Part == PATHSTR(".."))149return PK_DotDot;150if (Part == PATHSTR("/"))151return PK_RootSep;152#if defined(_LIBCPP_WIN32API)153if (Part == PATHSTR("\\"))154return PK_RootSep;155#endif156return PK_Filename;157}158159path path::lexically_normal() const {160if (__pn_.empty())161return *this;162163using PartKindPair = pair<string_view_t, PathPartKind>;164vector<PartKindPair> Parts;165// Guess as to how many elements the path has to avoid reallocating.166Parts.reserve(32);167168// Track the total size of the parts as we collect them. This allows the169// resulting path to reserve the correct amount of memory.170size_t NewPathSize = 0;171auto AddPart = [&](PathPartKind K, string_view_t P) {172NewPathSize += P.size();173Parts.emplace_back(P, K);174};175auto LastPartKind = [&]() {176if (Parts.empty())177return PK_None;178return Parts.back().second;179};180181bool MaybeNeedTrailingSep = false;182// Build a stack containing the remaining elements of the path, popping off183// elements which occur before a '..' entry.184for (auto PP = PathParser::CreateBegin(__pn_); PP; ++PP) {185auto Part = *PP;186PathPartKind Kind = ClassifyPathPart(Part);187switch (Kind) {188case PK_Filename:189case PK_RootSep: {190// Add all non-dot and non-dot-dot elements to the stack of elements.191AddPart(Kind, Part);192MaybeNeedTrailingSep = false;193break;194}195case PK_DotDot: {196// Only push a ".." element if there are no elements preceding the "..",197// or if the preceding element is itself "..".198auto LastKind = LastPartKind();199if (LastKind == PK_Filename) {200NewPathSize -= Parts.back().first.size();201Parts.pop_back();202} else if (LastKind != PK_RootSep)203AddPart(PK_DotDot, PATHSTR(".."));204MaybeNeedTrailingSep = LastKind == PK_Filename;205break;206}207case PK_Dot:208case PK_TrailingSep: {209MaybeNeedTrailingSep = true;210break;211}212case PK_None:213__libcpp_unreachable();214}215}216// [fs.path.generic]p6.8: If the path is empty, add a dot.217if (Parts.empty())218return PATHSTR(".");219220// [fs.path.generic]p6.7: If the last filename is dot-dot, remove any221// trailing directory-separator.222bool NeedTrailingSep = MaybeNeedTrailingSep && LastPartKind() == PK_Filename;223224path Result;225Result.__pn_.reserve(Parts.size() + NewPathSize + NeedTrailingSep);226for (auto& PK : Parts)227Result /= PK.first;228229if (NeedTrailingSep)230Result /= PATHSTR("");231232Result.make_preferred();233return Result;234}235236static int DetermineLexicalElementCount(PathParser PP) {237int Count = 0;238for (; PP; ++PP) {239auto Elem = *PP;240if (Elem == PATHSTR(".."))241--Count;242else if (Elem != PATHSTR(".") && Elem != PATHSTR(""))243++Count;244}245return Count;246}247248path path::lexically_relative(const path& base) const {249{ // perform root-name/root-directory mismatch checks250auto PP = PathParser::CreateBegin(__pn_);251auto PPBase = PathParser::CreateBegin(base.__pn_);252auto CheckIterMismatchAtBase = [&]() {253return PP.State_ != PPBase.State_ && (PP.inRootPath() || PPBase.inRootPath());254};255if (PP.inRootName() && PPBase.inRootName()) {256if (*PP != *PPBase)257return {};258} else if (CheckIterMismatchAtBase())259return {};260261if (PP.inRootPath())262++PP;263if (PPBase.inRootPath())264++PPBase;265if (CheckIterMismatchAtBase())266return {};267}268269// Find the first mismatching element270auto PP = PathParser::CreateBegin(__pn_);271auto PPBase = PathParser::CreateBegin(base.__pn_);272while (PP && PPBase && PP.State_ == PPBase.State_ && (*PP == *PPBase || PP.inRootDir())) {273++PP;274++PPBase;275}276277// If there is no mismatch, return ".".278if (!PP && !PPBase)279return ".";280281// Otherwise, determine the number of elements, 'n', which are not dot or282// dot-dot minus the number of dot-dot elements.283int ElemCount = DetermineLexicalElementCount(PPBase);284if (ElemCount < 0)285return {};286287// if n == 0 and (a == end() || a->empty()), returns path("."); otherwise288if (ElemCount == 0 && (PP.atEnd() || *PP == PATHSTR("")))289return PATHSTR(".");290291// return a path constructed with 'n' dot-dot elements, followed by the292// elements of '*this' after the mismatch.293path Result;294// FIXME: Reserve enough room in Result that it won't have to re-allocate.295while (ElemCount--)296Result /= PATHSTR("..");297for (; PP; ++PP)298Result /= *PP;299return Result;300}301302////////////////////////////////////////////////////////////////////////////303// path.comparisons304static int CompareRootName(PathParser* LHS, PathParser* RHS) {305if (!LHS->inRootName() && !RHS->inRootName())306return 0;307308auto GetRootName = [](PathParser* Parser) -> string_view_t { return Parser->inRootName() ? **Parser : PATHSTR(""); };309int res = GetRootName(LHS).compare(GetRootName(RHS));310ConsumeRootName(LHS);311ConsumeRootName(RHS);312return res;313}314315static int CompareRootDir(PathParser* LHS, PathParser* RHS) {316if (!LHS->inRootDir() && RHS->inRootDir())317return -1;318else if (LHS->inRootDir() && !RHS->inRootDir())319return 1;320else {321ConsumeRootDir(LHS);322ConsumeRootDir(RHS);323return 0;324}325}326327static int CompareRelative(PathParser* LHSPtr, PathParser* RHSPtr) {328auto& LHS = *LHSPtr;329auto& RHS = *RHSPtr;330331int res;332while (LHS && RHS) {333if ((res = (*LHS).compare(*RHS)) != 0)334return res;335++LHS;336++RHS;337}338return 0;339}340341static int CompareEndState(PathParser* LHS, PathParser* RHS) {342if (LHS->atEnd() && !RHS->atEnd())343return -1;344else if (!LHS->atEnd() && RHS->atEnd())345return 1;346return 0;347}348349int path::__compare(string_view_t __s) const {350auto LHS = PathParser::CreateBegin(__pn_);351auto RHS = PathParser::CreateBegin(__s);352int res;353354if ((res = CompareRootName(&LHS, &RHS)) != 0)355return res;356357if ((res = CompareRootDir(&LHS, &RHS)) != 0)358return res;359360if ((res = CompareRelative(&LHS, &RHS)) != 0)361return res;362363return CompareEndState(&LHS, &RHS);364}365366////////////////////////////////////////////////////////////////////////////367// path.nonmembers368size_t hash_value(const path& __p) noexcept {369auto PP = PathParser::CreateBegin(__p.native());370size_t hash_value = 0;371hash<string_view_t> hasher;372while (PP) {373string_view_t Part = PP.inRootDir() ? PATHSTR("/") : *PP;374hash_value = __hash_combine(hash_value, hasher(Part));375++PP;376}377return hash_value;378}379380////////////////////////////////////////////////////////////////////////////381// path.itr382path::iterator path::begin() const {383auto PP = PathParser::CreateBegin(__pn_);384iterator it;385it.__path_ptr_ = this;386it.__state_ = static_cast<path::iterator::_ParserState>(PP.State_);387it.__entry_ = PP.RawEntry;388it.__stashed_elem_.__assign_view(*PP);389return it;390}391392path::iterator path::end() const {393iterator it{};394it.__state_ = path::iterator::_AtEnd;395it.__path_ptr_ = this;396return it;397}398399path::iterator& path::iterator::__increment() {400PathParser PP(__path_ptr_->native(), __entry_, __state_);401++PP;402__state_ = static_cast<_ParserState>(PP.State_);403__entry_ = PP.RawEntry;404__stashed_elem_.__assign_view(*PP);405return *this;406}407408path::iterator& path::iterator::__decrement() {409PathParser PP(__path_ptr_->native(), __entry_, __state_);410--PP;411__state_ = static_cast<_ParserState>(PP.State_);412__entry_ = PP.RawEntry;413__stashed_elem_.__assign_view(*PP);414return *this;415}416417#if defined(_LIBCPP_WIN32API)418////////////////////////////////////////////////////////////////////////////419// Windows path conversions420size_t __wide_to_char(const wstring& str, char* out, size_t outlen) {421if (str.empty())422return 0;423ErrorHandler<size_t> err("__wide_to_char", nullptr);424UINT codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;425BOOL used_default = FALSE;426int ret = WideCharToMultiByte(codepage, 0, str.data(), str.size(), out, outlen, nullptr, &used_default);427if (ret <= 0 || used_default)428return err.report(errc::illegal_byte_sequence);429return ret;430}431432size_t __char_to_wide(const string& str, wchar_t* out, size_t outlen) {433if (str.empty())434return 0;435ErrorHandler<size_t> err("__char_to_wide", nullptr);436UINT codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;437int ret = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, str.data(), str.size(), out, outlen);438if (ret <= 0)439return err.report(errc::illegal_byte_sequence);440return ret;441}442#endif443444_LIBCPP_END_NAMESPACE_FILESYSTEM445446447