Index: include/clang/Basic/VirtualFileSystem.h =================================================================== --- include/clang/Basic/VirtualFileSystem.h +++ include/clang/Basic/VirtualFileSystem.h @@ -265,6 +265,15 @@ /// the operating system. IntrusiveRefCntPtr getRealFileSystem(); +/// \brief Creates a \p vfs::FileSystem for the 'real' file system that does not +/// change to global state of the program on calls to +/// setCurrentWorkingDirectory(). Internally it has to do more paths +/// manipulations than the vfs::FileSystem returned by getRealFileSystem(). Note +/// that returned instance of vfs::FileSystem is not thread-safe. +/// Thread-friendly refers to the fact that it does not use global +/// WorkingDirectory. +IntrusiveRefCntPtr createThreadFriendlyRealFS(); + /// \brief A file system that allows overlaying one \p AbstractFileSystem on top /// of another. /// Index: lib/Basic/VirtualFileSystem.cpp =================================================================== --- lib/Basic/VirtualFileSystem.cpp +++ lib/Basic/VirtualFileSystem.cpp @@ -189,18 +189,40 @@ /// \brief The file system according to your operating system. class RealFileSystem : public FileSystem { public: + RealFileSystem(); + ~RealFileSystem(); + ErrorOr status(const Twine &Path) override; ErrorOr> openFileForRead(const Twine &Path) override; directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override; llvm::ErrorOr getCurrentWorkingDirectory() const override; std::error_code setCurrentWorkingDirectory(const Twine &Path) override; + +private: + std::string WorkingDir; + int WorkingDirFD; }; } // end anonymous namespace +RealFileSystem::RealFileSystem() { + SmallString<128> CWD; + + auto Err = llvm::sys::fs::openDirectoryForAt(".", /*ref*/WorkingDirFD, &CWD); + // TODO: handle errors gracefully. + assert(!Err && "couldn't open CWD'"); + + WorkingDir = CWD.str(); +} + +RealFileSystem::~RealFileSystem() { + sys::Process::SafelyCloseFileDescriptor(WorkingDirFD); +} + ErrorOr RealFileSystem::status(const Twine &Path) { sys::fs::file_status RealStatus; - if (std::error_code EC = sys::fs::status(Path, RealStatus)) + // TODO: handle errors gracefully. + if (std::error_code EC = sys::fs::status_at(WorkingDirFD, Path, RealStatus)) return EC; return Status::copyWithNewName(RealStatus, Path.str()); } @@ -209,27 +231,30 @@ RealFileSystem::openFileForRead(const Twine &Name) { int FD; SmallString<256> RealName; - if (std::error_code EC = sys::fs::openFileForRead(Name, FD, &RealName)) + // TODO: handle errors gracefully. + if (std::error_code EC = sys::fs::openFileForRead(WorkingDirFD, Name, FD, &RealName)) return EC; return std::unique_ptr(new RealFile(FD, Name.str(), RealName.str())); } llvm::ErrorOr RealFileSystem::getCurrentWorkingDirectory() const { - SmallString<256> Dir; - if (std::error_code EC = llvm::sys::fs::current_path(Dir)) - return EC; - return Dir.str().str(); + // TODO: Handle errors. + return WorkingDir; + // SmallString<256> Dir; + // if (std::error_code EC = llvm::sys::fs::current_path(Dir)) + // return EC; + // return Dir.str().str(); } std::error_code RealFileSystem::setCurrentWorkingDirectory(const Twine &Path) { - // FIXME: chdir is thread hostile; on the other hand, creating the same - // behavior as chdir is complex: chdir resolves the path once, thus - // guaranteeing that all subsequent relative path operations work - // on the same path the original chdir resulted in. This makes a - // difference for example on network filesystems, where symlinks might be - // switched during runtime of the tool. Fixing this depends on having a - // file system abstraction that allows openat() style interactions. - return llvm::sys::fs::set_current_path(Path); + SmallString<128> CWD; + auto Err = llvm::sys::fs::openFileForRead(Path, /*ref*/WorkingDirFD, &CWD); + if (Err) + return Err; + + // TODO: handle errors + WorkingDir = CWD.str(); + return std::error_code(); } IntrusiveRefCntPtr vfs::getRealFileSystem() { @@ -237,11 +262,15 @@ return FS; } +IntrusiveRefCntPtr vfs::createThreadFriendlyRealFS() { + return new RealFileSystem(); +} + namespace { class RealFSDirIter : public clang::vfs::detail::DirIterImpl { llvm::sys::fs::directory_iterator Iter; public: - RealFSDirIter(const Twine &Path, std::error_code &EC) : Iter(Path, EC) { + RealFSDirIter(int WorkingDirFD, const Twine &Path, std::error_code &EC) : Iter(WorkingDirFD, Path, EC) { if (!EC && Iter != llvm::sys::fs::directory_iterator()) { llvm::sys::fs::file_status S; EC = Iter->status(S); @@ -268,7 +297,7 @@ directory_iterator RealFileSystem::dir_begin(const Twine &Dir, std::error_code &EC) { - return directory_iterator(std::make_shared(Dir, EC)); + return directory_iterator(std::make_shared(WorkingDirFD, Dir, EC)); } //===-----------------------------------------------------------------------===/ Index: unittests/Basic/VirtualFileSystemTest.cpp =================================================================== --- unittests/Basic/VirtualFileSystemTest.cpp +++ unittests/Basic/VirtualFileSystemTest.cpp @@ -12,6 +12,7 @@ #include "llvm/Support/Errc.h" #include "llvm/Support/Host.h" #include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" #include "llvm/Support/SourceMgr.h" #include "gtest/gtest.h" #include @@ -477,6 +478,75 @@ } #endif +TEST(VirtualFileSystemTest, ThreadFriendlyRealFSWorkingDirTest) { + ScopedDir TestDirectory("thread-friendly-vfs-test", /*Unique*/ true); + + SmallString<128> PathA = TestDirectory.Path; + llvm::sys::path::append(PathA, "a"); + SmallString<128> PathAC = PathA; + llvm::sys::path::append(PathAC, "c"); + SmallString<128> PathB = TestDirectory.Path; + llvm::sys::path::append(PathB, "b"); + SmallString<128> PathBD = PathB; + llvm::sys::path::append(PathBD, "d"); + + ScopedDir A(PathA); + ScopedDir AC(PathAC); + ScopedDir B(PathB); + ScopedDir BD(PathBD); + + IntrusiveRefCntPtr FSA = vfs::createThreadFriendlyRealFS(); + // setCurrentWorkingDirectory should fininsh without error + ASSERT_TRUE(!FSA->setCurrentWorkingDirectory(Twine(A))); + + IntrusiveRefCntPtr FSB = vfs::createThreadFriendlyRealFS(); + // setCurrentWorkingDirectory should fininsh without error + ASSERT_TRUE(!FSB->setCurrentWorkingDirectory(Twine(B))); + + ASSERT_TRUE(FSA->getCurrentWorkingDirectory()); + EXPECT_EQ(*FSA->getCurrentWorkingDirectory(), StringRef(A)); + + ASSERT_TRUE(FSB->getCurrentWorkingDirectory()); + EXPECT_EQ(*FSB->getCurrentWorkingDirectory(), StringRef(B)); + + auto C = FSA->status("c"); + // a/c should be found in FSA + ASSERT_TRUE(!!C); + // name of 'c' must be relative + EXPECT_EQ(C->getName(), "c"); + EXPECT_TRUE(C->isDirectory()); + + // "a", "b" and "d" should not be found in FSA. + EXPECT_FALSE(!!FSA->status("a")); + EXPECT_FALSE(!!FSA->status("b")); + EXPECT_FALSE(!!FSA->status("d")); + + auto D = FSB->status("d"); + // b/d should be found in FSB + ASSERT_TRUE(!!D); + // name of 'd' must be relative + EXPECT_EQ(D->getName(), "d"); + EXPECT_TRUE(D->isDirectory()); + + // "a", "b" and "c" should not be found in FSB. + EXPECT_FALSE(!!FSB->status("a")); + EXPECT_FALSE(!!FSB->status("b")); + EXPECT_FALSE(!!FSB->status("c")); + + SmallString<128> RelPathC = StringRef(".."); + llvm::sys::path::append(RelPathC, "a", "c"); + auto RelCFromFSA = FSA->status(RelPathC); + ASSERT_TRUE(!!RelCFromFSA); + EXPECT_EQ(RelCFromFSA->getName(), StringRef(RelPathC)); + + auto RelCFromFSB = FSB->status(RelPathC); + ASSERT_TRUE(!!RelCFromFSB); + EXPECT_EQ(RelCFromFSB->getName(), StringRef(RelPathC)); + + EXPECT_TRUE(C->equivalent(*RelCFromFSA)); + EXPECT_TRUE(C->equivalent(*RelCFromFSB)); +} + template static void checkContents(DirIter I, ArrayRef ExpectedOut) { std::error_code EC;