diff options
| author | Dimitry Andric <dim@FreeBSD.org> | 2014-11-24 09:15:30 +0000 | 
|---|---|---|
| committer | Dimitry Andric <dim@FreeBSD.org> | 2014-11-24 09:15:30 +0000 | 
| commit | 9f4dbff6669c8037f3b036bcf580d14f1a4f12a5 (patch) | |
| tree | 47df2c12b57214af6c31e47404b005675b8b7ffc /lib/Basic/VirtualFileSystem.cpp | |
| parent | f73d5f23a889b93d89ddef61ac0995df40286bb8 (diff) | |
Notes
Diffstat (limited to 'lib/Basic/VirtualFileSystem.cpp')
| -rw-r--r-- | lib/Basic/VirtualFileSystem.cpp | 1209 | 
1 files changed, 1209 insertions, 0 deletions
diff --git a/lib/Basic/VirtualFileSystem.cpp b/lib/Basic/VirtualFileSystem.cpp new file mode 100644 index 000000000000..a5c83b88af50 --- /dev/null +++ b/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; +}  | 
