usaxena95 updated this revision to Diff 163330.
usaxena95 marked 3 inline comments as done.
usaxena95 added a comment.

- Moved Stat to the subclasses of InMemoryNode


Repository:
  rC Clang

https://reviews.llvm.org/D51359

Files:
  include/clang/Basic/VirtualFileSystem.h
  lib/Basic/VirtualFileSystem.cpp
  unittests/Basic/VirtualFileSystemTest.cpp

Index: unittests/Basic/VirtualFileSystemTest.cpp
===================================================================
--- unittests/Basic/VirtualFileSystemTest.cpp
+++ unittests/Basic/VirtualFileSystemTest.cpp
@@ -17,6 +17,7 @@
 #include "llvm/Support/SourceMgr.h"
 #include "gtest/gtest.h"
 #include <map>
+#include <string>
 
 using namespace clang;
 using namespace llvm;
@@ -695,6 +696,22 @@
   InMemoryFileSystemTest()
       : FS(/*UseNormalizedPaths=*/false),
         NormalizedFS(/*UseNormalizedPaths=*/true) {}
+
+public:
+  void ExpectHardLink(Twine From, Twine To, const string &ExpectedBuffer) {
+    auto OpenedFrom = FS.openFileForRead(From);
+    ASSERT_FALSE(OpenedFrom.getError());
+    auto OpenedTo = FS.openFileForRead(To);
+    ASSERT_FALSE(OpenedTo.getError());
+    ASSERT_EQ((*OpenedFrom)->status()->getSize(),
+              (*OpenedTo)->status()->getSize());
+    ASSERT_EQ((*OpenedFrom)->status()->getUniqueID(),
+              (*OpenedTo)->status()->getUniqueID());
+    ASSERT_EQ((*OpenedFrom)->getBuffer(From)->get()->getBuffer().data(),
+              (*OpenedTo)->getBuffer(To)->get()->getBuffer().data());
+    ASSERT_EQ((*OpenedFrom)->getBuffer(From)->get()->getBuffer().data(),
+              ExpectedBuffer);
+  }
 };
 
 TEST_F(InMemoryFileSystemTest, IsEmpty) {
@@ -958,6 +975,126 @@
   ASSERT_EQ("../b/c", getPosixPath(It->getName()));
 }
 
