2988 lines · cpp
1//===- VirtualFileSystem.cpp - Virtual File System Layer ------------------===//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// This file implements the VirtualFileSystem interface.10//11//===----------------------------------------------------------------------===//12 13#include "llvm/Support/VirtualFileSystem.h"14#include "llvm/ADT/ArrayRef.h"15#include "llvm/ADT/DenseMap.h"16#include "llvm/ADT/IntrusiveRefCntPtr.h"17#include "llvm/ADT/STLExtras.h"18#include "llvm/ADT/SmallString.h"19#include "llvm/ADT/SmallVector.h"20#include "llvm/ADT/StringRef.h"21#include "llvm/ADT/StringSet.h"22#include "llvm/ADT/Twine.h"23#include "llvm/ADT/iterator_range.h"24#include "llvm/Config/llvm-config.h"25#include "llvm/Support/Casting.h"26#include "llvm/Support/Chrono.h"27#include "llvm/Support/Compiler.h"28#include "llvm/Support/Debug.h"29#include "llvm/Support/Errc.h"30#include "llvm/Support/ErrorHandling.h"31#include "llvm/Support/ErrorOr.h"32#include "llvm/Support/FileSystem.h"33#include "llvm/Support/FileSystem/UniqueID.h"34#include "llvm/Support/MemoryBuffer.h"35#include "llvm/Support/Path.h"36#include "llvm/Support/SMLoc.h"37#include "llvm/Support/SourceMgr.h"38#include "llvm/Support/YAMLParser.h"39#include "llvm/Support/raw_ostream.h"40#include <atomic>41#include <cassert>42#include <cstdint>43#include <iterator>44#include <limits>45#include <map>46#include <memory>47#include <optional>48#include <string>49#include <system_error>50#include <utility>51#include <vector>52 53using namespace llvm;54using namespace llvm::vfs;55 56using llvm::sys::fs::file_t;57using llvm::sys::fs::file_status;58using llvm::sys::fs::file_type;59using llvm::sys::fs::kInvalidFile;60using llvm::sys::fs::perms;61using llvm::sys::fs::UniqueID;62 63Status::Status(const file_status &Status)64 : UID(Status.getUniqueID()), MTime(Status.getLastModificationTime()),65 User(Status.getUser()), Group(Status.getGroup()), Size(Status.getSize()),66 Type(Status.type()), Perms(Status.permissions()) {}67 68Status::Status(const Twine &Name, UniqueID UID, sys::TimePoint<> MTime,69 uint32_t User, uint32_t Group, uint64_t Size, file_type Type,70 perms Perms)71 : Name(Name.str()), UID(UID), MTime(MTime), User(User), Group(Group),72 Size(Size), Type(Type), Perms(Perms) {}73 74Status Status::copyWithNewSize(const Status &In, uint64_t NewSize) {75 return Status(In.getName(), In.getUniqueID(), In.getLastModificationTime(),76 In.getUser(), In.getGroup(), NewSize, In.getType(),77 In.getPermissions());78}79 80Status Status::copyWithNewName(const Status &In, const Twine &NewName) {81 return Status(NewName, In.getUniqueID(), In.getLastModificationTime(),82 In.getUser(), In.getGroup(), In.getSize(), In.getType(),83 In.getPermissions());84}85 86Status Status::copyWithNewName(const file_status &In, const Twine &NewName) {87 return Status(NewName, In.getUniqueID(), In.getLastModificationTime(),88 In.getUser(), In.getGroup(), In.getSize(), In.type(),89 In.permissions());90}91 92bool Status::equivalent(const Status &Other) const {93 assert(isStatusKnown() && Other.isStatusKnown());94 return getUniqueID() == Other.getUniqueID();95}96 97bool Status::isDirectory() const { return Type == file_type::directory_file; }98 99bool Status::isRegularFile() const { return Type == file_type::regular_file; }100 101bool Status::isOther() const {102 return exists() && !isRegularFile() && !isDirectory() && !isSymlink();103}104 105bool Status::isSymlink() const { return Type == file_type::symlink_file; }106 107bool Status::isStatusKnown() const { return Type != file_type::status_error; }108 109bool Status::exists() const {110 return isStatusKnown() && Type != file_type::file_not_found;111}112 113File::~File() = default;114 115FileSystem::~FileSystem() = default;116 117ErrorOr<std::unique_ptr<MemoryBuffer>>118FileSystem::getBufferForFile(const llvm::Twine &Name, int64_t FileSize,119 bool RequiresNullTerminator, bool IsVolatile,120 bool IsText) {121 auto F = IsText ? openFileForRead(Name) : openFileForReadBinary(Name);122 if (!F)123 return F.getError();124 125 return (*F)->getBuffer(Name, FileSize, RequiresNullTerminator, IsVolatile);126}127 128std::error_code FileSystem::makeAbsolute(SmallVectorImpl<char> &Path) const {129 if (llvm::sys::path::is_absolute(Path))130 return {};131 132 auto WorkingDir = getCurrentWorkingDirectory();133 if (!WorkingDir)134 return WorkingDir.getError();135 136 sys::path::make_absolute(WorkingDir.get(), Path);137 return {};138}139 140std::error_code FileSystem::getRealPath(const Twine &Path,141 SmallVectorImpl<char> &Output) {142 return errc::operation_not_permitted;143}144 145std::error_code FileSystem::isLocal(const Twine &Path, bool &Result) {146 return errc::operation_not_permitted;147}148 149bool FileSystem::exists(const Twine &Path) {150 auto Status = status(Path);151 return Status && Status->exists();152}153 154llvm::ErrorOr<bool> FileSystem::equivalent(const Twine &A, const Twine &B) {155 auto StatusA = status(A);156 if (!StatusA)157 return StatusA.getError();158 auto StatusB = status(B);159 if (!StatusB)160 return StatusB.getError();161 return StatusA->equivalent(*StatusB);162}163 164#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)165void FileSystem::dump() const { print(dbgs(), PrintType::RecursiveContents); }166#endif167 168#ifndef NDEBUG169static bool isTraversalComponent(StringRef Component) {170 return Component == ".." || Component == ".";171}172 173static bool pathHasTraversal(StringRef Path) {174 using namespace llvm::sys;175 176 for (StringRef Comp : llvm::make_range(path::begin(Path), path::end(Path)))177 if (isTraversalComponent(Comp))178 return true;179 return false;180}181#endif182 183//===-----------------------------------------------------------------------===/184// RealFileSystem implementation185//===-----------------------------------------------------------------------===/186 187namespace {188 189/// Wrapper around a raw file descriptor.190class RealFile : public File {191 friend class RealFileSystem;192 193 file_t FD;194 Status S;195 std::string RealName;196 197 RealFile(file_t RawFD, StringRef NewName, StringRef NewRealPathName)198 : FD(RawFD), S(NewName, {}, {}, {}, {}, {},199 llvm::sys::fs::file_type::status_error, {}),200 RealName(NewRealPathName.str()) {201 assert(FD != kInvalidFile && "Invalid or inactive file descriptor");202 }203 204public:205 ~RealFile() override;206 207 ErrorOr<Status> status() override;208 ErrorOr<std::string> getName() override;209 ErrorOr<std::unique_ptr<MemoryBuffer>> getBuffer(const Twine &Name,210 int64_t FileSize,211 bool RequiresNullTerminator,212 bool IsVolatile) override;213 std::error_code close() override;214 void setPath(const Twine &Path) override;215};216 217} // namespace218 219RealFile::~RealFile() { close(); }220 221ErrorOr<Status> RealFile::status() {222 assert(FD != kInvalidFile && "cannot stat closed file");223 if (!S.isStatusKnown()) {224 file_status RealStatus;225 if (std::error_code EC = sys::fs::status(FD, RealStatus))226 return EC;227 S = Status::copyWithNewName(RealStatus, S.getName());228 }229 return S;230}231 232ErrorOr<std::string> RealFile::getName() {233 return RealName.empty() ? S.getName().str() : RealName;234}235 236ErrorOr<std::unique_ptr<MemoryBuffer>>237RealFile::getBuffer(const Twine &Name, int64_t FileSize,238 bool RequiresNullTerminator, bool IsVolatile) {239 assert(FD != kInvalidFile && "cannot get buffer for closed file");240 return MemoryBuffer::getOpenFile(FD, Name, FileSize, RequiresNullTerminator,241 IsVolatile);242}243 244std::error_code RealFile::close() {245 std::error_code EC = sys::fs::closeFile(FD);246 FD = kInvalidFile;247 return EC;248}249 250void RealFile::setPath(const Twine &Path) {251 RealName = Path.str();252 if (auto Status = status())253 S = Status.get().copyWithNewName(Status.get(), Path);254}255 256namespace {257 258/// A file system according to your operating system.259/// This may be linked to the process's working directory, or maintain its own.260///261/// Currently, its own working directory is emulated by storing the path and262/// sending absolute paths to llvm::sys::fs:: functions.263/// A more principled approach would be to push this down a level, modelling264/// the working dir as an llvm::sys::fs::WorkingDir or similar.265/// This would enable the use of openat()-style functions on some platforms.266class RealFileSystem : public FileSystem {267public:268 explicit RealFileSystem(bool LinkCWDToProcess) {269 if (!LinkCWDToProcess) {270 SmallString<128> PWD, RealPWD;271 if (std::error_code EC = llvm::sys::fs::current_path(PWD))272 WD = EC;273 else if (llvm::sys::fs::real_path(PWD, RealPWD))274 WD = WorkingDirectory{PWD, PWD};275 else276 WD = WorkingDirectory{PWD, RealPWD};277 }278 }279 280 ErrorOr<Status> status(const Twine &Path) override;281 ErrorOr<std::unique_ptr<File>> openFileForRead(const Twine &Path) override;282 ErrorOr<std::unique_ptr<File>>283 openFileForReadBinary(const Twine &Path) override;284 directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override;285 286 llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override;287 std::error_code setCurrentWorkingDirectory(const Twine &Path) override;288 std::error_code isLocal(const Twine &Path, bool &Result) override;289 std::error_code getRealPath(const Twine &Path,290 SmallVectorImpl<char> &Output) override;291 292protected:293 void printImpl(raw_ostream &OS, PrintType Type,294 unsigned IndentLevel) const override;295 296private:297 // If this FS has its own working dir, use it to make Path absolute.298 // The returned twine is safe to use as long as both Storage and Path live.299 Twine adjustPath(const Twine &Path, SmallVectorImpl<char> &Storage) const {300 if (!WD || !*WD)301 return Path;302 Path.toVector(Storage);303 sys::path::make_absolute(WD->get().Resolved, Storage);304 return Storage;305 }306 307 ErrorOr<std::unique_ptr<File>>308 openFileForReadWithFlags(const Twine &Name, sys::fs::OpenFlags Flags) {309 SmallString<256> RealName, Storage;310 Expected<file_t> FDOrErr = sys::fs::openNativeFileForRead(311 adjustPath(Name, Storage), Flags, &RealName);312 if (!FDOrErr)313 return errorToErrorCode(FDOrErr.takeError());314 return std::unique_ptr<File>(315 new RealFile(*FDOrErr, Name.str(), RealName.str()));316 }317 318 struct WorkingDirectory {319 // The current working directory, without symlinks resolved. (echo $PWD).320 SmallString<128> Specified;321 // The current working directory, with links resolved. (readlink .).322 SmallString<128> Resolved;323 };324 std::optional<llvm::ErrorOr<WorkingDirectory>> WD;325};326 327} // namespace328 329ErrorOr<Status> RealFileSystem::status(const Twine &Path) {330 SmallString<256> Storage;331 sys::fs::file_status RealStatus;332 if (std::error_code EC =333 sys::fs::status(adjustPath(Path, Storage), RealStatus))334 return EC;335 return Status::copyWithNewName(RealStatus, Path);336}337 338ErrorOr<std::unique_ptr<File>>339RealFileSystem::openFileForRead(const Twine &Name) {340 return openFileForReadWithFlags(Name, sys::fs::OF_Text);341}342 343ErrorOr<std::unique_ptr<File>>344RealFileSystem::openFileForReadBinary(const Twine &Name) {345 return openFileForReadWithFlags(Name, sys::fs::OF_None);346}347 348llvm::ErrorOr<std::string> RealFileSystem::getCurrentWorkingDirectory() const {349 if (WD && *WD)350 return std::string(WD->get().Specified);351 if (WD)352 return WD->getError();353 354 SmallString<128> Dir;355 if (std::error_code EC = llvm::sys::fs::current_path(Dir))356 return EC;357 return std::string(Dir);358}359 360std::error_code RealFileSystem::setCurrentWorkingDirectory(const Twine &Path) {361 if (!WD)362 return llvm::sys::fs::set_current_path(Path);363 364 SmallString<128> Absolute, Resolved, Storage;365 adjustPath(Path, Storage).toVector(Absolute);366 bool IsDir;367 if (auto Err = llvm::sys::fs::is_directory(Absolute, IsDir))368 return Err;369 if (!IsDir)370 return std::make_error_code(std::errc::not_a_directory);371 if (auto Err = llvm::sys::fs::real_path(Absolute, Resolved))372 return Err;373 WD = WorkingDirectory{Absolute, Resolved};374 return std::error_code();375}376 377std::error_code RealFileSystem::isLocal(const Twine &Path, bool &Result) {378 SmallString<256> Storage;379 return llvm::sys::fs::is_local(adjustPath(Path, Storage), Result);380}381 382std::error_code RealFileSystem::getRealPath(const Twine &Path,383 SmallVectorImpl<char> &Output) {384 SmallString<256> Storage;385 return llvm::sys::fs::real_path(adjustPath(Path, Storage), Output);386}387 388void RealFileSystem::printImpl(raw_ostream &OS, PrintType Type,389 unsigned IndentLevel) const {390 printIndent(OS, IndentLevel);391 OS << "RealFileSystem using ";392 if (WD)393 OS << "own";394 else395 OS << "process";396 OS << " CWD\n";397}398 399IntrusiveRefCntPtr<FileSystem> vfs::getRealFileSystem() {400 static IntrusiveRefCntPtr<FileSystem> FS =401 makeIntrusiveRefCnt<RealFileSystem>(true);402 return FS;403}404 405std::unique_ptr<FileSystem> vfs::createPhysicalFileSystem() {406 return std::make_unique<RealFileSystem>(false);407}408 409namespace {410 411class RealFSDirIter : public llvm::vfs::detail::DirIterImpl {412 llvm::sys::fs::directory_iterator Iter;413 414public:415 RealFSDirIter(const Twine &Path, std::error_code &EC) : Iter(Path, EC) {416 if (Iter != llvm::sys::fs::directory_iterator())417 CurrentEntry = directory_entry(Iter->path(), Iter->type());418 }419 420 std::error_code increment() override {421 std::error_code EC;422 Iter.increment(EC);423 CurrentEntry = (Iter == llvm::sys::fs::directory_iterator())424 ? directory_entry()425 : directory_entry(Iter->path(), Iter->type());426 return EC;427 }428};429 430} // namespace431 432directory_iterator RealFileSystem::dir_begin(const Twine &Dir,433 std::error_code &EC) {434 SmallString<128> Storage;435 return directory_iterator(436 std::make_shared<RealFSDirIter>(adjustPath(Dir, Storage), EC));437}438 439//===-----------------------------------------------------------------------===/440// OverlayFileSystem implementation441//===-----------------------------------------------------------------------===/442 443OverlayFileSystem::OverlayFileSystem(IntrusiveRefCntPtr<FileSystem> BaseFS) {444 FSList.push_back(std::move(BaseFS));445}446 447void OverlayFileSystem::pushOverlay(IntrusiveRefCntPtr<FileSystem> FS) {448 FSList.push_back(FS);449 // Synchronize added file systems by duplicating the working directory from450 // the first one in the list.451 FS->setCurrentWorkingDirectory(getCurrentWorkingDirectory().get());452}453 454ErrorOr<Status> OverlayFileSystem::status(const Twine &Path) {455 // FIXME: handle symlinks that cross file systems456 for (iterator I = overlays_begin(), E = overlays_end(); I != E; ++I) {457 ErrorOr<Status> Status = (*I)->status(Path);458 if (Status || Status.getError() != llvm::errc::no_such_file_or_directory)459 return Status;460 }461 return make_error_code(llvm::errc::no_such_file_or_directory);462}463 464bool OverlayFileSystem::exists(const Twine &Path) {465 // FIXME: handle symlinks that cross file systems466 for (iterator I = overlays_begin(), E = overlays_end(); I != E; ++I) {467 if ((*I)->exists(Path))468 return true;469 }470 return false;471}472 473ErrorOr<std::unique_ptr<File>>474OverlayFileSystem::openFileForRead(const llvm::Twine &Path) {475 // FIXME: handle symlinks that cross file systems476 for (iterator I = overlays_begin(), E = overlays_end(); I != E; ++I) {477 auto Result = (*I)->openFileForRead(Path);478 if (Result || Result.getError() != llvm::errc::no_such_file_or_directory)479 return Result;480 }481 return make_error_code(llvm::errc::no_such_file_or_directory);482}483 484llvm::ErrorOr<std::string>485OverlayFileSystem::getCurrentWorkingDirectory() const {486 // All file systems are synchronized, just take the first working directory.487 return FSList.front()->getCurrentWorkingDirectory();488}489 490std::error_code491OverlayFileSystem::setCurrentWorkingDirectory(const Twine &Path) {492 for (auto &FS : FSList)493 if (std::error_code EC = FS->setCurrentWorkingDirectory(Path))494 return EC;495 return {};496}497 498std::error_code OverlayFileSystem::isLocal(const Twine &Path, bool &Result) {499 for (auto &FS : FSList)500 if (FS->exists(Path))501 return FS->isLocal(Path, Result);502 return errc::no_such_file_or_directory;503}504 505std::error_code OverlayFileSystem::getRealPath(const Twine &Path,506 SmallVectorImpl<char> &Output) {507 for (const auto &FS : FSList)508 if (FS->exists(Path))509 return FS->getRealPath(Path, Output);510 return errc::no_such_file_or_directory;511}512 513void OverlayFileSystem::visitChildFileSystems(VisitCallbackTy Callback) {514 for (IntrusiveRefCntPtr<FileSystem> FS : overlays_range()) {515 Callback(*FS);516 FS->visitChildFileSystems(Callback);517 }518}519 520void OverlayFileSystem::printImpl(raw_ostream &OS, PrintType Type,521 unsigned IndentLevel) const {522 printIndent(OS, IndentLevel);523 OS << "OverlayFileSystem\n";524 if (Type == PrintType::Summary)525 return;526 527 if (Type == PrintType::Contents)528 Type = PrintType::Summary;529 for (const auto &FS : overlays_range())530 FS->print(OS, Type, IndentLevel + 1);531}532 533llvm::vfs::detail::DirIterImpl::~DirIterImpl() = default;534 535namespace {536 537/// Combines and deduplicates directory entries across multiple file systems.538class CombiningDirIterImpl : public llvm::vfs::detail::DirIterImpl {539 using FileSystemPtr = llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>;540 541 /// Iterators to combine, processed in reverse order.542 SmallVector<directory_iterator, 8> IterList;543 /// The iterator currently being traversed.544 directory_iterator CurrentDirIter;545 /// The set of names already returned as entries.546 llvm::StringSet<> SeenNames;547 548 /// Sets \c CurrentDirIter to the next iterator in the list, or leaves it as549 /// is (at its end position) if we've already gone through them all.550 std::error_code incrementIter(bool IsFirstTime) {551 while (!IterList.empty()) {552 CurrentDirIter = IterList.back();553 IterList.pop_back();554 if (CurrentDirIter != directory_iterator())555 break; // found556 }557 558 if (IsFirstTime && CurrentDirIter == directory_iterator())559 return errc::no_such_file_or_directory;560 return {};561 }562 563 std::error_code incrementDirIter(bool IsFirstTime) {564 assert((IsFirstTime || CurrentDirIter != directory_iterator()) &&565 "incrementing past end");566 std::error_code EC;567 if (!IsFirstTime)568 CurrentDirIter.increment(EC);569 if (!EC && CurrentDirIter == directory_iterator())570 EC = incrementIter(IsFirstTime);571 return EC;572 }573 574 std::error_code incrementImpl(bool IsFirstTime) {575 while (true) {576 std::error_code EC = incrementDirIter(IsFirstTime);577 if (EC || CurrentDirIter == directory_iterator()) {578 CurrentEntry = directory_entry();579 return EC;580 }581 CurrentEntry = *CurrentDirIter;582 StringRef Name = llvm::sys::path::filename(CurrentEntry.path());583 if (SeenNames.insert(Name).second)584 return EC; // name not seen before585 }586 llvm_unreachable("returned above");587 }588 589public:590 CombiningDirIterImpl(ArrayRef<FileSystemPtr> FileSystems, std::string Dir,591 std::error_code &EC) {592 for (const auto &FS : FileSystems) {593 std::error_code FEC;594 directory_iterator Iter = FS->dir_begin(Dir, FEC);595 if (FEC && FEC != errc::no_such_file_or_directory) {596 EC = FEC;597 return;598 }599 if (!FEC)600 IterList.push_back(Iter);601 }602 EC = incrementImpl(true);603 }604 605 CombiningDirIterImpl(ArrayRef<directory_iterator> DirIters,606 std::error_code &EC)607 : IterList(DirIters) {608 EC = incrementImpl(true);609 }610 611 std::error_code increment() override { return incrementImpl(false); }612};613 614} // namespace615 616directory_iterator OverlayFileSystem::dir_begin(const Twine &Dir,617 std::error_code &EC) {618 directory_iterator Combined = directory_iterator(619 std::make_shared<CombiningDirIterImpl>(FSList, Dir.str(), EC));620 if (EC)621 return {};622 return Combined;623}624 625void ProxyFileSystem::anchor() {}626 627namespace llvm {628namespace vfs {629 630namespace detail {631 632enum InMemoryNodeKind {633 IME_File,634 IME_Directory,635 IME_HardLink,636 IME_SymbolicLink,637};638 639/// The in memory file system is a tree of Nodes. Every node can either be a640/// file, symlink, hardlink or a directory.641class InMemoryNode {642 InMemoryNodeKind Kind;643 std::string FileName;644 645public:646 InMemoryNode(llvm::StringRef FileName, InMemoryNodeKind Kind)647 : Kind(Kind), FileName(std::string(llvm::sys::path::filename(FileName))) {648 }649 virtual ~InMemoryNode() = default;650 651 /// Return the \p Status for this node. \p RequestedName should be the name652 /// through which the caller referred to this node. It will override653 /// \p Status::Name in the return value, to mimic the behavior of \p RealFile.654 virtual Status getStatus(const Twine &RequestedName) const = 0;655 656 /// Get the filename of this node (the name without the directory part).657 StringRef getFileName() const { return FileName; }658 InMemoryNodeKind getKind() const { return Kind; }659 virtual std::string toString(unsigned Indent) const = 0;660};661 662class InMemoryFile : public InMemoryNode {663 Status Stat;664 std::unique_ptr<llvm::MemoryBuffer> Buffer;665 666public:667 InMemoryFile(Status Stat, std::unique_ptr<llvm::MemoryBuffer> Buffer)668 : InMemoryNode(Stat.getName(), IME_File), Stat(std::move(Stat)),669 Buffer(std::move(Buffer)) {}670 671 Status getStatus(const Twine &RequestedName) const override {672 return Status::copyWithNewName(Stat, RequestedName);673 }674 llvm::MemoryBuffer *getBuffer() const { return Buffer.get(); }675 676 std::string toString(unsigned Indent) const override {677 return (std::string(Indent, ' ') + Stat.getName() + "\n").str();678 }679 680 static bool classof(const InMemoryNode *N) {681 return N->getKind() == IME_File;682 }683};684 685namespace {686 687class InMemoryHardLink : public InMemoryNode {688 const InMemoryFile &ResolvedFile;689 690public:691 InMemoryHardLink(StringRef Path, const InMemoryFile &ResolvedFile)692 : InMemoryNode(Path, IME_HardLink), ResolvedFile(ResolvedFile) {}693 const InMemoryFile &getResolvedFile() const { return ResolvedFile; }694 695 Status getStatus(const Twine &RequestedName) const override {696 return ResolvedFile.getStatus(RequestedName);697 }698 699 std::string toString(unsigned Indent) const override {700 return std::string(Indent, ' ') + "HardLink to -> " +701 ResolvedFile.toString(0);702 }703 704 static bool classof(const InMemoryNode *N) {705 return N->getKind() == IME_HardLink;706 }707};708 709class InMemorySymbolicLink : public InMemoryNode {710 std::string TargetPath;711 Status Stat;712 713public:714 InMemorySymbolicLink(StringRef Path, StringRef TargetPath, Status Stat)715 : InMemoryNode(Path, IME_SymbolicLink), TargetPath(std::move(TargetPath)),716 Stat(Stat) {}717 718 std::string toString(unsigned Indent) const override {719 return std::string(Indent, ' ') + "SymbolicLink to -> " + TargetPath;720 }721 722 Status getStatus(const Twine &RequestedName) const override {723 return Status::copyWithNewName(Stat, RequestedName);724 }725 726 StringRef getTargetPath() const { return TargetPath; }727 728 static bool classof(const InMemoryNode *N) {729 return N->getKind() == IME_SymbolicLink;730 }731};732 733/// Adapt a InMemoryFile for VFS' File interface. The goal is to make734/// \p InMemoryFileAdaptor mimic as much as possible the behavior of735/// \p RealFile.736class InMemoryFileAdaptor : public File {737 const InMemoryFile &Node;738 /// The name to use when returning a Status for this file.739 std::string RequestedName;740 741public:742 explicit InMemoryFileAdaptor(const InMemoryFile &Node,743 std::string RequestedName)744 : Node(Node), RequestedName(std::move(RequestedName)) {}745 746 llvm::ErrorOr<Status> status() override {747 return Node.getStatus(RequestedName);748 }749 750 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>751 getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator,752 bool IsVolatile) override {753 llvm::MemoryBuffer *Buf = Node.getBuffer();754 return llvm::MemoryBuffer::getMemBuffer(755 Buf->getBuffer(), Buf->getBufferIdentifier(), RequiresNullTerminator);756 }757 758 std::error_code close() override { return {}; }759 760 void setPath(const Twine &Path) override { RequestedName = Path.str(); }761};762} // namespace763 764class InMemoryDirectory : public InMemoryNode {765 Status Stat;766 std::map<std::string, std::unique_ptr<InMemoryNode>, std::less<>> Entries;767 768public:769 InMemoryDirectory(Status Stat)770 : InMemoryNode(Stat.getName(), IME_Directory), Stat(std::move(Stat)) {}771 772 /// Return the \p Status for this node. \p RequestedName should be the name773 /// through which the caller referred to this node. It will override774 /// \p Status::Name in the return value, to mimic the behavior of \p RealFile.775 Status getStatus(const Twine &RequestedName) const override {776 return Status::copyWithNewName(Stat, RequestedName);777 }778 779 UniqueID getUniqueID() const { return Stat.getUniqueID(); }780 781 InMemoryNode *getChild(StringRef Name) const {782 auto I = Entries.find(Name);783 if (I != Entries.end())784 return I->second.get();785 return nullptr;786 }787 788 InMemoryNode *addChild(StringRef Name, std::unique_ptr<InMemoryNode> Child) {789 return Entries.emplace(Name, std::move(Child)).first->second.get();790 }791 792 using const_iterator = decltype(Entries)::const_iterator;793 794 const_iterator begin() const { return Entries.begin(); }795 const_iterator end() const { return Entries.end(); }796 797 std::string toString(unsigned Indent) const override {798 std::string Result =799 (std::string(Indent, ' ') + Stat.getName() + "\n").str();800 for (const auto &Entry : Entries)801 Result += Entry.second->toString(Indent + 2);802 return Result;803 }804 805 static bool classof(const InMemoryNode *N) {806 return N->getKind() == IME_Directory;807 }808};809 810} // namespace detail811 812// The UniqueID of in-memory files is derived from path and content.813// This avoids difficulties in creating exactly equivalent in-memory FSes,814// as often needed in multithreaded programs.815static sys::fs::UniqueID getUniqueID(hash_code Hash) {816 return sys::fs::UniqueID(std::numeric_limits<uint64_t>::max(),817 uint64_t(size_t(Hash)));818}819static sys::fs::UniqueID getFileID(sys::fs::UniqueID Parent,820 llvm::StringRef Name,821 llvm::StringRef Contents) {822 return getUniqueID(llvm::hash_combine(Parent.getFile(), Name, Contents));823}824static sys::fs::UniqueID getDirectoryID(sys::fs::UniqueID Parent,825 llvm::StringRef Name) {826 return getUniqueID(llvm::hash_combine(Parent.getFile(), Name));827}828 829Status detail::NewInMemoryNodeInfo::makeStatus() const {830 UniqueID UID =831 (Type == sys::fs::file_type::directory_file)832 ? getDirectoryID(DirUID, Name)833 : getFileID(DirUID, Name, Buffer ? Buffer->getBuffer() : "");834 835 return Status(Path, UID, llvm::sys::toTimePoint(ModificationTime), User,836 Group, Buffer ? Buffer->getBufferSize() : 0, Type, Perms);837}838 839InMemoryFileSystem::InMemoryFileSystem(bool UseNormalizedPaths)840 : Root(new detail::InMemoryDirectory(841 Status("", getDirectoryID(llvm::sys::fs::UniqueID(), ""),842 llvm::sys::TimePoint<>(), 0, 0, 0,843 llvm::sys::fs::file_type::directory_file,844 llvm::sys::fs::perms::all_all))),845 UseNormalizedPaths(UseNormalizedPaths) {}846 847InMemoryFileSystem::~InMemoryFileSystem() = default;848 849std::string InMemoryFileSystem::toString() const {850 return Root->toString(/*Indent=*/0);851}852 853bool InMemoryFileSystem::addFile(const Twine &P, time_t ModificationTime,854 std::unique_ptr<llvm::MemoryBuffer> Buffer,855 std::optional<uint32_t> User,856 std::optional<uint32_t> Group,857 std::optional<llvm::sys::fs::file_type> Type,858 std::optional<llvm::sys::fs::perms> Perms,859 MakeNodeFn MakeNode) {860 SmallString<128> Path;861 P.toVector(Path);862 863 // Fix up relative paths. This just prepends the current working directory.864 std::error_code EC = makeAbsolute(Path);865 assert(!EC);866 (void)EC;867 868 if (useNormalizedPaths())869 llvm::sys::path::remove_dots(Path, /*remove_dot_dot=*/true);870 871 if (Path.empty())872 return false;873 874 detail::InMemoryDirectory *Dir = Root.get();875 auto I = llvm::sys::path::begin(Path), E = sys::path::end(Path);876 const auto ResolvedUser = User.value_or(0);877 const auto ResolvedGroup = Group.value_or(0);878 const auto ResolvedType = Type.value_or(sys::fs::file_type::regular_file);879 const auto ResolvedPerms = Perms.value_or(sys::fs::all_all);880 // Any intermediate directories we create should be accessible by881 // the owner, even if Perms says otherwise for the final path.882 const auto NewDirectoryPerms = ResolvedPerms | sys::fs::owner_all;883 884 StringRef Name = *I;885 while (true) {886 Name = *I;887 ++I;888 if (I == E)889 break;890 detail::InMemoryNode *Node = Dir->getChild(Name);891 if (!Node) {892 // This isn't the last element, so we create a new directory.893 Status Stat(894 StringRef(Path.str().begin(), Name.end() - Path.str().begin()),895 getDirectoryID(Dir->getUniqueID(), Name),896 llvm::sys::toTimePoint(ModificationTime), ResolvedUser, ResolvedGroup,897 0, sys::fs::file_type::directory_file, NewDirectoryPerms);898 Dir = cast<detail::InMemoryDirectory>(Dir->addChild(899 Name, std::make_unique<detail::InMemoryDirectory>(std::move(Stat))));900 continue;901 }902 // Creating file under another file.903 if (!isa<detail::InMemoryDirectory>(Node))904 return false;905 Dir = cast<detail::InMemoryDirectory>(Node);906 }907 detail::InMemoryNode *Node = Dir->getChild(Name);908 if (!Node) {909 Dir->addChild(Name,910 MakeNode({Dir->getUniqueID(), Path, Name, ModificationTime,911 std::move(Buffer), ResolvedUser, ResolvedGroup,912 ResolvedType, ResolvedPerms}));913 return true;914 }915 if (isa<detail::InMemoryDirectory>(Node))916 return ResolvedType == sys::fs::file_type::directory_file;917 918 assert((isa<detail::InMemoryFile>(Node) ||919 isa<detail::InMemoryHardLink>(Node)) &&920 "Must be either file, hardlink or directory!");921 922 // Return false only if the new file is different from the existing one.923 if (auto *Link = dyn_cast<detail::InMemoryHardLink>(Node)) {924 return Link->getResolvedFile().getBuffer()->getBuffer() ==925 Buffer->getBuffer();926 }927 return cast<detail::InMemoryFile>(Node)->getBuffer()->getBuffer() ==928 Buffer->getBuffer();929}930 931bool InMemoryFileSystem::addFile(const Twine &P, time_t ModificationTime,932 std::unique_ptr<llvm::MemoryBuffer> Buffer,933 std::optional<uint32_t> User,934 std::optional<uint32_t> Group,935 std::optional<llvm::sys::fs::file_type> Type,936 std::optional<llvm::sys::fs::perms> Perms) {937 return addFile(P, ModificationTime, std::move(Buffer), User, Group, Type,938 Perms,939 [](detail::NewInMemoryNodeInfo NNI)940 -> std::unique_ptr<detail::InMemoryNode> {941 Status Stat = NNI.makeStatus();942 if (Stat.getType() == sys::fs::file_type::directory_file)943 return std::make_unique<detail::InMemoryDirectory>(Stat);944 return std::make_unique<detail::InMemoryFile>(945 Stat, std::move(NNI.Buffer));946 });947}948 949bool InMemoryFileSystem::addFileNoOwn(950 const Twine &P, time_t ModificationTime,951 const llvm::MemoryBufferRef &Buffer, std::optional<uint32_t> User,952 std::optional<uint32_t> Group, std::optional<llvm::sys::fs::file_type> Type,953 std::optional<llvm::sys::fs::perms> Perms) {954 return addFile(P, ModificationTime, llvm::MemoryBuffer::getMemBuffer(Buffer),955 std::move(User), std::move(Group), std::move(Type),956 std::move(Perms),957 [](detail::NewInMemoryNodeInfo NNI)958 -> std::unique_ptr<detail::InMemoryNode> {959 Status Stat = NNI.makeStatus();960 if (Stat.getType() == sys::fs::file_type::directory_file)961 return std::make_unique<detail::InMemoryDirectory>(Stat);962 return std::make_unique<detail::InMemoryFile>(963 Stat, std::move(NNI.Buffer));964 });965}966 967detail::NamedNodeOrError968InMemoryFileSystem::lookupNode(const Twine &P, bool FollowFinalSymlink,969 size_t SymlinkDepth) const {970 SmallString<128> Path;971 P.toVector(Path);972 973 // Fix up relative paths. This just prepends the current working directory.974 std::error_code EC = makeAbsolute(Path);975 assert(!EC);976 (void)EC;977 978 if (useNormalizedPaths())979 llvm::sys::path::remove_dots(Path, /*remove_dot_dot=*/true);980 981 const detail::InMemoryDirectory *Dir = Root.get();982 if (Path.empty())983 return detail::NamedNodeOrError(Path, Dir);984 985 auto I = llvm::sys::path::begin(Path), E = llvm::sys::path::end(Path);986 while (true) {987 detail::InMemoryNode *Node = Dir->getChild(*I);988 ++I;989 if (!Node)990 return errc::no_such_file_or_directory;991 992 if (auto Symlink = dyn_cast<detail::InMemorySymbolicLink>(Node)) {993 // If we're at the end of the path, and we're not following through994 // terminal symlinks, then we're done.995 if (I == E && !FollowFinalSymlink)996 return detail::NamedNodeOrError(Path, Symlink);997 998 if (SymlinkDepth > InMemoryFileSystem::MaxSymlinkDepth)999 return errc::no_such_file_or_directory;1000 1001 SmallString<128> TargetPath = Symlink->getTargetPath();1002 if (std::error_code EC = makeAbsolute(TargetPath))1003 return EC;1004 1005 // Keep going with the target. We always want to follow symlinks here1006 // because we're either at the end of a path that we want to follow, or1007 // not at the end of a path, in which case we need to follow the symlink1008 // regardless.1009 auto Target =1010 lookupNode(TargetPath, /*FollowFinalSymlink=*/true, SymlinkDepth + 1);1011 if (!Target || I == E)1012 return Target;1013 1014 if (!isa<detail::InMemoryDirectory>(*Target))1015 return errc::no_such_file_or_directory;1016 1017 // Otherwise, continue on the search in the symlinked directory.1018 Dir = cast<detail::InMemoryDirectory>(*Target);1019 continue;1020 }1021 1022 // Return the file if it's at the end of the path.1023 if (auto File = dyn_cast<detail::InMemoryFile>(Node)) {1024 if (I == E)1025 return detail::NamedNodeOrError(Path, File);1026 return errc::no_such_file_or_directory;1027 }1028 1029 // If Node is HardLink then return the resolved file.1030 if (auto File = dyn_cast<detail::InMemoryHardLink>(Node)) {1031 if (I == E)1032 return detail::NamedNodeOrError(Path, &File->getResolvedFile());1033 return errc::no_such_file_or_directory;1034 }1035 // Traverse directories.1036 Dir = cast<detail::InMemoryDirectory>(Node);1037 if (I == E)1038 return detail::NamedNodeOrError(Path, Dir);1039 }1040}1041 1042bool InMemoryFileSystem::addHardLink(const Twine &NewLink,1043 const Twine &Target) {1044 auto NewLinkNode = lookupNode(NewLink, /*FollowFinalSymlink=*/false);1045 // Whether symlinks in the hardlink target are followed is1046 // implementation-defined in POSIX.1047 // We're following symlinks here to be consistent with macOS.1048 auto TargetNode = lookupNode(Target, /*FollowFinalSymlink=*/true);1049 // FromPath must not have been added before. ToPath must have been added1050 // before. Resolved ToPath must be a File.1051 if (!TargetNode || NewLinkNode || !isa<detail::InMemoryFile>(*TargetNode))1052 return false;1053 return addFile(NewLink, 0, nullptr, std::nullopt, std::nullopt, std::nullopt,1054 std::nullopt, [&](detail::NewInMemoryNodeInfo NNI) {1055 return std::make_unique<detail::InMemoryHardLink>(1056 NNI.Path.str(),1057 *cast<detail::InMemoryFile>(*TargetNode));1058 });1059}1060 1061bool InMemoryFileSystem::addSymbolicLink(1062 const Twine &NewLink, const Twine &Target, time_t ModificationTime,1063 std::optional<uint32_t> User, std::optional<uint32_t> Group,1064 std::optional<llvm::sys::fs::perms> Perms) {1065 auto NewLinkNode = lookupNode(NewLink, /*FollowFinalSymlink=*/false);1066 if (NewLinkNode)1067 return false;1068 1069 SmallString<128> NewLinkStr, TargetStr;1070 NewLink.toVector(NewLinkStr);1071 Target.toVector(TargetStr);1072 1073 return addFile(NewLinkStr, ModificationTime, nullptr, User, Group,1074 sys::fs::file_type::symlink_file, Perms,1075 [&](detail::NewInMemoryNodeInfo NNI) {1076 return std::make_unique<detail::InMemorySymbolicLink>(1077 NewLinkStr, TargetStr, NNI.makeStatus());1078 });1079}1080 1081llvm::ErrorOr<Status> InMemoryFileSystem::status(const Twine &Path) {1082 auto Node = lookupNode(Path, /*FollowFinalSymlink=*/true);1083 if (Node)1084 return (*Node)->getStatus(Path);1085 return Node.getError();1086}1087 1088llvm::ErrorOr<std::unique_ptr<File>>1089InMemoryFileSystem::openFileForRead(const Twine &Path) {1090 auto Node = lookupNode(Path,/*FollowFinalSymlink=*/true);1091 if (!Node)1092 return Node.getError();1093 1094 // When we have a file provide a heap-allocated wrapper for the memory buffer1095 // to match the ownership semantics for File.1096 if (auto *F = dyn_cast<detail::InMemoryFile>(*Node))1097 return std::unique_ptr<File>(1098 new detail::InMemoryFileAdaptor(*F, Path.str()));1099 1100 // FIXME: errc::not_a_file?1101 return make_error_code(llvm::errc::invalid_argument);1102}1103 1104/// Adaptor from InMemoryDir::iterator to directory_iterator.1105class InMemoryFileSystem::DirIterator : public llvm::vfs::detail::DirIterImpl {1106 const InMemoryFileSystem *FS;1107 detail::InMemoryDirectory::const_iterator I;1108 detail::InMemoryDirectory::const_iterator E;1109 std::string RequestedDirName;1110 1111 void setCurrentEntry() {1112 if (I != E) {1113 SmallString<256> Path(RequestedDirName);1114 llvm::sys::path::append(Path, I->second->getFileName());1115 sys::fs::file_type Type = sys::fs::file_type::type_unknown;1116 switch (I->second->getKind()) {1117 case detail::IME_File:1118 case detail::IME_HardLink:1119 Type = sys::fs::file_type::regular_file;1120 break;1121 case detail::IME_Directory:1122 Type = sys::fs::file_type::directory_file;1123 break;1124 case detail::IME_SymbolicLink:1125 if (auto SymlinkTarget =1126 FS->lookupNode(Path, /*FollowFinalSymlink=*/true)) {1127 Path = SymlinkTarget.getName();1128 Type = (*SymlinkTarget)->getStatus(Path).getType();1129 }1130 break;1131 }1132 CurrentEntry = directory_entry(std::string(Path), Type);1133 } else {1134 // When we're at the end, make CurrentEntry invalid and DirIterImpl will1135 // do the rest.1136 CurrentEntry = directory_entry();1137 }1138 }1139 1140public:1141 DirIterator() = default;1142 1143 DirIterator(const InMemoryFileSystem *FS,1144 const detail::InMemoryDirectory &Dir,1145 std::string RequestedDirName)1146 : FS(FS), I(Dir.begin()), E(Dir.end()),1147 RequestedDirName(std::move(RequestedDirName)) {1148 setCurrentEntry();1149 }1150 1151 std::error_code increment() override {1152 ++I;1153 setCurrentEntry();1154 return {};1155 }1156};1157 1158directory_iterator InMemoryFileSystem::dir_begin(const Twine &Dir,1159 std::error_code &EC) {1160 auto Node = lookupNode(Dir, /*FollowFinalSymlink=*/true);1161 if (!Node) {1162 EC = Node.getError();1163 return directory_iterator(std::make_shared<DirIterator>());1164 }1165 1166 if (auto *DirNode = dyn_cast<detail::InMemoryDirectory>(*Node))1167 return directory_iterator(1168 std::make_shared<DirIterator>(this, *DirNode, Dir.str()));1169 1170 EC = make_error_code(llvm::errc::not_a_directory);1171 return directory_iterator(std::make_shared<DirIterator>());1172}1173 1174std::error_code InMemoryFileSystem::setCurrentWorkingDirectory(const Twine &P) {1175 SmallString<128> Path;1176 P.toVector(Path);1177 1178 // Fix up relative paths. This just prepends the current working directory.1179 std::error_code EC = makeAbsolute(Path);1180 assert(!EC);1181 (void)EC;1182 1183 if (useNormalizedPaths())1184 llvm::sys::path::remove_dots(Path, /*remove_dot_dot=*/true);1185 1186 if (!Path.empty())1187 WorkingDirectory = std::string(Path);1188 return {};1189}1190 1191std::error_code InMemoryFileSystem::getRealPath(const Twine &Path,1192 SmallVectorImpl<char> &Output) {1193 auto CWD = getCurrentWorkingDirectory();1194 if (!CWD || CWD->empty())1195 return errc::operation_not_permitted;1196 Path.toVector(Output);1197 if (auto EC = makeAbsolute(Output))1198 return EC;1199 llvm::sys::path::remove_dots(Output, /*remove_dot_dot=*/true);1200 return {};1201}1202 1203std::error_code InMemoryFileSystem::isLocal(const Twine &Path, bool &Result) {1204 Result = false;1205 return {};1206}1207 1208void InMemoryFileSystem::printImpl(raw_ostream &OS, PrintType PrintContents,1209 unsigned IndentLevel) const {1210 printIndent(OS, IndentLevel);1211 OS << "InMemoryFileSystem\n";1212}1213 1214} // namespace vfs1215} // namespace llvm1216 1217//===-----------------------------------------------------------------------===/1218// RedirectingFileSystem implementation1219//===-----------------------------------------------------------------------===/1220 1221namespace {1222 1223static llvm::sys::path::Style getExistingStyle(llvm::StringRef Path) {1224 // Detect the path style in use by checking the first separator.1225 llvm::sys::path::Style style = llvm::sys::path::Style::native;1226 const size_t n = Path.find_first_of("/\\");1227 // Can't distinguish between posix and windows_slash here.1228 if (n != static_cast<size_t>(-1))1229 style = (Path[n] == '/') ? llvm::sys::path::Style::posix1230 : llvm::sys::path::Style::windows_backslash;1231 return style;1232}1233 1234/// Removes leading "./" as well as path components like ".." and ".".1235static llvm::SmallString<256> canonicalize(llvm::StringRef Path) {1236 // First detect the path style in use by checking the first separator.1237 llvm::sys::path::Style style = getExistingStyle(Path);1238 1239 // Now remove the dots. Explicitly specifying the path style prevents the1240 // direction of the slashes from changing.1241 llvm::SmallString<256> result =1242 llvm::sys::path::remove_leading_dotslash(Path, style);1243 llvm::sys::path::remove_dots(result, /*remove_dot_dot=*/true, style);1244 return result;1245}1246 1247/// Whether the error and entry specify a file/directory that was not found.1248static bool isFileNotFound(std::error_code EC,1249 RedirectingFileSystem::Entry *E = nullptr) {1250 if (E && !isa<RedirectingFileSystem::DirectoryRemapEntry>(E))1251 return false;1252 return EC == llvm::errc::no_such_file_or_directory;1253}1254 1255} // anonymous namespace1256 1257 1258RedirectingFileSystem::RedirectingFileSystem(IntrusiveRefCntPtr<FileSystem> FS)1259 : ExternalFS(std::move(FS)) {1260 if (ExternalFS)1261 if (auto ExternalWorkingDirectory =1262 ExternalFS->getCurrentWorkingDirectory()) {1263 WorkingDirectory = *ExternalWorkingDirectory;1264 }1265}1266 1267/// Directory iterator implementation for \c RedirectingFileSystem's1268/// directory entries.1269class llvm::vfs::RedirectingFSDirIterImpl1270 : public llvm::vfs::detail::DirIterImpl {1271 std::string Dir;1272 RedirectingFileSystem::DirectoryEntry::iterator Current, End;1273 1274 std::error_code incrementImpl(bool IsFirstTime) {1275 assert((IsFirstTime || Current != End) && "cannot iterate past end");1276 if (!IsFirstTime)1277 ++Current;1278 if (Current != End) {1279 SmallString<128> PathStr(Dir);1280 llvm::sys::path::append(PathStr, (*Current)->getName());1281 sys::fs::file_type Type = sys::fs::file_type::type_unknown;1282 switch ((*Current)->getKind()) {1283 case RedirectingFileSystem::EK_Directory:1284 [[fallthrough]];1285 case RedirectingFileSystem::EK_DirectoryRemap:1286 Type = sys::fs::file_type::directory_file;1287 break;1288 case RedirectingFileSystem::EK_File:1289 Type = sys::fs::file_type::regular_file;1290 break;1291 }1292 CurrentEntry = directory_entry(std::string(PathStr), Type);1293 } else {1294 CurrentEntry = directory_entry();1295 }1296 return {};1297 };1298 1299public:1300 RedirectingFSDirIterImpl(1301 const Twine &Path, RedirectingFileSystem::DirectoryEntry::iterator Begin,1302 RedirectingFileSystem::DirectoryEntry::iterator End, std::error_code &EC)1303 : Dir(Path.str()), Current(Begin), End(End) {1304 EC = incrementImpl(/*IsFirstTime=*/true);1305 }1306 1307 std::error_code increment() override {1308 return incrementImpl(/*IsFirstTime=*/false);1309 }1310};1311 1312namespace {1313/// Directory iterator implementation for \c RedirectingFileSystem's1314/// directory remap entries that maps the paths reported by the external1315/// file system's directory iterator back to the virtual directory's path.1316class RedirectingFSDirRemapIterImpl : public llvm::vfs::detail::DirIterImpl {1317 std::string Dir;1318 llvm::sys::path::Style DirStyle;1319 llvm::vfs::directory_iterator ExternalIter;1320 1321public:1322 RedirectingFSDirRemapIterImpl(std::string DirPath,1323 llvm::vfs::directory_iterator ExtIter)1324 : Dir(std::move(DirPath)), DirStyle(getExistingStyle(Dir)),1325 ExternalIter(ExtIter) {1326 if (ExternalIter != llvm::vfs::directory_iterator())1327 setCurrentEntry();1328 }1329 1330 void setCurrentEntry() {1331 StringRef ExternalPath = ExternalIter->path();1332 llvm::sys::path::Style ExternalStyle = getExistingStyle(ExternalPath);1333 StringRef File = llvm::sys::path::filename(ExternalPath, ExternalStyle);1334 1335 SmallString<128> NewPath(Dir);1336 llvm::sys::path::append(NewPath, DirStyle, File);1337 1338 CurrentEntry = directory_entry(std::string(NewPath), ExternalIter->type());1339 }1340 1341 std::error_code increment() override {1342 std::error_code EC;1343 ExternalIter.increment(EC);1344 if (!EC && ExternalIter != llvm::vfs::directory_iterator())1345 setCurrentEntry();1346 else1347 CurrentEntry = directory_entry();1348 return EC;1349 }1350};1351} // namespace1352 1353llvm::ErrorOr<std::string>1354RedirectingFileSystem::getCurrentWorkingDirectory() const {1355 return WorkingDirectory;1356}1357 1358std::error_code1359RedirectingFileSystem::setCurrentWorkingDirectory(const Twine &Path) {1360 // Don't change the working directory if the path doesn't exist.1361 if (!exists(Path))1362 return errc::no_such_file_or_directory;1363 1364 SmallString<128> AbsolutePath;1365 Path.toVector(AbsolutePath);1366 if (std::error_code EC = makeAbsolute(AbsolutePath))1367 return EC;1368 WorkingDirectory = std::string(AbsolutePath);1369 return {};1370}1371 1372std::error_code RedirectingFileSystem::isLocal(const Twine &Path_,1373 bool &Result) {1374 SmallString<256> Path;1375 Path_.toVector(Path);1376 1377 if (makeAbsolute(Path))1378 return {};1379 1380 return ExternalFS->isLocal(Path, Result);1381}1382 1383std::error_code RedirectingFileSystem::makeAbsolute(SmallVectorImpl<char> &Path) const {1384 // is_absolute(..., Style::windows_*) accepts paths with both slash types.1385 if (llvm::sys::path::is_absolute(Path, llvm::sys::path::Style::posix) ||1386 llvm::sys::path::is_absolute(Path,1387 llvm::sys::path::Style::windows_backslash))1388 // This covers windows absolute path with forward slash as well, as the1389 // forward slashes are treated as path separation in llvm::path1390 // regardless of what path::Style is used.1391 return {};1392 1393 auto WorkingDir = getCurrentWorkingDirectory();1394 if (!WorkingDir)1395 return WorkingDir.getError();1396 1397 return makeAbsolute(WorkingDir.get(), Path);1398}1399 1400std::error_code1401RedirectingFileSystem::makeAbsolute(StringRef WorkingDir,1402 SmallVectorImpl<char> &Path) const {1403 // We can't use sys::fs::make_absolute because that assumes the path style1404 // is native and there is no way to override that. Since we know WorkingDir1405 // is absolute, we can use it to determine which style we actually have and1406 // append Path ourselves.1407 if (!WorkingDir.empty() &&1408 !sys::path::is_absolute(WorkingDir, sys::path::Style::posix) &&1409 !sys::path::is_absolute(WorkingDir,1410 sys::path::Style::windows_backslash)) {1411 return std::error_code();1412 }1413 sys::path::Style style = sys::path::Style::windows_backslash;1414 if (sys::path::is_absolute(WorkingDir, sys::path::Style::posix)) {1415 style = sys::path::Style::posix;1416 } else {1417 // Distinguish between windows_backslash and windows_slash; getExistingStyle1418 // returns posix for a path with windows_slash.1419 if (getExistingStyle(WorkingDir) != sys::path::Style::windows_backslash)1420 style = sys::path::Style::windows_slash;1421 }1422 1423 std::string Result = std::string(WorkingDir);1424 StringRef Dir(Result);1425 if (!Dir.ends_with(sys::path::get_separator(style))) {1426 Result += sys::path::get_separator(style);1427 }1428 // backslashes '\' are legit path charactors under POSIX. Windows APIs1429 // like CreateFile accepts forward slashes '/' as path1430 // separator (even when mixed with backslashes). Therefore,1431 // `Path` should be directly appended to `WorkingDir` without converting1432 // path separator.1433 Result.append(Path.data(), Path.size());1434 Path.assign(Result.begin(), Result.end());1435 1436 return {};1437}1438 1439directory_iterator RedirectingFileSystem::dir_begin(const Twine &Dir,1440 std::error_code &EC) {1441 SmallString<256> Path;1442 Dir.toVector(Path);1443 1444 EC = makeAbsolute(Path);1445 if (EC)1446 return {};1447 1448 ErrorOr<RedirectingFileSystem::LookupResult> Result = lookupPath(Path);1449 if (!Result) {1450 if (Redirection != RedirectKind::RedirectOnly &&1451 isFileNotFound(Result.getError()))1452 return ExternalFS->dir_begin(Path, EC);1453 1454 EC = Result.getError();1455 return {};1456 }1457 1458 // Use status to make sure the path exists and refers to a directory.1459 ErrorOr<Status> S = status(Path, Dir, *Result);1460 if (!S) {1461 if (Redirection != RedirectKind::RedirectOnly &&1462 isFileNotFound(S.getError(), Result->E))1463 return ExternalFS->dir_begin(Dir, EC);1464 1465 EC = S.getError();1466 return {};1467 }1468 1469 if (!S->isDirectory()) {1470 EC = errc::not_a_directory;1471 return {};1472 }1473 1474 // Create the appropriate directory iterator based on whether we found a1475 // DirectoryRemapEntry or DirectoryEntry.1476 directory_iterator RedirectIter;1477 std::error_code RedirectEC;1478 if (auto ExtRedirect = Result->getExternalRedirect()) {1479 auto RE = cast<RedirectingFileSystem::RemapEntry>(Result->E);1480 RedirectIter = ExternalFS->dir_begin(*ExtRedirect, RedirectEC);1481 1482 if (!RE->useExternalName(UseExternalNames)) {1483 // Update the paths in the results to use the virtual directory's path.1484 RedirectIter =1485 directory_iterator(std::make_shared<RedirectingFSDirRemapIterImpl>(1486 std::string(Path), RedirectIter));1487 }1488 } else {1489 auto DE = cast<DirectoryEntry>(Result->E);1490 RedirectIter =1491 directory_iterator(std::make_shared<RedirectingFSDirIterImpl>(1492 Path, DE->contents_begin(), DE->contents_end(), RedirectEC));1493 }1494 1495 if (RedirectEC) {1496 if (RedirectEC != errc::no_such_file_or_directory) {1497 EC = RedirectEC;1498 return {};1499 }1500 RedirectIter = {};1501 }1502 1503 if (Redirection == RedirectKind::RedirectOnly) {1504 EC = RedirectEC;1505 return RedirectIter;1506 }1507 1508 std::error_code ExternalEC;1509 directory_iterator ExternalIter = ExternalFS->dir_begin(Path, ExternalEC);1510 if (ExternalEC) {1511 if (ExternalEC != errc::no_such_file_or_directory) {1512 EC = ExternalEC;1513 return {};1514 }1515 ExternalIter = {};1516 }1517 1518 SmallVector<directory_iterator, 2> Iters;1519 switch (Redirection) {1520 case RedirectKind::Fallthrough:1521 Iters.push_back(ExternalIter);1522 Iters.push_back(RedirectIter);1523 break;1524 case RedirectKind::Fallback:1525 Iters.push_back(RedirectIter);1526 Iters.push_back(ExternalIter);1527 break;1528 default:1529 llvm_unreachable("unhandled RedirectKind");1530 }1531 1532 directory_iterator Combined{1533 std::make_shared<CombiningDirIterImpl>(Iters, EC)};1534 if (EC)1535 return {};1536 return Combined;1537}1538 1539void RedirectingFileSystem::setOverlayFileDir(StringRef Dir) {1540 OverlayFileDir = Dir.str();1541}1542 1543StringRef RedirectingFileSystem::getOverlayFileDir() const {1544 return OverlayFileDir;1545}1546 1547void RedirectingFileSystem::setFallthrough(bool Fallthrough) {1548 if (Fallthrough) {1549 Redirection = RedirectingFileSystem::RedirectKind::Fallthrough;1550 } else {1551 Redirection = RedirectingFileSystem::RedirectKind::RedirectOnly;1552 }1553}1554 1555void RedirectingFileSystem::setRedirection(1556 RedirectingFileSystem::RedirectKind Kind) {1557 Redirection = Kind;1558}1559 1560std::vector<StringRef> RedirectingFileSystem::getRoots() const {1561 std::vector<StringRef> R;1562 R.reserve(Roots.size());1563 for (const auto &Root : Roots)1564 R.push_back(Root->getName());1565 return R;1566}1567 1568void RedirectingFileSystem::printImpl(raw_ostream &OS, PrintType Type,1569 unsigned IndentLevel) const {1570 printIndent(OS, IndentLevel);1571 OS << "RedirectingFileSystem (UseExternalNames: "1572 << (UseExternalNames ? "true" : "false") << ")\n";1573 if (Type == PrintType::Summary)1574 return;1575 1576 for (const auto &Root : Roots)1577 printEntry(OS, Root.get(), IndentLevel);1578 1579 printIndent(OS, IndentLevel);1580 OS << "ExternalFS:\n";1581 ExternalFS->print(OS, Type == PrintType::Contents ? PrintType::Summary : Type,1582 IndentLevel + 1);1583}1584 1585void RedirectingFileSystem::printEntry(raw_ostream &OS,1586 RedirectingFileSystem::Entry *E,1587 unsigned IndentLevel) const {1588 printIndent(OS, IndentLevel);1589 OS << "'" << E->getName() << "'";1590 1591 switch (E->getKind()) {1592 case EK_Directory: {1593 auto *DE = cast<RedirectingFileSystem::DirectoryEntry>(E);1594 1595 OS << "\n";1596 for (std::unique_ptr<Entry> &SubEntry :1597 llvm::make_range(DE->contents_begin(), DE->contents_end()))1598 printEntry(OS, SubEntry.get(), IndentLevel + 1);1599 break;1600 }1601 case EK_DirectoryRemap:1602 case EK_File: {1603 auto *RE = cast<RedirectingFileSystem::RemapEntry>(E);1604 OS << " -> '" << RE->getExternalContentsPath() << "'";1605 switch (RE->getUseName()) {1606 case NK_NotSet:1607 break;1608 case NK_External:1609 OS << " (UseExternalName: true)";1610 break;1611 case NK_Virtual:1612 OS << " (UseExternalName: false)";1613 break;1614 }1615 OS << "\n";1616 break;1617 }1618 }1619}1620 1621void RedirectingFileSystem::visitChildFileSystems(VisitCallbackTy Callback) {1622 if (ExternalFS) {1623 Callback(*ExternalFS);1624 ExternalFS->visitChildFileSystems(Callback);1625 }1626}1627 1628/// A helper class to hold the common YAML parsing state.1629class llvm::vfs::RedirectingFileSystemParser {1630 yaml::Stream &Stream;1631 1632 void error(yaml::Node *N, const Twine &Msg) { Stream.printError(N, Msg); }1633 1634 // false on error1635 bool parseScalarString(yaml::Node *N, StringRef &Result,1636 SmallVectorImpl<char> &Storage) {1637 const auto *S = dyn_cast<yaml::ScalarNode>(N);1638 1639 if (!S) {1640 error(N, "expected string");1641 return false;1642 }1643 Result = S->getValue(Storage);1644 return true;1645 }1646 1647 // false on error1648 bool parseScalarBool(yaml::Node *N, bool &Result) {1649 SmallString<5> Storage;1650 StringRef Value;1651 if (!parseScalarString(N, Value, Storage))1652 return false;1653 1654 if (Value.equals_insensitive("true") || Value.equals_insensitive("on") ||1655 Value.equals_insensitive("yes") || Value == "1") {1656 Result = true;1657 return true;1658 } else if (Value.equals_insensitive("false") ||1659 Value.equals_insensitive("off") ||1660 Value.equals_insensitive("no") || Value == "0") {1661 Result = false;1662 return true;1663 }1664 1665 error(N, "expected boolean value");1666 return false;1667 }1668 1669 std::optional<RedirectingFileSystem::RedirectKind>1670 parseRedirectKind(yaml::Node *N) {1671 SmallString<12> Storage;1672 StringRef Value;1673 if (!parseScalarString(N, Value, Storage))1674 return std::nullopt;1675 1676 if (Value.equals_insensitive("fallthrough")) {1677 return RedirectingFileSystem::RedirectKind::Fallthrough;1678 } else if (Value.equals_insensitive("fallback")) {1679 return RedirectingFileSystem::RedirectKind::Fallback;1680 } else if (Value.equals_insensitive("redirect-only")) {1681 return RedirectingFileSystem::RedirectKind::RedirectOnly;1682 }1683 return std::nullopt;1684 }1685 1686 std::optional<RedirectingFileSystem::RootRelativeKind>1687 parseRootRelativeKind(yaml::Node *N) {1688 SmallString<12> Storage;1689 StringRef Value;1690 if (!parseScalarString(N, Value, Storage))1691 return std::nullopt;1692 if (Value.equals_insensitive("cwd")) {1693 return RedirectingFileSystem::RootRelativeKind::CWD;1694 } else if (Value.equals_insensitive("overlay-dir")) {1695 return RedirectingFileSystem::RootRelativeKind::OverlayDir;1696 }1697 return std::nullopt;1698 }1699 1700 struct KeyStatus {1701 bool Required;1702 bool Seen = false;1703 1704 KeyStatus(bool Required = false) : Required(Required) {}1705 };1706 1707 using KeyStatusPair = std::pair<StringRef, KeyStatus>;1708 1709 // false on error1710 bool checkDuplicateOrUnknownKey(yaml::Node *KeyNode, StringRef Key,1711 DenseMap<StringRef, KeyStatus> &Keys) {1712 auto It = Keys.find(Key);1713 if (It == Keys.end()) {1714 error(KeyNode, "unknown key");1715 return false;1716 }1717 KeyStatus &S = It->second;1718 if (S.Seen) {1719 error(KeyNode, Twine("duplicate key '") + Key + "'");1720 return false;1721 }1722 S.Seen = true;1723 return true;1724 }1725 1726 // false on error1727 bool checkMissingKeys(yaml::Node *Obj, DenseMap<StringRef, KeyStatus> &Keys) {1728 for (const auto &I : Keys) {1729 if (I.second.Required && !I.second.Seen) {1730 error(Obj, Twine("missing key '") + I.first + "'");1731 return false;1732 }1733 }1734 return true;1735 }1736 1737public:1738 static RedirectingFileSystem::Entry *1739 lookupOrCreateEntry(RedirectingFileSystem *FS, StringRef Name,1740 RedirectingFileSystem::Entry *ParentEntry = nullptr) {1741 if (!ParentEntry) { // Look for a existent root1742 for (const auto &Root : FS->Roots) {1743 if (Name == Root->getName()) {1744 ParentEntry = Root.get();1745 return ParentEntry;1746 }1747 }1748 } else { // Advance to the next component1749 auto *DE = dyn_cast<RedirectingFileSystem::DirectoryEntry>(ParentEntry);1750 for (std::unique_ptr<RedirectingFileSystem::Entry> &Content :1751 llvm::make_range(DE->contents_begin(), DE->contents_end())) {1752 auto *DirContent =1753 dyn_cast<RedirectingFileSystem::DirectoryEntry>(Content.get());1754 if (DirContent && Name == Content->getName())1755 return DirContent;1756 }1757 }1758 1759 // ... or create a new one1760 std::unique_ptr<RedirectingFileSystem::Entry> E =1761 std::make_unique<RedirectingFileSystem::DirectoryEntry>(1762 Name, Status("", getNextVirtualUniqueID(),1763 std::chrono::system_clock::now(), 0, 0, 0,1764 file_type::directory_file, sys::fs::all_all));1765 1766 if (!ParentEntry) { // Add a new root to the overlay1767 FS->Roots.push_back(std::move(E));1768 ParentEntry = FS->Roots.back().get();1769 return ParentEntry;1770 }1771 1772 auto *DE = cast<RedirectingFileSystem::DirectoryEntry>(ParentEntry);1773 DE->addContent(std::move(E));1774 return DE->getLastContent();1775 }1776 1777private:1778 void uniqueOverlayTree(RedirectingFileSystem *FS,1779 RedirectingFileSystem::Entry *SrcE,1780 RedirectingFileSystem::Entry *NewParentE = nullptr) {1781 StringRef Name = SrcE->getName();1782 switch (SrcE->getKind()) {1783 case RedirectingFileSystem::EK_Directory: {1784 auto *DE = cast<RedirectingFileSystem::DirectoryEntry>(SrcE);1785 // Empty directories could be present in the YAML as a way to1786 // describe a file for a current directory after some of its subdir1787 // is parsed. This only leads to redundant walks, ignore it.1788 if (!Name.empty())1789 NewParentE = lookupOrCreateEntry(FS, Name, NewParentE);1790 for (std::unique_ptr<RedirectingFileSystem::Entry> &SubEntry :1791 llvm::make_range(DE->contents_begin(), DE->contents_end()))1792 uniqueOverlayTree(FS, SubEntry.get(), NewParentE);1793 break;1794 }1795 case RedirectingFileSystem::EK_DirectoryRemap: {1796 assert(NewParentE && "Parent entry must exist");1797 auto *DR = cast<RedirectingFileSystem::DirectoryRemapEntry>(SrcE);1798 auto *DE = cast<RedirectingFileSystem::DirectoryEntry>(NewParentE);1799 DE->addContent(1800 std::make_unique<RedirectingFileSystem::DirectoryRemapEntry>(1801 Name, DR->getExternalContentsPath(), DR->getUseName()));1802 break;1803 }1804 case RedirectingFileSystem::EK_File: {1805 assert(NewParentE && "Parent entry must exist");1806 auto *FE = cast<RedirectingFileSystem::FileEntry>(SrcE);1807 auto *DE = cast<RedirectingFileSystem::DirectoryEntry>(NewParentE);1808 DE->addContent(std::make_unique<RedirectingFileSystem::FileEntry>(1809 Name, FE->getExternalContentsPath(), FE->getUseName()));1810 break;1811 }1812 }1813 }1814 1815 std::unique_ptr<RedirectingFileSystem::Entry>1816 parseEntry(yaml::Node *N, RedirectingFileSystem *FS, bool IsRootEntry) {1817 auto *M = dyn_cast<yaml::MappingNode>(N);1818 if (!M) {1819 error(N, "expected mapping node for file or directory entry");1820 return nullptr;1821 }1822 1823 KeyStatusPair Fields[] = {1824 KeyStatusPair("name", true),1825 KeyStatusPair("type", true),1826 KeyStatusPair("contents", false),1827 KeyStatusPair("external-contents", false),1828 KeyStatusPair("use-external-name", false),1829 };1830 1831 DenseMap<StringRef, KeyStatus> Keys(std::begin(Fields), std::end(Fields));1832 1833 enum { CF_NotSet, CF_List, CF_External } ContentsField = CF_NotSet;1834 std::vector<std::unique_ptr<RedirectingFileSystem::Entry>>1835 EntryArrayContents;1836 SmallString<256> ExternalContentsPath;1837 SmallString<256> Name;1838 yaml::Node *NameValueNode = nullptr;1839 auto UseExternalName = RedirectingFileSystem::NK_NotSet;1840 RedirectingFileSystem::EntryKind Kind;1841 1842 for (auto &I : *M) {1843 StringRef Key;1844 // Reuse the buffer for key and value, since we don't look at key after1845 // parsing value.1846 SmallString<256> Buffer;1847 if (!parseScalarString(I.getKey(), Key, Buffer))1848 return nullptr;1849 1850 if (!checkDuplicateOrUnknownKey(I.getKey(), Key, Keys))1851 return nullptr;1852 1853 StringRef Value;1854 if (Key == "name") {1855 if (!parseScalarString(I.getValue(), Value, Buffer))1856 return nullptr;1857 1858 NameValueNode = I.getValue();1859 // Guarantee that old YAML files containing paths with ".." and "."1860 // are properly canonicalized before read into the VFS.1861 Name = canonicalize(Value).str();1862 } else if (Key == "type") {1863 if (!parseScalarString(I.getValue(), Value, Buffer))1864 return nullptr;1865 if (Value == "file")1866 Kind = RedirectingFileSystem::EK_File;1867 else if (Value == "directory")1868 Kind = RedirectingFileSystem::EK_Directory;1869 else if (Value == "directory-remap")1870 Kind = RedirectingFileSystem::EK_DirectoryRemap;1871 else {1872 error(I.getValue(), "unknown value for 'type'");1873 return nullptr;1874 }1875 } else if (Key == "contents") {1876 if (ContentsField != CF_NotSet) {1877 error(I.getKey(),1878 "entry already has 'contents' or 'external-contents'");1879 return nullptr;1880 }1881 ContentsField = CF_List;1882 auto *Contents = dyn_cast<yaml::SequenceNode>(I.getValue());1883 if (!Contents) {1884 // FIXME: this is only for directories, what about files?1885 error(I.getValue(), "expected array");1886 return nullptr;1887 }1888 1889 for (auto &I : *Contents) {1890 if (std::unique_ptr<RedirectingFileSystem::Entry> E =1891 parseEntry(&I, FS, /*IsRootEntry*/ false))1892 EntryArrayContents.push_back(std::move(E));1893 else1894 return nullptr;1895 }1896 } else if (Key == "external-contents") {1897 if (ContentsField != CF_NotSet) {1898 error(I.getKey(),1899 "entry already has 'contents' or 'external-contents'");1900 return nullptr;1901 }1902 ContentsField = CF_External;1903 if (!parseScalarString(I.getValue(), Value, Buffer))1904 return nullptr;1905 1906 SmallString<256> FullPath;1907 if (FS->IsRelativeOverlay) {1908 FullPath = FS->getOverlayFileDir();1909 assert(!FullPath.empty() &&1910 "External contents prefix directory must exist");1911 SmallString<256> AbsFullPath = Value;1912 if (FS->makeAbsolute(FullPath, AbsFullPath)) {1913 error(N, "failed to make 'external-contents' absolute");1914 return nullptr;1915 }1916 FullPath = AbsFullPath;1917 } else {1918 FullPath = Value;1919 }1920 1921 // Guarantee that old YAML files containing paths with ".." and "."1922 // are properly canonicalized before read into the VFS.1923 FullPath = canonicalize(FullPath);1924 ExternalContentsPath = FullPath.str();1925 } else if (Key == "use-external-name") {1926 bool Val;1927 if (!parseScalarBool(I.getValue(), Val))1928 return nullptr;1929 UseExternalName = Val ? RedirectingFileSystem::NK_External1930 : RedirectingFileSystem::NK_Virtual;1931 } else {1932 llvm_unreachable("key missing from Keys");1933 }1934 }1935 1936 if (Stream.failed())1937 return nullptr;1938 1939 // check for missing keys1940 if (ContentsField == CF_NotSet) {1941 error(N, "missing key 'contents' or 'external-contents'");1942 return nullptr;1943 }1944 if (!checkMissingKeys(N, Keys))1945 return nullptr;1946 1947 // check invalid configuration1948 if (Kind == RedirectingFileSystem::EK_Directory &&1949 UseExternalName != RedirectingFileSystem::NK_NotSet) {1950 error(N, "'use-external-name' is not supported for 'directory' entries");1951 return nullptr;1952 }1953 1954 if (Kind == RedirectingFileSystem::EK_DirectoryRemap &&1955 ContentsField == CF_List) {1956 error(N, "'contents' is not supported for 'directory-remap' entries");1957 return nullptr;1958 }1959 1960 sys::path::Style path_style = sys::path::Style::native;1961 if (IsRootEntry) {1962 // VFS root entries may be in either Posix or Windows style. Figure out1963 // which style we have, and use it consistently.1964 if (sys::path::is_absolute(Name, sys::path::Style::posix)) {1965 path_style = sys::path::Style::posix;1966 } else if (sys::path::is_absolute(Name,1967 sys::path::Style::windows_backslash)) {1968 path_style = sys::path::Style::windows_backslash;1969 } else {1970 // Relative VFS root entries are made absolute to either the overlay1971 // directory, or the current working directory, then we can determine1972 // the path style from that.1973 std::error_code EC;1974 if (FS->RootRelative ==1975 RedirectingFileSystem::RootRelativeKind::OverlayDir) {1976 StringRef FullPath = FS->getOverlayFileDir();1977 assert(!FullPath.empty() && "Overlay file directory must exist");1978 EC = FS->makeAbsolute(FullPath, Name);1979 Name = canonicalize(Name);1980 } else {1981 EC = FS->makeAbsolute(Name);1982 }1983 if (EC) {1984 assert(NameValueNode && "Name presence should be checked earlier");1985 error(1986 NameValueNode,1987 "entry with relative path at the root level is not discoverable");1988 return nullptr;1989 }1990 path_style = sys::path::is_absolute(Name, sys::path::Style::posix)1991 ? sys::path::Style::posix1992 : sys::path::Style::windows_backslash;1993 }1994 // is::path::is_absolute(Name, sys::path::Style::windows_backslash) will1995 // return true even if `Name` is using forward slashes. Distinguish1996 // between windows_backslash and windows_slash.1997 if (path_style == sys::path::Style::windows_backslash &&1998 getExistingStyle(Name) != sys::path::Style::windows_backslash)1999 path_style = sys::path::Style::windows_slash;2000 }2001 2002 // Remove trailing slash(es), being careful not to remove the root path2003 StringRef Trimmed = Name;2004 size_t RootPathLen = sys::path::root_path(Trimmed, path_style).size();2005 while (Trimmed.size() > RootPathLen &&2006 sys::path::is_separator(Trimmed.back(), path_style))2007 Trimmed = Trimmed.slice(0, Trimmed.size() - 1);2008 2009 // Get the last component2010 StringRef LastComponent = sys::path::filename(Trimmed, path_style);2011 2012 std::unique_ptr<RedirectingFileSystem::Entry> Result;2013 switch (Kind) {2014 case RedirectingFileSystem::EK_File:2015 Result = std::make_unique<RedirectingFileSystem::FileEntry>(2016 LastComponent, std::move(ExternalContentsPath), UseExternalName);2017 break;2018 case RedirectingFileSystem::EK_DirectoryRemap:2019 Result = std::make_unique<RedirectingFileSystem::DirectoryRemapEntry>(2020 LastComponent, std::move(ExternalContentsPath), UseExternalName);2021 break;2022 case RedirectingFileSystem::EK_Directory:2023 Result = std::make_unique<RedirectingFileSystem::DirectoryEntry>(2024 LastComponent, std::move(EntryArrayContents),2025 Status("", getNextVirtualUniqueID(), std::chrono::system_clock::now(),2026 0, 0, 0, file_type::directory_file, sys::fs::all_all));2027 break;2028 }2029 2030 StringRef Parent = sys::path::parent_path(Trimmed, path_style);2031 if (Parent.empty())2032 return Result;2033 2034 // if 'name' contains multiple components, create implicit directory entries2035 for (sys::path::reverse_iterator I = sys::path::rbegin(Parent, path_style),2036 E = sys::path::rend(Parent);2037 I != E; ++I) {2038 std::vector<std::unique_ptr<RedirectingFileSystem::Entry>> Entries;2039 Entries.push_back(std::move(Result));2040 Result = std::make_unique<RedirectingFileSystem::DirectoryEntry>(2041 *I, std::move(Entries),2042 Status("", getNextVirtualUniqueID(), std::chrono::system_clock::now(),2043 0, 0, 0, file_type::directory_file, sys::fs::all_all));2044 }2045 return Result;2046 }2047 2048public:2049 RedirectingFileSystemParser(yaml::Stream &S) : Stream(S) {}2050 2051 // false on error2052 bool parse(yaml::Node *Root, RedirectingFileSystem *FS) {2053 auto *Top = dyn_cast<yaml::MappingNode>(Root);2054 if (!Top) {2055 error(Root, "expected mapping node");2056 return false;2057 }2058 2059 KeyStatusPair Fields[] = {2060 KeyStatusPair("version", true),2061 KeyStatusPair("case-sensitive", false),2062 KeyStatusPair("use-external-names", false),2063 KeyStatusPair("root-relative", false),2064 KeyStatusPair("overlay-relative", false),2065 KeyStatusPair("fallthrough", false),2066 KeyStatusPair("redirecting-with", false),2067 KeyStatusPair("roots", true),2068 };2069 2070 DenseMap<StringRef, KeyStatus> Keys(std::begin(Fields), std::end(Fields));2071 std::vector<std::unique_ptr<RedirectingFileSystem::Entry>> RootEntries;2072 2073 // Parse configuration and 'roots'2074 for (auto &I : *Top) {2075 SmallString<10> KeyBuffer;2076 StringRef Key;2077 if (!parseScalarString(I.getKey(), Key, KeyBuffer))2078 return false;2079 2080 if (!checkDuplicateOrUnknownKey(I.getKey(), Key, Keys))2081 return false;2082 2083 if (Key == "roots") {2084 auto *Roots = dyn_cast<yaml::SequenceNode>(I.getValue());2085 if (!Roots) {2086 error(I.getValue(), "expected array");2087 return false;2088 }2089 2090 for (auto &I : *Roots) {2091 if (std::unique_ptr<RedirectingFileSystem::Entry> E =2092 parseEntry(&I, FS, /*IsRootEntry*/ true))2093 RootEntries.push_back(std::move(E));2094 else2095 return false;2096 }2097 } else if (Key == "version") {2098 StringRef VersionString;2099 SmallString<4> Storage;2100 if (!parseScalarString(I.getValue(), VersionString, Storage))2101 return false;2102 int Version;2103 if (VersionString.getAsInteger<int>(10, Version)) {2104 error(I.getValue(), "expected integer");2105 return false;2106 }2107 if (Version < 0) {2108 error(I.getValue(), "invalid version number");2109 return false;2110 }2111 if (Version != 0) {2112 error(I.getValue(), "version mismatch, expected 0");2113 return false;2114 }2115 } else if (Key == "case-sensitive") {2116 if (!parseScalarBool(I.getValue(), FS->CaseSensitive))2117 return false;2118 } else if (Key == "overlay-relative") {2119 if (!parseScalarBool(I.getValue(), FS->IsRelativeOverlay))2120 return false;2121 } else if (Key == "use-external-names") {2122 if (!parseScalarBool(I.getValue(), FS->UseExternalNames))2123 return false;2124 } else if (Key == "fallthrough") {2125 if (Keys["redirecting-with"].Seen) {2126 error(I.getValue(),2127 "'fallthrough' and 'redirecting-with' are mutually exclusive");2128 return false;2129 }2130 2131 bool ShouldFallthrough = false;2132 if (!parseScalarBool(I.getValue(), ShouldFallthrough))2133 return false;2134 2135 if (ShouldFallthrough) {2136 FS->Redirection = RedirectingFileSystem::RedirectKind::Fallthrough;2137 } else {2138 FS->Redirection = RedirectingFileSystem::RedirectKind::RedirectOnly;2139 }2140 } else if (Key == "redirecting-with") {2141 if (Keys["fallthrough"].Seen) {2142 error(I.getValue(),2143 "'fallthrough' and 'redirecting-with' are mutually exclusive");2144 return false;2145 }2146 2147 if (auto Kind = parseRedirectKind(I.getValue())) {2148 FS->Redirection = *Kind;2149 } else {2150 error(I.getValue(), "expected valid redirect kind");2151 return false;2152 }2153 } else if (Key == "root-relative") {2154 if (auto Kind = parseRootRelativeKind(I.getValue())) {2155 FS->RootRelative = *Kind;2156 } else {2157 error(I.getValue(), "expected valid root-relative kind");2158 return false;2159 }2160 } else {2161 llvm_unreachable("key missing from Keys");2162 }2163 }2164 2165 if (Stream.failed())2166 return false;2167 2168 if (!checkMissingKeys(Top, Keys))2169 return false;2170 2171 // Now that we sucessefully parsed the YAML file, canonicalize the internal2172 // representation to a proper directory tree so that we can search faster2173 // inside the VFS.2174 for (auto &E : RootEntries)2175 uniqueOverlayTree(FS, E.get());2176 2177 return true;2178 }2179};2180 2181std::unique_ptr<RedirectingFileSystem>2182RedirectingFileSystem::create(std::unique_ptr<MemoryBuffer> Buffer,2183 SourceMgr::DiagHandlerTy DiagHandler,2184 StringRef YAMLFilePath, void *DiagContext,2185 IntrusiveRefCntPtr<FileSystem> ExternalFS) {2186 SourceMgr SM;2187 yaml::Stream Stream(Buffer->getMemBufferRef(), SM);2188 2189 SM.setDiagHandler(DiagHandler, DiagContext);2190 yaml::document_iterator DI = Stream.begin();2191 yaml::Node *Root = DI->getRoot();2192 if (DI == Stream.end() || !Root) {2193 SM.PrintMessage(SMLoc(), SourceMgr::DK_Error, "expected root node");2194 return nullptr;2195 }2196 2197 RedirectingFileSystemParser P(Stream);2198 2199 std::unique_ptr<RedirectingFileSystem> FS(2200 new RedirectingFileSystem(ExternalFS));2201 2202 if (!YAMLFilePath.empty()) {2203 // Use the YAML path from -ivfsoverlay to compute the dir to be prefixed2204 // to each 'external-contents' path.2205 //2206 // Example:2207 // -ivfsoverlay dummy.cache/vfs/vfs.yaml2208 // yields:2209 // FS->OverlayFileDir => /<absolute_path_to>/dummy.cache/vfs2210 //2211 SmallString<256> OverlayAbsDir = sys::path::parent_path(YAMLFilePath);2212 std::error_code EC = FS->makeAbsolute(OverlayAbsDir);2213 assert(!EC && "Overlay dir final path must be absolute");2214 (void)EC;2215 FS->setOverlayFileDir(OverlayAbsDir);2216 }2217 2218 if (!P.parse(Root, FS.get()))2219 return nullptr;2220 2221 return FS;2222}2223 2224std::unique_ptr<RedirectingFileSystem> RedirectingFileSystem::create(2225 ArrayRef<std::pair<std::string, std::string>> RemappedFiles,2226 bool UseExternalNames, llvm::IntrusiveRefCntPtr<FileSystem> ExternalFS) {2227 std::unique_ptr<RedirectingFileSystem> FS(2228 new RedirectingFileSystem(ExternalFS));2229 FS->UseExternalNames = UseExternalNames;2230 2231 StringMap<RedirectingFileSystem::Entry *> Entries;2232 2233 for (auto &Mapping : llvm::reverse(RemappedFiles)) {2234 SmallString<128> From = StringRef(Mapping.first);2235 SmallString<128> To = StringRef(Mapping.second);2236 {2237 auto EC = ExternalFS->makeAbsolute(From);2238 (void)EC;2239 assert(!EC && "Could not make absolute path");2240 }2241 2242 // Check if we've already mapped this file. The first one we see (in the2243 // reverse iteration) wins.2244 RedirectingFileSystem::Entry *&ToEntry = Entries[From];2245 if (ToEntry)2246 continue;2247 2248 // Add parent directories.2249 RedirectingFileSystem::Entry *Parent = nullptr;2250 StringRef FromDirectory = llvm::sys::path::parent_path(From);2251 for (auto I = llvm::sys::path::begin(FromDirectory),2252 E = llvm::sys::path::end(FromDirectory);2253 I != E; ++I) {2254 Parent = RedirectingFileSystemParser::lookupOrCreateEntry(FS.get(), *I,2255 Parent);2256 }2257 assert(Parent && "File without a directory?");2258 {2259 auto EC = ExternalFS->makeAbsolute(To);2260 (void)EC;2261 assert(!EC && "Could not make absolute path");2262 }2263 2264 // Add the file.2265 auto NewFile = std::make_unique<RedirectingFileSystem::FileEntry>(2266 llvm::sys::path::filename(From), To,2267 UseExternalNames ? RedirectingFileSystem::NK_External2268 : RedirectingFileSystem::NK_Virtual);2269 ToEntry = NewFile.get();2270 cast<RedirectingFileSystem::DirectoryEntry>(Parent)->addContent(2271 std::move(NewFile));2272 }2273 2274 return FS;2275}2276 2277RedirectingFileSystem::LookupResult::LookupResult(2278 Entry *E, sys::path::const_iterator Start, sys::path::const_iterator End)2279 : E(E) {2280 assert(E != nullptr);2281 // If the matched entry is a DirectoryRemapEntry, set ExternalRedirect to the2282 // path of the directory it maps to in the external file system plus any2283 // remaining path components in the provided iterator.2284 if (auto *DRE = dyn_cast<RedirectingFileSystem::DirectoryRemapEntry>(E)) {2285 SmallString<256> Redirect(DRE->getExternalContentsPath());2286 sys::path::append(Redirect, Start, End,2287 getExistingStyle(DRE->getExternalContentsPath()));2288 ExternalRedirect = std::string(Redirect);2289 }2290}2291 2292void RedirectingFileSystem::LookupResult::getPath(2293 llvm::SmallVectorImpl<char> &Result) const {2294 Result.clear();2295 for (Entry *Parent : Parents)2296 llvm::sys::path::append(Result, Parent->getName());2297 llvm::sys::path::append(Result, E->getName());2298}2299 2300std::error_code RedirectingFileSystem::makeCanonicalForLookup(2301 SmallVectorImpl<char> &Path) const {2302 if (std::error_code EC = makeAbsolute(Path))2303 return EC;2304 2305 llvm::SmallString<256> CanonicalPath =2306 canonicalize(StringRef(Path.data(), Path.size()));2307 if (CanonicalPath.empty())2308 return make_error_code(llvm::errc::invalid_argument);2309 2310 Path.assign(CanonicalPath.begin(), CanonicalPath.end());2311 return {};2312}2313 2314ErrorOr<RedirectingFileSystem::LookupResult>2315RedirectingFileSystem::lookupPath(StringRef Path) const {2316 llvm::SmallString<128> CanonicalPath(Path);2317 if (std::error_code EC = makeCanonicalForLookup(CanonicalPath))2318 return EC;2319 2320 // RedirectOnly means the VFS is always used.2321 if (UsageTrackingActive && Redirection == RedirectKind::RedirectOnly)2322 HasBeenUsed = true;2323 2324 sys::path::const_iterator Start = sys::path::begin(CanonicalPath);2325 sys::path::const_iterator End = sys::path::end(CanonicalPath);2326 llvm::SmallVector<Entry *, 32> Entries;2327 for (const auto &Root : Roots) {2328 ErrorOr<RedirectingFileSystem::LookupResult> Result =2329 lookupPathImpl(Start, End, Root.get(), Entries);2330 if (UsageTrackingActive && Result && isa<RemapEntry>(Result->E))2331 HasBeenUsed = true;2332 if (Result || Result.getError() != llvm::errc::no_such_file_or_directory) {2333 Result->Parents = std::move(Entries);2334 return Result;2335 }2336 }2337 return make_error_code(llvm::errc::no_such_file_or_directory);2338}2339 2340ErrorOr<RedirectingFileSystem::LookupResult>2341RedirectingFileSystem::lookupPathImpl(2342 sys::path::const_iterator Start, sys::path::const_iterator End,2343 RedirectingFileSystem::Entry *From,2344 llvm::SmallVectorImpl<Entry *> &Entries) const {2345 assert(!isTraversalComponent(*Start) &&2346 !isTraversalComponent(From->getName()) &&2347 "Paths should not contain traversal components");2348 2349 StringRef FromName = From->getName();2350 2351 // Forward the search to the next component in case this is an empty one.2352 if (!FromName.empty()) {2353 if (!pathComponentMatches(*Start, FromName))2354 return make_error_code(llvm::errc::no_such_file_or_directory);2355 2356 ++Start;2357 2358 if (Start == End) {2359 // Match!2360 return LookupResult(From, Start, End);2361 }2362 }2363 2364 if (isa<RedirectingFileSystem::FileEntry>(From))2365 return make_error_code(llvm::errc::not_a_directory);2366 2367 if (isa<RedirectingFileSystem::DirectoryRemapEntry>(From))2368 return LookupResult(From, Start, End);2369 2370 auto *DE = cast<RedirectingFileSystem::DirectoryEntry>(From);2371 for (const std::unique_ptr<RedirectingFileSystem::Entry> &DirEntry :2372 llvm::make_range(DE->contents_begin(), DE->contents_end())) {2373 Entries.push_back(From);2374 ErrorOr<RedirectingFileSystem::LookupResult> Result =2375 lookupPathImpl(Start, End, DirEntry.get(), Entries);2376 if (Result || Result.getError() != llvm::errc::no_such_file_or_directory)2377 return Result;2378 Entries.pop_back();2379 }2380 2381 return make_error_code(llvm::errc::no_such_file_or_directory);2382}2383 2384static Status getRedirectedFileStatus(const Twine &OriginalPath,2385 bool UseExternalNames,2386 Status ExternalStatus) {2387 // The path has been mapped by some nested VFS and exposes an external path,2388 // don't override it with the original path.2389 if (ExternalStatus.ExposesExternalVFSPath)2390 return ExternalStatus;2391 2392 Status S = ExternalStatus;2393 if (!UseExternalNames)2394 S = Status::copyWithNewName(S, OriginalPath);2395 else2396 S.ExposesExternalVFSPath = true;2397 return S;2398}2399 2400ErrorOr<Status> RedirectingFileSystem::status(2401 const Twine &LookupPath, const Twine &OriginalPath,2402 const RedirectingFileSystem::LookupResult &Result) {2403 if (std::optional<StringRef> ExtRedirect = Result.getExternalRedirect()) {2404 SmallString<256> RemappedPath((*ExtRedirect).str());2405 if (std::error_code EC = makeAbsolute(RemappedPath))2406 return EC;2407 2408 ErrorOr<Status> S = ExternalFS->status(RemappedPath);2409 if (!S)2410 return S;2411 S = Status::copyWithNewName(*S, *ExtRedirect);2412 auto *RE = cast<RedirectingFileSystem::RemapEntry>(Result.E);2413 return getRedirectedFileStatus(OriginalPath,2414 RE->useExternalName(UseExternalNames), *S);2415 }2416 2417 auto *DE = cast<RedirectingFileSystem::DirectoryEntry>(Result.E);2418 return Status::copyWithNewName(DE->getStatus(), LookupPath);2419}2420 2421ErrorOr<Status>2422RedirectingFileSystem::getExternalStatus(const Twine &LookupPath,2423 const Twine &OriginalPath) const {2424 auto Result = ExternalFS->status(LookupPath);2425 2426 // The path has been mapped by some nested VFS, don't override it with the2427 // original path.2428 if (!Result || Result->ExposesExternalVFSPath)2429 return Result;2430 return Status::copyWithNewName(Result.get(), OriginalPath);2431}2432 2433ErrorOr<Status> RedirectingFileSystem::status(const Twine &OriginalPath) {2434 SmallString<256> Path;2435 OriginalPath.toVector(Path);2436 2437 if (std::error_code EC = makeAbsolute(Path))2438 return EC;2439 2440 if (Redirection == RedirectKind::Fallback) {2441 // Attempt to find the original file first, only falling back to the2442 // mapped file if that fails.2443 ErrorOr<Status> S = getExternalStatus(Path, OriginalPath);2444 if (S)2445 return S;2446 }2447 2448 ErrorOr<RedirectingFileSystem::LookupResult> Result = lookupPath(Path);2449 if (!Result) {2450 // Was not able to map file, fallthrough to using the original path if2451 // that was the specified redirection type.2452 if (Redirection == RedirectKind::Fallthrough &&2453 isFileNotFound(Result.getError()))2454 return getExternalStatus(Path, OriginalPath);2455 return Result.getError();2456 }2457 2458 ErrorOr<Status> S = status(Path, OriginalPath, *Result);2459 if (!S && Redirection == RedirectKind::Fallthrough &&2460 isFileNotFound(S.getError(), Result->E)) {2461 // Mapped the file but it wasn't found in the underlying filesystem,2462 // fallthrough to using the original path if that was the specified2463 // redirection type.2464 return getExternalStatus(Path, OriginalPath);2465 }2466 2467 return S;2468}2469 2470bool RedirectingFileSystem::exists(const Twine &OriginalPath) {2471 SmallString<256> Path;2472 OriginalPath.toVector(Path);2473 2474 if (makeAbsolute(Path))2475 return false;2476 2477 if (Redirection == RedirectKind::Fallback) {2478 // Attempt to find the original file first, only falling back to the2479 // mapped file if that fails.2480 if (ExternalFS->exists(Path))2481 return true;2482 }2483 2484 ErrorOr<RedirectingFileSystem::LookupResult> Result = lookupPath(Path);2485 if (!Result) {2486 // Was not able to map file, fallthrough to using the original path if2487 // that was the specified redirection type.2488 if (Redirection == RedirectKind::Fallthrough &&2489 isFileNotFound(Result.getError()))2490 return ExternalFS->exists(Path);2491 return false;2492 }2493 2494 std::optional<StringRef> ExtRedirect = Result->getExternalRedirect();2495 if (!ExtRedirect) {2496 assert(isa<RedirectingFileSystem::DirectoryEntry>(Result->E));2497 return true;2498 }2499 2500 SmallString<256> RemappedPath((*ExtRedirect).str());2501 if (makeAbsolute(RemappedPath))2502 return false;2503 2504 if (ExternalFS->exists(RemappedPath))2505 return true;2506 2507 if (Redirection == RedirectKind::Fallthrough) {2508 // Mapped the file but it wasn't found in the underlying filesystem,2509 // fallthrough to using the original path if that was the specified2510 // redirection type.2511 return ExternalFS->exists(Path);2512 }2513 2514 return false;2515}2516 2517namespace {2518 2519/// Provide a file wrapper with an overriden status.2520class FileWithFixedStatus : public File {2521 std::unique_ptr<File> InnerFile;2522 Status S;2523 2524public:2525 FileWithFixedStatus(std::unique_ptr<File> InnerFile, Status S)2526 : InnerFile(std::move(InnerFile)), S(std::move(S)) {}2527 2528 ErrorOr<Status> status() override { return S; }2529 ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>2530 2531 getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator,2532 bool IsVolatile) override {2533 return InnerFile->getBuffer(Name, FileSize, RequiresNullTerminator,2534 IsVolatile);2535 }2536 2537 std::error_code close() override { return InnerFile->close(); }2538 2539 void setPath(const Twine &Path) override { S = S.copyWithNewName(S, Path); }2540};2541 2542} // namespace2543 2544ErrorOr<std::unique_ptr<File>>2545File::getWithPath(ErrorOr<std::unique_ptr<File>> Result, const Twine &P) {2546 // See \c getRedirectedFileStatus - don't update path if it's exposing an2547 // external path.2548 if (!Result || (*Result)->status()->ExposesExternalVFSPath)2549 return Result;2550 2551 ErrorOr<std::unique_ptr<File>> F = std::move(*Result);2552 auto Name = F->get()->getName();2553 if (Name && Name.get() != P.str())2554 F->get()->setPath(P);2555 return F;2556}2557 2558ErrorOr<std::unique_ptr<File>>2559RedirectingFileSystem::openFileForRead(const Twine &OriginalPath) {2560 SmallString<256> Path;2561 OriginalPath.toVector(Path);2562 2563 if (std::error_code EC = makeAbsolute(Path))2564 return EC;2565 2566 if (Redirection == RedirectKind::Fallback) {2567 // Attempt to find the original file first, only falling back to the2568 // mapped file if that fails.2569 auto F = File::getWithPath(ExternalFS->openFileForRead(Path), OriginalPath);2570 if (F)2571 return F;2572 }2573 2574 ErrorOr<RedirectingFileSystem::LookupResult> Result = lookupPath(Path);2575 if (!Result) {2576 // Was not able to map file, fallthrough to using the original path if2577 // that was the specified redirection type.2578 if (Redirection == RedirectKind::Fallthrough &&2579 isFileNotFound(Result.getError()))2580 return File::getWithPath(ExternalFS->openFileForRead(Path), OriginalPath);2581 return Result.getError();2582 }2583 2584 if (!Result->getExternalRedirect()) // FIXME: errc::not_a_file?2585 return make_error_code(llvm::errc::invalid_argument);2586 2587 StringRef ExtRedirect = *Result->getExternalRedirect();2588 SmallString<256> RemappedPath(ExtRedirect.str());2589 if (std::error_code EC = makeAbsolute(RemappedPath))2590 return EC;2591 2592 auto *RE = cast<RedirectingFileSystem::RemapEntry>(Result->E);2593 2594 auto ExternalFile =2595 File::getWithPath(ExternalFS->openFileForRead(RemappedPath), ExtRedirect);2596 if (!ExternalFile) {2597 if (Redirection == RedirectKind::Fallthrough &&2598 isFileNotFound(ExternalFile.getError(), Result->E)) {2599 // Mapped the file but it wasn't found in the underlying filesystem,2600 // fallthrough to using the original path if that was the specified2601 // redirection type.2602 return File::getWithPath(ExternalFS->openFileForRead(Path), OriginalPath);2603 }2604 return ExternalFile;2605 }2606 2607 auto ExternalStatus = (*ExternalFile)->status();2608 if (!ExternalStatus)2609 return ExternalStatus.getError();2610 2611 // Otherwise, the file was successfully remapped. Mark it as such. Also2612 // replace the underlying path if the external name is being used.2613 Status S = getRedirectedFileStatus(2614 OriginalPath, RE->useExternalName(UseExternalNames), *ExternalStatus);2615 return std::unique_ptr<File>(2616 std::make_unique<FileWithFixedStatus>(std::move(*ExternalFile), S));2617}2618 2619std::error_code2620RedirectingFileSystem::getRealPath(const Twine &OriginalPath,2621 SmallVectorImpl<char> &Output) {2622 SmallString<256> Path;2623 OriginalPath.toVector(Path);2624 2625 if (std::error_code EC = makeAbsolute(Path))2626 return EC;2627 2628 if (Redirection == RedirectKind::Fallback) {2629 // Attempt to find the original file first, only falling back to the2630 // mapped file if that fails.2631 std::error_code EC = ExternalFS->getRealPath(Path, Output);2632 if (!EC)2633 return EC;2634 }2635 2636 ErrorOr<RedirectingFileSystem::LookupResult> Result = lookupPath(Path);2637 if (!Result) {2638 // Was not able to map file, fallthrough to using the original path if2639 // that was the specified redirection type.2640 if (Redirection == RedirectKind::Fallthrough &&2641 isFileNotFound(Result.getError()))2642 return ExternalFS->getRealPath(Path, Output);2643 return Result.getError();2644 }2645 2646 // If we found FileEntry or DirectoryRemapEntry, look up the mapped2647 // path in the external file system.2648 if (auto ExtRedirect = Result->getExternalRedirect()) {2649 auto P = ExternalFS->getRealPath(*ExtRedirect, Output);2650 if (P && Redirection == RedirectKind::Fallthrough &&2651 isFileNotFound(P, Result->E)) {2652 // Mapped the file but it wasn't found in the underlying filesystem,2653 // fallthrough to using the original path if that was the specified2654 // redirection type.2655 return ExternalFS->getRealPath(Path, Output);2656 }2657 return P;2658 }2659 2660 // We found a DirectoryEntry, which does not have a single external contents2661 // path. Use the canonical virtual path.2662 if (Redirection == RedirectKind::Fallthrough) {2663 Result->getPath(Output);2664 return {};2665 }2666 return llvm::errc::invalid_argument;2667}2668 2669std::unique_ptr<FileSystem>2670vfs::getVFSFromYAML(std::unique_ptr<MemoryBuffer> Buffer,2671 SourceMgr::DiagHandlerTy DiagHandler,2672 StringRef YAMLFilePath, void *DiagContext,2673 IntrusiveRefCntPtr<FileSystem> ExternalFS) {2674 return RedirectingFileSystem::create(std::move(Buffer), DiagHandler,2675 YAMLFilePath, DiagContext,2676 std::move(ExternalFS));2677}2678 2679static void getVFSEntries(RedirectingFileSystem::Entry *SrcE,2680 SmallVectorImpl<StringRef> &Path,2681 SmallVectorImpl<YAMLVFSEntry> &Entries) {2682 auto Kind = SrcE->getKind();2683 if (Kind == RedirectingFileSystem::EK_Directory) {2684 auto *DE = dyn_cast<RedirectingFileSystem::DirectoryEntry>(SrcE);2685 assert(DE && "Must be a directory");2686 for (std::unique_ptr<RedirectingFileSystem::Entry> &SubEntry :2687 llvm::make_range(DE->contents_begin(), DE->contents_end())) {2688 Path.push_back(SubEntry->getName());2689 getVFSEntries(SubEntry.get(), Path, Entries);2690 Path.pop_back();2691 }2692 return;2693 }2694 2695 if (Kind == RedirectingFileSystem::EK_DirectoryRemap) {2696 auto *DR = dyn_cast<RedirectingFileSystem::DirectoryRemapEntry>(SrcE);2697 assert(DR && "Must be a directory remap");2698 SmallString<128> VPath;2699 for (auto &Comp : Path)2700 llvm::sys::path::append(VPath, Comp);2701 Entries.push_back(2702 YAMLVFSEntry(VPath.c_str(), DR->getExternalContentsPath()));2703 return;2704 }2705 2706 assert(Kind == RedirectingFileSystem::EK_File && "Must be a EK_File");2707 auto *FE = dyn_cast<RedirectingFileSystem::FileEntry>(SrcE);2708 assert(FE && "Must be a file");2709 SmallString<128> VPath;2710 for (auto &Comp : Path)2711 llvm::sys::path::append(VPath, Comp);2712 Entries.push_back(YAMLVFSEntry(VPath.c_str(), FE->getExternalContentsPath()));2713}2714 2715void vfs::collectVFSEntries(RedirectingFileSystem &VFS,2716 SmallVectorImpl<YAMLVFSEntry> &CollectedEntries) {2717 ErrorOr<RedirectingFileSystem::LookupResult> RootResult = VFS.lookupPath("/");2718 if (!RootResult)2719 return;2720 SmallVector<StringRef, 8> Components;2721 Components.push_back("/");2722 getVFSEntries(RootResult->E, Components, CollectedEntries);2723}2724 2725UniqueID vfs::getNextVirtualUniqueID() {2726 static std::atomic<unsigned> UID;2727 unsigned ID = ++UID;2728 // The following assumes that uint64_t max will never collide with a real2729 // dev_t value from the OS.2730 return UniqueID(std::numeric_limits<uint64_t>::max(), ID);2731}2732 2733void YAMLVFSWriter::addEntry(StringRef VirtualPath, StringRef RealPath,2734 bool IsDirectory) {2735 assert(sys::path::is_absolute(VirtualPath) && "virtual path not absolute");2736 assert(sys::path::is_absolute(RealPath) && "real path not absolute");2737 assert(!pathHasTraversal(VirtualPath) && "path traversal is not supported");2738 Mappings.emplace_back(VirtualPath, RealPath, IsDirectory);2739}2740 2741void YAMLVFSWriter::addFileMapping(StringRef VirtualPath, StringRef RealPath) {2742 addEntry(VirtualPath, RealPath, /*IsDirectory=*/false);2743}2744 2745void YAMLVFSWriter::addDirectoryMapping(StringRef VirtualPath,2746 StringRef RealPath) {2747 addEntry(VirtualPath, RealPath, /*IsDirectory=*/true);2748}2749 2750namespace {2751 2752class JSONWriter {2753 llvm::raw_ostream &OS;2754 SmallVector<StringRef, 16> DirStack;2755 2756 unsigned getDirIndent() { return 4 * DirStack.size(); }2757 unsigned getFileIndent() { return 4 * (DirStack.size() + 1); }2758 bool containedIn(StringRef Parent, StringRef Path);2759 StringRef containedPart(StringRef Parent, StringRef Path);2760 void startDirectory(StringRef Path);2761 void endDirectory();2762 void writeEntry(StringRef VPath, StringRef RPath);2763 2764public:2765 JSONWriter(llvm::raw_ostream &OS) : OS(OS) {}2766 2767 void write(ArrayRef<YAMLVFSEntry> Entries,2768 std::optional<bool> UseExternalNames,2769 std::optional<bool> IsCaseSensitive,2770 std::optional<bool> IsOverlayRelative, StringRef OverlayDir);2771};2772 2773} // namespace2774 2775bool JSONWriter::containedIn(StringRef Parent, StringRef Path) {2776 using namespace llvm::sys;2777 2778 // Compare each path component.2779 auto IParent = path::begin(Parent), EParent = path::end(Parent);2780 for (auto IChild = path::begin(Path), EChild = path::end(Path);2781 IParent != EParent && IChild != EChild; ++IParent, ++IChild) {2782 if (*IParent != *IChild)2783 return false;2784 }2785 // Have we exhausted the parent path?2786 return IParent == EParent;2787}2788 2789StringRef JSONWriter::containedPart(StringRef Parent, StringRef Path) {2790 assert(!Parent.empty());2791 assert(containedIn(Parent, Path));2792 return Path.substr(Parent.size() + 1);2793}2794 2795void JSONWriter::startDirectory(StringRef Path) {2796 StringRef Name =2797 DirStack.empty() ? Path : containedPart(DirStack.back(), Path);2798 DirStack.push_back(Path);2799 unsigned Indent = getDirIndent();2800 OS.indent(Indent) << "{\n";2801 OS.indent(Indent + 2) << "'type': 'directory',\n";2802 OS.indent(Indent + 2) << "'name': \"" << llvm::yaml::escape(Name) << "\",\n";2803 OS.indent(Indent + 2) << "'contents': [\n";2804}2805 2806void JSONWriter::endDirectory() {2807 unsigned Indent = getDirIndent();2808 OS.indent(Indent + 2) << "]\n";2809 OS.indent(Indent) << "}";2810 2811 DirStack.pop_back();2812}2813 2814void JSONWriter::writeEntry(StringRef VPath, StringRef RPath) {2815 unsigned Indent = getFileIndent();2816 OS.indent(Indent) << "{\n";2817 OS.indent(Indent + 2) << "'type': 'file',\n";2818 OS.indent(Indent + 2) << "'name': \"" << llvm::yaml::escape(VPath) << "\",\n";2819 OS.indent(Indent + 2) << "'external-contents': \""2820 << llvm::yaml::escape(RPath) << "\"\n";2821 OS.indent(Indent) << "}";2822}2823 2824void JSONWriter::write(ArrayRef<YAMLVFSEntry> Entries,2825 std::optional<bool> UseExternalNames,2826 std::optional<bool> IsCaseSensitive,2827 std::optional<bool> IsOverlayRelative,2828 StringRef OverlayDir) {2829 using namespace llvm::sys;2830 2831 OS << "{\n"2832 " 'version': 0,\n";2833 if (IsCaseSensitive)2834 OS << " 'case-sensitive': '" << (*IsCaseSensitive ? "true" : "false")2835 << "',\n";2836 if (UseExternalNames)2837 OS << " 'use-external-names': '" << (*UseExternalNames ? "true" : "false")2838 << "',\n";2839 bool UseOverlayRelative = false;2840 if (IsOverlayRelative) {2841 UseOverlayRelative = *IsOverlayRelative;2842 OS << " 'overlay-relative': '" << (UseOverlayRelative ? "true" : "false")2843 << "',\n";2844 }2845 OS << " 'roots': [\n";2846 2847 if (!Entries.empty()) {2848 const YAMLVFSEntry &Entry = Entries.front();2849 2850 startDirectory(2851 Entry.IsDirectory ? Entry.VPath : path::parent_path(Entry.VPath)2852 );2853 2854 StringRef RPath = Entry.RPath;2855 if (UseOverlayRelative) {2856 assert(RPath.starts_with(OverlayDir) &&2857 "Overlay dir must be contained in RPath");2858 RPath = RPath.substr(OverlayDir.size());2859 }2860 2861 bool IsCurrentDirEmpty = true;2862 if (!Entry.IsDirectory) {2863 writeEntry(path::filename(Entry.VPath), RPath);2864 IsCurrentDirEmpty = false;2865 }2866 2867 for (const auto &Entry : Entries.slice(1)) {2868 StringRef Dir =2869 Entry.IsDirectory ? Entry.VPath : path::parent_path(Entry.VPath);2870 if (Dir == DirStack.back()) {2871 if (!IsCurrentDirEmpty) {2872 OS << ",\n";2873 }2874 } else {2875 bool IsDirPoppedFromStack = false;2876 while (!DirStack.empty() && !containedIn(DirStack.back(), Dir)) {2877 OS << "\n";2878 endDirectory();2879 IsDirPoppedFromStack = true;2880 }2881 if (IsDirPoppedFromStack || !IsCurrentDirEmpty) {2882 OS << ",\n";2883 }2884 startDirectory(Dir);2885 IsCurrentDirEmpty = true;2886 }2887 StringRef RPath = Entry.RPath;2888 if (UseOverlayRelative) {2889 assert(RPath.starts_with(OverlayDir) &&2890 "Overlay dir must be contained in RPath");2891 RPath = RPath.substr(OverlayDir.size());2892 }2893 if (!Entry.IsDirectory) {2894 writeEntry(path::filename(Entry.VPath), RPath);2895 IsCurrentDirEmpty = false;2896 }2897 }2898 2899 while (!DirStack.empty()) {2900 OS << "\n";2901 endDirectory();2902 }2903 OS << "\n";2904 }2905 2906 OS << " ]\n"2907 << "}\n";2908}2909 2910void YAMLVFSWriter::write(llvm::raw_ostream &OS) {2911 llvm::sort(Mappings, [](const YAMLVFSEntry &LHS, const YAMLVFSEntry &RHS) {2912 return LHS.VPath < RHS.VPath;2913 });2914 2915 JSONWriter(OS).write(Mappings, UseExternalNames, IsCaseSensitive,2916 IsOverlayRelative, OverlayDir);2917}2918 2919vfs::recursive_directory_iterator::recursive_directory_iterator(2920 FileSystem &FS_, const Twine &Path, std::error_code &EC)2921 : FS(&FS_) {2922 directory_iterator I = FS->dir_begin(Path, EC);2923 if (I != directory_iterator()) {2924 State = std::make_shared<detail::RecDirIterState>();2925 State->Stack.push_back(I);2926 }2927}2928 2929vfs::recursive_directory_iterator &2930recursive_directory_iterator::increment(std::error_code &EC) {2931 assert(FS && State && !State->Stack.empty() && "incrementing past end");2932 assert(!State->Stack.back()->path().empty() && "non-canonical end iterator");2933 vfs::directory_iterator End;2934 2935 if (State->HasNoPushRequest)2936 State->HasNoPushRequest = false;2937 else {2938 if (State->Stack.back()->type() == sys::fs::file_type::directory_file) {2939 vfs::directory_iterator I =2940 FS->dir_begin(State->Stack.back()->path(), EC);2941 if (I != End) {2942 State->Stack.push_back(I);2943 return *this;2944 }2945 }2946 }2947 2948 while (!State->Stack.empty() && State->Stack.back().increment(EC) == End)2949 State->Stack.pop_back();2950 2951 if (State->Stack.empty())2952 State.reset(); // end iterator2953 2954 return *this;2955}2956 2957void TracingFileSystem::printImpl(raw_ostream &OS, PrintType Type,2958 unsigned IndentLevel) const {2959 printIndent(OS, IndentLevel);2960 OS << "TracingFileSystem\n";2961 if (Type == PrintType::Summary)2962 return;2963 2964 printIndent(OS, IndentLevel);2965 OS << "NumStatusCalls=" << NumStatusCalls << "\n";2966 printIndent(OS, IndentLevel);2967 OS << "NumOpenFileForReadCalls=" << NumOpenFileForReadCalls << "\n";2968 printIndent(OS, IndentLevel);2969 OS << "NumDirBeginCalls=" << NumDirBeginCalls << "\n";2970 printIndent(OS, IndentLevel);2971 OS << "NumGetRealPathCalls=" << NumGetRealPathCalls << "\n";2972 printIndent(OS, IndentLevel);2973 OS << "NumExistsCalls=" << NumExistsCalls << "\n";2974 printIndent(OS, IndentLevel);2975 OS << "NumIsLocalCalls=" << NumIsLocalCalls << "\n";2976 2977 if (Type == PrintType::Contents)2978 Type = PrintType::Summary;2979 getUnderlyingFS().print(OS, Type, IndentLevel + 1);2980}2981 2982const char FileSystem::ID = 0;2983const char OverlayFileSystem::ID = 0;2984const char ProxyFileSystem::ID = 0;2985const char InMemoryFileSystem::ID = 0;2986const char RedirectingFileSystem::ID = 0;2987const char TracingFileSystem::ID = 0;2988