Path: blob/main/contrib/llvm-project/libcxx/src/filesystem/path.cpp
35231 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///////////////////////////////////////////////////////////////////////////////2526constexpr path::value_type path::preferred_separator;2728path& path::replace_extension(path const& replacement) {29path p = extension();30if (not p.empty()) {31__pn_.erase(__pn_.size() - p.native().size());32}33if (!replacement.empty()) {34if (replacement.native()[0] != '.') {35__pn_ += PATHSTR(".");36}37__pn_.append(replacement.__pn_);38}39return *this;40}4142///////////////////////////////////////////////////////////////////////////////43// path.decompose4445string_view_t path::__root_name() const {46auto PP = PathParser::CreateBegin(__pn_);47if (PP.State_ == PathParser::PS_InRootName)48return *PP;49return {};50}5152string_view_t path::__root_directory() const {53auto PP = PathParser::CreateBegin(__pn_);54if (PP.State_ == PathParser::PS_InRootName)55++PP;56if (PP.State_ == PathParser::PS_InRootDir)57return *PP;58return {};59}6061string_view_t path::__root_path_raw() const {62auto PP = PathParser::CreateBegin(__pn_);63if (PP.State_ == PathParser::PS_InRootName) {64auto NextCh = PP.peek();65if (NextCh && isSeparator(*NextCh)) {66++PP;67return createView(__pn_.data(), &PP.RawEntry.back());68}69return PP.RawEntry;70}71if (PP.State_ == PathParser::PS_InRootDir)72return *PP;73return {};74}7576static bool ConsumeRootName(PathParser* PP) {77static_assert(PathParser::PS_BeforeBegin == 1 && PathParser::PS_InRootName == 2, "Values for enums are incorrect");78while (PP->State_ <= PathParser::PS_InRootName)79++(*PP);80return PP->State_ == PathParser::PS_AtEnd;81}8283static bool ConsumeRootDir(PathParser* PP) {84static_assert(PathParser::PS_BeforeBegin == 1 && PathParser::PS_InRootName == 2 && PathParser::PS_InRootDir == 3,85"Values for enums are incorrect");86while (PP->State_ <= PathParser::PS_InRootDir)87++(*PP);88return PP->State_ == PathParser::PS_AtEnd;89}9091string_view_t path::__relative_path() const {92auto PP = PathParser::CreateBegin(__pn_);93if (ConsumeRootDir(&PP))94return {};95return createView(PP.RawEntry.data(), &__pn_.back());96}9798string_view_t path::__parent_path() const {99if (empty())100return {};101// Determine if we have a root path but not a relative path. In that case102// return *this.103{104auto PP = PathParser::CreateBegin(__pn_);105if (ConsumeRootDir(&PP))106return __pn_;107}108// Otherwise remove a single element from the end of the path, and return109// a string representing that path110{111auto PP = PathParser::CreateEnd(__pn_);112--PP;113if (PP.RawEntry.data() == __pn_.data())114return {};115--PP;116return createView(__pn_.data(), &PP.RawEntry.back());117}118}119120string_view_t path::__filename() const {121if (empty())122return {};123{124PathParser PP = PathParser::CreateBegin(__pn_);125if (ConsumeRootDir(&PP))126return {};127}128return *(--PathParser::CreateEnd(__pn_));129}130131string_view_t path::__stem() const { return parser::separate_filename(__filename()).first; }132133string_view_t path::__extension() const { return parser::separate_filename(__filename()).second; }134135////////////////////////////////////////////////////////////////////////////136// path.gen137138enum PathPartKind : unsigned char { PK_None, PK_RootSep, PK_Filename, PK_Dot, PK_DotDot, PK_TrailingSep };139140static PathPartKind ClassifyPathPart(string_view_t Part) {141if (Part.empty())142return PK_TrailingSep;143if (Part == PATHSTR("."))144return PK_Dot;145if (Part == PATHSTR(".."))146return PK_DotDot;147if (Part == PATHSTR("/"))148return PK_RootSep;149#if defined(_LIBCPP_WIN32API)150if (Part == PATHSTR("\\"))151return PK_RootSep;152#endif153return PK_Filename;154}155156path path::lexically_normal() const {157if (__pn_.empty())158return *this;159160using PartKindPair = pair<string_view_t, PathPartKind>;161vector<PartKindPair> Parts;162// Guess as to how many elements the path has to avoid reallocating.163Parts.reserve(32);164165// Track the total size of the parts as we collect them. This allows the166// resulting path to reserve the correct amount of memory.167size_t NewPathSize = 0;168auto AddPart = [&](PathPartKind K, string_view_t P) {169NewPathSize += P.size();170Parts.emplace_back(P, K);171};172auto LastPartKind = [&]() {173if (Parts.empty())174return PK_None;175return Parts.back().second;176};177178bool MaybeNeedTrailingSep = false;179// Build a stack containing the remaining elements of the path, popping off180// elements which occur before a '..' entry.181for (auto PP = PathParser::CreateBegin(__pn_); PP; ++PP) {182auto Part = *PP;183PathPartKind Kind = ClassifyPathPart(Part);184switch (Kind) {185case PK_Filename:186case PK_RootSep: {187// Add all non-dot and non-dot-dot elements to the stack of elements.188AddPart(Kind, Part);189MaybeNeedTrailingSep = false;190break;191}192case PK_DotDot: {193// Only push a ".." element if there are no elements preceding the "..",194// or if the preceding element is itself "..".195auto LastKind = LastPartKind();196if (LastKind == PK_Filename) {197NewPathSize -= Parts.back().first.size();198Parts.pop_back();199} else if (LastKind != PK_RootSep)200AddPart(PK_DotDot, PATHSTR(".."));201MaybeNeedTrailingSep = LastKind == PK_Filename;202break;203}204case PK_Dot:205case PK_TrailingSep: {206MaybeNeedTrailingSep = true;207break;208}209case PK_None:210__libcpp_unreachable();211}212}213// [fs.path.generic]p6.8: If the path is empty, add a dot.214if (Parts.empty())215return PATHSTR(".");216217// [fs.path.generic]p6.7: If the last filename is dot-dot, remove any218// trailing directory-separator.219bool NeedTrailingSep = MaybeNeedTrailingSep && LastPartKind() == PK_Filename;220221path Result;222Result.__pn_.reserve(Parts.size() + NewPathSize + NeedTrailingSep);223for (auto& PK : Parts)224Result /= PK.first;225226if (NeedTrailingSep)227Result /= PATHSTR("");228229Result.make_preferred();230return Result;231}232233static int DetermineLexicalElementCount(PathParser PP) {234int Count = 0;235for (; PP; ++PP) {236auto Elem = *PP;237if (Elem == PATHSTR(".."))238--Count;239else if (Elem != PATHSTR(".") && Elem != PATHSTR(""))240++Count;241}242return Count;243}244245path path::lexically_relative(const path& base) const {246{ // perform root-name/root-directory mismatch checks247auto PP = PathParser::CreateBegin(__pn_);248auto PPBase = PathParser::CreateBegin(base.__pn_);249auto CheckIterMismatchAtBase = [&]() {250return PP.State_ != PPBase.State_ && (PP.inRootPath() || PPBase.inRootPath());251};252if (PP.inRootName() && PPBase.inRootName()) {253if (*PP != *PPBase)254return {};255} else if (CheckIterMismatchAtBase())256return {};257258if (PP.inRootPath())259++PP;260if (PPBase.inRootPath())261++PPBase;262if (CheckIterMismatchAtBase())263return {};264}265266// Find the first mismatching element267auto PP = PathParser::CreateBegin(__pn_);268auto PPBase = PathParser::CreateBegin(base.__pn_);269while (PP && PPBase && PP.State_ == PPBase.State_ && *PP == *PPBase) {270++PP;271++PPBase;272}273274// If there is no mismatch, return ".".275if (!PP && !PPBase)276return ".";277278// Otherwise, determine the number of elements, 'n', which are not dot or279// dot-dot minus the number of dot-dot elements.280int ElemCount = DetermineLexicalElementCount(PPBase);281if (ElemCount < 0)282return {};283284// if n == 0 and (a == end() || a->empty()), returns path("."); otherwise285if (ElemCount == 0 && (PP.atEnd() || *PP == PATHSTR("")))286return PATHSTR(".");287288// return a path constructed with 'n' dot-dot elements, followed by the289// elements of '*this' after the mismatch.290path Result;291// FIXME: Reserve enough room in Result that it won't have to re-allocate.292while (ElemCount--)293Result /= PATHSTR("..");294for (; PP; ++PP)295Result /= *PP;296return Result;297}298299////////////////////////////////////////////////////////////////////////////300// path.comparisons301static int CompareRootName(PathParser* LHS, PathParser* RHS) {302if (!LHS->inRootName() && !RHS->inRootName())303return 0;304305auto GetRootName = [](PathParser* Parser) -> string_view_t { return Parser->inRootName() ? **Parser : PATHSTR(""); };306int res = GetRootName(LHS).compare(GetRootName(RHS));307ConsumeRootName(LHS);308ConsumeRootName(RHS);309return res;310}311312static int CompareRootDir(PathParser* LHS, PathParser* RHS) {313if (!LHS->inRootDir() && RHS->inRootDir())314return -1;315else if (LHS->inRootDir() && !RHS->inRootDir())316return 1;317else {318ConsumeRootDir(LHS);319ConsumeRootDir(RHS);320return 0;321}322}323324static int CompareRelative(PathParser* LHSPtr, PathParser* RHSPtr) {325auto& LHS = *LHSPtr;326auto& RHS = *RHSPtr;327328int res;329while (LHS && RHS) {330if ((res = (*LHS).compare(*RHS)) != 0)331return res;332++LHS;333++RHS;334}335return 0;336}337338static int CompareEndState(PathParser* LHS, PathParser* RHS) {339if (LHS->atEnd() && !RHS->atEnd())340return -1;341else if (!LHS->atEnd() && RHS->atEnd())342return 1;343return 0;344}345346int path::__compare(string_view_t __s) const {347auto LHS = PathParser::CreateBegin(__pn_);348auto RHS = PathParser::CreateBegin(__s);349int res;350351if ((res = CompareRootName(&LHS, &RHS)) != 0)352return res;353354if ((res = CompareRootDir(&LHS, &RHS)) != 0)355return res;356357if ((res = CompareRelative(&LHS, &RHS)) != 0)358return res;359360return CompareEndState(&LHS, &RHS);361}362363////////////////////////////////////////////////////////////////////////////364// path.nonmembers365size_t hash_value(const path& __p) noexcept {366auto PP = PathParser::CreateBegin(__p.native());367size_t hash_value = 0;368hash<string_view_t> hasher;369while (PP) {370hash_value = __hash_combine(hash_value, hasher(*PP));371++PP;372}373return hash_value;374}375376////////////////////////////////////////////////////////////////////////////377// path.itr378path::iterator path::begin() const {379auto PP = PathParser::CreateBegin(__pn_);380iterator it;381it.__path_ptr_ = this;382it.__state_ = static_cast<path::iterator::_ParserState>(PP.State_);383it.__entry_ = PP.RawEntry;384it.__stashed_elem_.__assign_view(*PP);385return it;386}387388path::iterator path::end() const {389iterator it{};390it.__state_ = path::iterator::_AtEnd;391it.__path_ptr_ = this;392return it;393}394395path::iterator& path::iterator::__increment() {396PathParser PP(__path_ptr_->native(), __entry_, __state_);397++PP;398__state_ = static_cast<_ParserState>(PP.State_);399__entry_ = PP.RawEntry;400__stashed_elem_.__assign_view(*PP);401return *this;402}403404path::iterator& path::iterator::__decrement() {405PathParser PP(__path_ptr_->native(), __entry_, __state_);406--PP;407__state_ = static_cast<_ParserState>(PP.State_);408__entry_ = PP.RawEntry;409__stashed_elem_.__assign_view(*PP);410return *this;411}412413#if defined(_LIBCPP_WIN32API)414////////////////////////////////////////////////////////////////////////////415// Windows path conversions416size_t __wide_to_char(const wstring& str, char* out, size_t outlen) {417if (str.empty())418return 0;419ErrorHandler<size_t> err("__wide_to_char", nullptr);420UINT codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;421BOOL used_default = FALSE;422int ret = WideCharToMultiByte(codepage, 0, str.data(), str.size(), out, outlen, nullptr, &used_default);423if (ret <= 0 || used_default)424return err.report(errc::illegal_byte_sequence);425return ret;426}427428size_t __char_to_wide(const string& str, wchar_t* out, size_t outlen) {429if (str.empty())430return 0;431ErrorHandler<size_t> err("__char_to_wide", nullptr);432UINT codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;433int ret = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, str.data(), str.size(), out, outlen);434if (ret <= 0)435return err.report(errc::illegal_byte_sequence);436return ret;437}438#endif439440_LIBCPP_END_NAMESPACE_FILESYSTEM441442443