diff --git a/clang/test/VFS/Inputs/vfsoverlay-directory-relative.yaml b/clang/test/VFS/Inputs/vfsoverlay-directory-relative.yaml new file mode 100644 --- /dev/null +++ b/clang/test/VFS/Inputs/vfsoverlay-directory-relative.yaml @@ -0,0 +1,11 @@ +{ + 'version': 0, + 'fallthrough': true, + 'overlay-relative': true, + 'roots': [ + { 'name': 'OUT_DIR', + 'type': 'directory-remap', + 'external-contents': 'INPUT_DIR' + } + ] +} diff --git a/clang/test/VFS/Inputs/vfsoverlay-directory.yaml b/clang/test/VFS/Inputs/vfsoverlay-directory.yaml new file mode 100644 --- /dev/null +++ b/clang/test/VFS/Inputs/vfsoverlay-directory.yaml @@ -0,0 +1,10 @@ +{ + 'version': 0, + 'fallthrough': true, + 'roots': [ + { 'name': 'OUT_DIR', + 'type': 'directory-remap', + 'external-contents': 'INPUT_DIR' + } + ] +} diff --git a/clang/test/VFS/directory.c b/clang/test/VFS/directory.c new file mode 100644 --- /dev/null +++ b/clang/test/VFS/directory.c @@ -0,0 +1,48 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t/Underlying +// RUN: mkdir -p %t/Overlay +// RUN: mkdir -p %t/Middle +// RUN: echo '// B.h in Underlying' > %t/Underlying/B.h +// RUN: echo '#ifdef NESTED' >> %t/Underlying/B.h +// RUN: echo '#include "C.h"' >> %t/Underlying/B.h +// RUN: echo '#endif' >> %t/Underlying/B.h +// RUN: echo '// C.h in Underlying' > %t/Underlying/C.h +// RUN: echo '// C.h in Middle' > %t/Middle/C.h +// RUN: echo '// C.h in Overlay' > %t/Overlay/C.h + +// 1) Underlying -> Overlay (C.h found, B.h falling back to Underlying) +// RUN: sed -e "s@INPUT_DIR@%{/t:regex_replacement}/Overlay@g" -e "s@OUT_DIR@%{/t:regex_replacement}/Underlying@g" %S/Inputs/vfsoverlay-directory.yaml > %t/vfs.yaml +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=DIRECT %s +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -fsyntax-only -DNESTED -E -C %s 2>&1 | FileCheck --check-prefix=DIRECT %s +// RUN: sed -e "s@INPUT_DIR@Overlay@g" -e "s@OUT_DIR@%{/t:regex_replacement}/Underlying@g" %S/Inputs/vfsoverlay-directory-relative.yaml > %t/vfs-relative.yaml +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs-relative.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=DIRECT %s + +// DIRECT: {{^}}// B.h in Underlying +// DIRECT: {{^}}// C.h in Overlay + +// 2) Underlying -> Middle -> Overlay (C.h found, B.h falling back to Underlying) +// RUN: sed -e "s@INPUT_DIR@%{/t:regex_replacement}/Overlay@g" -e "s@OUT_DIR@%{/t:regex_replacement}/Middle@g" %S/Inputs/vfsoverlay-directory.yaml > %t/vfs.yaml +// RUN: sed -e "s@INPUT_DIR@%{/t:regex_replacement}/Middle@g" -e "s@OUT_DIR@%{/t:regex_replacement}/Underlying@g" %S/Inputs/vfsoverlay-directory.yaml > %t/vfs2.yaml +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -ivfsoverlay %t/vfs2.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=DIRECT %s +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -ivfsoverlay %t/vfs2.yaml -DNESTED -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=DIRECT %s + +// Same as direct above + +// 3) Underlying -> Middle -> Overlay (C.h falling back to Middle, B.h falling back to Underlying) +// RUN: rm -f %t/Overlay/C.h +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -ivfsoverlay %t/vfs2.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=FALLBACK %s + +// FALLBACK: {{^}}// B.h in Underlying +// FALLBACK: {{^}}// C.h in Middle + +// 3) Underlying -> Middle -> Overlay (C.h falling back to Underlying, B.h falling back to Underlying) +// RUN: rm -f %t/Middle/C.h +// RUN: %clang_cc1 -Werror -I %t/Underlying -ivfsoverlay %t/vfs.yaml -ivfsoverlay %t/vfs2.yaml -fsyntax-only -E -C %s 2>&1 | FileCheck --check-prefix=FALLBACK2 %s + +// FALLBACK2: {{^}}// B.h in Underlying +// FALLBACK2: {{^}}// C.h in Underlying + +#include "B.h" +#ifndef NESTED +#include "C.h" +#endif diff --git a/lldb/source/Host/common/FileSystem.cpp b/lldb/source/Host/common/FileSystem.cpp --- a/lldb/source/Host/common/FileSystem.cpp +++ b/lldb/source/Host/common/FileSystem.cpp @@ -478,20 +478,18 @@ return path.str(); // If VFS mapped we know the underlying FS is a RedirectingFileSystem. - ErrorOr E = + ErrorOr Result = static_cast(*m_fs).lookupPath(path.str()); - if (!E) { - if (E.getError() == llvm::errc::no_such_file_or_directory) { + if (!Result) { + if (Result.getError() == llvm::errc::no_such_file_or_directory) { return path.str(); } - return E.getError(); + return Result.getError(); } - auto *F = dyn_cast(*E); - if (!F) - return make_error_code(llvm::errc::not_supported); - - return F->getExternalContentsPath().str(); + if (Optional ExtRedirect = Result->getExternalRedirect()) + return std::string(*ExtRedirect); + return make_error_code(llvm::errc::not_supported); } ErrorOr FileSystem::GetExternalPath(const FileSpec &file_spec) { diff --git a/llvm/include/llvm/Support/VirtualFileSystem.h b/llvm/include/llvm/Support/VirtualFileSystem.h --- a/llvm/include/llvm/Support/VirtualFileSystem.h +++ b/llvm/include/llvm/Support/VirtualFileSystem.h @@ -521,8 +521,10 @@ /// A virtual file system parsed from a YAML file. /// -/// Currently, this class allows creating virtual directories and mapping -/// virtual file paths to existing external files, available in \c ExternalFS. +/// Currently, this class allows creating virtual files and directories. Virtual +/// files map to existing external files in \c ExternalFS, and virtual +/// directories may either map to existing directories in \c ExternalFS or list +/// their contents in the form of other virtual directories and/or files. /// /// The basic structure of the parsed file is: /// \verbatim @@ -541,7 +543,7 @@ /// 'overlay-relative': /// 'fallthrough': /// -/// Virtual directories are represented as +/// Virtual directories that list their contents are represented as /// \verbatim /// { /// 'type': 'directory', @@ -550,7 +552,7 @@ /// } /// \endverbatim /// -/// The default attributes for virtual directories are: +/// The default attributes for such virtual directories are: /// \verbatim /// MTime = now() when created /// Perms = 0777 @@ -559,24 +561,45 @@ /// UniqueID = unspecified unique value /// \endverbatim /// +/// When a path prefix matches such a directory, the next component in the path +/// is matched against the entries in the 'contents' array. +/// +/// Re-mapped directories, on the other hand, are represented as +/// /// \verbatim +/// { +/// 'type': 'directory-remap', +/// 'name': , +/// 'use-external-name': , # Optional +/// 'external-contents': +/// } +/// \endverbatim +/// +/// and inherit their attributes from the external directory. When a path +/// prefix matches such an entry, the unmatched components are appended to the +/// 'external-contents' path, and the resulting path is looked up in the +/// external file system instead. +/// /// Re-mapped files are represented as /// \verbatim /// { /// 'type': 'file', /// 'name': , -/// 'use-external-name': # Optional +/// 'use-external-name': , # Optional /// 'external-contents': /// } /// \endverbatim /// -/// and inherit their attributes from the external contents. +/// Their attributes and file contents are determined by looking up the file at +/// their 'external-contents' path in the external file system. /// -/// In both cases, the 'name' field may contain multiple path components (e.g. -/// /path/to/file). However, any directory that contains more than one child -/// must be uniquely represented by a directory entry. +/// For 'file', 'directory' and 'directory-remap' entries the 'name' field may +/// contain multiple path components (e.g. /path/to/file). However, any +/// directory in such a path that contains more than one child must be uniquely +/// represented by a 'directory' entry. class RedirectingFileSystem : public vfs::FileSystem { public: - enum EntryKind { EK_Directory, EK_File }; + enum EntryKind { EK_Directory, EK_DirectoryRemap, EK_File }; + enum NameKind { NK_NotSet, NK_External, NK_Virtual }; /// A single file or directory in the VFS. class Entry { @@ -591,6 +614,7 @@ EntryKind getKind() const { return Kind; } }; + /// A directory in the vfs with explicitly specified contents. class DirectoryEntry : public Entry { std::vector> Contents; Status S; @@ -622,22 +646,22 @@ static bool classof(const Entry *E) { return E->getKind() == EK_Directory; } }; - class FileEntry : public Entry { - public: - enum NameKind { NK_NotSet, NK_External, NK_Virtual }; - - private: + /// A file or directory in the vfs that is mapped to a file or directory in + /// the external filesystem. + class RemapEntry : public Entry { std::string ExternalContentsPath; NameKind UseName; - public: - FileEntry(StringRef Name, StringRef ExternalContentsPath, NameKind UseName) - : Entry(EK_File, Name), ExternalContentsPath(ExternalContentsPath), + protected: + RemapEntry(EntryKind K, StringRef Name, StringRef ExternalContentsPath, + NameKind UseName) + : Entry(K, Name), ExternalContentsPath(ExternalContentsPath), UseName(UseName) {} + public: StringRef getExternalContentsPath() const { return ExternalContentsPath; } - /// whether to use the external path as the name for this file. + /// Whether to use the external path as the name for this file or directory. bool useExternalName(bool GlobalUseExternalName) const { return UseName == NK_NotSet ? GlobalUseExternalName : (UseName == NK_External); @@ -645,9 +669,67 @@ NameKind getUseName() const { return UseName; } + static bool classof(const Entry *E) { + switch (E->getKind()) { + case EK_DirectoryRemap: + LLVM_FALLTHROUGH; + case EK_File: + return true; + case EK_Directory: + return false; + } + } + }; + + /// A directory in the vfs that maps to a directory in the external file + /// system. + class DirectoryRemapEntry : public RemapEntry { + public: + DirectoryRemapEntry(StringRef Name, StringRef ExternalContentsPath, + NameKind UseName) + : RemapEntry(EK_DirectoryRemap, Name, ExternalContentsPath, UseName) {} + + static bool classof(const Entry *E) { + return E->getKind() == EK_DirectoryRemap; + } + }; + + /// A file in the vfs that maps to a file in the external file system. + class FileEntry : public RemapEntry { + public: + FileEntry(StringRef Name, StringRef ExternalContentsPath, NameKind UseName) + : RemapEntry(EK_File, Name, ExternalContentsPath, UseName) {} + static bool classof(const Entry *E) { return E->getKind() == EK_File; } }; + /// Represents the result of a path lookup into the RedirectingFileSystem. + struct LookupResult { + /// The entry the looked-up path corresponds to. + Entry *E; + + private: + /// When the found Entry is a DirectoryRemapEntry, stores the path in the + /// external file system that the looked-up path in the virtual file system + // corresponds to. + Optional ExternalRedirect; + + public: + LookupResult(Entry *E, sys::path::const_iterator Start, + sys::path::const_iterator End); + + /// If the found Entry maps the the input path to a path in the external + /// file system (i.e. it is a FileEntry or DirectoryRemapEntry), returns + /// that path. + Optional getExternalRedirect() const { + if (isa(E)) + return StringRef(*ExternalRedirect); + if (auto *FE = dyn_cast(E)) + return FE->getExternalContentsPath(); + return None; + } + }; + private: friend class RedirectingFSDirIterImpl; friend class RedirectingFileSystemParser; @@ -660,8 +742,8 @@ std::error_code makeCanonical(SmallVectorImpl &Path) const; /// Whether to fall back to the external file system when an operation fails - /// with the given error code. - bool shouldFallBackToExternalFS(std::error_code EC) const; + /// with the given error code on a path associated with the provided Entry. + bool shouldFallBackToExternalFS(std::error_code EC, Entry *E = nullptr) const; // In a RedirectingFileSystem, keys can be specified in Posix or Windows // style (or even a mixture of both), so this comparison helper allows @@ -716,18 +798,22 @@ RedirectingFileSystem(IntrusiveRefCntPtr ExternalFS); - /// Looks up the path [Start, End) in \p From, possibly - /// recursing into the contents of \p From if it is a directory. - ErrorOr lookupPath(llvm::sys::path::const_iterator Start, - llvm::sys::path::const_iterator End, - Entry *From) const; + /// Looks up the path [Start, End) in \p From, possibly recursing + /// into the contents of \p From if it is a directory. Returns a LookupResult + /// giving the matched entry and, if that entry is a FileEntry or + /// DirectoryRemapEntry, the path it redirects to in the external file system. + ErrorOr lookupPathImpl(llvm::sys::path::const_iterator Start, + llvm::sys::path::const_iterator End, + Entry *From) const; - /// Get the status of a given an \c Entry. - ErrorOr status(const Twine &Path, Entry *E); + /// Get the status for a path with the provided \c LookupResult. + ErrorOr status(const Twine &Path, const LookupResult &Result); public: - /// Looks up \p Path in \c Roots. - ErrorOr lookupPath(StringRef Path) const; + /// Looks up \p Path in \c Roots and returns a LookupResult giving the + /// matched entry and, if the entry was a FileEntry or DirectoryRemapEntry, + /// the path it redirects to in the external file system. + ErrorOr lookupPath(StringRef Path) const; /// Parses \p Buffer, which is expected to be in YAML format and /// returns a virtual file system representing its contents. diff --git a/llvm/lib/Support/VirtualFileSystem.cpp b/llvm/lib/Support/VirtualFileSystem.cpp --- a/llvm/lib/Support/VirtualFileSystem.cpp +++ b/llvm/lib/Support/VirtualFileSystem.cpp @@ -1011,14 +1011,20 @@ namespace { -/// Removes leading "./" as well as path components like ".." and ".". -static llvm::SmallString<256> canonicalize(llvm::StringRef Path) { - // First detect the path style in use by checking the first separator. +static llvm::sys::path::Style getExistingStyle(llvm::StringRef Path) { + // Detect the path style in use by checking the first separator. llvm::sys::path::Style style = llvm::sys::path::Style::native; const size_t n = Path.find_first_of("/\\"); if (n != static_cast(-1)) style = (Path[n] == '/') ? llvm::sys::path::Style::posix : llvm::sys::path::Style::windows; + return style; +} + +/// Removes leading "./" as well as path components like ".." and ".". +static llvm::SmallString<256> canonicalize(llvm::StringRef Path) { + // First detect the path style in use by checking the first separator. + llvm::sys::path::Style style = getExistingStyle(Path); // Now remove the dots. Explicitly specifying the path style prevents the // direction of the slashes from changing. @@ -1057,6 +1063,8 @@ sys::fs::file_type Type = sys::fs::file_type::type_unknown; switch ((*Current)->getKind()) { case RedirectingFileSystem::EK_Directory: + LLVM_FALLTHROUGH; + case RedirectingFileSystem::EK_DirectoryRemap: Type = sys::fs::file_type::directory_file; break; case RedirectingFileSystem::EK_File: @@ -1083,6 +1091,45 @@ } }; +/// Directory iterator implementation for \c RedirectingFileSystem's +/// directory remap entries that maps the paths reported by the external +/// file system's directory iterator back to the virtual directory's path. +class RedirectingFSDirRemapIterImpl : public llvm::vfs::detail::DirIterImpl { + std::string Dir; + llvm::sys::path::Style DirStyle; + llvm::vfs::directory_iterator ExternalIter; + +public: + RedirectingFSDirRemapIterImpl(std::string DirPath, + llvm::vfs::directory_iterator ExtIter) + : Dir(std::move(DirPath)), DirStyle(getExistingStyle(Dir)), + ExternalIter(ExtIter) { + if (ExternalIter != llvm::vfs::directory_iterator()) + setCurrentEntry(); + } + + void setCurrentEntry() { + StringRef ExternalPath = ExternalIter->path(); + llvm::sys::path::Style ExternalStyle = getExistingStyle(ExternalPath); + StringRef File = llvm::sys::path::filename(ExternalPath, ExternalStyle); + + SmallString<128> NewPath(Dir); + llvm::sys::path::append(NewPath, DirStyle, File); + + CurrentEntry = directory_entry(std::string(NewPath), ExternalIter->type()); + } + + std::error_code increment() override { + std::error_code EC; + ExternalIter.increment(EC); + if (!EC && ExternalIter != llvm::vfs::directory_iterator()) + setCurrentEntry(); + else + CurrentEntry = directory_entry(); + return EC; + } +}; + llvm::ErrorOr RedirectingFileSystem::getCurrentWorkingDirectory() const { return WorkingDirectory; @@ -1151,15 +1198,19 @@ if (EC) return {}; - ErrorOr E = lookupPath(Path); - if (!E) { - EC = E.getError(); + ErrorOr Result = lookupPath(Path); + if (!Result) { + EC = Result.getError(); if (shouldFallBackToExternalFS(EC)) return ExternalFS->dir_begin(Path, EC); return {}; } - ErrorOr S = status(Path, *E); + + // Use status to make sure the path exists and refers to a directory. + ErrorOr S = status(Path, *Result); if (!S) { + if (shouldFallBackToExternalFS(S.getError(), Result->E)) + return ExternalFS->dir_begin(Dir, EC); EC = S.getError(); return {}; } @@ -1169,9 +1220,24 @@ return {}; } - auto *D = cast(*E); - auto DirIter = directory_iterator(std::make_shared( - Path, D->contents_begin(), D->contents_end(), EC)); + // Create the appropriate directory iterator based on whether we found a + // DirectoryRemapEntry or DirectoryEntry. + directory_iterator DirIter; + if (auto ExtRedirect = Result->getExternalRedirect()) { + auto RE = cast(Result->E); + DirIter = ExternalFS->dir_begin(*ExtRedirect, EC); + + if (!RE->useExternalName(UseExternalNames)) { + // Update the paths in the results to use the virtual directory's path. + DirIter = + directory_iterator(std::make_shared( + std::string(Path), DirIter)); + } + } else { + auto DE = cast(Result->E); + DirIter = directory_iterator(std::make_shared( + Path, DE->contents_begin(), DE->contents_end(), EC)); + } if (!shouldUseExternalFS()) return DirIter; @@ -1360,6 +1426,15 @@ uniqueOverlayTree(FS, SubEntry.get(), NewParentE); break; } + case RedirectingFileSystem::EK_DirectoryRemap: { + assert(NewParentE && "Parent entry must exist"); + auto *DR = cast(SrcE); + auto *DE = cast(NewParentE); + DE->addContent( + std::make_unique( + Name, DR->getExternalContentsPath(), DR->getUseName())); + break; + } case RedirectingFileSystem::EK_File: { assert(NewParentE && "Parent entry must exist"); auto *FE = cast(SrcE); @@ -1389,13 +1464,13 @@ DenseMap Keys(std::begin(Fields), std::end(Fields)); - bool HasContents = false; // external or otherwise + enum { CF_NotSet, CF_List, CF_External } ContentsField = CF_NotSet; std::vector> EntryArrayContents; SmallString<256> ExternalContentsPath; SmallString<256> Name; yaml::Node *NameValueNode = nullptr; - auto UseExternalName = RedirectingFileSystem::FileEntry::NK_NotSet; + auto UseExternalName = RedirectingFileSystem::NK_NotSet; RedirectingFileSystem::EntryKind Kind; for (auto &I : *M) { @@ -1425,17 +1500,19 @@ Kind = RedirectingFileSystem::EK_File; else if (Value == "directory") Kind = RedirectingFileSystem::EK_Directory; + else if (Value == "directory-remap") + Kind = RedirectingFileSystem::EK_DirectoryRemap; else { error(I.getValue(), "unknown value for 'type'"); return nullptr; } } else if (Key == "contents") { - if (HasContents) { + if (ContentsField != CF_NotSet) { error(I.getKey(), "entry already has 'contents' or 'external-contents'"); return nullptr; } - HasContents = true; + ContentsField = CF_List; auto *Contents = dyn_cast(I.getValue()); if (!Contents) { // FIXME: this is only for directories, what about files? @@ -1451,12 +1528,12 @@ return nullptr; } } else if (Key == "external-contents") { - if (HasContents) { + if (ContentsField != CF_NotSet) { error(I.getKey(), "entry already has 'contents' or 'external-contents'"); return nullptr; } - HasContents = true; + ContentsField = CF_External; if (!parseScalarString(I.getValue(), Value, Buffer)) return nullptr; @@ -1478,8 +1555,8 @@ bool Val; if (!parseScalarBool(I.getValue(), Val)) return nullptr; - UseExternalName = Val ? RedirectingFileSystem::FileEntry::NK_External - : RedirectingFileSystem::FileEntry::NK_Virtual; + UseExternalName = Val ? RedirectingFileSystem::NK_External + : RedirectingFileSystem::NK_Virtual; } else { llvm_unreachable("key missing from Keys"); } @@ -1489,7 +1566,7 @@ return nullptr; // check for missing keys - if (!HasContents) { + if (ContentsField == CF_NotSet) { error(N, "missing key 'contents' or 'external-contents'"); return nullptr; } @@ -1498,8 +1575,14 @@ // check invalid configuration if (Kind == RedirectingFileSystem::EK_Directory && - UseExternalName != RedirectingFileSystem::FileEntry::NK_NotSet) { - error(N, "'use-external-name' is not supported for directories"); + UseExternalName != RedirectingFileSystem::NK_NotSet) { + error(N, "'use-external-name' is not supported for 'directory' entries"); + return nullptr; + } + + if (Kind == RedirectingFileSystem::EK_DirectoryRemap && + ContentsField == CF_List) { + error(N, "'contents' is not supported for 'directory-remap' entries"); return nullptr; } @@ -1535,6 +1618,10 @@ Result = std::make_unique( LastComponent, std::move(ExternalContentsPath), UseExternalName); break; + case RedirectingFileSystem::EK_DirectoryRemap: + Result = std::make_unique( + LastComponent, std::move(ExternalContentsPath), UseExternalName); + break; case RedirectingFileSystem::EK_Directory: Result = std::make_unique( LastComponent, std::move(EntryArrayContents), @@ -1745,8 +1832,8 @@ // Add the file. auto NewFile = std::make_unique( llvm::sys::path::filename(From), To, - UseExternalNames ? RedirectingFileSystem::FileEntry::NK_External - : RedirectingFileSystem::FileEntry::NK_Virtual); + UseExternalNames ? RedirectingFileSystem::NK_External + : RedirectingFileSystem::NK_Virtual); ToEntry = NewFile.get(); cast(Parent)->addContent( std::move(NewFile)); @@ -1755,8 +1842,25 @@ return FS; } +RedirectingFileSystem::LookupResult::LookupResult( + Entry *E, sys::path::const_iterator Start, sys::path::const_iterator End) + : E(E) { + assert(E != nullptr); + // If the matched entry is a DirectoryRemapEntry, set ExternalRedirect to the + // path of the directory it maps to in the external file system plus any + // remaining path components in the provided iterator. + if (auto *DRE = dyn_cast(E)) { + SmallString<256> Redirect(DRE->getExternalContentsPath()); + sys::path::append(Redirect, Start, End, + getExistingStyle(DRE->getExternalContentsPath())); + ExternalRedirect = std::string(Redirect); + } +} + bool RedirectingFileSystem::shouldFallBackToExternalFS( - std::error_code EC) const { + std::error_code EC, RedirectingFileSystem::Entry *E) const { + if (E && !isa(E)) + return false; return shouldUseExternalFS() && EC == llvm::errc::no_such_file_or_directory; } @@ -1774,23 +1878,23 @@ return {}; } -ErrorOr +ErrorOr RedirectingFileSystem::lookupPath(StringRef Path) const { sys::path::const_iterator Start = sys::path::begin(Path); sys::path::const_iterator End = sys::path::end(Path); for (const auto &Root : Roots) { - ErrorOr Result = - lookupPath(Start, End, Root.get()); + ErrorOr Result = + lookupPathImpl(Start, End, Root.get()); if (Result || Result.getError() != llvm::errc::no_such_file_or_directory) return Result; } return make_error_code(llvm::errc::no_such_file_or_directory); } -ErrorOr -RedirectingFileSystem::lookupPath(sys::path::const_iterator Start, - sys::path::const_iterator End, - RedirectingFileSystem::Entry *From) const { +ErrorOr +RedirectingFileSystem::lookupPathImpl( + sys::path::const_iterator Start, sys::path::const_iterator End, + RedirectingFileSystem::Entry *From) const { assert(!isTraversalComponent(*Start) && !isTraversalComponent(From->getName()) && "Paths should not contain traversal components"); @@ -1806,18 +1910,21 @@ if (Start == End) { // Match! - return From; + return LookupResult(From, Start, End); } } - auto *DE = dyn_cast(From); - if (!DE) + if (isa(From)) return make_error_code(llvm::errc::not_a_directory); + if (isa(From)) + return LookupResult(From, Start, End); + + auto *DE = cast(From); for (const std::unique_ptr &DirEntry : llvm::make_range(DE->contents_begin(), DE->contents_end())) { - ErrorOr Result = - lookupPath(Start, End, DirEntry.get()); + ErrorOr Result = + lookupPathImpl(Start, End, DirEntry.get()); if (Result || Result.getError() != llvm::errc::no_such_file_or_directory) return Result; } @@ -1834,20 +1941,19 @@ return S; } -ErrorOr RedirectingFileSystem::status(const Twine &Path, - RedirectingFileSystem::Entry *E) { - assert(E != nullptr); - if (auto *F = dyn_cast(E)) { - ErrorOr S = ExternalFS->status(F->getExternalContentsPath()); - assert(!S || S->getName() == F->getExternalContentsPath()); - if (S) - return getRedirectedFileStatus(Path, F->useExternalName(UseExternalNames), - *S); - return S; - } else { // directory - auto *DE = cast(E); - return Status::copyWithNewName(DE->getStatus(), Path); +ErrorOr RedirectingFileSystem::status( + const Twine &Path, const RedirectingFileSystem::LookupResult &Result) { + if (Optional ExtRedirect = Result.getExternalRedirect()) { + ErrorOr S = ExternalFS->status(*ExtRedirect); + if (!S) + return S; + auto *RE = cast(Result.E); + return getRedirectedFileStatus(Path, RE->useExternalName(UseExternalNames), + *S); } + + auto *DE = cast(Result.E); + return Status::copyWithNewName(DE->getStatus(), Path); } ErrorOr RedirectingFileSystem::status(const Twine &Path_) { @@ -1857,13 +1963,17 @@ if (std::error_code EC = makeCanonical(Path)) return EC; - ErrorOr Result = lookupPath(Path); + ErrorOr Result = lookupPath(Path); if (!Result) { if (shouldFallBackToExternalFS(Result.getError())) return ExternalFS->status(Path); return Result.getError(); } - return status(Path, *Result); + + ErrorOr S = status(Path, *Result); + if (!S && shouldFallBackToExternalFS(S.getError(), Result->E)) + S = ExternalFS->status(Path); + return S; } namespace { @@ -1899,30 +2009,35 @@ if (std::error_code EC = makeCanonical(Path)) return EC; - ErrorOr E = lookupPath(Path); - if (!E) { - if (shouldFallBackToExternalFS(E.getError())) + ErrorOr Result = lookupPath(Path); + if (!Result) { + if (shouldFallBackToExternalFS(Result.getError())) return ExternalFS->openFileForRead(Path); - return E.getError(); + return Result.getError(); } - auto *F = dyn_cast(*E); - if (!F) // FIXME: errc::not_a_file? + if (!Result->getExternalRedirect()) // FIXME: errc::not_a_file? return make_error_code(llvm::errc::invalid_argument); - auto Result = ExternalFS->openFileForRead(F->getExternalContentsPath()); - if (!Result) - return Result; + StringRef ExtRedirect = *Result->getExternalRedirect(); + auto *RE = cast(Result->E); - auto ExternalStatus = (*Result)->status(); + auto ExternalFile = ExternalFS->openFileForRead(ExtRedirect); + if (!ExternalFile) { + if (shouldFallBackToExternalFS(ExternalFile.getError(), Result->E)) + return ExternalFS->openFileForRead(Path); + return ExternalFile; + } + + auto ExternalStatus = (*ExternalFile)->status(); if (!ExternalStatus) return ExternalStatus.getError(); // FIXME: Update the status with the name and VFSMapped. - Status S = getRedirectedFileStatus(Path, F->useExternalName(UseExternalNames), - *ExternalStatus); + Status S = getRedirectedFileStatus( + Path, RE->useExternalName(UseExternalNames), *ExternalStatus); return std::unique_ptr( - std::make_unique(std::move(*Result), S)); + std::make_unique(std::move(*ExternalFile), S)); } std::error_code @@ -1934,17 +2049,24 @@ if (std::error_code EC = makeCanonical(Path)) return EC; - ErrorOr Result = lookupPath(Path); + ErrorOr Result = lookupPath(Path); if (!Result) { if (shouldFallBackToExternalFS(Result.getError())) return ExternalFS->getRealPath(Path, Output); return Result.getError(); } - if (auto *F = dyn_cast(*Result)) { - return ExternalFS->getRealPath(F->getExternalContentsPath(), Output); + // If we found FileEntry or DirectoryRemapEntry, look up the mapped + // path in the external file system. + if (auto ExtRedirect = Result->getExternalRedirect()) { + auto P = ExternalFS->getRealPath(*ExtRedirect, Output); + if (!P && shouldFallBackToExternalFS(P, Result->E)) { + return ExternalFS->getRealPath(Path, Output); + } + return P; } - // Even if there is a directory entry, fall back to ExternalFS if allowed, + + // If we found a DirectoryEntry, still fall back to ExternalFS if allowed, // because directories don't have a single external contents path. return shouldUseExternalFS() ? ExternalFS->getRealPath(Path, Output) : llvm::errc::invalid_argument; @@ -1976,6 +2098,17 @@ return; } + if (Kind == RedirectingFileSystem::EK_DirectoryRemap) { + auto *DR = dyn_cast(SrcE); + assert(DR && "Must be a directory remap"); + SmallString<128> VPath; + for (auto &Comp : Path) + llvm::sys::path::append(VPath, Comp); + Entries.push_back( + YAMLVFSEntry(VPath.c_str(), DR->getExternalContentsPath())); + return; + } + assert(Kind == RedirectingFileSystem::EK_File && "Must be a EK_File"); auto *FE = dyn_cast(SrcE); assert(FE && "Must be a file"); @@ -1994,12 +2127,13 @@ std::unique_ptr VFS = RedirectingFileSystem::create( std::move(Buffer), DiagHandler, YAMLFilePath, DiagContext, std::move(ExternalFS)); - ErrorOr RootE = VFS->lookupPath("/"); - if (!RootE) + ErrorOr RootResult = + VFS->lookupPath("/"); + if (!RootResult) return; SmallVector Components; Components.push_back("/"); - getVFSEntries(*RootE, Components, CollectedEntries); + getVFSEntries(RootResult->E, Components, CollectedEntries); } UniqueID vfs::getNextVirtualUniqueID() { diff --git a/llvm/unittests/Support/VirtualFileSystemTest.cpp b/llvm/unittests/Support/VirtualFileSystemTest.cpp --- a/llvm/unittests/Support/VirtualFileSystemTest.cpp +++ b/llvm/unittests/Support/VirtualFileSystemTest.cpp @@ -1328,6 +1328,7 @@ TEST_F(VFSFromYAMLTest, MappedFiles) { IntrusiveRefCntPtr Lower(new DummyFileSystem()); + Lower->addDirectory("//root/foo/bar"); Lower->addRegularFile("//root/foo/bar/a"); IntrusiveRefCntPtr FS = getFromYAMLString( "{ 'roots': [\n" @@ -1343,6 +1344,17 @@ " 'type': 'file',\n" " 'name': 'file2',\n" " 'external-contents': '//root/foo/b'\n" + " },\n" + " {\n" + " 'type': 'directory-remap',\n" + " 'name': 'mappeddir',\n" + " 'external-contents': '//root/foo/bar'\n" + " },\n" + " {\n" + " 'type': 'directory-remap',\n" + " 'name': 'mappeddir2',\n" + " 'use-external-name': false,\n" + " 'external-contents': '//root/foo/bar'\n" " }\n" " ]\n" "}\n" @@ -1380,12 +1392,221 @@ EXPECT_TRUE(S->isDirectory()); EXPECT_TRUE(S->equivalent(*O->status("//root/"))); // non-volatile UniqueID + // remapped directory + S = O->status("//root/mappeddir"); + ASSERT_FALSE(S.getError()); + EXPECT_TRUE(S->isDirectory()); + EXPECT_TRUE(S->IsVFSMapped); + EXPECT_TRUE(S->equivalent(*O->status("//root/foo/bar"))); + + SLower = O->status("//root/foo/bar"); + EXPECT_EQ("//root/foo/bar", SLower->getName()); + EXPECT_TRUE(S->equivalent(*SLower)); + EXPECT_FALSE(SLower->IsVFSMapped); + + // file in remapped directory + S = O->status("//root/mappeddir/a"); + ASSERT_FALSE(S.getError()); + ASSERT_FALSE(S->isDirectory()); + ASSERT_TRUE(S->IsVFSMapped); + ASSERT_EQ("//root/foo/bar/a", S->getName()); + + // file in remapped directory, with use-external-name=false + S = O->status("//root/mappeddir2/a"); + ASSERT_FALSE(S.getError()); + ASSERT_FALSE(S->isDirectory()); + ASSERT_TRUE(S->IsVFSMapped); + ASSERT_EQ("//root/mappeddir2/a", S->getName()); + + // file contents in remapped directory + OpenedF = O->openFileForRead("//root/mappeddir/a"); + ASSERT_FALSE(OpenedF.getError()); + OpenedS = (*OpenedF)->status(); + ASSERT_FALSE(OpenedS.getError()); + EXPECT_EQ("//root/foo/bar/a", OpenedS->getName()); + EXPECT_TRUE(OpenedS->IsVFSMapped); + + // file contents in remapped directory, with use-external-name=false + OpenedF = O->openFileForRead("//root/mappeddir2/a"); + ASSERT_FALSE(OpenedF.getError()); + OpenedS = (*OpenedF)->status(); + ASSERT_FALSE(OpenedS.getError()); + EXPECT_EQ("//root/mappeddir2/a", OpenedS->getName()); + EXPECT_TRUE(OpenedS->IsVFSMapped); + // broken mapping EXPECT_EQ(O->status("//root/file2").getError(), llvm::errc::no_such_file_or_directory); EXPECT_EQ(0, NumDiagnostics); } +TEST_F(VFSFromYAMLTest, MappedRoot) { + IntrusiveRefCntPtr Lower(new DummyFileSystem()); + Lower->addDirectory("//root/foo/bar"); + Lower->addRegularFile("//root/foo/bar/a"); + IntrusiveRefCntPtr FS = + getFromYAMLString("{ 'roots': [\n" + "{\n" + " 'type': 'directory-remap',\n" + " 'name': '//mappedroot/',\n" + " 'external-contents': '//root/foo/bar'\n" + "}\n" + "]\n" + "}", + Lower); + ASSERT_TRUE(FS.get() != nullptr); + + IntrusiveRefCntPtr O( + new vfs::OverlayFileSystem(Lower)); + O->pushOverlay(FS); + + // file + ErrorOr S = O->status("//mappedroot/a"); + ASSERT_FALSE(S.getError()); + EXPECT_EQ("//root/foo/bar/a", S->getName()); + EXPECT_TRUE(S->IsVFSMapped); + + ErrorOr SLower = O->status("//root/foo/bar/a"); + EXPECT_EQ("//root/foo/bar/a", SLower->getName()); + EXPECT_TRUE(S->equivalent(*SLower)); + EXPECT_FALSE(SLower->IsVFSMapped); + + // file after opening + auto OpenedF = O->openFileForRead("//mappedroot/a"); + ASSERT_FALSE(OpenedF.getError()); + auto OpenedS = (*OpenedF)->status(); + ASSERT_FALSE(OpenedS.getError()); + EXPECT_EQ("//root/foo/bar/a", OpenedS->getName()); + EXPECT_TRUE(OpenedS->IsVFSMapped); + + EXPECT_EQ(0, NumDiagnostics); +} + +TEST_F(VFSFromYAMLTest, RemappedDirectoryOverlay) { + IntrusiveRefCntPtr Lower(new DummyFileSystem()); + Lower->addDirectory("//root/foo"); + Lower->addRegularFile("//root/foo/a"); + Lower->addDirectory("//root/bar"); + Lower->addRegularFile("//root/bar/b"); + Lower->addRegularFile("//root/bar/c"); + IntrusiveRefCntPtr FS = + getFromYAMLString("{ 'roots': [\n" + "{\n" + " 'type': 'directory',\n" + " 'name': '//root/',\n" + " 'contents': [ {\n" + " 'type': 'directory-remap',\n" + " 'name': 'bar',\n" + " 'external-contents': '//root/foo'\n" + " }\n" + " ]\n" + "}]}", + Lower); + ASSERT_TRUE(FS.get() != nullptr); + + IntrusiveRefCntPtr O( + new vfs::OverlayFileSystem(Lower)); + O->pushOverlay(FS); + + ErrorOr S = O->status("//root/foo"); + ASSERT_FALSE(S.getError()); + + ErrorOr SS = O->status("//root/bar"); + ASSERT_FALSE(SS.getError()); + EXPECT_TRUE(S->equivalent(*SS)); + + std::error_code EC; + checkContents(O->dir_begin("//root/bar", EC), + {"//root/foo/a", "//root/bar/b", "//root/bar/c"}); + + Lower->addRegularFile("//root/foo/b"); + checkContents(O->dir_begin("//root/bar", EC), + {"//root/foo/a", "//root/foo/b", "//root/bar/c"}); + + EXPECT_EQ(0, NumDiagnostics); +} + +TEST_F(VFSFromYAMLTest, RemappedDirectoryOverlayNoExternalNames) { + IntrusiveRefCntPtr Lower(new DummyFileSystem()); + Lower->addDirectory("//root/foo"); + Lower->addRegularFile("//root/foo/a"); + Lower->addDirectory("//root/bar"); + Lower->addRegularFile("//root/bar/b"); + Lower->addRegularFile("//root/bar/c"); + IntrusiveRefCntPtr FS = + getFromYAMLString("{ 'use-external-names': false,\n" + " 'roots': [\n" + "{\n" + " 'type': 'directory',\n" + " 'name': '//root/',\n" + " 'contents': [ {\n" + " 'type': 'directory-remap',\n" + " 'name': 'bar',\n" + " 'external-contents': '//root/foo'\n" + " }\n" + " ]\n" + "}]}", + Lower); + ASSERT_TRUE(FS.get() != nullptr); + + ErrorOr S = FS->status("//root/foo"); + ASSERT_FALSE(S.getError()); + + ErrorOr SS = FS->status("//root/bar"); + ASSERT_FALSE(SS.getError()); + EXPECT_TRUE(S->equivalent(*SS)); + + std::error_code EC; + checkContents(FS->dir_begin("//root/bar", EC), + {"//root/bar/a", "//root/bar/b", "//root/bar/c"}); + + Lower->addRegularFile("//root/foo/b"); + checkContents(FS->dir_begin("//root/bar", EC), + {"//root/bar/a", "//root/bar/b", "//root/bar/c"}); + + EXPECT_EQ(0, NumDiagnostics); +} + +TEST_F(VFSFromYAMLTest, RemappedDirectoryOverlayNoFallthrough) { + IntrusiveRefCntPtr Lower(new DummyFileSystem()); + Lower->addDirectory("//root/foo"); + Lower->addRegularFile("//root/foo/a"); + Lower->addDirectory("//root/bar"); + Lower->addRegularFile("//root/bar/b"); + Lower->addRegularFile("//root/bar/c"); + IntrusiveRefCntPtr FS = + getFromYAMLString("{ 'fallthrough': false,\n" + " 'roots': [\n" + "{\n" + " 'type': 'directory',\n" + " 'name': '//root/',\n" + " 'contents': [ {\n" + " 'type': 'directory-remap',\n" + " 'name': 'bar',\n" + " 'external-contents': '//root/foo'\n" + " }\n" + " ]\n" + "}]}", + Lower); + ASSERT_TRUE(FS.get() != nullptr); + + ErrorOr S = Lower->status("//root/foo"); + ASSERT_FALSE(S.getError()); + + ErrorOr SS = FS->status("//root/bar"); + ASSERT_FALSE(SS.getError()); + EXPECT_TRUE(S->equivalent(*SS)); + + std::error_code EC; + checkContents(FS->dir_begin("//root/bar", EC), {"//root/foo/a"}); + + Lower->addRegularFile("//root/foo/b"); + checkContents(FS->dir_begin("//root/bar", EC), + {"//root/foo/a", "//root/foo/b"}); + + EXPECT_EQ(0, NumDiagnostics); +} + TEST_F(VFSFromYAMLTest, CaseInsensitive) { IntrusiveRefCntPtr Lower(new DummyFileSystem()); Lower->addRegularFile("//root/foo/bar/a"); @@ -1542,7 +1763,24 @@ EXPECT_EQ(nullptr, FS.get()); FS = getFromYAMLRawString("{ 'version':100000, 'roots':[] }", Lower); EXPECT_EQ(nullptr, FS.get()); - EXPECT_EQ(24, NumDiagnostics); + + // both 'external-contents' and 'contents' specified + Lower->addDirectory("//root/external/dir"); + FS = getFromYAMLString( + "{ 'roots':[ \n" + "{ 'type': 'directory', 'name': '//root/A', 'contents': [],\n" + " 'external-contents': '//root/external/dir'}]}", + Lower); + EXPECT_EQ(nullptr, FS.get()); + + // 'directory-remap' with 'contents' + FS = getFromYAMLString( + "{ 'roots':[ \n" + "{ 'type': 'directory-remap', 'name': '//root/A', 'contents': [] }]}", + Lower); + EXPECT_EQ(nullptr, FS.get()); + + EXPECT_EQ(26, NumDiagnostics); } TEST_F(VFSFromYAMLTest, UseExternalName) {