#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"
#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"
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; }
+ std::string FileName;
public:
- InMemoryNode(Status Stat, InMemoryNodeKind Kind)
- : Stat(std::move(Stat)), Kind(Kind) {}
+ InMemoryNode(llvm::StringRef FileName, InMemoryNodeKind Kind)
+ : Kind(Kind), FileName(llvm::sys::path::filename(FileName)) {}
virtual ~InMemoryNode() = default;
+ /// Get the filename of this node (the name without the directory part).
+ StringRef getFileName() const { return FileName; }
+ InMemoryNodeKind getKind() const { return Kind; }
+ virtual std::string toString(unsigned Indent) const = 0;
+};
+
+class InMemoryFile : public InMemoryNode {
+ Status Stat;
+ std::unique_ptr<llvm::MemoryBuffer> Buffer;
+
+public:
+ InMemoryFile(Status Stat, std::unique_ptr<llvm::MemoryBuffer> Buffer)
+ : InMemoryNode(Stat.getName(), 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() const { return Buffer.get(); }
- /// Get the filename of this node (the name without the directory part).
- StringRef getFileName() const {
- return llvm::sys::path::filename(Stat.getName());
+ std::string toString(unsigned Indent) const override {
+ return (std::string(Indent, ' ') + Stat.getName() + "\n").str();
+ }
+
+ static bool classof(const InMemoryNode *N) {
+ return N->getKind() == IME_File;
}
- InMemoryNodeKind getKind() const { return Kind; }
- virtual std::string toString(unsigned Indent) const = 0;
};
namespace {
-class InMemoryFile : public InMemoryNode {
- std::unique_ptr<llvm::MemoryBuffer> Buffer;
+class InMemoryHardLink : public InMemoryNode {
+ const InMemoryFile &ResolvedFile;
public:
- InMemoryFile(Status Stat, std::unique_ptr<llvm::MemoryBuffer> Buffer)
- : InMemoryNode(std::move(Stat), IME_File), Buffer(std::move(Buffer)) {}
-
- llvm::MemoryBuffer *getBuffer() { return Buffer.get(); }
+ InMemoryHardLink(StringRef Path, const InMemoryFile &ResolvedFile)
+ : InMemoryNode(Path, IME_HardLink), ResolvedFile(ResolvedFile) {}
+ const InMemoryFile &getResolvedFile() const { return ResolvedFile; }
std::string toString(unsigned Indent) const override {
- return (std::string(Indent, ' ') + getStatus().getName() + "\n").str();
+ return std::string(Indent, ' ') + "HardLink to -> " +
+ ResolvedFile.toString(0);
}
static bool classof(const InMemoryNode *N) {
- return N->getKind() == IME_File;
+ return N->getKind() == IME_HardLink;
}
};
/// \p InMemoryFileAdaptor mimic as much as possible the behavior of
/// \p RealFile.
class InMemoryFileAdaptor : public File {
- InMemoryFile &Node;
+ const InMemoryFile &Node;
/// The name to use when returning a Status for this file.
std::string RequestedName;
public:
- explicit InMemoryFileAdaptor(InMemoryFile &Node, std::string RequestedName)
+ explicit InMemoryFileAdaptor(const InMemoryFile &Node,
+ std::string RequestedName)
: Node(Node), RequestedName(std::move(RequestedName)) {}
llvm::ErrorOr<Status> status() override {
std::error_code close() override { return {}; }
};
-
} // 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(), 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())
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;
}
};
+namespace {
+Status getNodeStatus(const InMemoryNode *Node, StringRef RequestedName) {
+ if (auto Dir = dyn_cast<detail::InMemoryDirectory>(Node))
+ return Dir->getStatus(RequestedName);
+ if (auto File = dyn_cast<detail::InMemoryFile>(Node))
+ return File->getStatus(RequestedName);
+ if (auto Link = dyn_cast<detail::InMemoryHardLink>(Node))
+ return Link->getResolvedFile().getStatus(RequestedName);
+ llvm_unreachable("Unknown node type");
+}
+} // namespace
} // namespace detail
InMemoryFileSystem::InMemoryFileSystem(bool UseNormalizedPaths)
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,
+ const detail::InMemoryFile *HardLinkTarget) {
SmallString<128> Path;
P.toVector(Path);
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);
+ assert(!(HardLinkTarget && Buffer) && "HardLink cannot have a buffer");
// 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;
++I;
if (!Node) {
if (I == E) {
- // End of the path, create a new file or directory.
- Status Stat(P.str(), getNextVirtualUniqueID(),
- llvm::sys::toTimePoint(ModificationTime), ResolvedUser,
- ResolvedGroup, Buffer->getBufferSize(), ResolvedType,
- ResolvedPerms);
+ // End of the path.
std::unique_ptr<detail::InMemoryNode> Child;
- 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 (HardLinkTarget)
+ Child.reset(new detail::InMemoryHardLink(P.str(), *HardLinkTarget));
+ else {
+ // Create a new file or directory.
+ Status Stat(P.str(), getNextVirtualUniqueID(),
+ llvm::sys::toTimePoint(ModificationTime), ResolvedUser,
+ ResolvedGroup, Buffer->getBufferSize(), ResolvedType,
+ ResolvedPerms);
+ 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)));
+ }
}
Dir->addChild(Name, std::move(Child));
return true;
Status Stat(
StringRef(Path.str().begin(), Name.end() - Path.str().begin()),
getNextVirtualUniqueID(), llvm::sys::toTimePoint(ModificationTime),
- ResolvedUser, ResolvedGroup, Buffer->getBufferSize(),
- sys::fs::file_type::directory_file, NewDirectoryPerms);
+ ResolvedUser, ResolvedGroup, 0, sys::fs::file_type::directory_file,
+ NewDirectoryPerms);
Dir = cast<detail::InMemoryDirectory>(Dir->addChild(
Name, llvm::make_unique<detail::InMemoryDirectory>(std::move(Stat))));
continue;
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, std::move(Buffer), User, Group, Type,
+ Perms, /*HardLinkTarget=*/nullptr);
+}
+
bool InMemoryFileSystem::addFileNoOwn(const Twine &P, time_t ModificationTime,
llvm::MemoryBuffer *Buffer,
Optional<uint32_t> User,
std::move(Perms));
}
-static ErrorOr<detail::InMemoryNode *>
+static ErrorOr<const detail::InMemoryNode *>
lookupInMemoryNode(const InMemoryFileSystem &FS, detail::InMemoryDirectory *Dir,
const Twine &P) {
SmallString<128> Path;
return errc::no_such_file_or_directory;
}
+ // If Node is HardLink then return the resolved file.
+ 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)
}
}
+bool InMemoryFileSystem::addHardLink(const Twine &FromPath,
+ const Twine &ToPath) {
+ 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, None, None, None, None,
+ 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());
+ return detail::getNodeStatus(*Node, Path.str());
return Node.getError();
}
if (I != E) {
SmallString<256> Path(RequestedDirName);
llvm::sys::path::append(Path, I->second->getFileName());
- CurrentEntry = I->second->getStatus(Path);
+ CurrentEntry = detail::getNodeStatus(I->second.get(), Path);
} else {
// When we're at the end, make CurrentEntry invalid and DirIterImpl will
// do the rest.
public:
InMemoryDirIterator() = default;
- explicit InMemoryDirIterator(detail::InMemoryDirectory &Dir,
+ explicit InMemoryDirIterator(const detail::InMemoryDirectory &Dir,
std::string RequestedDirName)
: I(Dir.begin()), E(Dir.end()),
RequestedDirName(std::move(RequestedDirName)) {
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/SourceMgr.h"
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <map>
+#include <string>
using namespace clang;
using namespace llvm;
NormalizedFS(/*UseNormalizedPaths=*/true) {}
};
+MATCHER_P2(IsHardLinkTo, FS, Target, "") {
+ StringRef From = arg;
+ StringRef To = Target;
+ auto OpenedFrom = FS->openFileForRead(From);
+ auto OpenedTo = FS->openFileForRead(To);
+ return !OpenedFrom.getError() && !OpenedTo.getError() &&
+ (*OpenedFrom)->status()->getUniqueID() ==
+ (*OpenedTo)->status()->getUniqueID();
+}
+
TEST_F(InMemoryFileSystemTest, IsEmpty) {
auto Stat = FS.status("/a");
ASSERT_EQ(Stat.getError(),errc::no_such_file_or_directory) << FS.toString();
ASSERT_EQ("../b/c", getPosixPath(It->getName()));
}
+TEST_F(InMemoryFileSystemTest, AddHardLinkToFile) {
+ StringRef FromLink = "/path/to/FROM/link";
+ StringRef Target = "/path/to/TO/file";
+ FS.addFile(Target, 0, MemoryBuffer::getMemBuffer("content of target"));
+ EXPECT_TRUE(FS.addHardLink(FromLink, Target));
+ EXPECT_THAT(FromLink, IsHardLinkTo(&FS, Target));
+ EXPECT_TRUE(FS.status(FromLink)->getSize() == FS.status(Target)->getSize());
+ EXPECT_TRUE(FS.getBufferForFile(FromLink)->get()->getBuffer() ==
+ FS.getBufferForFile(Target)->get()->getBuffer());
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkInChainPattern) {
+ StringRef Link0 = "/path/to/0/link";
+ StringRef Link1 = "/path/to/1/link";
+ StringRef Link2 = "/path/to/2/link";
+ StringRef Target = "/path/to/target";
+ FS.addFile(Target, 0, MemoryBuffer::getMemBuffer("content of target file"));
+ EXPECT_TRUE(FS.addHardLink(Link2, Target));
+ EXPECT_TRUE(FS.addHardLink(Link1, Link2));
+ EXPECT_TRUE(FS.addHardLink(Link0, Link1));
+ EXPECT_THAT(Link0, IsHardLinkTo(&FS, Target));
+ EXPECT_THAT(Link1, IsHardLinkTo(&FS, Target));
+ EXPECT_THAT(Link2, IsHardLinkTo(&FS, Target));
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkToAFileThatWasNotAddedBefore) {
+ EXPECT_FALSE(FS.addHardLink("/path/to/link", "/path/to/target"));
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkFromAFileThatWasAddedBefore) {
+ StringRef Link = "/path/to/link";
+ StringRef Target = "/path/to/target";
+ FS.addFile(Target, 0, MemoryBuffer::getMemBuffer("content of target"));
+ FS.addFile(Link, 0, MemoryBuffer::getMemBuffer("content of link"));
+ EXPECT_FALSE(FS.addHardLink(Link, Target));
+}
+
+TEST_F(InMemoryFileSystemTest, AddSameHardLinkMoreThanOnce) {
+ StringRef Link = "/path/to/link";
+ StringRef Target = "/path/to/target";
+ FS.addFile(Target, 0, MemoryBuffer::getMemBuffer("content of target"));
+ EXPECT_TRUE(FS.addHardLink(Link, Target));
+ EXPECT_FALSE(FS.addHardLink(Link, Target));
+}
+
+TEST_F(InMemoryFileSystemTest, AddFileInPlaceOfAHardLinkWithSameContent) {
+ StringRef Link = "/path/to/link";
+ StringRef 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) {
+ StringRef Link = "/path/to/link";
+ StringRef Target = "/path/to/target";
+ StringRef Content = "content of target";
+ StringRef LinkContent = "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(LinkContent)));
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkToADirectory) {
+ StringRef Dir = "path/to/dummy/dir";
+ StringRef Link = "/path/to/link";
+ StringRef File = "path/to/dummy/dir/target";
+ StringRef Content = "content of target";
+ EXPECT_TRUE(FS.addFile(File, 0, MemoryBuffer::getMemBuffer(Content)));
+ EXPECT_FALSE(FS.addHardLink(Link, Dir));
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkFromADirectory) {
+ StringRef Dir = "path/to/dummy/dir";
+ StringRef Target = "path/to/dummy/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 CommonContent = "content string";
+ FS.addFile("/a/b", 0, MemoryBuffer::getMemBuffer(CommonContent));
+ FS.addFile("/c/d", 0, MemoryBuffer::getMemBuffer(CommonContent));
+ EXPECT_FALSE(FS.addHardLink("/c/d/e", "/a/b"));
+}
+
+TEST_F(InMemoryFileSystemTest, RecursiveIterationWithHardLink) {
+ std::error_code EC;
+ FS.addFile("/a/b", 0, MemoryBuffer::getMemBuffer("content string"));
+ 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(getPosixPath(I->getName()));
+ }
+ EXPECT_THAT(Nodes, testing::UnorderedElementsAre("/a", "/a/b", "/c", "/c/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 {