Index: include/clang/Basic/VirtualFileSystem.h =================================================================== --- include/clang/Basic/VirtualFileSystem.h +++ include/clang/Basic/VirtualFileSystem.h @@ -29,7 +29,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -75,6 +77,9 @@ /// Returns the name that should be used for this file or directory. StringRef getName() const { return Name; } + void setUniqueID(llvm::sys::fs::UniqueID UID) { this->UID = UID; } + void setSize(uint64_t Size) { this->Size = Size; } + /// @name Status interface from llvm::sys::fs /// @{ llvm::sys::fs::file_type getType() const { return Type; } @@ -323,12 +328,15 @@ namespace detail { class InMemoryDirectory; +class InMemoryNode; } // namespace detail /// An in-memory file system. class InMemoryFileSystem : public FileSystem { std::unique_ptr Root; + std::map> + Files_by_ID; std::string WorkingDirectory; bool UseNormalizedPaths = true; @@ -348,6 +356,11 @@ Optional Type = None, Optional Perms = None); + /// Add a HardLink from one file to another. + /// Makes the UniqueID of To file same as that of From file. If CopyBuffer is + /// true then contents of from buffer is copied into the buffer of To file. + void addHardlink(const Twine &From, const Twine &To, bool CopyBuffer); + /// 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. @@ -452,3 +465,4 @@ } // namespace clang #endif // LLVM_CLANG_BASIC_VIRTUALFILESYSTEM_H + Index: lib/Basic/VirtualFileSystem.cpp =================================================================== --- lib/Basic/VirtualFileSystem.cpp +++ lib/Basic/VirtualFileSystem.cpp @@ -12,6 +12,8 @@ //===----------------------------------------------------------------------===// #include "clang/Basic/VirtualFileSystem.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/MemoryBuffer.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" @@ -25,9 +27,9 @@ #include "llvm/ADT/Twine.h" #include "llvm/ADT/iterator_range.h" #include "llvm/Config/llvm-config.h" -#include "llvm/Support/Compiler.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Chrono.h" +#include "llvm/Support/Compiler.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Errc.h" #include "llvm/Support/ErrorHandling.h" @@ -485,6 +487,8 @@ : Stat(std::move(Stat)), Kind(Kind) {} virtual ~InMemoryNode() = default; + void setUniqueId(UniqueID unique_id) { Stat.setUniqueID(unique_id); } + void setSize(uint64_t Size) { Stat.setSize(Size); } /// 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. @@ -509,6 +513,13 @@ InMemoryFile(Status Stat, std::unique_ptr Buffer) : InMemoryNode(std::move(Stat), IME_File), Buffer(std::move(Buffer)) {} + void setBuffer(std::unique_ptr buffer) { + this->Buffer = std::move(buffer); + setSize(this->Buffer->getBufferSize()); + } + void setBuffer(StringRef buffer) { + setBuffer(llvm::MemoryBuffer::getMemBuffer(buffer)); + } llvm::MemoryBuffer *getBuffer() { return Buffer.get(); } std::string toString(unsigned Indent) const override { @@ -601,6 +612,43 @@ return Root->toString(/*Indent=*/0); } +void InMemoryFileSystem::addHardlink(const Twine &FromPath, const Twine &ToPath, + bool CopyBuffer = true) { + llvm::ErrorOr FromStat = this->status(FromPath.str()); + llvm::ErrorOr ToStat = this->status(ToPath.str()); + assert(FromStat && FromStat->exists() && + "From File must be added before adding hardlink"); + std::set &FromIDSet = + this->Files_by_ID[FromStat->getUniqueID()]; + StringRef FromBuffer = static_cast(*FromIDSet.begin()) + ->getBuffer() + ->getBuffer(); + // If ToFile does not exist, create one containing buffer of From file. + if (!ToStat || !ToStat->exists()) { + this->addFile(ToPath, 0, llvm::MemoryBuffer::getMemBuffer(FromBuffer)); + ToStat = this->status(ToPath.str()); + } + // Check if link already exists. + if (ToStat->getUniqueID() == FromStat->getUniqueID()) { + return; + } + std::set &ToIDSet = + this->Files_by_ID[ToStat->getUniqueID()]; + if (CopyBuffer) { + // Copy Buffer from From to all To nodes. + for (detail::InMemoryNode *node : ToIDSet) { + detail::InMemoryFile *ToFile = static_cast(node); + ToFile->setBuffer(FromBuffer); + } + } + // Merging ToIDSet into FromIDSet. + for (detail::InMemoryNode *to_node : ToIDSet) { + to_node->setUniqueId(FromStat->getUniqueID()); + FromIDSet.insert(to_node); + } + this->Files_by_ID.erase(ToStat->getUniqueID()); +} + bool InMemoryFileSystem::addFile(const Twine &P, time_t ModificationTime, std::unique_ptr Buffer, Optional User, @@ -609,7 +657,6 @@ Optional Perms) { SmallString<128> Path; P.toVector(Path); - // Fix up relative paths. This just prepends the current working directory. std::error_code EC = makeAbsolute(Path); assert(!EC); @@ -645,8 +692,9 @@ 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), + Child.reset(new detail::InMemoryFile(Stat, std::move(Buffer))); + this->Files_by_ID[Stat.getUniqueID()].insert(Child.get()); } Dir->addChild(Name, std::move(Child)); return true; @@ -2064,3 +2112,4 @@ return *this; } + 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,20 @@ InMemoryFileSystemTest() : FS(/*UseNormalizedPaths=*/false), NormalizedFS(/*UseNormalizedPaths=*/true) {} + +public: + void ExpectHardLink(Twine From, Twine To) { + 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(), + (*OpenedTo)->getBuffer(To)->get()->getBuffer()); + } }; TEST_F(InMemoryFileSystemTest, IsEmpty) { @@ -958,6 +973,96 @@ ASSERT_EQ("../b/c", getPosixPath(It->getName())); } +TEST_F(InMemoryFileSystemTest, AddHardLinkWithToFileNotAddedBefore) { + std::pair From = {"/path/to/FROM/file", + "content of FROM file"}; + std::pair To = {"/path/to/TO/file", + "content of TO file"}; + FS.addFile(From.first, 0, MemoryBuffer::getMemBuffer(From.second)); + FS.addHardlink(From.first, To.first, /*CopyBuffer=*/true); + ExpectHardLink(From.first, To.first); +} + +TEST_F(InMemoryFileSystemTest, AddHardLinkWithToFileThatWasAlreadyAdded) { + std::pair From = {"/path/to/FROM/file", + "content of FROM file"}; + std::pair To = {"/path/to/TO/file", + "content of TO file"}; + FS.addFile(From.first, 0, MemoryBuffer::getMemBuffer(From.second)); + FS.addFile(To.first, 0, MemoryBuffer::getMemBuffer(To.second)); + FS.addHardlink(From.first, To.first, /*CopyBuffer=*/true); + ExpectHardLink(From.first, To.first); +} + +TEST_F(InMemoryFileSystemTest, AddMoreThanOneHardLinkToSameFile) { + std::pair From = {"/path/to/FROM/file", + "content of FROM file"}; + std::pair To = {"/path/to/TO/file", + "content of TO file"}; + FS.addFile(From.first, 0, MemoryBuffer::getMemBuffer(From.second)); + FS.addFile(To.first, 0, MemoryBuffer::getMemBuffer(To.second)); + for (int i = 0; i < 5; ++i) + FS.addHardlink(From.first, To.first, /*CopyBuffer=*/true); + ExpectHardLink(From.first, To.first); +} + +TEST_F(InMemoryFileSystemTest, AddHardLinkToAlreadyLinkedFiles) { + std::pair From = {"/path/to/FROM/file", + "content of FROM file"}; + FS.addFile(From.first, 0, MemoryBuffer::getMemBuffer(From.second.data())); + std::vector> ToFiles; + ToFiles.reserve(5); + for (int i = 0; i < 5; ++i) { + ToFiles.push_back( + {"/path/to/TO/file_" + std::to_string(i), "content of to file"}); + } + for (const auto &to : ToFiles) { + FS.addFile(to.first.data(), 0, + MemoryBuffer::getMemBuffer(to.second.data())); + } + for (int i = 1; i < 5; ++i) { + FS.addHardlink(ToFiles[i - 1].first.data(), ToFiles[i].first.data(), + /*CopyBuffer=*/false); + } + FS.addHardlink(From.first, ToFiles[0].first.data(), /*CopyBuffer=*/true); + for (const auto &to : ToFiles) { + ExpectHardLink(From.first, to.first.data()); + } +} + +TEST_F(InMemoryFileSystemTest, AddHardLinkBetweenTwoGroups) { + std::vector> FileGroup1, FileGroup2; + for (int i = 0; i < 5; ++i) { + string str_i = std::to_string(i); + FileGroup1.push_back( + {"/path/to/group/1/file_" + str_i, "group1, file =" + str_i}); + FileGroup2.push_back( + {"/path/to/group/2/file_" + str_i, "group2, file =" + str_i}); + } + // Create files for both groups. + for (int i = 0; i < 5; ++i) { + FS.addFile(FileGroup1[i].first.data(), 0, + MemoryBuffer::getMemBuffer(FileGroup1[i].second.data())); + FS.addFile(FileGroup2[i].first.data(), 0, + MemoryBuffer::getMemBuffer(FileGroup2[i].second.data())); + } + // Link files that belong to the same group. + for (int i = 1; i < 5; ++i) { + FS.addHardlink(FileGroup1[i - 1].first.data(), FileGroup1[i].first.data(), + /*CopyBuffer=*/true); + FS.addHardlink(FileGroup2[i - 1].first.data(), FileGroup2[i].first.data(), + /*CopyBuffer=*/true); + } + // Link 1st files of both groups together. Copy buffers also. + FS.addHardlink(FileGroup1[0].first.data(), FileGroup2[0].first.data(), + /*CopyBuffer=*/true); + // Expect all files to be linked with content as of 1st file of 1st group. + for (int i = 0; i < 5; ++i) { + ExpectHardLink(FileGroup1[0].first.data(), FileGroup1[i].first.data()); + ExpectHardLink(FileGroup1[0].first.data(), FileGroup2[i].first.data()); + } +} + // 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 { @@ -1500,3 +1605,4 @@ EXPECT_EQ(3, NumDiagnostics); } +