+TEST_F(InMemoryFileSystemTest, AddHardLinkToFile) {
+  std::pair<llvm::Twine, StringRef> From = {"/path/to/FROM/file",
+                                            "content of FROM file"};
+  std::pair<llvm::Twine, StringRef> To = {"/path/to/TO/file",
+                                          "content of TO file"};
+  FS.addFile(To.first, 0, MemoryBuffer::getMemBuffer(To.second));
+  EXPECT_TRUE(FS.addHardLink(From.first, To.first));
+  ExpectHardLink(From.first, To.first, To.second.data());
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkInChainPattern) {
+  StringRef content = "content of target file";
+  Twine link0 = "/path/to/0/link";
+  Twine link1 = "/path/to/1/link";
+  Twine link2 = "/path/to/2/link";
+  Twine target = "/path/to/target";
+  FS.addFile(target, 0, MemoryBuffer::getMemBuffer(content));
+  EXPECT_TRUE(FS.addHardLink(link2, target));
+  EXPECT_TRUE(FS.addHardLink(link1, link2));
+  EXPECT_TRUE(FS.addHardLink(link0, link1));
+  ExpectHardLink(link0, target, content.data());
+  ExpectHardLink(link1, target, content.data());
+  ExpectHardLink(link2, target, content.data());
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkToAFileThatWasNotAddedBefore) {
+  Twine link = "/path/to/link";
+  Twine target = "/path/to/target";
+  EXPECT_FALSE(FS.addHardLink(link, target));
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkFromAFileThatWasAddedBefore) {
+  Twine link = "/path/to/link";
+  StringRef content_link = "content of link";
+  Twine target = "/path/to/target";
+  StringRef content = "content of target";
+  FS.addFile(target, 0, MemoryBuffer::getMemBuffer(content));
+  FS.addFile(link, 0, MemoryBuffer::getMemBuffer(content_link));
+  EXPECT_FALSE(FS.addHardLink(link, target));
+}
+
+TEST_F(InMemoryFileSystemTest, AddSameHardLinkMoreThanOnce) {
+  Twine link = "/path/to/link";
+  Twine target = "/path/to/target";
+  StringRef content = "content of target";
+  FS.addFile(target, 0, MemoryBuffer::getMemBuffer(content));
+  EXPECT_TRUE(FS.addHardLink(link, target));
+  EXPECT_FALSE(FS.addHardLink(link, target));
+}
+
+TEST_F(InMemoryFileSystemTest, AddFileInPlaceOfAHardLinkWithSameContent) {
+  Twine link = "/path/to/link";
+  Twine target = "/path/to/target";
+  StringRef content = "content of target";
+  EXPECT_TRUE(FS.addFile(target, 0, MemoryBuffer::getMemBuffer(content)));
+  EXPECT_TRUE(FS.addHardLink(link, target));
+  EXPECT_TRUE(FS.addFile(link, 0, MemoryBuffer::getMemBuffer(content)));
+}
+
+TEST_F(InMemoryFileSystemTest, AddFileInPlaceOfAHardLinkWithDifferentContent) {
+  Twine link = "/path/to/link";
+  Twine target = "/path/to/target";
+  StringRef content = "content of target";
+  StringRef link_content = "different content of link";
+  EXPECT_TRUE(FS.addFile(target, 0, MemoryBuffer::getMemBuffer(content)));
+  EXPECT_TRUE(FS.addHardLink(link, target));
+  EXPECT_FALSE(FS.addFile(link, 0, MemoryBuffer::getMemBuffer(link_content)));
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkToADirectory) {
+  Twine dir = "path/to/dummy/dir";
+  Twine link = "/path/to/link";
+  Twine dummy_file = dir + "/target";
+  StringRef content = "content of target";
+  EXPECT_TRUE(FS.addFile(dummy_file, 0, MemoryBuffer::getMemBuffer(content)));
+  EXPECT_FALSE(FS.addHardLink(link, dir));
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkFromADirectory) {
+  Twine dir = "path/to/dummy/dir";
+  Twine target = dir + "/target";
+  StringRef content = "content of target";
+  EXPECT_TRUE(FS.addFile(target, 0, MemoryBuffer::getMemBuffer(content)));
+  EXPECT_FALSE(FS.addHardLink(dir, target));
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkUnderAFile) {
+  StringRef common_content = "content string";
+  FS.addFile("/a/b", 0, MemoryBuffer::getMemBuffer(common_content));
+  FS.addFile("/c/d", 0, MemoryBuffer::getMemBuffer(common_content));
+  EXPECT_FALSE(FS.addHardLink("/c/d/e", "/a/b"));
+}
+
+TEST_F(InMemoryFileSystemTest, RecursiveIterationWithHardLink) {
+  std::error_code EC;
+  StringRef common_content = "content string";
+  FS.addFile("/a/b", 0, MemoryBuffer::getMemBuffer(common_content));
+  EXPECT_TRUE(FS.addHardLink("/c/d", "/a/b"));
+  auto I = vfs::recursive_directory_iterator(FS, "/", EC);
+  ASSERT_FALSE(EC);
+  std::vector<std::string> Nodes;
+  for (auto E = vfs::recursive_directory_iterator(); !EC && I != E;
+       I.increment(EC)) {
+    Nodes.push_back(I->getName());
+  }
+
+  // Check contents, which may be in any order
+  EXPECT_EQ(4U, Nodes.size());
+  int Counts[4] = {0, 0, 0, 0};
+  for (const std::string &Name : Nodes) {
+    ASSERT_FALSE(Name.empty());
+    int Index = Name[Name.size() - 1] - 'a';
+    ASSERT_TRUE(Index >= 0 && Index < 4);
+    Counts[Index]++;
+  }
+  EXPECT_EQ(1, Counts[0]);  // a
+  EXPECT_EQ(1, Counts[1]);  // b
+  EXPECT_EQ(1, Counts[2]);  // c
+  EXPECT_EQ(1, Counts[3]);  // d
+}
 // NOTE: in the tests below, we use '//root/' as our root directory, since it is
 // a legal *absolute* path on Windows as well as *nix.
 class VFSFromYAMLTest : public ::testing::Test {
Index: lib/Basic/VirtualFileSystem.cpp
===================================================================
--- lib/Basic/VirtualFileSystem.cpp
+++ lib/Basic/VirtualFileSystem.cpp
@@ -16,6 +16,7 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/ADT/None.h"
 #include "llvm/ADT/Optional.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallString.h"
@@ -466,60 +467,73 @@
 
 namespace detail {
 
-enum InMemoryNodeKind { IME_File, IME_Directory };
+enum InMemoryNodeKind { IME_File, IME_Directory, IME_HardLink };
 
 /// The in memory file system is a tree of Nodes. Every node can either be a
-/// file or a directory.
+/// file , hardlink or a directory.
 class InMemoryNode {
-  Status Stat;
   InMemoryNodeKind Kind;
-
-protected:
-  /// Return Stat.  This should only be used for internal/debugging use.  When
-  /// clients wants the Status of this node, they should use
-  /// \p getStatus(StringRef).
-  const Status &getStatus() const { return Stat; }
+  string FileName;
 
 public:
-  InMemoryNode(Status Stat, InMemoryNodeKind Kind)
-      : Stat(std::move(Stat)), Kind(Kind) {}
+  InMemoryNode(string FileName, InMemoryNodeKind Kind)
+      : Kind(Kind), FileName(std::move(FileName)) {}
   virtual ~InMemoryNode() = default;
 
-  /// Return the \p Status for this node. \p RequestedName should be the name
-  /// through which the caller referred to this node. It will override
-  /// \p Status::Name in the return value, to mimic the behavior of \p RealFile.
-  Status getStatus(StringRef RequestedName) const {
-    return Status::copyWithNewName(Stat, RequestedName);
-  }
-
   /// Get the filename of this node (the name without the directory part).
   StringRef getFileName() const {
-    return llvm::sys::path::filename(Stat.getName());
+    return llvm::sys::path::filename(FileName.data());
   }
   InMemoryNodeKind getKind() const { return Kind; }
   virtual std::string toString(unsigned Indent) const = 0;
 };
 
 namespace {
 
 class InMemoryFile : public InMemoryNode {
+  Status Stat;
   std::unique_ptr<llvm::MemoryBuffer> Buffer;
 
 public:
   InMemoryFile(Status Stat, std::unique_ptr<llvm::MemoryBuffer> Buffer)
-      : InMemoryNode(std::move(Stat), IME_File), Buffer(std::move(Buffer)) {}
+      : InMemoryNode(Stat.getName().str(), IME_File), Stat(std::move(Stat)),
+        Buffer(std::move(Buffer)) {}
 
+  /// Return the \p Status for this node. \p RequestedName should be the name
+  /// through which the caller referred to this node. It will override
+  /// \p Status::Name in the return value, to mimic the behavior of \p RealFile.
+  Status getStatus(StringRef RequestedName) const {
+    return Status::copyWithNewName(Stat, RequestedName);
+  }
   llvm::MemoryBuffer *getBuffer() { return Buffer.get(); }
 
   std::string toString(unsigned Indent) const override {
-    return (std::string(Indent, ' ') + getStatus().getName() + "\n").str();
+    return (std::string(Indent, ' ') + Stat.getName() + "\n").str();
   }
 
   static bool classof(const InMemoryNode *N) {
     return N->getKind() == IME_File;
   }
 };
 
+class InMemoryHardLink : public InMemoryNode {
+  InMemoryFile &ResolvedFile;
+
+public:
+  InMemoryHardLink(StringRef Path, InMemoryFile &ResolvedFile)
+      : InMemoryNode(Path.str(), IME_HardLink), ResolvedFile(ResolvedFile) {}
+  InMemoryFile *getResolvedFile() { return &ResolvedFile; }
+
+  std::string toString(unsigned Indent) const override {
+    return std::string(Indent, ' ') + "HardLink to -> " +
+           ResolvedFile.toString(0);
+  }
+
+  static bool classof(const InMemoryNode *N) {
+    return N->getKind() == IME_HardLink;
+  }
+};
+
 /// Adapt a InMemoryFile for VFS' File interface.  The goal is to make
 /// \p InMemoryFileAdaptor mimic as much as possible the behavior of
 /// \p RealFile.
@@ -550,12 +564,20 @@
 } // namespace
 
 class InMemoryDirectory : public InMemoryNode {
+  Status Stat;
   std::map<std::string, std::unique_ptr<InMemoryNode>> Entries;
 
 public:
   InMemoryDirectory(Status Stat)
-      : InMemoryNode(std::move(Stat), IME_Directory) {}
+      : InMemoryNode(Stat.getName().str(), IME_Directory),
+        Stat(std::move(Stat)) {}
 
+  /// Return the \p Status for this node. \p RequestedName should be the name
+  /// through which the caller referred to this node. It will override
+  /// \p Status::Name in the return value, to mimic the behavior of \p RealFile.
+  Status getStatus(StringRef RequestedName) const {
+    return Status::copyWithNewName(Stat, RequestedName);
+  }
   InMemoryNode *getChild(StringRef Name) {
     auto I = Entries.find(Name);
     if (I != Entries.end())
@@ -575,7 +597,7 @@
 
   std::string toString(unsigned Indent) const override {
     std::string Result =
-        (std::string(Indent, ' ') + getStatus().getName() + "\n").str();
+        (std::string(Indent, ' ') + Stat.getName() + "\n").str();
     for (const auto &Entry : Entries)
       Result += Entry.second->toString(Indent + 2);
     return Result;
@@ -606,7 +628,8 @@
                                  Optional<uint32_t> User,
                                  Optional<uint32_t> Group,
                                  Optional<llvm::sys::fs::file_type> Type,
-                                 Optional<llvm::sys::fs::perms> Perms) {
+                                 Optional<llvm::sys::fs::perms> Perms,
+                                 detail::InMemoryNode *HardLink) {
   SmallString<128> Path;
   P.toVector(Path);
 
@@ -627,6 +650,9 @@
   const auto ResolvedGroup = Group.getValueOr(0);
   const auto ResolvedType = Type.getValueOr(sys::fs::file_type::regular_file);
   const auto ResolvedPerms = Perms.getValueOr(sys::fs::all_all);
+  // Cannot create HardLink from a directory.
+  if (HardLink && !isa<detail::InMemoryFile>(HardLink))
+    return false;
   // Any intermediate directories we create should be accessible by
   // the owner, even if Perms says otherwise for the final path.
   const auto NewDirectoryPerms = ResolvedPerms | sys::fs::owner_all;
@@ -645,8 +671,12 @@
         if (ResolvedType == sys::fs::file_type::directory_file) {
           Child.reset(new detail::InMemoryDirectory(std::move(Stat)));
         } else {
-          Child.reset(new detail::InMemoryFile(std::move(Stat),
-                                               std::move(Buffer)));
+          if (HardLink)
+            Child.reset(new detail::InMemoryHardLink(
+                P.str(), *static_cast<detail::InMemoryFile *>(HardLink)));
+          else
+            Child.reset(
+                new detail::InMemoryFile(std::move(Stat), std::move(Buffer)));
         }
         Dir->addChild(Name, std::move(Child));
         return true;
@@ -666,20 +696,37 @@
     if (auto *NewDir = dyn_cast<detail::InMemoryDirectory>(Node)) {
       Dir = NewDir;
     } else {
-      assert(isa<detail::InMemoryFile>(Node) &&
-             "Must be either file or directory!");
+      assert((isa<detail::InMemoryFile>(Node) ||
+              isa<detail::InMemoryHardLink>(Node)) &&
+             "Must be either file, hardlink or directory!");
 
       // Trying to insert a directory in place of a file.
       if (I != E)
         return false;
 
       // Return false only if the new file is different from the existing one.
+      if (auto Link = dyn_cast<detail::InMemoryHardLink>(Node)) {
+        return Link->getResolvedFile()->getBuffer()->getBuffer() ==
+               Buffer->getBuffer();
+      }
       return cast<detail::InMemoryFile>(Node)->getBuffer()->getBuffer() ==
              Buffer->getBuffer();
     }
   }
 }
 
+bool InMemoryFileSystem::addFile(const Twine &P, time_t ModificationTime,
+                                 std::unique_ptr<llvm::MemoryBuffer> Buffer,
+                                 Optional<uint32_t> User,
+                                 Optional<uint32_t> Group,
+                                 Optional<llvm::sys::fs::file_type> Type,
+                                 Optional<llvm::sys::fs::perms> Perms) {
+  return addFile(P, ModificationTime,
+                 llvm::MemoryBuffer::getMemBuffer(
+                     Buffer->getBuffer(), Buffer->getBufferIdentifier()),
+                 User, Group, Type, Perms, /*HardLink=*/nullptr);
+}
+
 bool InMemoryFileSystem::addFileNoOwn(const Twine &P, time_t ModificationTime,
                                       llvm::MemoryBuffer *Buffer,
                                       Optional<uint32_t> User,
@@ -724,17 +771,45 @@
       return errc::no_such_file_or_directory;
     }
 
+    // If Node is HardLink then return the resolved link.
+    if (auto File = dyn_cast<detail::InMemoryHardLink>(Node)) {
+      if (I == E)
+        return File->getResolvedFile();
+      return errc::no_such_file_or_directory;
+    }
     // Traverse directories.
     Dir = cast<detail::InMemoryDirectory>(Node);
     if (I == E)
       return Dir;
   }
 }
 
+bool InMemoryFileSystem::addHardLink(const Twine &FromPath, const Twine &ToPath,
+                                     Optional<uint32_t> User,
+                                     Optional<uint32_t> Group,
+                                     Optional<llvm::sys::fs::file_type> Type,
+                                     Optional<llvm::sys::fs::perms> Perms) {
+  auto FromNode = lookupInMemoryNode(*this, Root.get(), FromPath);
+  auto ToNode = lookupInMemoryNode(*this, Root.get(), ToPath);
+  // FromPath must not have been added before. ToPath must have been added
+  // before. Resolved ToPath must be a File.
+  if (!ToNode || FromNode || !isa<detail::InMemoryFile>(*ToNode))
+    return false;
+  return this->addFile(FromPath, 0, nullptr, User,
+                       Group, Type, Perms,
+                       static_cast<detail::InMemoryFile *>(*ToNode));
+}
+
 llvm::ErrorOr<Status> InMemoryFileSystem::status(const Twine &Path) {
   auto Node = lookupInMemoryNode(*this, Root.get(), Path);
-  if (Node)
-    return (*Node)->getStatus(Path.str());
+  if (Node) {
+    if (auto Dir = dyn_cast<detail::InMemoryDirectory>(*Node))
+      return Dir->getStatus(Path.str());
+    if (auto File = dyn_cast<detail::InMemoryFile>(*Node))
+      return File->getStatus(Path.str());
+    if (auto Link = dyn_cast<detail::InMemoryHardLink>(*Node))
+      return Link->getResolvedFile()->getStatus(Path.str());
+  }
   return Node.getError();
 }
 
@@ -766,7 +841,12 @@
     if (I != E) {
       SmallString<256> Path(RequestedDirName);
       llvm::sys::path::append(Path, I->second->getFileName());
-      CurrentEntry = I->second->getStatus(Path);
+      if (auto Dir = dyn_cast<detail::InMemoryDirectory>(I->second.get()))
+        CurrentEntry = Dir->getStatus(Path);
+      if (auto File = dyn_cast<detail::InMemoryFile>(I->second.get()))
+        CurrentEntry = File->getStatus(Path);
+      if (auto Link = dyn_cast<detail::InMemoryHardLink>(I->second.get()))
+        CurrentEntry = Link->getResolvedFile()->getStatus(Path);
     } else {
       // When we're at the end, make CurrentEntry invalid and DirIterImpl will
       // do the rest.
Index: include/clang/Basic/VirtualFileSystem.h
===================================================================
--- include/clang/Basic/VirtualFileSystem.h
+++ include/clang/Basic/VirtualFileSystem.h
@@ -323,14 +323,21 @@
 namespace detail {
 
 class InMemoryDirectory;
+class InMemoryNode;
 
 } // namespace detail
 
 /// An in-memory file system.
 class InMemoryFileSystem : public FileSystem {
   std::unique_ptr<detail::InMemoryDirectory> Root;
   std::string WorkingDirectory;
   bool UseNormalizedPaths = true;
+  bool addFile(const Twine &Path, time_t ModificationTime,
+               std::unique_ptr<llvm::MemoryBuffer> Buffer,
+               Optional<uint32_t> User, Optional<uint32_t> Group,
+               Optional<llvm::sys::fs::file_type> Type,
+               Optional<llvm::sys::fs::perms> Perms,
+               detail::InMemoryNode *HardLink);
 
 public:
   explicit InMemoryFileSystem(bool UseNormalizedPaths = true);
@@ -348,6 +355,17 @@
                Optional<llvm::sys::fs::file_type> Type = None,
                Optional<llvm::sys::fs::perms> Perms = None);
 
+  public:
+  /// Add a HardLink to a File.
+  /// The To path must be an existing file or a hardlink. The From file must not
+  /// have been added before. The From Node is added as an InMemoryHardLink
+  /// which points to the resolved file of To Node.
+  bool addHardLink(const Twine &From, const Twine &To,
+                   Optional<uint32_t> User = None,
+                   Optional<uint32_t> Group = None,
+                   Optional<llvm::sys::fs::file_type> Type = None,
+                   Optional<llvm::sys::fs::perms> Perms = None);
+
   /// Add a buffer to the VFS with a path. The VFS does not own the buffer.
   /// If present, User, Group, Type and Perms apply to the newly-created file
   /// or directory.
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to