Index: include/clang/Basic/VirtualFileSystem.h =================================================================== --- include/clang/Basic/VirtualFileSystem.h +++ include/clang/Basic/VirtualFileSystem.h @@ -323,6 +323,7 @@ namespace detail { class InMemoryDirectory; +class InMemoryNode; } // namespace detail @@ -331,7 +332,12 @@ std::unique_ptr Root; std::string WorkingDirectory; bool UseNormalizedPaths = true; - + bool addFile(const Twine &Path, time_t ModificationTime, + std::unique_ptr Buffer, + Optional User, Optional Group, + Optional Type, + Optional Perms, + Optional HardLink); public: explicit InMemoryFileSystem(bool UseNormalizedPaths = true); ~InMemoryFileSystem() override; @@ -348,6 +354,17 @@ Optional Type = None, Optional 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 User = None, + Optional Group = None, + Optional Type = None, + Optional 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. Index: lib/Basic/VirtualFileSystem.cpp =================================================================== --- lib/Basic/VirtualFileSystem.cpp +++ lib/Basic/VirtualFileSystem.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "clang/Basic/VirtualFileSystem.h" +#include "llvm/ADT/None.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" @@ -466,7 +467,7 @@ namespace detail { -enum InMemoryNodeKind { IME_File, IME_Directory }; +enum InMemoryNodeKind { IME_File, IME_Directory, IME_HARD_LINK }; /// The in memory file system is a tree of Nodes. Every node can either be a /// file or a directory. @@ -520,6 +521,34 @@ } }; +class InMemoryHardLink : public InMemoryNode { + InMemoryNode *ResolvedNode; + StringRef Name; + +public: + InMemoryHardLink(Status Stat, InMemoryNode *ResolvedNode) + : InMemoryNode(std::move(Stat), IME_HARD_LINK), + ResolvedNode(ResolvedNode) { + Name = Stat.getName(); + } + InMemoryNode *getResolvedNode() { return ResolvedNode; } + llvm::MemoryBuffer *getBuffer() { + return static_cast(ResolvedNode)->getBuffer(); + } + + Status getStatus(StringRef RequestedName) const { + return ResolvedNode->getStatus(RequestedName); + } + + std::string toString(unsigned Indent) const override { + return (std::string(Indent, ' ') + Name + "\n").str(); + } + + static bool classof(const InMemoryNode *N) { + return N->getKind() == IME_HARD_LINK; + } +}; + /// Adapt a InMemoryFile for VFS' File interface. The goal is to make /// \p InMemoryFileAdaptor mimic as much as possible the behavior of /// \p RealFile. @@ -606,7 +635,8 @@ Optional User, Optional Group, Optional Type, - Optional Perms) { + Optional Perms, + Optional HardLink) { SmallString<128> Path; P.toVector(Path); @@ -627,6 +657,10 @@ 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); + const auto ResolvedHardLink = HardLink.getValueOr(nullptr); + // Cannot create HardLink from a directory. + if (ResolvedHardLink && ResolvedType == sys::fs::file_type::directory_file) + 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 +679,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 (ResolvedHardLink) + Child.reset(new detail::InMemoryHardLink(std::move(Stat), + ResolvedHardLink)); + else + Child.reset( + new detail::InMemoryFile(std::move(Stat), std::move(Buffer))); } Dir->addChild(Name, std::move(Child)); return true; @@ -666,20 +704,36 @@ if (auto *NewDir = dyn_cast(Node)) { Dir = NewDir; } else { - assert(isa(Node) && - "Must be either file or directory!"); + assert((isa(Node) || + isa(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 File = dyn_cast(Node)) { + return File->getBuffer()->getBuffer() == Buffer->getBuffer(); + } return cast(Node)->getBuffer()->getBuffer() == Buffer->getBuffer(); } } } +bool InMemoryFileSystem::addFile(const Twine &P, time_t ModificationTime, + std::unique_ptr Buffer, + Optional User, + Optional Group, + Optional Type, + Optional Perms) { + return addFile(P, ModificationTime, + llvm::MemoryBuffer::getMemBuffer( + Buffer->getBuffer(), Buffer->getBufferIdentifier()), + User, Group, Type, Perms, /*HardLink=*/None); +} + bool InMemoryFileSystem::addFileNoOwn(const Twine &P, time_t ModificationTime, llvm::MemoryBuffer *Buffer, Optional User, @@ -724,6 +778,12 @@ return errc::no_such_file_or_directory; } + // If Node is HardLink then return the resolved link. + if (auto File = dyn_cast(Node)) { + if (I == E) + return File->getResolvedNode(); + return errc::no_such_file_or_directory; + } // Traverse directories. Dir = cast(Node); if (I == E) @@ -731,6 +791,23 @@ } } +bool InMemoryFileSystem::addHardLink(const Twine &FromPath, const Twine &ToPath, + Optional User, + Optional Group, + Optional Type, + Optional 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(*ToNode)) + return false; + detail::InMemoryNode *ResolvedHardLink; + ResolvedHardLink = *ToNode; + return this->addFile(FromPath, 0, llvm::MemoryBuffer::getMemBuffer(""), User, + Group, Type, Perms, ResolvedHardLink); +} + llvm::ErrorOr InMemoryFileSystem::status(const Twine &Path) { auto Node = lookupInMemoryNode(*this, Root.get(), Path); if (Node) 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 +#include 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,84 @@ ASSERT_EQ("../b/c", getPosixPath(It->getName())); } +TEST_F(InMemoryFileSystemTest, AddHardLinkToFile) { + std::pair From = {"/path/to/FROM/file", + "content of FROM file"}; + std::pair 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)); +} + // 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 {