448 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include <__config>10#include <filesystem>11#include <vector>12 13#include "error.h"14#include "path_parser.h"15 16_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM17 18using detail::ErrorHandler;19using parser::createView;20using parser::PathParser;21using parser::string_view_t;22 23///////////////////////////////////////////////////////////////////////////////24// path definitions25///////////////////////////////////////////////////////////////////////////////26 27_LIBCPP_DIAGNOSTIC_PUSH28_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wdeprecated")29constexpr path::value_type path::preferred_separator;30_LIBCPP_DIAGNOSTIC_POP31 32path& path::replace_extension(path const& replacement) {33 path p = extension();34 if (not p.empty()) {35 __pn_.erase(__pn_.size() - p.native().size());36 }37 if (!replacement.empty()) {38 if (replacement.native()[0] != '.') {39 __pn_ += PATHSTR(".");40 }41 __pn_.append(replacement.__pn_);42 }43 return *this;44}45 46///////////////////////////////////////////////////////////////////////////////47// path.decompose48 49string_view_t path::__root_name() const {50 auto PP = PathParser::CreateBegin(__pn_);51 if (PP.State_ == PathParser::PS_InRootName)52 return *PP;53 return {};54}55 56string_view_t path::__root_directory() const {57 auto PP = PathParser::CreateBegin(__pn_);58 if (PP.State_ == PathParser::PS_InRootName)59 ++PP;60 if (PP.State_ == PathParser::PS_InRootDir)61 return *PP;62 return {};63}64 65string_view_t path::__root_path_raw() const {66 auto PP = PathParser::CreateBegin(__pn_);67 if (PP.State_ == PathParser::PS_InRootName) {68 auto NextCh = PP.peek();69 if (NextCh && isSeparator(*NextCh)) {70 ++PP;71 return createView(__pn_.data(), &PP.RawEntry.back());72 }73 return PP.RawEntry;74 }75 if (PP.State_ == PathParser::PS_InRootDir)76 return *PP;77 return {};78}79 80static bool ConsumeRootName(PathParser* PP) {81 static_assert(PathParser::PS_BeforeBegin == 1 && PathParser::PS_InRootName == 2, "Values for enums are incorrect");82 while (PP->State_ <= PathParser::PS_InRootName)83 ++(*PP);84 return PP->State_ == PathParser::PS_AtEnd;85}86 87static bool ConsumeRootDir(PathParser* PP) {88 static_assert(PathParser::PS_BeforeBegin == 1 && PathParser::PS_InRootName == 2 && PathParser::PS_InRootDir == 3,89 "Values for enums are incorrect");90 while (PP->State_ <= PathParser::PS_InRootDir)91 ++(*PP);92 return PP->State_ == PathParser::PS_AtEnd;93}94 95string_view_t path::__relative_path() const {96 auto PP = PathParser::CreateBegin(__pn_);97 if (ConsumeRootDir(&PP))98 return {};99 return createView(PP.RawEntry.data(), &__pn_.back());100}101 102string_view_t path::__parent_path() const {103 if (empty())104 return {};105 // Determine if we have a root path but not a relative path. In that case106 // return *this.107 {108 auto PP = PathParser::CreateBegin(__pn_);109 if (ConsumeRootDir(&PP))110 return __pn_;111 }112 // Otherwise remove a single element from the end of the path, and return113 // a string representing that path114 {115 auto PP = PathParser::CreateEnd(__pn_);116 --PP;117 if (PP.RawEntry.data() == __pn_.data())118 return {};119 --PP;120 return createView(__pn_.data(), &PP.RawEntry.back());121 }122}123 124string_view_t path::__filename() const {125 if (empty())126 return {};127 {128 PathParser PP = PathParser::CreateBegin(__pn_);129 if (ConsumeRootDir(&PP))130 return {};131 }132 return *(--PathParser::CreateEnd(__pn_));133}134 135string_view_t path::__stem() const { return parser::separate_filename(__filename()).first; }136 137string_view_t path::__extension() const { return parser::separate_filename(__filename()).second; }138 139////////////////////////////////////////////////////////////////////////////140// path.gen141 142enum PathPartKind : unsigned char { PK_None, PK_RootSep, PK_Filename, PK_Dot, PK_DotDot, PK_TrailingSep };143 144static PathPartKind ClassifyPathPart(string_view_t Part) {145 if (Part.empty())146 return PK_TrailingSep;147 if (Part == PATHSTR("."))148 return PK_Dot;149 if (Part == PATHSTR(".."))150 return PK_DotDot;151 if (Part == PATHSTR("/"))152 return PK_RootSep;153#if defined(_LIBCPP_WIN32API)154 if (Part == PATHSTR("\\"))155 return PK_RootSep;156#endif157 return PK_Filename;158}159 160path path::lexically_normal() const {161 if (__pn_.empty())162 return *this;163 164 using PartKindPair = pair<string_view_t, PathPartKind>;165 vector<PartKindPair> Parts;166 // Guess as to how many elements the path has to avoid reallocating.167 Parts.reserve(32);168 169 // Track the total size of the parts as we collect them. This allows the170 // resulting path to reserve the correct amount of memory.171 size_t NewPathSize = 0;172 auto AddPart = [&](PathPartKind K, string_view_t P) {173 NewPathSize += P.size();174 Parts.emplace_back(P, K);175 };176 auto LastPartKind = [&]() {177 if (Parts.empty())178 return PK_None;179 return Parts.back().second;180 };181 182 bool MaybeNeedTrailingSep = false;183 // Build a stack containing the remaining elements of the path, popping off184 // elements which occur before a '..' entry.185 for (auto PP = PathParser::CreateBegin(__pn_); PP; ++PP) {186 auto Part = *PP;187 PathPartKind Kind = ClassifyPathPart(Part);188 switch (Kind) {189 case PK_Filename:190 case PK_RootSep: {191 // Add all non-dot and non-dot-dot elements to the stack of elements.192 AddPart(Kind, Part);193 MaybeNeedTrailingSep = false;194 break;195 }196 case PK_DotDot: {197 // Only push a ".." element if there are no elements preceding the "..",198 // or if the preceding element is itself "..".199 auto LastKind = LastPartKind();200 if (LastKind == PK_Filename) {201 NewPathSize -= Parts.back().first.size();202 Parts.pop_back();203 } else if (LastKind != PK_RootSep)204 AddPart(PK_DotDot, PATHSTR(".."));205 MaybeNeedTrailingSep = LastKind == PK_Filename;206 break;207 }208 case PK_Dot:209 case PK_TrailingSep: {210 MaybeNeedTrailingSep = true;211 break;212 }213 case PK_None:214 __libcpp_unreachable();215 }216 }217 // [fs.path.generic]p6.8: If the path is empty, add a dot.218 if (Parts.empty())219 return PATHSTR(".");220 221 // [fs.path.generic]p6.7: If the last filename is dot-dot, remove any222 // trailing directory-separator.223 bool NeedTrailingSep = MaybeNeedTrailingSep && LastPartKind() == PK_Filename;224 225 path Result;226 Result.__pn_.reserve(Parts.size() + NewPathSize + NeedTrailingSep);227 for (auto& PK : Parts)228 Result /= PK.first;229 230 if (NeedTrailingSep)231 Result /= PATHSTR("");232 233 Result.make_preferred();234 return Result;235}236 237static int DetermineLexicalElementCount(PathParser PP) {238 int Count = 0;239 for (; PP; ++PP) {240 auto Elem = *PP;241 if (Elem == PATHSTR(".."))242 --Count;243 else if (Elem != PATHSTR(".") && Elem != PATHSTR(""))244 ++Count;245 }246 return Count;247}248 249path path::lexically_relative(const path& base) const {250 { // perform root-name/root-directory mismatch checks251 auto PP = PathParser::CreateBegin(__pn_);252 auto PPBase = PathParser::CreateBegin(base.__pn_);253 auto CheckIterMismatchAtBase = [&]() {254 return PP.State_ != PPBase.State_ && (PP.inRootPath() || PPBase.inRootPath());255 };256 if (PP.inRootName() && PPBase.inRootName()) {257 if (*PP != *PPBase)258 return {};259 } else if (CheckIterMismatchAtBase())260 return {};261 262 if (PP.inRootPath())263 ++PP;264 if (PPBase.inRootPath())265 ++PPBase;266 if (CheckIterMismatchAtBase())267 return {};268 }269 270 // Find the first mismatching element271 auto PP = PathParser::CreateBegin(__pn_);272 auto PPBase = PathParser::CreateBegin(base.__pn_);273 while (PP && PPBase && PP.State_ == PPBase.State_ && (*PP == *PPBase || PP.inRootDir())) {274 ++PP;275 ++PPBase;276 }277 278 // If there is no mismatch, return ".".279 if (!PP && !PPBase)280 return ".";281 282 // Otherwise, determine the number of elements, 'n', which are not dot or283 // dot-dot minus the number of dot-dot elements.284 int ElemCount = DetermineLexicalElementCount(PPBase);285 if (ElemCount < 0)286 return {};287 288 // if n == 0 and (a == end() || a->empty()), returns path("."); otherwise289 if (ElemCount == 0 && (PP.atEnd() || *PP == PATHSTR("")))290 return PATHSTR(".");291 292 // return a path constructed with 'n' dot-dot elements, followed by the293 // elements of '*this' after the mismatch.294 path Result;295 constexpr size_t ElemSize = 2; // ".."296 constexpr size_t SeparatorSize = 1; // separator is always a single char297 Result.__reserve(ElemCount * (ElemSize + SeparatorSize) + SeparatorSize + PP.Path.size());298 while (ElemCount--)299 Result /= PATHSTR("..");300 for (; PP; ++PP)301 Result /= *PP;302 return Result;303}304 305////////////////////////////////////////////////////////////////////////////306// path.comparisons307static int CompareRootName(PathParser* LHS, PathParser* RHS) {308 if (!LHS->inRootName() && !RHS->inRootName())309 return 0;310 311 auto GetRootName = [](PathParser* Parser) -> string_view_t { return Parser->inRootName() ? **Parser : PATHSTR(""); };312 int res = GetRootName(LHS).compare(GetRootName(RHS));313 ConsumeRootName(LHS);314 ConsumeRootName(RHS);315 return res;316}317 318static int CompareRootDir(PathParser* LHS, PathParser* RHS) {319 if (!LHS->inRootDir() && RHS->inRootDir())320 return -1;321 else if (LHS->inRootDir() && !RHS->inRootDir())322 return 1;323 else {324 ConsumeRootDir(LHS);325 ConsumeRootDir(RHS);326 return 0;327 }328}329 330static int CompareRelative(PathParser* LHSPtr, PathParser* RHSPtr) {331 auto& LHS = *LHSPtr;332 auto& RHS = *RHSPtr;333 334 int res;335 while (LHS && RHS) {336 if ((res = (*LHS).compare(*RHS)) != 0)337 return res;338 ++LHS;339 ++RHS;340 }341 return 0;342}343 344static int CompareEndState(PathParser* LHS, PathParser* RHS) {345 if (LHS->atEnd() && !RHS->atEnd())346 return -1;347 else if (!LHS->atEnd() && RHS->atEnd())348 return 1;349 return 0;350}351 352int path::__compare(string_view_t __s) const {353 auto LHS = PathParser::CreateBegin(__pn_);354 auto RHS = PathParser::CreateBegin(__s);355 int res;356 357 if ((res = CompareRootName(&LHS, &RHS)) != 0)358 return res;359 360 if ((res = CompareRootDir(&LHS, &RHS)) != 0)361 return res;362 363 if ((res = CompareRelative(&LHS, &RHS)) != 0)364 return res;365 366 return CompareEndState(&LHS, &RHS);367}368 369////////////////////////////////////////////////////////////////////////////370// path.nonmembers371size_t hash_value(const path& __p) noexcept {372 auto PP = PathParser::CreateBegin(__p.native());373 size_t hash_value = 0;374 hash<string_view_t> hasher;375 while (PP) {376 string_view_t Part = PP.inRootDir() ? PATHSTR("/") : *PP;377 hash_value = __hash_combine(hash_value, hasher(Part));378 ++PP;379 }380 return hash_value;381}382 383////////////////////////////////////////////////////////////////////////////384// path.itr385path::iterator path::begin() const {386 auto PP = PathParser::CreateBegin(__pn_);387 iterator it;388 it.__path_ptr_ = this;389 it.__state_ = static_cast<path::iterator::_ParserState>(PP.State_);390 it.__entry_ = PP.RawEntry;391 it.__stashed_elem_.__assign_view(*PP);392 return it;393}394 395path::iterator path::end() const {396 iterator it{};397 it.__state_ = path::iterator::_AtEnd;398 it.__path_ptr_ = this;399 return it;400}401 402path::iterator& path::iterator::__increment() {403 PathParser PP(__path_ptr_->native(), __entry_, __state_);404 ++PP;405 __state_ = static_cast<_ParserState>(PP.State_);406 __entry_ = PP.RawEntry;407 __stashed_elem_.__assign_view(*PP);408 return *this;409}410 411path::iterator& path::iterator::__decrement() {412 PathParser PP(__path_ptr_->native(), __entry_, __state_);413 --PP;414 __state_ = static_cast<_ParserState>(PP.State_);415 __entry_ = PP.RawEntry;416 __stashed_elem_.__assign_view(*PP);417 return *this;418}419 420#if defined(_LIBCPP_WIN32API)421////////////////////////////////////////////////////////////////////////////422// Windows path conversions423size_t __wide_to_char(const wstring& str, char* out, size_t outlen) {424 if (str.empty())425 return 0;426 ErrorHandler<size_t> err("__wide_to_char", nullptr);427 UINT codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;428 BOOL used_default = FALSE;429 int ret = WideCharToMultiByte(codepage, 0, str.data(), str.size(), out, outlen, nullptr, &used_default);430 if (ret <= 0 || used_default)431 return err.report(errc::illegal_byte_sequence);432 return ret;433}434 435size_t __char_to_wide(const string& str, wchar_t* out, size_t outlen) {436 if (str.empty())437 return 0;438 ErrorHandler<size_t> err("__char_to_wide", nullptr);439 UINT codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;440 int ret = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, str.data(), str.size(), out, outlen);441 if (ret <= 0)442 return err.report(errc::illegal_byte_sequence);443 return ret;444}445#endif446 447_LIBCPP_END_NAMESPACE_FILESYSTEM448