diff --git a/llvm/include/llvm/Support/VirtualFileSystem.h b/llvm/include/llvm/Support/VirtualFileSystem.h --- a/llvm/include/llvm/Support/VirtualFileSystem.h +++ b/llvm/include/llvm/Support/VirtualFileSystem.h @@ -343,6 +343,10 @@ /// their addition. FileSystemList FSList; + /// The last CWD successfully set on at least one of the filesystems in + /// \c FSList. + std::string CWD; + public: OverlayFileSystem(IntrusiveRefCntPtr Base); @@ -353,8 +357,14 @@ llvm::ErrorOr> openFileForRead(const Twine &Path) override; directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override; + + /// Gets the last successfully set CWD, which starts as the CWD from \c Base. llvm::ErrorOr getCurrentWorkingDirectory() const override; + + /// Attempts to set the CWD on all contained FS. Only errors if they all + /// fail. std::error_code setCurrentWorkingDirectory(const Twine &Path) override; + std::error_code isLocal(const Twine &Path, bool &Result) override; std::error_code getRealPath(const Twine &Path, SmallVectorImpl &Output) const override; @@ -363,6 +373,8 @@ using const_iterator = FileSystemList::const_reverse_iterator; using reverse_iterator = FileSystemList::iterator; using const_reverse_iterator = FileSystemList::const_iterator; + using range = iterator_range; + using const_range = iterator_range; /// Get an iterator pointing to the most recently added file system. iterator overlays_begin() { return FSList.rbegin(); } @@ -380,6 +392,10 @@ reverse_iterator overlays_rend() { return FSList.end(); } const_reverse_iterator overlays_rend() const { return FSList.end(); } + range overlays_range() { return llvm::reverse(FSList); } + + const_range overlays_range() const { return llvm::reverse(FSList); } + void dump(raw_ostream &OS) const override; }; diff --git a/llvm/lib/Support/VirtualFileSystem.cpp b/llvm/lib/Support/VirtualFileSystem.cpp --- a/llvm/lib/Support/VirtualFileSystem.cpp +++ b/llvm/lib/Support/VirtualFileSystem.cpp @@ -413,64 +413,86 @@ //===-----------------------------------------------------------------------===/ OverlayFileSystem::OverlayFileSystem(IntrusiveRefCntPtr BaseFS) { + if (auto NewCWD = BaseFS->getCurrentWorkingDirectory()) + CWD = *NewCWD; FSList.push_back(std::move(BaseFS)); } void OverlayFileSystem::pushOverlay(IntrusiveRefCntPtr FS) { FSList.push_back(FS); - // Synchronize added file systems by duplicating the working directory from - // the first one in the list. - FS->setCurrentWorkingDirectory(getCurrentWorkingDirectory().get()); } ErrorOr OverlayFileSystem::status(const Twine &Path) { // FIXME: handle symlinks that cross file systems - for (iterator I = overlays_begin(), E = overlays_end(); I != E; ++I) { - ErrorOr Status = (*I)->status(Path); - if (Status || Status.getError() != llvm::errc::no_such_file_or_directory) + for (auto &FS : overlays_range()) { + ErrorOr Status = FS->status(Path); + if (Status || Status.getError() != errc::no_such_file_or_directory) return Status; } - return make_error_code(llvm::errc::no_such_file_or_directory); + return errc::no_such_file_or_directory; } ErrorOr> OverlayFileSystem::openFileForRead(const llvm::Twine &Path) { // FIXME: handle symlinks that cross file systems - for (iterator I = overlays_begin(), E = overlays_end(); I != E; ++I) { - auto Result = (*I)->openFileForRead(Path); - if (Result || Result.getError() != llvm::errc::no_such_file_or_directory) + for (auto &FS : overlays_range()) { + auto Result = FS->openFileForRead(Path); + if (Result || Result.getError() != errc::no_such_file_or_directory) return Result; } - return make_error_code(llvm::errc::no_such_file_or_directory); + return errc::no_such_file_or_directory; } -llvm::ErrorOr -OverlayFileSystem::getCurrentWorkingDirectory() const { - // All file systems are synchronized, just take the first working directory. - return FSList.front()->getCurrentWorkingDirectory(); +ErrorOr OverlayFileSystem::getCurrentWorkingDirectory() const { + if (CWD.empty()) + return errc::operation_not_permitted; + return CWD; } std::error_code OverlayFileSystem::setCurrentWorkingDirectory(const Twine &Path) { - for (auto &FS : FSList) - if (std::error_code EC = FS->setCurrentWorkingDirectory(Path)) - return EC; + // There is no guarantee that setting the CWD on the underlying filesystems + // actually succeeds for them all, eg. RealFileSystem only sets its CWD if + // the directory actually exists. But we also don't want to fail if *any* + // fails - it would be a fairly common case that eg. there's a virtual + // directory that doesn't exist in the underlying filesystem. + // + // Instead, keep track of the most-recent CWD that was successfully set on at + // least one FS and use that as the CWD of this FS. + + bool Success = false; + for (auto &FS : overlays_range()) { + std::error_code EC = FS->setCurrentWorkingDirectory(Path); + if (!EC) { + Success = true; + CWD = *FS->getCurrentWorkingDirectory(); + } + } + + // Theoretically all the filesystems could have failed with an unrelated + // error, but this seems the most reasonable error to return. + if (!Success) + return errc::no_such_file_or_directory; return {}; } std::error_code OverlayFileSystem::isLocal(const Twine &Path, bool &Result) { - for (auto &FS : FSList) - if (FS->exists(Path)) - return FS->isLocal(Path, Result); + for (auto &FS : overlays_range()) { + std::error_code EC = FS->isLocal(Path, Result); + if (!EC || EC != errc::no_such_file_or_directory) + return EC; + } return errc::no_such_file_or_directory; } std::error_code OverlayFileSystem::getRealPath(const Twine &Path, SmallVectorImpl &Output) const { - for (const auto &FS : FSList) - if (FS->exists(Path)) - return FS->getRealPath(Path, Output); + for (auto &FS : overlays_range()) { + std::error_code EC = FS->getRealPath(Path, Output); + if (!EC || EC != errc::no_such_file_or_directory) + return EC; + } return errc::no_such_file_or_directory; } diff --git a/llvm/unittests/Support/VirtualFileSystemTest.cpp b/llvm/unittests/Support/VirtualFileSystemTest.cpp --- a/llvm/unittests/Support/VirtualFileSystemTest.cpp +++ b/llvm/unittests/Support/VirtualFileSystemTest.cpp @@ -850,6 +850,73 @@ } } +TEST(OverlayFileSystemTest, BaseNoWorkingDir) { + auto ErrorFS = makeIntrusiveRefCnt(); + // Base has no CWD, Overlay shouldn't either + auto OverlayFS = makeIntrusiveRefCnt(ErrorFS); + ASSERT_TRUE(OverlayFS->getCurrentWorkingDirectory().getError()); + + auto DummyFS = makeIntrusiveRefCnt(); + DummyFS->setCurrentWorkingDirectory("/root"); + + // Should still have CWD on underlying + OverlayFS->pushOverlay(DummyFS); + auto CWD = DummyFS->getCurrentWorkingDirectory(); + ASSERT_FALSE(CWD.getError()); + ASSERT_EQ("/root", *CWD); + // But not on overlay + CWD = OverlayFS->getCurrentWorkingDirectory(); + ASSERT_TRUE(CWD.getError()); + + // setCWD should now succeed + auto EC = OverlayFS->setCurrentWorkingDirectory("/root2"); + ASSERT_FALSE(EC); + CWD = OverlayFS->getCurrentWorkingDirectory(); + ASSERT_FALSE(CWD.getError()); + ASSERT_EQ("/root2", *CWD); + + // And change the underlying as well + CWD = DummyFS->getCurrentWorkingDirectory(); + ASSERT_FALSE(CWD.getError()); + ASSERT_EQ("/root2", *CWD); +} + +TEST(OverlayFileSystemTest, BaseWithWorkingDir) { + // Base has CWD, overlay should too + auto DummyFS1 = makeIntrusiveRefCnt(); + DummyFS1->setCurrentWorkingDirectory("/root"); + auto OverlayFS = makeIntrusiveRefCnt(DummyFS1); + auto CWD = OverlayFS->getCurrentWorkingDirectory(); + ASSERT_FALSE(CWD.getError()); + ASSERT_EQ("/root", *CWD); + + // Check CWD is uneffected when adding to an overlay + auto DummyFS2 = makeIntrusiveRefCnt(); + DummyFS2->setCurrentWorkingDirectory("/root2"); + OverlayFS->pushOverlay(DummyFS2); + CWD = DummyFS2->getCurrentWorkingDirectory(); + ASSERT_FALSE(CWD.getError()); + ASSERT_EQ("/root2", *CWD); + + // setCWD should still work, even if a FS fails + auto ErrorFS = makeIntrusiveRefCnt(); + OverlayFS->pushOverlay(ErrorFS); + auto EC = OverlayFS->setCurrentWorkingDirectory("/root3"); + ASSERT_FALSE(EC); + + CWD = OverlayFS->getCurrentWorkingDirectory(); + ASSERT_FALSE(CWD.getError()); + ASSERT_EQ("/root3", *CWD); + + // And it should set all underlying filesystems + CWD = DummyFS1->getCurrentWorkingDirectory(); + ASSERT_FALSE(CWD.getError()); + ASSERT_EQ("/root3", *CWD); + CWD = DummyFS2->getCurrentWorkingDirectory(); + ASSERT_FALSE(CWD.getError()); + ASSERT_EQ("/root3", *CWD); +} + TEST(ProxyFileSystemTest, Basic) { IntrusiveRefCntPtr Base( new vfs::InMemoryFileSystem());