Index: include/llvm/Support/VirtualFileSystem.h =================================================================== --- include/llvm/Support/VirtualFileSystem.h +++ include/llvm/Support/VirtualFileSystem.h @@ -298,8 +298,16 @@ /// Gets an \p vfs::FileSystem for the 'real' file system, as seen by /// the operating system. +/// The working directory is linked to the process's working directory. +/// (This is usually thread-hostile). IntrusiveRefCntPtr getRealFileSystem(); +/// Create an \p vfs::FileSystem for the 'real' file system, as seen by +/// the operating system. +/// It has its own working directory, independent of (but initially equal to) +/// that of the process. +std::unique_ptr createPhysicalFileSystem(); + /// A file system that allows overlaying one \p AbstractFileSystem on top /// of another. /// Index: lib/Support/VirtualFileSystem.cpp =================================================================== --- lib/Support/VirtualFileSystem.cpp +++ lib/Support/VirtualFileSystem.cpp @@ -228,9 +228,28 @@ namespace { -/// The file system according to your operating system. +/// A file system according to your operating system. +/// This may be linked to the process's working directory, or maintain its own. +/// +/// Currently, its own working directory is emulated by storing the path and +/// sending absolute paths to llvm::sys::fs:: functions. +/// A more principled approach would be to push this down a level, modelling +/// the working dir as an llvm::sys::fs::WorkingDir or similar. +/// This would enable the use of openat()-style functions on some platforms. class RealFileSystem : public FileSystem { public: + RealFileSystem(bool LinkCWDToProcess) { + if (!LinkCWDToProcess) { + SmallString<128> PWD, RealPWD; + if (llvm::sys::fs::current_path(PWD)) + return; // Awful, but nothing to do here. + if (auto Err = llvm::sys::fs::real_path(PWD, RealPWD)) + WD = {PWD, PWD}; + else + WD = {PWD, RealPWD}; + } + } + ErrorOr status(const Twine &Path) override; ErrorOr> openFileForRead(const Twine &Path) override; directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override; @@ -242,72 +261,102 @@ SmallVectorImpl &Output) const override; private: - mutable std::mutex CWDMutex; - mutable std::string CWDCache; + template ()(Twine()))> + R WithFixedPath(const Twine& Path, const Func &F) const { + if (!WD) + return F(Path); + SmallString<256> Fixed; + Path.toVector(Fixed); + sys::fs::make_absolute(WD->Resolved, Fixed); + return F(Fixed); + } + + struct WorkingDirectory { + // The current working directory, without symlinks resolved. (echo $PWD). + SmallString<128> Specified; + // The current working directory, with links resolved. (readlink .). + SmallString<128> Resolved; + }; + Optional WD; }; } // namespace -ErrorOr RealFileSystem::status(const Twine &Path) { - sys::fs::file_status RealStatus; - if (std::error_code EC = sys::fs::status(Path, RealStatus)) - return EC; - return Status::copyWithNewName(RealStatus, Path.str()); +ErrorOr RealFileSystem::status(const Twine &OrigPath) { + return WithFixedPath(OrigPath, [&](const Twine &Path) -> ErrorOr { + sys::fs::file_status RealStatus; + if (std::error_code EC = sys::fs::status(Path, RealStatus)) + return EC; + return Status::copyWithNewName(RealStatus, OrigPath.str()); + }); } ErrorOr> -RealFileSystem::openFileForRead(const Twine &Name) { +RealFileSystem::openFileForRead(const Twine &OrigName) { int FD; SmallString<256> RealName; - if (std::error_code EC = - sys::fs::openFileForRead(Name, FD, sys::fs::OF_None, &RealName)) - return EC; - return std::unique_ptr(new RealFile(FD, Name.str(), RealName.str())); + return WithFixedPath( + OrigName, [&](const Twine &Name) -> ErrorOr> { + if (std::error_code EC = + sys::fs::openFileForRead(Name, FD, sys::fs::OF_None, &RealName)) + return EC; + return std::unique_ptr( + new RealFile(FD, OrigName.str(), RealName.str())); + }); } llvm::ErrorOr RealFileSystem::getCurrentWorkingDirectory() const { - std::lock_guard Lock(CWDMutex); - if (!CWDCache.empty()) - return CWDCache; - SmallString<256> Dir; + if (WD) + return WD->Specified.str(); + + SmallString<128> Dir; if (std::error_code EC = llvm::sys::fs::current_path(Dir)) return EC; - CWDCache = Dir.str(); - return CWDCache; + return Dir.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. - if (auto EC = llvm::sys::fs::set_current_path(Path)) - return EC; - - // Invalidate cache. - std::lock_guard Lock(CWDMutex); - CWDCache.clear(); - return std::error_code(); + if (!WD) + return llvm::sys::fs::set_current_path(Path); + + SmallString<128> Absolute, Resolved; + return WithFixedPath(Path, [&](const Twine &Path) { + bool IsDir; + if (auto Err = llvm::sys::fs::is_directory(Path, IsDir)) + return Err; + if (!IsDir) + return std::make_error_code(std::errc::not_a_directory); + if (auto Err = llvm::sys::fs::real_path(Path, Resolved)) + return Err; + Path.toVector(Absolute); + WD = {Absolute, Resolved}; + return std::error_code(); + }); } std::error_code RealFileSystem::isLocal(const Twine &Path, bool &Result) { - return llvm::sys::fs::is_local(Path, Result); + return WithFixedPath(Path, [&](const Twine &Path) { + return llvm::sys::fs::is_local(Path, Result); + }); } std::error_code RealFileSystem::getRealPath(const Twine &Path, SmallVectorImpl &Output) const { - return llvm::sys::fs::real_path(Path, Output); + return WithFixedPath(Path, [&](const Twine &Path) { + return llvm::sys::fs::real_path(Path, Output); + }); } IntrusiveRefCntPtr vfs::getRealFileSystem() { - static IntrusiveRefCntPtr FS = new RealFileSystem(); + static IntrusiveRefCntPtr FS(new RealFileSystem(true)); return FS; } +std::unique_ptr vfs::createPhysicalFileSystem() { + return llvm::make_unique(false); +} + namespace { class RealFSDirIter : public llvm::vfs::detail::DirIterImpl { @@ -333,7 +382,9 @@ directory_iterator RealFileSystem::dir_begin(const Twine &Dir, std::error_code &EC) { - return directory_iterator(std::make_shared(Dir, EC)); + return WithFixedPath(Dir, [&](const Twine &Dir) { + return directory_iterator(std::make_shared(Dir, EC)); + }); } //===-----------------------------------------------------------------------===/ Index: unittests/Support/VirtualFileSystemTest.cpp =================================================================== --- unittests/Support/VirtualFileSystemTest.cpp +++ unittests/Support/VirtualFileSystemTest.cpp @@ -382,6 +382,25 @@ } operator StringRef() { return Path.str(); } }; + +struct ScopedFile { + SmallString<128> Path; + ScopedFile(const Twine &Path, StringRef Contents) { + Path.toVector(this->Path); + std::error_code EC; + raw_fd_ostream OS(this->Path, EC); + EXPECT_FALSE(EC); + OS << Contents; + OS.flush(); + EXPECT_FALSE(OS.error()); + if (EC || OS.error()) + this->Path = ""; + } + ~ScopedFile() { + if (Path != "") + EXPECT_FALSE(llvm::sys::fs::remove(Path.str())); + } +}; } // end anonymous namespace TEST(VirtualFileSystemTest, BasicRealFSIteration) { @@ -411,6 +430,67 @@ EXPECT_EQ(vfs::directory_iterator(), I); } +TEST(VirtualFileSystemTest, MultipleWorkingDirs) { + // Our root contains a/aa, b/bb, c, where c is a link to a/. + // Run tests both in root/b/ and root/c/ (to test "normal" and symlink dirs). + // Interleave operations to show the working directories are independent. + ScopedDir Root("r", true), ADir(Root.Path + "/a"), BDir(Root.Path + "/b"); + ScopedLink C(ADir.Path, Root.Path + "/c"); + ScopedFile AA(ADir.Path + "/aa", "aaaa"), BB(BDir.Path + "/bb", "bbbb"); + std::unique_ptr BFS = vfs::createPhysicalFileSystem(), + CFS = vfs::createPhysicalFileSystem(); + + ASSERT_FALSE(BFS->setCurrentWorkingDirectory(BDir.Path)); + ASSERT_FALSE(CFS->setCurrentWorkingDirectory(C.Path)); + EXPECT_EQ(BDir.Path, *BFS->getCurrentWorkingDirectory()); + EXPECT_EQ(C.Path, *CFS->getCurrentWorkingDirectory()); + + // openFileForRead(), indirectly. + auto BBuf = BFS->getBufferForFile("bb"); + ASSERT_TRUE(BBuf); + EXPECT_EQ("bbbb", (*BBuf)->getBuffer()); + + auto ABuf = CFS->getBufferForFile("aa"); + ASSERT_TRUE(ABuf); + EXPECT_EQ("aaaa", (*ABuf)->getBuffer()); + + // status() + auto BStat = BFS->status("bb"); + ASSERT_TRUE(BStat); + EXPECT_EQ("bb", BStat->getName()); + + auto AStat = CFS->status("aa"); + ASSERT_TRUE(AStat); + EXPECT_EQ("aa", AStat->getName()); // unresolved name + + // getRealPath() + SmallString<128> BPath; + ASSERT_FALSE(BFS->getRealPath("bb", BPath)); + EXPECT_EQ(BB.Path, BPath); + + SmallString<128> APath; + ASSERT_FALSE(CFS->getRealPath("aa", APath)); + EXPECT_EQ(AA.Path, APath); // Reports resolved name. + + // dir_begin + std::error_code EC; + auto BIt = BFS->dir_begin(".", EC); + ASSERT_FALSE(EC); + ASSERT_NE(BIt, vfs::directory_iterator()); + EXPECT_EQ((BDir.Path + "/./bb").str(), BIt->path()); + BIt.increment(EC); + ASSERT_FALSE(EC); + ASSERT_EQ(BIt, vfs::directory_iterator()); + + auto CIt = CFS->dir_begin(".", EC); + ASSERT_FALSE(EC); + ASSERT_NE(CIt, vfs::directory_iterator()); + EXPECT_EQ((ADir.Path + "/./aa").str(), CIt->path()); // Partly resolved name! + CIt.increment(EC); // Because likely to read through this path. + ASSERT_FALSE(EC); + ASSERT_EQ(CIt, vfs::directory_iterator()); +} + #ifdef LLVM_ON_UNIX TEST(VirtualFileSystemTest, BrokenSymlinkRealFSIteration) { ScopedDir TestDirectory("virtual-file-system-test", /*Unique*/ true);