usaxena95 updated this revision to Diff 163128.
usaxena95 added a comment.
Created InMemoryHardLink Class which can handle the resolved InMemoryFile
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,84 @@
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));
+}
+
// 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
@@ -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<InMemoryFile *>(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<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,
+ Optional<detail::InMemoryNode *> 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<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 File = dyn_cast<detail::InMemoryHardLink>(Node)) {
+ return File->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=*/None);
+}
+
bool InMemoryFileSystem::addFileNoOwn(const Twine &P, time_t ModificationTime,
llvm::MemoryBuffer *Buffer,
Optional<uint32_t> User,
@@ -724,13 +778,36 @@
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->getResolvedNode();
+ 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;
+ detail::InMemoryNode *ResolvedHardLink;
+ ResolvedHardLink = *ToNode;
+ return this->addFile(FromPath, 0, llvm::MemoryBuffer::getMemBuffer(""), User,
+ Group, Type, Perms, ResolvedHardLink);
+}
+
llvm::ErrorOr<Status> InMemoryFileSystem::status(const Twine &Path) {
auto Node = lookupInMemoryNode(*this, Root.get(), Path);
if (Node)
Index: include/clang/Basic/VirtualFileSystem.h
===================================================================
--- include/clang/Basic/VirtualFileSystem.h
+++ include/clang/Basic/VirtualFileSystem.h
@@ -323,15 +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,
+ Optional<detail::InMemoryNode *> HardLink);
public:
explicit InMemoryFileSystem(bool UseNormalizedPaths = true);
~InMemoryFileSystem() override;
@@ -348,6 +354,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
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits