diff options
Diffstat (limited to 'contrib/llvm/tools/clang/lib/Basic/VirtualFileSystem.cpp')
-rw-r--r-- | contrib/llvm/tools/clang/lib/Basic/VirtualFileSystem.cpp | 1209 |
1 files changed, 1209 insertions, 0 deletions
diff --git a/contrib/llvm/tools/clang/lib/Basic/VirtualFileSystem.cpp b/contrib/llvm/tools/clang/lib/Basic/VirtualFileSystem.cpp new file mode 100644 index 0000000..a5c83b8 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Basic/VirtualFileSystem.cpp @@ -0,0 +1,1209 @@ +//===- VirtualFileSystem.cpp - Virtual File System Layer --------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// This file implements the VirtualFileSystem interface. +//===----------------------------------------------------------------------===// + +#include "clang/Basic/VirtualFileSystem.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/iterator_range.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/YAMLParser.h" +#include <atomic> +#include <memory> + +using namespace clang; +using namespace clang::vfs; +using namespace llvm; +using llvm::sys::fs::file_status; +using llvm::sys::fs::file_type; +using llvm::sys::fs::perms; +using llvm::sys::fs::UniqueID; + +Status::Status(const file_status &Status) + : UID(Status.getUniqueID()), MTime(Status.getLastModificationTime()), + User(Status.getUser()), Group(Status.getGroup()), Size(Status.getSize()), + Type(Status.type()), Perms(Status.permissions()), IsVFSMapped(false) {} + +Status::Status(StringRef Name, StringRef ExternalName, UniqueID UID, + sys::TimeValue MTime, uint32_t User, uint32_t Group, + uint64_t Size, file_type Type, perms Perms) + : Name(Name), UID(UID), MTime(MTime), User(User), Group(Group), Size(Size), + Type(Type), Perms(Perms), IsVFSMapped(false) {} + +bool Status::equivalent(const Status &Other) const { + return getUniqueID() == Other.getUniqueID(); +} +bool Status::isDirectory() const { + return Type == file_type::directory_file; +} +bool Status::isRegularFile() const { + return Type == file_type::regular_file; +} +bool Status::isOther() const { + return exists() && !isRegularFile() && !isDirectory() && !isSymlink(); +} +bool Status::isSymlink() const { + return Type == file_type::symlink_file; +} +bool Status::isStatusKnown() const { + return Type != file_type::status_error; +} +bool Status::exists() const { + return isStatusKnown() && Type != file_type::file_not_found; +} + +File::~File() {} + +FileSystem::~FileSystem() {} + +std::error_code FileSystem::getBufferForFile( + const llvm::Twine &Name, std::unique_ptr<MemoryBuffer> &Result, + int64_t FileSize, bool RequiresNullTerminator, bool IsVolatile) { + std::unique_ptr<File> F; + if (std::error_code EC = openFileForRead(Name, F)) + return EC; + + std::error_code EC = + F->getBuffer(Name, Result, FileSize, RequiresNullTerminator, IsVolatile); + return EC; +} + +//===-----------------------------------------------------------------------===/ +// RealFileSystem implementation +//===-----------------------------------------------------------------------===/ + +namespace { +/// \brief Wrapper around a raw file descriptor. +class RealFile : public File { + int FD; + Status S; + friend class RealFileSystem; + RealFile(int FD) : FD(FD) { + assert(FD >= 0 && "Invalid or inactive file descriptor"); + } + +public: + ~RealFile(); + ErrorOr<Status> status() override; + std::error_code getBuffer(const Twine &Name, + std::unique_ptr<MemoryBuffer> &Result, + int64_t FileSize = -1, + bool RequiresNullTerminator = true, + bool IsVolatile = false) override; + std::error_code close() override; + void setName(StringRef Name) override; +}; +} // end anonymous namespace +RealFile::~RealFile() { close(); } + +ErrorOr<Status> RealFile::status() { + assert(FD != -1 && "cannot stat closed file"); + if (!S.isStatusKnown()) { + file_status RealStatus; + if (std::error_code EC = sys::fs::status(FD, RealStatus)) + return EC; + Status NewS(RealStatus); + NewS.setName(S.getName()); + S = std::move(NewS); + } + return S; +} + +std::error_code RealFile::getBuffer(const Twine &Name, + std::unique_ptr<MemoryBuffer> &Result, + int64_t FileSize, + bool RequiresNullTerminator, + bool IsVolatile) { + assert(FD != -1 && "cannot get buffer for closed file"); + ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = + MemoryBuffer::getOpenFile(FD, Name.str().c_str(), FileSize, + RequiresNullTerminator, IsVolatile); + if (std::error_code EC = BufferOrErr.getError()) + return EC; + Result = std::move(BufferOrErr.get()); + return std::error_code(); +} + +// FIXME: This is terrible, we need this for ::close. +#if !defined(_MSC_VER) && !defined(__MINGW32__) +#include <unistd.h> +#include <sys/uio.h> +#else +#include <io.h> +#ifndef S_ISFIFO +#define S_ISFIFO(x) (0) +#endif +#endif +std::error_code RealFile::close() { + if (::close(FD)) + return std::error_code(errno, std::generic_category()); + FD = -1; + return std::error_code(); +} + +void RealFile::setName(StringRef Name) { + S.setName(Name); +} + +namespace { +/// \brief The file system according to your operating system. +class RealFileSystem : public FileSystem { +public: + ErrorOr<Status> status(const Twine &Path) override; + std::error_code openFileForRead(const Twine &Path, + std::unique_ptr<File> &Result) override; + directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override; +}; +} // end anonymous namespace + +ErrorOr<Status> RealFileSystem::status(const Twine &Path) { + sys::fs::file_status RealStatus; + if (std::error_code EC = sys::fs::status(Path, RealStatus)) + return EC; + Status Result(RealStatus); + Result.setName(Path.str()); + return Result; +} + +std::error_code RealFileSystem::openFileForRead(const Twine &Name, + std::unique_ptr<File> &Result) { + int FD; + if (std::error_code EC = sys::fs::openFileForRead(Name, FD)) + return EC; + Result.reset(new RealFile(FD)); + Result->setName(Name.str()); + return std::error_code(); +} + +IntrusiveRefCntPtr<FileSystem> vfs::getRealFileSystem() { + static IntrusiveRefCntPtr<FileSystem> FS = new RealFileSystem(); + return FS; +} + +namespace { +class RealFSDirIter : public clang::vfs::detail::DirIterImpl { + std::string Path; + llvm::sys::fs::directory_iterator Iter; +public: + RealFSDirIter(const Twine &_Path, std::error_code &EC) + : Path(_Path.str()), Iter(Path, EC) { + if (!EC && Iter != llvm::sys::fs::directory_iterator()) { + llvm::sys::fs::file_status S; + EC = Iter->status(S); + if (!EC) { + CurrentEntry = Status(S); + CurrentEntry.setName(Iter->path()); + } + } + } + + std::error_code increment() override { + std::error_code EC; + Iter.increment(EC); + if (EC) { + return EC; + } else if (Iter == llvm::sys::fs::directory_iterator()) { + CurrentEntry = Status(); + } else { + llvm::sys::fs::file_status S; + EC = Iter->status(S); + CurrentEntry = Status(S); + CurrentEntry.setName(Iter->path()); + } + return EC; + } +}; +} + +directory_iterator RealFileSystem::dir_begin(const Twine &Dir, + std::error_code &EC) { + return directory_iterator(std::make_shared<RealFSDirIter>(Dir, EC)); +} + +//===-----------------------------------------------------------------------===/ +// OverlayFileSystem implementation +//===-----------------------------------------------------------------------===/ +OverlayFileSystem::OverlayFileSystem(IntrusiveRefCntPtr<FileSystem> BaseFS) { + pushOverlay(BaseFS); +} + +void OverlayFileSystem::pushOverlay(IntrusiveRefCntPtr<FileSystem> FS) { + FSList.push_back(FS); +} + +ErrorOr<Status> OverlayFileSystem::status(const Twine &Path) { + // FIXME: handle symlinks that cross file systems + for (iterator I = overlays_begin(), E = overlays_end(); I != E; ++I) { + ErrorOr<Status> Status = (*I)->status(Path); + if (Status || Status.getError() != llvm::errc::no_such_file_or_directory) + return Status; + } + return make_error_code(llvm::errc::no_such_file_or_directory); +} + +std::error_code +OverlayFileSystem::openFileForRead(const llvm::Twine &Path, + std::unique_ptr<File> &Result) { + // FIXME: handle symlinks that cross file systems + for (iterator I = overlays_begin(), E = overlays_end(); I != E; ++I) { + std::error_code EC = (*I)->openFileForRead(Path, Result); + if (!EC || EC != llvm::errc::no_such_file_or_directory) + return EC; + } + return make_error_code(llvm::errc::no_such_file_or_directory); +} + +clang::vfs::detail::DirIterImpl::~DirIterImpl() { } + +namespace { +class OverlayFSDirIterImpl : public clang::vfs::detail::DirIterImpl { + OverlayFileSystem &Overlays; + std::string Path; + OverlayFileSystem::iterator CurrentFS; + directory_iterator CurrentDirIter; + llvm::StringSet<> SeenNames; + + std::error_code incrementFS() { + assert(CurrentFS != Overlays.overlays_end() && "incrementing past end"); + ++CurrentFS; + for (auto E = Overlays.overlays_end(); CurrentFS != E; ++CurrentFS) { + std::error_code EC; + CurrentDirIter = (*CurrentFS)->dir_begin(Path, EC); + if (EC && EC != errc::no_such_file_or_directory) + return EC; + if (CurrentDirIter != directory_iterator()) + break; // found + } + return std::error_code(); + } + + std::error_code incrementDirIter(bool IsFirstTime) { + assert((IsFirstTime || CurrentDirIter != directory_iterator()) && + "incrementing past end"); + std::error_code EC; + if (!IsFirstTime) + CurrentDirIter.increment(EC); + if (!EC && CurrentDirIter == directory_iterator()) + EC = incrementFS(); + return EC; + } + + std::error_code incrementImpl(bool IsFirstTime) { + while (true) { + std::error_code EC = incrementDirIter(IsFirstTime); + if (EC || CurrentDirIter == directory_iterator()) { + CurrentEntry = Status(); + return EC; + } + CurrentEntry = *CurrentDirIter; + StringRef Name = llvm::sys::path::filename(CurrentEntry.getName()); + if (SeenNames.insert(Name)) + return EC; // name not seen before + } + llvm_unreachable("returned above"); + } + +public: + OverlayFSDirIterImpl(const Twine &Path, OverlayFileSystem &FS, + std::error_code &EC) + : Overlays(FS), Path(Path.str()), CurrentFS(Overlays.overlays_begin()) { + CurrentDirIter = (*CurrentFS)->dir_begin(Path, EC); + EC = incrementImpl(true); + } + + std::error_code increment() override { return incrementImpl(false); } +}; +} // end anonymous namespace + +directory_iterator OverlayFileSystem::dir_begin(const Twine &Dir, + std::error_code &EC) { + return directory_iterator( + std::make_shared<OverlayFSDirIterImpl>(Dir, *this, EC)); +} + +//===-----------------------------------------------------------------------===/ +// VFSFromYAML implementation +//===-----------------------------------------------------------------------===/ + +// Allow DenseMap<StringRef, ...>. This is useful below because we know all the +// strings are literals and will outlive the map, and there is no reason to +// store them. +namespace llvm { + template<> + struct DenseMapInfo<StringRef> { + // This assumes that "" will never be a valid key. + static inline StringRef getEmptyKey() { return StringRef(""); } + static inline StringRef getTombstoneKey() { return StringRef(); } + static unsigned getHashValue(StringRef Val) { return HashString(Val); } + static bool isEqual(StringRef LHS, StringRef RHS) { return LHS == RHS; } + }; +} + +namespace { + +enum EntryKind { + EK_Directory, + EK_File +}; + +/// \brief A single file or directory in the VFS. +class Entry { + EntryKind Kind; + std::string Name; + +public: + virtual ~Entry(); + Entry(EntryKind K, StringRef Name) : Kind(K), Name(Name) {} + StringRef getName() const { return Name; } + EntryKind getKind() const { return Kind; } +}; + +class DirectoryEntry : public Entry { + std::vector<Entry *> Contents; + Status S; + +public: + virtual ~DirectoryEntry(); + DirectoryEntry(StringRef Name, std::vector<Entry *> Contents, Status S) + : Entry(EK_Directory, Name), Contents(std::move(Contents)), + S(std::move(S)) {} + Status getStatus() { return S; } + typedef std::vector<Entry *>::iterator iterator; + iterator contents_begin() { return Contents.begin(); } + iterator contents_end() { return Contents.end(); } + 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: + std::string ExternalContentsPath; + NameKind UseName; +public: + FileEntry(StringRef Name, StringRef ExternalContentsPath, NameKind UseName) + : Entry(EK_File, Name), ExternalContentsPath(ExternalContentsPath), + UseName(UseName) {} + StringRef getExternalContentsPath() const { return ExternalContentsPath; } + /// \brief whether to use the external path as the name for this file. + bool useExternalName(bool GlobalUseExternalName) const { + return UseName == NK_NotSet ? GlobalUseExternalName + : (UseName == NK_External); + } + static bool classof(const Entry *E) { return E->getKind() == EK_File; } +}; + +class VFSFromYAML; + +class VFSFromYamlDirIterImpl : public clang::vfs::detail::DirIterImpl { + std::string Dir; + VFSFromYAML &FS; + DirectoryEntry::iterator Current, End; +public: + VFSFromYamlDirIterImpl(const Twine &Path, VFSFromYAML &FS, + DirectoryEntry::iterator Begin, + DirectoryEntry::iterator End, std::error_code &EC); + std::error_code increment() override; +}; + +/// \brief 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. +/// +/// The basic structure of the parsed file is: +/// \verbatim +/// { +/// 'version': <version number>, +/// <optional configuration> +/// 'roots': [ +/// <directory entries> +/// ] +/// } +/// \endverbatim +/// +/// All configuration options are optional. +/// 'case-sensitive': <boolean, default=true> +/// 'use-external-names': <boolean, default=true> +/// +/// Virtual directories are represented as +/// \verbatim +/// { +/// 'type': 'directory', +/// 'name': <string>, +/// 'contents': [ <file or directory entries> ] +/// } +/// \endverbatim +/// +/// The default attributes for virtual directories are: +/// \verbatim +/// MTime = now() when created +/// Perms = 0777 +/// User = Group = 0 +/// Size = 0 +/// UniqueID = unspecified unique value +/// \endverbatim +/// +/// Re-mapped files are represented as +/// \verbatim +/// { +/// 'type': 'file', +/// 'name': <string>, +/// 'use-external-name': <boolean> # Optional +/// 'external-contents': <path to external file>) +/// } +/// \endverbatim +/// +/// and inherit their attributes from the external contents. +/// +/// 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. +class VFSFromYAML : public vfs::FileSystem { + std::vector<Entry *> Roots; ///< The root(s) of the virtual file system. + /// \brief The file system to use for external references. + IntrusiveRefCntPtr<FileSystem> ExternalFS; + + /// @name Configuration + /// @{ + + /// \brief Whether to perform case-sensitive comparisons. + /// + /// Currently, case-insensitive matching only works correctly with ASCII. + bool CaseSensitive; + + /// \brief Whether to use to use the value of 'external-contents' for the + /// names of files. This global value is overridable on a per-file basis. + bool UseExternalNames; + /// @} + + friend class VFSFromYAMLParser; + +private: + VFSFromYAML(IntrusiveRefCntPtr<FileSystem> ExternalFS) + : ExternalFS(ExternalFS), CaseSensitive(true), UseExternalNames(true) {} + + /// \brief Looks up \p Path in \c Roots. + ErrorOr<Entry *> lookupPath(const Twine &Path); + + /// \brief Looks up the path <tt>[Start, End)</tt> in \p From, possibly + /// recursing into the contents of \p From if it is a directory. + ErrorOr<Entry *> lookupPath(sys::path::const_iterator Start, + sys::path::const_iterator End, Entry *From); + + /// \brief Get the status of a given an \c Entry. + ErrorOr<Status> status(const Twine &Path, Entry *E); + +public: + ~VFSFromYAML(); + + /// \brief Parses \p Buffer, which is expected to be in YAML format and + /// returns a virtual file system representing its contents. + /// + /// Takes ownership of \p Buffer. + static VFSFromYAML *create(MemoryBuffer *Buffer, + SourceMgr::DiagHandlerTy DiagHandler, + void *DiagContext, + IntrusiveRefCntPtr<FileSystem> ExternalFS); + + ErrorOr<Status> status(const Twine &Path) override; + std::error_code openFileForRead(const Twine &Path, + std::unique_ptr<File> &Result) override; + + directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override{ + ErrorOr<Entry *> E = lookupPath(Dir); + if (!E) { + EC = E.getError(); + return directory_iterator(); + } + ErrorOr<Status> S = status(Dir, *E); + if (!S) { + EC = S.getError(); + return directory_iterator(); + } + if (!S->isDirectory()) { + EC = std::error_code(static_cast<int>(errc::not_a_directory), + std::system_category()); + return directory_iterator(); + } + + DirectoryEntry *D = cast<DirectoryEntry>(*E); + return directory_iterator(std::make_shared<VFSFromYamlDirIterImpl>(Dir, + *this, D->contents_begin(), D->contents_end(), EC)); + } +}; + +/// \brief A helper class to hold the common YAML parsing state. +class VFSFromYAMLParser { + yaml::Stream &Stream; + + void error(yaml::Node *N, const Twine &Msg) { + Stream.printError(N, Msg); + } + + // false on error + bool parseScalarString(yaml::Node *N, StringRef &Result, + SmallVectorImpl<char> &Storage) { + yaml::ScalarNode *S = dyn_cast<yaml::ScalarNode>(N); + if (!S) { + error(N, "expected string"); + return false; + } + Result = S->getValue(Storage); + return true; + } + + // false on error + bool parseScalarBool(yaml::Node *N, bool &Result) { + SmallString<5> Storage; + StringRef Value; + if (!parseScalarString(N, Value, Storage)) + return false; + + if (Value.equals_lower("true") || Value.equals_lower("on") || + Value.equals_lower("yes") || Value == "1") { + Result = true; + return true; + } else if (Value.equals_lower("false") || Value.equals_lower("off") || + Value.equals_lower("no") || Value == "0") { + Result = false; + return true; + } + + error(N, "expected boolean value"); + return false; + } + + struct KeyStatus { + KeyStatus(bool Required=false) : Required(Required), Seen(false) {} + bool Required; + bool Seen; + }; + typedef std::pair<StringRef, KeyStatus> KeyStatusPair; + + // false on error + bool checkDuplicateOrUnknownKey(yaml::Node *KeyNode, StringRef Key, + DenseMap<StringRef, KeyStatus> &Keys) { + if (!Keys.count(Key)) { + error(KeyNode, "unknown key"); + return false; + } + KeyStatus &S = Keys[Key]; + if (S.Seen) { + error(KeyNode, Twine("duplicate key '") + Key + "'"); + return false; + } + S.Seen = true; + return true; + } + + // false on error + bool checkMissingKeys(yaml::Node *Obj, DenseMap<StringRef, KeyStatus> &Keys) { + for (DenseMap<StringRef, KeyStatus>::iterator I = Keys.begin(), + E = Keys.end(); + I != E; ++I) { + if (I->second.Required && !I->second.Seen) { + error(Obj, Twine("missing key '") + I->first + "'"); + return false; + } + } + return true; + } + + Entry *parseEntry(yaml::Node *N) { + yaml::MappingNode *M = dyn_cast<yaml::MappingNode>(N); + if (!M) { + error(N, "expected mapping node for file or directory entry"); + return nullptr; + } + + KeyStatusPair Fields[] = { + KeyStatusPair("name", true), + KeyStatusPair("type", true), + KeyStatusPair("contents", false), + KeyStatusPair("external-contents", false), + KeyStatusPair("use-external-name", false), + }; + + DenseMap<StringRef, KeyStatus> Keys( + &Fields[0], Fields + sizeof(Fields)/sizeof(Fields[0])); + + bool HasContents = false; // external or otherwise + std::vector<Entry *> EntryArrayContents; + std::string ExternalContentsPath; + std::string Name; + FileEntry::NameKind UseExternalName = FileEntry::NK_NotSet; + EntryKind Kind; + + for (yaml::MappingNode::iterator I = M->begin(), E = M->end(); I != E; + ++I) { + StringRef Key; + // Reuse the buffer for key and value, since we don't look at key after + // parsing value. + SmallString<256> Buffer; + if (!parseScalarString(I->getKey(), Key, Buffer)) + return nullptr; + + if (!checkDuplicateOrUnknownKey(I->getKey(), Key, Keys)) + return nullptr; + + StringRef Value; + if (Key == "name") { + if (!parseScalarString(I->getValue(), Value, Buffer)) + return nullptr; + Name = Value; + } else if (Key == "type") { + if (!parseScalarString(I->getValue(), Value, Buffer)) + return nullptr; + if (Value == "file") + Kind = EK_File; + else if (Value == "directory") + Kind = EK_Directory; + else { + error(I->getValue(), "unknown value for 'type'"); + return nullptr; + } + } else if (Key == "contents") { + if (HasContents) { + error(I->getKey(), + "entry already has 'contents' or 'external-contents'"); + return nullptr; + } + HasContents = true; + yaml::SequenceNode *Contents = + dyn_cast<yaml::SequenceNode>(I->getValue()); + if (!Contents) { + // FIXME: this is only for directories, what about files? + error(I->getValue(), "expected array"); + return nullptr; + } + + for (yaml::SequenceNode::iterator I = Contents->begin(), + E = Contents->end(); + I != E; ++I) { + if (Entry *E = parseEntry(&*I)) + EntryArrayContents.push_back(E); + else + return nullptr; + } + } else if (Key == "external-contents") { + if (HasContents) { + error(I->getKey(), + "entry already has 'contents' or 'external-contents'"); + return nullptr; + } + HasContents = true; + if (!parseScalarString(I->getValue(), Value, Buffer)) + return nullptr; + ExternalContentsPath = Value; + } else if (Key == "use-external-name") { + bool Val; + if (!parseScalarBool(I->getValue(), Val)) + return nullptr; + UseExternalName = Val ? FileEntry::NK_External : FileEntry::NK_Virtual; + } else { + llvm_unreachable("key missing from Keys"); + } + } + + if (Stream.failed()) + return nullptr; + + // check for missing keys + if (!HasContents) { + error(N, "missing key 'contents' or 'external-contents'"); + return nullptr; + } + if (!checkMissingKeys(N, Keys)) + return nullptr; + + // check invalid configuration + if (Kind == EK_Directory && UseExternalName != FileEntry::NK_NotSet) { + error(N, "'use-external-name' is not supported for directories"); + return nullptr; + } + + // Remove trailing slash(es), being careful not to remove the root path + StringRef Trimmed(Name); + size_t RootPathLen = sys::path::root_path(Trimmed).size(); + while (Trimmed.size() > RootPathLen && + sys::path::is_separator(Trimmed.back())) + Trimmed = Trimmed.slice(0, Trimmed.size()-1); + // Get the last component + StringRef LastComponent = sys::path::filename(Trimmed); + + Entry *Result = nullptr; + switch (Kind) { + case EK_File: + Result = new FileEntry(LastComponent, std::move(ExternalContentsPath), + UseExternalName); + break; + case EK_Directory: + Result = new DirectoryEntry(LastComponent, std::move(EntryArrayContents), + Status("", "", getNextVirtualUniqueID(), sys::TimeValue::now(), 0, 0, + 0, file_type::directory_file, sys::fs::all_all)); + break; + } + + StringRef Parent = sys::path::parent_path(Trimmed); + if (Parent.empty()) + return Result; + + // if 'name' contains multiple components, create implicit directory entries + for (sys::path::reverse_iterator I = sys::path::rbegin(Parent), + E = sys::path::rend(Parent); + I != E; ++I) { + Result = new DirectoryEntry(*I, llvm::makeArrayRef(Result), + Status("", "", getNextVirtualUniqueID(), sys::TimeValue::now(), 0, 0, + 0, file_type::directory_file, sys::fs::all_all)); + } + return Result; + } + +public: + VFSFromYAMLParser(yaml::Stream &S) : Stream(S) {} + + // false on error + bool parse(yaml::Node *Root, VFSFromYAML *FS) { + yaml::MappingNode *Top = dyn_cast<yaml::MappingNode>(Root); + if (!Top) { + error(Root, "expected mapping node"); + return false; + } + + KeyStatusPair Fields[] = { + KeyStatusPair("version", true), + KeyStatusPair("case-sensitive", false), + KeyStatusPair("use-external-names", false), + KeyStatusPair("roots", true), + }; + + DenseMap<StringRef, KeyStatus> Keys( + &Fields[0], Fields + sizeof(Fields)/sizeof(Fields[0])); + + // Parse configuration and 'roots' + for (yaml::MappingNode::iterator I = Top->begin(), E = Top->end(); I != E; + ++I) { + SmallString<10> KeyBuffer; + StringRef Key; + if (!parseScalarString(I->getKey(), Key, KeyBuffer)) + return false; + + if (!checkDuplicateOrUnknownKey(I->getKey(), Key, Keys)) + return false; + + if (Key == "roots") { + yaml::SequenceNode *Roots = dyn_cast<yaml::SequenceNode>(I->getValue()); + if (!Roots) { + error(I->getValue(), "expected array"); + return false; + } + + for (yaml::SequenceNode::iterator I = Roots->begin(), E = Roots->end(); + I != E; ++I) { + if (Entry *E = parseEntry(&*I)) + FS->Roots.push_back(E); + else + return false; + } + } else if (Key == "version") { + StringRef VersionString; + SmallString<4> Storage; + if (!parseScalarString(I->getValue(), VersionString, Storage)) + return false; + int Version; + if (VersionString.getAsInteger<int>(10, Version)) { + error(I->getValue(), "expected integer"); + return false; + } + if (Version < 0) { + error(I->getValue(), "invalid version number"); + return false; + } + if (Version != 0) { + error(I->getValue(), "version mismatch, expected 0"); + return false; + } + } else if (Key == "case-sensitive") { + if (!parseScalarBool(I->getValue(), FS->CaseSensitive)) + return false; + } else if (Key == "use-external-names") { + if (!parseScalarBool(I->getValue(), FS->UseExternalNames)) + return false; + } else { + llvm_unreachable("key missing from Keys"); + } + } + + if (Stream.failed()) + return false; + + if (!checkMissingKeys(Top, Keys)) + return false; + return true; + } +}; +} // end of anonymous namespace + +Entry::~Entry() {} +DirectoryEntry::~DirectoryEntry() { llvm::DeleteContainerPointers(Contents); } + +VFSFromYAML::~VFSFromYAML() { llvm::DeleteContainerPointers(Roots); } + +VFSFromYAML *VFSFromYAML::create(MemoryBuffer *Buffer, + SourceMgr::DiagHandlerTy DiagHandler, + void *DiagContext, + IntrusiveRefCntPtr<FileSystem> ExternalFS) { + + SourceMgr SM; + yaml::Stream Stream(Buffer, SM); + + SM.setDiagHandler(DiagHandler, DiagContext); + yaml::document_iterator DI = Stream.begin(); + yaml::Node *Root = DI->getRoot(); + if (DI == Stream.end() || !Root) { + SM.PrintMessage(SMLoc(), SourceMgr::DK_Error, "expected root node"); + return nullptr; + } + + VFSFromYAMLParser P(Stream); + + std::unique_ptr<VFSFromYAML> FS(new VFSFromYAML(ExternalFS)); + if (!P.parse(Root, FS.get())) + return nullptr; + + return FS.release(); +} + +ErrorOr<Entry *> VFSFromYAML::lookupPath(const Twine &Path_) { + SmallString<256> Path; + Path_.toVector(Path); + + // Handle relative paths + if (std::error_code EC = sys::fs::make_absolute(Path)) + return EC; + + if (Path.empty()) + return make_error_code(llvm::errc::invalid_argument); + + sys::path::const_iterator Start = sys::path::begin(Path); + sys::path::const_iterator End = sys::path::end(Path); + for (std::vector<Entry *>::iterator I = Roots.begin(), E = Roots.end(); + I != E; ++I) { + ErrorOr<Entry *> Result = lookupPath(Start, End, *I); + 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<Entry *> VFSFromYAML::lookupPath(sys::path::const_iterator Start, + sys::path::const_iterator End, + Entry *From) { + if (Start->equals(".")) + ++Start; + + // FIXME: handle .. + if (CaseSensitive ? !Start->equals(From->getName()) + : !Start->equals_lower(From->getName())) + // failure to match + return make_error_code(llvm::errc::no_such_file_or_directory); + + ++Start; + + if (Start == End) { + // Match! + return From; + } + + DirectoryEntry *DE = dyn_cast<DirectoryEntry>(From); + if (!DE) + return make_error_code(llvm::errc::not_a_directory); + + for (DirectoryEntry::iterator I = DE->contents_begin(), + E = DE->contents_end(); + I != E; ++I) { + ErrorOr<Entry *> Result = lookupPath(Start, End, *I); + 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<Status> VFSFromYAML::status(const Twine &Path, Entry *E) { + assert(E != nullptr); + std::string PathStr(Path.str()); + if (FileEntry *F = dyn_cast<FileEntry>(E)) { + ErrorOr<Status> S = ExternalFS->status(F->getExternalContentsPath()); + assert(!S || S->getName() == F->getExternalContentsPath()); + if (S && !F->useExternalName(UseExternalNames)) + S->setName(PathStr); + if (S) + S->IsVFSMapped = true; + return S; + } else { // directory + DirectoryEntry *DE = cast<DirectoryEntry>(E); + Status S = DE->getStatus(); + S.setName(PathStr); + return S; + } +} + +ErrorOr<Status> VFSFromYAML::status(const Twine &Path) { + ErrorOr<Entry *> Result = lookupPath(Path); + if (!Result) + return Result.getError(); + return status(Path, *Result); +} + +std::error_code +VFSFromYAML::openFileForRead(const Twine &Path, + std::unique_ptr<vfs::File> &Result) { + ErrorOr<Entry *> E = lookupPath(Path); + if (!E) + return E.getError(); + + FileEntry *F = dyn_cast<FileEntry>(*E); + if (!F) // FIXME: errc::not_a_file? + return make_error_code(llvm::errc::invalid_argument); + + if (std::error_code EC = + ExternalFS->openFileForRead(F->getExternalContentsPath(), Result)) + return EC; + + if (!F->useExternalName(UseExternalNames)) + Result->setName(Path.str()); + + return std::error_code(); +} + +IntrusiveRefCntPtr<FileSystem> +vfs::getVFSFromYAML(MemoryBuffer *Buffer, SourceMgr::DiagHandlerTy DiagHandler, + void *DiagContext, + IntrusiveRefCntPtr<FileSystem> ExternalFS) { + return VFSFromYAML::create(Buffer, DiagHandler, DiagContext, ExternalFS); +} + +UniqueID vfs::getNextVirtualUniqueID() { + static std::atomic<unsigned> UID; + unsigned ID = ++UID; + // The following assumes that uint64_t max will never collide with a real + // dev_t value from the OS. + return UniqueID(std::numeric_limits<uint64_t>::max(), ID); +} + +#ifndef NDEBUG +static bool pathHasTraversal(StringRef Path) { + using namespace llvm::sys; + for (StringRef Comp : llvm::make_range(path::begin(Path), path::end(Path))) + if (Comp == "." || Comp == "..") + return true; + return false; +} +#endif + +void YAMLVFSWriter::addFileMapping(StringRef VirtualPath, StringRef RealPath) { + assert(sys::path::is_absolute(VirtualPath) && "virtual path not absolute"); + assert(sys::path::is_absolute(RealPath) && "real path not absolute"); + assert(!pathHasTraversal(VirtualPath) && "path traversal is not supported"); + Mappings.emplace_back(VirtualPath, RealPath); +} + +namespace { +class JSONWriter { + llvm::raw_ostream &OS; + SmallVector<StringRef, 16> DirStack; + inline unsigned getDirIndent() { return 4 * DirStack.size(); } + inline unsigned getFileIndent() { return 4 * (DirStack.size() + 1); } + bool containedIn(StringRef Parent, StringRef Path); + StringRef containedPart(StringRef Parent, StringRef Path); + void startDirectory(StringRef Path); + void endDirectory(); + void writeEntry(StringRef VPath, StringRef RPath); + +public: + JSONWriter(llvm::raw_ostream &OS) : OS(OS) {} + void write(ArrayRef<YAMLVFSEntry> Entries, Optional<bool> IsCaseSensitive); +}; +} + +bool JSONWriter::containedIn(StringRef Parent, StringRef Path) { + using namespace llvm::sys; + // Compare each path component. + auto IParent = path::begin(Parent), EParent = path::end(Parent); + for (auto IChild = path::begin(Path), EChild = path::end(Path); + IParent != EParent && IChild != EChild; ++IParent, ++IChild) { + if (*IParent != *IChild) + return false; + } + // Have we exhausted the parent path? + return IParent == EParent; +} + +StringRef JSONWriter::containedPart(StringRef Parent, StringRef Path) { + assert(!Parent.empty()); + assert(containedIn(Parent, Path)); + return Path.slice(Parent.size() + 1, StringRef::npos); +} + +void JSONWriter::startDirectory(StringRef Path) { + StringRef Name = + DirStack.empty() ? Path : containedPart(DirStack.back(), Path); + DirStack.push_back(Path); + unsigned Indent = getDirIndent(); + OS.indent(Indent) << "{\n"; + OS.indent(Indent + 2) << "'type': 'directory',\n"; + OS.indent(Indent + 2) << "'name': \"" << llvm::yaml::escape(Name) << "\",\n"; + OS.indent(Indent + 2) << "'contents': [\n"; +} + +void JSONWriter::endDirectory() { + unsigned Indent = getDirIndent(); + OS.indent(Indent + 2) << "]\n"; + OS.indent(Indent) << "}"; + + DirStack.pop_back(); +} + +void JSONWriter::writeEntry(StringRef VPath, StringRef RPath) { + unsigned Indent = getFileIndent(); + OS.indent(Indent) << "{\n"; + OS.indent(Indent + 2) << "'type': 'file',\n"; + OS.indent(Indent + 2) << "'name': \"" << llvm::yaml::escape(VPath) << "\",\n"; + OS.indent(Indent + 2) << "'external-contents': \"" + << llvm::yaml::escape(RPath) << "\"\n"; + OS.indent(Indent) << "}"; +} + +void JSONWriter::write(ArrayRef<YAMLVFSEntry> Entries, + Optional<bool> IsCaseSensitive) { + using namespace llvm::sys; + + OS << "{\n" + " 'version': 0,\n"; + if (IsCaseSensitive.hasValue()) + OS << " 'case-sensitive': '" + << (IsCaseSensitive.getValue() ? "true" : "false") << "',\n"; + OS << " 'roots': [\n"; + + if (!Entries.empty()) { + const YAMLVFSEntry &Entry = Entries.front(); + startDirectory(path::parent_path(Entry.VPath)); + writeEntry(path::filename(Entry.VPath), Entry.RPath); + + for (const auto &Entry : Entries.slice(1)) { + StringRef Dir = path::parent_path(Entry.VPath); + if (Dir == DirStack.back()) + OS << ",\n"; + else { + while (!DirStack.empty() && !containedIn(DirStack.back(), Dir)) { + OS << "\n"; + endDirectory(); + } + OS << ",\n"; + startDirectory(Dir); + } + writeEntry(path::filename(Entry.VPath), Entry.RPath); + } + + while (!DirStack.empty()) { + OS << "\n"; + endDirectory(); + } + OS << "\n"; + } + + OS << " ]\n" + << "}\n"; +} + +void YAMLVFSWriter::write(llvm::raw_ostream &OS) { + std::sort(Mappings.begin(), Mappings.end(), + [](const YAMLVFSEntry &LHS, const YAMLVFSEntry &RHS) { + return LHS.VPath < RHS.VPath; + }); + + JSONWriter(OS).write(Mappings, IsCaseSensitive); +} + +VFSFromYamlDirIterImpl::VFSFromYamlDirIterImpl(const Twine &_Path, + VFSFromYAML &FS, + DirectoryEntry::iterator Begin, + DirectoryEntry::iterator End, + std::error_code &EC) + : Dir(_Path.str()), FS(FS), Current(Begin), End(End) { + if (Current != End) { + SmallString<128> PathStr(Dir); + llvm::sys::path::append(PathStr, (*Current)->getName()); + llvm::ErrorOr<vfs::Status> S = FS.status(PathStr.str()); + if (S) + CurrentEntry = *S; + else + EC = S.getError(); + } +} + +std::error_code VFSFromYamlDirIterImpl::increment() { + assert(Current != End && "cannot iterate past end"); + if (++Current != End) { + SmallString<128> PathStr(Dir); + llvm::sys::path::append(PathStr, (*Current)->getName()); + llvm::ErrorOr<vfs::Status> S = FS.status(PathStr.str()); + if (!S) + return S.getError(); + CurrentEntry = *S; + } else { + CurrentEntry = Status(); + } + return std::error_code(); +} + +vfs::recursive_directory_iterator::recursive_directory_iterator(FileSystem &FS_, + const Twine &Path, + std::error_code &EC) + : FS(&FS_) { + directory_iterator I = FS->dir_begin(Path, EC); + if (!EC && I != directory_iterator()) { + State = std::make_shared<IterState>(); + State->push(I); + } +} + +vfs::recursive_directory_iterator & +recursive_directory_iterator::increment(std::error_code &EC) { + assert(FS && State && !State->empty() && "incrementing past end"); + assert(State->top()->isStatusKnown() && "non-canonical end iterator"); + vfs::directory_iterator End; + if (State->top()->isDirectory()) { + vfs::directory_iterator I = FS->dir_begin(State->top()->getName(), EC); + if (EC) + return *this; + if (I != End) { + State->push(I); + return *this; + } + } + + while (!State->empty() && State->top().increment(EC) == End) + State->pop(); + + if (State->empty()) + State.reset(); // end iterator + + return *this; +} |