/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This software may be used and distributed according to the terms of the * GNU General Public License version 2. */ #include "eden/fs/inodes/Overlay.h" #include #include #include #include #include #include #include #include #include #include #include #include "eden/fs/config/EdenConfig.h" #include "eden/fs/inodes/DirEntry.h" #include "eden/fs/inodes/FileContentStore.h" #include "eden/fs/inodes/InodeBase.h" #include "eden/fs/inodes/InodeTable.h" #include "eden/fs/inodes/OverlayFile.h" #include "eden/fs/inodes/TreeInode.h" #include "eden/fs/inodes/memcatalog/MemInodeCatalog.h" #include "eden/fs/inodes/sqlitecatalog/BufferedSqliteInodeCatalog.h" #include "eden/fs/inodes/sqlitecatalog/SqliteInodeCatalog.h" #include "eden/fs/sqlite/SqliteDatabase.h" #include "eden/fs/telemetry/EdenStats.h" #include "eden/fs/utils/Bug.h" #include "eden/fs/utils/PathFuncs.h" #ifndef _WIN32 #include "eden/fs/inodes/lmdbcatalog/BufferedLMDBInodeCatalog.h" // @manual #include "eden/fs/inodes/lmdbcatalog/LMDBFileContentStore.h" // @manual #include "eden/fs/inodes/lmdbcatalog/LMDBInodeCatalog.h" // @manual #endif namespace facebook::eden { namespace { constexpr uint64_t ioCountMask = 0x7FFFFFFFFFFFFFFFull; constexpr uint64_t ioClosedMask = 1ull << 63; std::unique_ptr makeInodeCatalog( AbsolutePathPiece localDir, InodeCatalogType inodeCatalogType, InodeCatalogOptions inodeCatalogOptions, const EdenConfig& config, FileContentStore* fileContentStore, const std::shared_ptr& logger) { if (inodeCatalogType == InodeCatalogType::Sqlite) { // Controlled via EdenConfig::unsafeInMemoryOverlay if (inodeCatalogOptions.containsAllOf(INODE_CATALOG_UNSAFE_IN_MEMORY)) { // Controlled via EdenConfig::overlayBuffered if (inodeCatalogOptions.containsAllOf(INODE_CATALOG_BUFFERED)) { XLOG(WARN) << "In-memory Sqlite buffered overlay requested. This will cause data loss."; return std::make_unique( std::make_unique(SqliteDatabase::inMemory), config); } else { XLOG(WARN) << "In-memory Sqlite overlay requested. This will cause data loss."; return std::make_unique( std::make_unique(SqliteDatabase::inMemory)); } } // Controlled via EdenConfig::overlaySynchronousMode if (inodeCatalogOptions.containsAllOf(INODE_CATALOG_SYNCHRONOUS_OFF)) { // Controlled via EdenConfig::overlayBuffered if (inodeCatalogOptions.containsAllOf(INODE_CATALOG_BUFFERED)) { XLOG(DBG2) << "Buffered Sqlite overlay being used with synchronous-mode = off"; return std::make_unique( localDir, logger, config, SqliteTreeStore::SynchronousMode::Off); } else { XLOG(DBG2) << "Sqlite overlay being used with synchronous-mode = off"; return std::make_unique( localDir, logger, SqliteTreeStore::SynchronousMode::Off); } } // Controlled via EdenConfig::overlayBuffered if (inodeCatalogOptions.containsAllOf(INODE_CATALOG_BUFFERED)) { XLOG(DBG4) << "Buffered Sqlite overlay being used"; return std::make_unique( localDir, logger, config); } XLOG(DBG4) << "Sqlite overlay being used."; return std::make_unique(localDir, logger); } else if (inodeCatalogType == InodeCatalogType::InMemory) { XLOG(DBG4) << "In-memory overlay being used."; return std::make_unique(); } #ifdef _WIN32 (void)fileContentStore; if (inodeCatalogType == InodeCatalogType::Legacy) { throw std::runtime_error( "Legacy overlay type is not supported. Please reclone."); } else if (inodeCatalogType == InodeCatalogType::LMDB) { throw std::runtime_error( "LMDB overlay type is not supported. Please reclone."); } XLOG(DBG4) << "Sqlite overlay being used."; return std::make_unique(localDir, logger); #else if (inodeCatalogType == InodeCatalogType::LMDB) { if (inodeCatalogOptions.containsAllOf(INODE_CATALOG_BUFFERED)) { XLOG(DBG4) << "Buffered LMDB overlay being used"; return std::make_unique( static_cast(fileContentStore), config); } XLOG(DBG4) << "LMDB overlay being used"; return std::make_unique( static_cast(fileContentStore)); } XLOG(DBG4) << "Legacy overlay being used."; return std::make_unique( static_cast(fileContentStore)); #endif } std::unique_ptr makeFileContentStore( AbsolutePathPiece localDir, const std::shared_ptr& logger, InodeCatalogType inodeCatalogType) { #ifdef _WIN32 (void)localDir; (void)logger; return nullptr; #else if (inodeCatalogType == InodeCatalogType::Legacy) { return std::make_unique(localDir); } else { return std::make_unique(localDir, logger); } #endif } } // namespace using folly::Unit; using std::optional; std::shared_ptr Overlay::create( AbsolutePathPiece localDir, CaseSensitivity caseSensitive, InodeCatalogType inodeCatalogType, InodeCatalogOptions inodeCatalogOptions, std::shared_ptr logger, EdenStatsPtr stats, bool windowsSymlinksEnabled, const EdenConfig& config) { // This allows us to access the private constructor. struct MakeSharedEnabler : public Overlay { explicit MakeSharedEnabler( AbsolutePathPiece localDir, CaseSensitivity caseSensitive, InodeCatalogType inodeCatalogType, InodeCatalogOptions inodeCatalogOptions, std::shared_ptr logger, EdenStatsPtr stats, bool windowsSymlinksEnabled, const EdenConfig& config) : Overlay( localDir, caseSensitive, inodeCatalogType, inodeCatalogOptions, logger, std::move(stats), windowsSymlinksEnabled, config) {} }; return std::make_shared( localDir, caseSensitive, inodeCatalogType, inodeCatalogOptions, logger, std::move(stats), windowsSymlinksEnabled, config); } Overlay::Overlay( AbsolutePathPiece localDir, CaseSensitivity caseSensitive, InodeCatalogType inodeCatalogType, InodeCatalogOptions inodeCatalogOptions, std::shared_ptr logger, EdenStatsPtr stats, bool windowsSymlinksEnabled, const EdenConfig& config) : fileContentStore_{makeFileContentStore( localDir, logger, inodeCatalogType)}, inodeCatalog_{makeInodeCatalog( localDir, inodeCatalogType, inodeCatalogOptions, config, fileContentStore_ ? fileContentStore_.get() : nullptr, logger)}, inodeCatalogType_{inodeCatalogType}, inodeCatalogOptions_(inodeCatalogOptions), supportsSemanticOperations_{inodeCatalog_->supportsSemanticOperations()}, filterAppleDouble_{ folly::kIsApple && !config.allowAppleDouble.getValue()}, localDir_{localDir}, caseSensitive_{caseSensitive}, structuredLogger_{logger}, stats_{std::move(stats)}, windowsSymlinksEnabled_(windowsSymlinksEnabled) {} Overlay::~Overlay() { close(); } void Overlay::close() { XCHECK_NE(std::this_thread::get_id(), gcThread_.get_id()); gcQueue_.lock()->stop = true; gcCondVar_.notify_one(); if (gcThread_.joinable()) { gcThread_.join(); } // Make sure everything is shut down in reverse of construction order. // Cleanup is not necessary if tree overlay was not initialized and either // there is no file content store or the it was not initalized if (!inodeCatalog_->initialized() && (!fileContentStore_ || (fileContentStore_ && !fileContentStore_->initialized()))) { return; } // Since we are closing the overlay, no other threads can still be using // it. They must have used some external synchronization mechanism to // ensure this, so it is okay for us to still use relaxed access to // nextInodeNumber_. std::optional optNextInodeNumber; auto nextInodeNumber = nextInodeNumber_.load(std::memory_order_relaxed); if (nextInodeNumber) { optNextInodeNumber = InodeNumber{nextInodeNumber}; } closeAndWaitForOutstandingIO(); #ifndef _WIN32 inodeMetadataTable_.reset(); #endif // !_WIN32 inodeCatalog_->close(optNextInodeNumber); if (fileContentStore_ && inodeCatalogType_ != InodeCatalogType::Legacy) { fileContentStore_->close(); } } bool Overlay::isClosed() { return outstandingIORequests_.load(std::memory_order_acquire) & ioClosedMask; } #ifndef _WIN32 struct statfs Overlay::statFs() { IORequest req{this}; XCHECK(fileContentStore_); return fileContentStore_->statFs(); } #endif // !_WIN32 folly::SemiFuture Overlay::initialize( std::shared_ptr config, std::optional mountPath, OverlayChecker::ProgressCallback&& progressCallback, InodeCatalog::LookupCallback&& lookupCallback) { // The initOverlay() call is potentially slow, so we want to avoid // performing it in the current thread and blocking returning to our caller. // // We already spawn a separate thread for garbage collection. It's convenient // to simply use this existing thread to perform the initialization logic // before waiting for GC work to do. auto [initPromise, initFuture] = folly::makePromiseContract(); gcThread_ = std::thread([this, config = std::move(config), mountPath = std::move(mountPath), progressCallback = std::move(progressCallback), lookupCallback = lookupCallback, promise = std::move(initPromise)]() mutable { try { initOverlay( std::move(config), std::move(mountPath), progressCallback, lookupCallback); } catch (...) { auto ew = folly::exception_wrapper{std::current_exception()}; XLOG(ERR) << "overlay initialization failed for " << localDir_ << ": " << ew; promise.setException(std::move(ew)); return; } promise.setValue(); gcThread(); }); return std::move(initFuture); } void Overlay::initOverlay( std::shared_ptr config, std::optional mountPath, [[maybe_unused]] const OverlayChecker::ProgressCallback& progressCallback, [[maybe_unused]] InodeCatalog::LookupCallback& lookupCallback) { IORequest req{this}; auto optNextInodeNumber = inodeCatalog_->initOverlay(/*createIfNonExisting=*/true); if (fileContentStore_ && inodeCatalogType_ == InodeCatalogType::Sqlite) { // Initialize the file content store after the inode catalog has been. // The fileContentStore will only exist on non-Windows platforms. // // We only need to do this for Sqlite overlays because they use a Legacy // FileContentStore on non-Windows platforms. Other InodeCatalogTypes use // their corresponding FileContentStore, meaning calling `initialize` here // would double-initialize the FileContentStore the objects. // // If we had a SQLiteFileContentStore, this code block would be unnecessary. fileContentStore_->initialize(/*createIfNonExisting=*/true); } if (!optNextInodeNumber.has_value()) { #ifndef _WIN32 // FSCK is not currently supported for LMDB overlays. If we cannot load the // next inode number, then we cannot continue. LMDB should always be able to // load the inode number, if this case is hit, then the assumption about // LMDB being resilient is incorrect (unless the user manually corrupted // their overlay directory). if (inodeCatalogType_ != InodeCatalogType::Legacy) { throw std::runtime_error( "Corrupted LMDB overlay " + localDir_.asString() + ": could not load next inode number"); } // If the next-inode-number data is missing it means that this overlay was // not shut down cleanly the last time it was used. If this was caused by a // hard system reboot this can sometimes cause corruption and/or missing // data in some of the on-disk state. // // Use OverlayChecker to scan the overlay for any issues, and also compute // correct next inode number as it does so. XLOG(WARN) << "Overlay " << localDir_ << " was not shut down cleanly. Performing fsck scan."; // TODO(zeyi): `OverlayCheck` should be associated with the specific // Overlay implementation. // // Note: lookupCallback is a reference but is stored on OverlayChecker. // Therefore OverlayChecker must not exist longer than this initOverlay // call. OverlayChecker checker( inodeCatalog_.get(), static_cast(fileContentStore_.get()), std::nullopt, lookupCallback); folly::stop_watch<> fsckRuntime; checker.scanForErrors(progressCallback); auto result = checker.repairErrors(); auto fsckRuntimeInSeconds = std::chrono::duration{fsckRuntime.elapsed()}.count(); if (result) { // If totalErrors - fixedErrors is nonzero, then we failed to // fix all of the problems. auto success = !(result->totalErrors - result->fixedErrors); structuredLogger_->logEvent( Fsck{fsckRuntimeInSeconds, success, true /*attempted_repair*/}); } else { structuredLogger_->logEvent(Fsck{ fsckRuntimeInSeconds, true /*success*/, false /*attempted_repair*/}); } optNextInodeNumber = checker.getNextInodeNumber(); #else // SqliteInodeCatalog will always return the value of next Inode number, if // we end up here - it's a bug. EDEN_BUG() << "Tree Overlay is null value for NextInodeNumber"; #endif } else { hadCleanStartup_ = true; } // On Windows, we need to scan the state of the repository every time at // start up to find any potential changes happened when EdenFS is not // running. // // mountPath will be empty during benchmarking so we must check the value // here to skip scanning in that case. if (folly::kIsWindows && mountPath.has_value()) { folly::stop_watch<> fsckRuntime; optNextInodeNumber = inodeCatalog_->scanLocalChanges( std::move(config), *mountPath, windowsSymlinksEnabled_, lookupCallback); auto fsckRuntimeInSeconds = std::chrono::duration{fsckRuntime.elapsed()}.count(); structuredLogger_->logEvent(Fsck{ fsckRuntimeInSeconds, true /*success*/, false /*attempted_repair*/}); } nextInodeNumber_.store(optNextInodeNumber->get(), std::memory_order_relaxed); #ifndef _WIN32 // Open after infoFile_'s lock is acquired because the InodeTable acquires // its own lock, which should be released prior to infoFile_. inodeMetadataTable_ = InodeMetadataTable::open( (localDir_ + PathComponentPiece{FsFileContentStore::kMetadataFile}) .c_str(), stats_.copy()); #endif // !_WIN32 } InodeNumber Overlay::allocateInodeNumber() { // InodeNumber should generally be 64-bits wide, in which case it isn't even // worth bothering to handle the case where nextInodeNumber_ wraps. We don't // need to bother checking for conflicts with existing inode numbers since // this can only happen if we wrap around. We don't currently support // platforms with 32-bit inode numbers. static_assert( sizeof(nextInodeNumber_) == sizeof(InodeNumber), "expected nextInodeNumber_ and InodeNumber to have the same size"); static_assert( sizeof(InodeNumber) >= 8, "expected InodeNumber to be at least 64 bits"); // This could be a relaxed atomic operation. It doesn't matter on x86 but // might on ARM. auto previous = nextInodeNumber_++; XDCHECK_NE(0u, previous) << "allocateInodeNumber called before initialize"; return InodeNumber{previous}; } DirContents Overlay::loadOverlayDir(InodeNumber inodeNumber) { DurationScope statScope{stats_, &OverlayStats::loadOverlayDir}; DirContents result(caseSensitive_); IORequest req{this}; auto dirData = inodeCatalog_->loadOverlayDir(inodeNumber); if (!dirData.has_value()) { stats_->increment(&OverlayStats::loadOverlayDirMiss); return result; } const auto& dir = dirData.value(); stats_->increment(&OverlayStats::loadOverlayDirHit); bool shouldRewriteOverlay = false; for (auto& iter : *dir.entries_ref()) { const auto& name = iter.first; const auto& value = iter.second; // If AppleDouble files (._) need to be filtered, omit them from the // returned DirContents and rewrite the overlay directory to remove them // from the Overlay entirely. if (filterAppleDouble_ && string_view{name}.starts_with("._")) { shouldRewriteOverlay = true; continue; } InodeNumber ino; if (*value.inodeNumber_ref()) { ino = InodeNumber::fromThrift(*value.inodeNumber_ref()); } else { ino = allocateInodeNumber(); shouldRewriteOverlay = true; } if (value.hash_ref() && !value.hash_ref()->empty()) { auto hash = ObjectId{folly::ByteRange{folly::StringPiece{*value.hash_ref()}}}; result.emplace(PathComponentPiece{name}, *value.mode_ref(), ino, hash); } else { // The inode is materialized result.emplace(PathComponentPiece{name}, *value.mode_ref(), ino); } } if (shouldRewriteOverlay) { saveOverlayDir(inodeNumber, result); } return result; } overlay::OverlayEntry Overlay::serializeOverlayEntry(const DirEntry& ent) { overlay::OverlayEntry entry; // TODO: Eventually, we should only serialize the child entry's dtype into // the Overlay. But, as of now, it's possible to create an inode under a // tree, serialize that tree into the overlay, then restart Eden. Since // writing mode bits into the InodeMetadataTable only occurs when the inode // is loaded, the initial mode bits must persist until the first load. entry.mode_ref() = ent.getInitialMode(); entry.inodeNumber_ref() = ent.getInodeNumber().get(); if (!ent.isMaterialized()) { entry.hash_ref() = ent.getHash().asString(); } return entry; } overlay::OverlayDir Overlay::serializeOverlayDir( InodeNumber inodeNumber, const DirContents& dir) { IORequest req{this}; auto nextInodeNumber = nextInodeNumber_.load(std::memory_order_relaxed); XCHECK_LT(inodeNumber.get(), nextInodeNumber) << "serializeOverlayDir called with unallocated inode number"; // TODO: T20282158 clean up access of child inode information. // // Translate the data to the thrift equivalents overlay::OverlayDir odir; for (auto& entIter : dir) { const auto& entName = entIter.first; const auto& ent = entIter.second; XCHECK_NE(entName, "") << "serializeOverlayDir called with entry with an empty path for directory with inodeNumber=" << inodeNumber; XCHECK_LT(ent.getInodeNumber().get(), nextInodeNumber) << "serializeOverlayDir called with entry using unallocated inode number"; odir.entries_ref()->emplace( std::make_pair(entName.asString(), serializeOverlayEntry(ent))); } return odir; } void Overlay::saveOverlayDir(InodeNumber inodeNumber, const DirContents& dir) { DurationScope statScope{stats_, &OverlayStats::saveOverlayDir}; inodeCatalog_->saveOverlayDir( inodeNumber, serializeOverlayDir(inodeNumber, dir)); } void Overlay::freeInodeFromMetadataTable(InodeNumber ino) { #ifndef _WIN32 // TODO: batch request during GC getInodeMetadataTable()->freeInode(ino); #else (void)ino; #endif } void Overlay::removeOverlayFile(InodeNumber inodeNumber) { DurationScope statScope{stats_, &OverlayStats::removeOverlayFile}; #ifndef _WIN32 IORequest req{this}; freeInodeFromMetadataTable(inodeNumber); fileContentStore_->removeOverlayFile(inodeNumber); #else (void)inodeNumber; #endif } void Overlay::removeOverlayDir(InodeNumber inodeNumber) { DurationScope statScope{stats_, &OverlayStats::removeOverlayDir}; IORequest req{this}; freeInodeFromMetadataTable(inodeNumber); inodeCatalog_->removeOverlayDir(inodeNumber); } void Overlay::recursivelyRemoveOverlayDir(InodeNumber inodeNumber) { IORequest req{this}; freeInodeFromMetadataTable(inodeNumber); // This inode's data must be removed from the overlay before // recursivelyRemoveOverlayDir returns to avoid a race condition if // recursivelyRemoveOverlayDir(I) is called immediately prior to // saveOverlayDir(I). There's also no risk of violating our durability // guarantees if the process dies after this call but before the thread could // remove this data. auto dirData = inodeCatalog_->loadAndRemoveOverlayDir(inodeNumber); if (dirData) { gcQueue_.lock()->queue.emplace_back(std::move(*dirData)); gcCondVar_.notify_one(); } } #ifndef _WIN32 folly::Future Overlay::flushPendingAsync() { folly::Promise promise; auto future = promise.getFuture(); gcQueue_.lock()->queue.emplace_back(std::move(promise)); gcCondVar_.notify_one(); return future; } #endif // !_WIN32 bool Overlay::hasOverlayDir(InodeNumber inodeNumber) { DurationScope statScope{stats_, &OverlayStats::hasOverlayDir}; IORequest req{this}; return inodeCatalog_->hasOverlayDir(inodeNumber); } #ifndef _WIN32 bool Overlay::hasOverlayFile(InodeNumber inodeNumber) { DurationScope statScope{stats_, &OverlayStats::hasOverlayFile}; IORequest req{this}; XCHECK(fileContentStore_); return fileContentStore_->hasOverlayFile(inodeNumber); } // Helper function to open,validate, // get file pointer of an overlay file OverlayFile Overlay::openFile( InodeNumber inodeNumber, folly::StringPiece headerId) { IORequest req{this}; XCHECK(fileContentStore_); return OverlayFile( fileContentStore_->openFile(inodeNumber, headerId), weak_from_this()); } OverlayFile Overlay::openFileNoVerify(InodeNumber inodeNumber) { IORequest req{this}; XCHECK(fileContentStore_); return OverlayFile( fileContentStore_->openFileNoVerify(inodeNumber), weak_from_this()); } OverlayFile Overlay::createOverlayFile( InodeNumber inodeNumber, folly::ByteRange contents) { IORequest req{this}; XCHECK_LT(inodeNumber.get(), nextInodeNumber_.load(std::memory_order_relaxed)) << "createOverlayFile called with unallocated inode number"; XCHECK(fileContentStore_); return OverlayFile( fileContentStore_->createOverlayFile(inodeNumber, contents), weak_from_this()); } OverlayFile Overlay::createOverlayFile( InodeNumber inodeNumber, const folly::IOBuf& contents) { IORequest req{this}; XCHECK_LT(inodeNumber.get(), nextInodeNumber_.load(std::memory_order_relaxed)) << "createOverlayFile called with unallocated inode number"; XCHECK(fileContentStore_); return OverlayFile( fileContentStore_->createOverlayFile(inodeNumber, contents), weak_from_this()); } #endif // !_WIN32 InodeNumber Overlay::getMaxInodeNumber() { auto ino = nextInodeNumber_.load(std::memory_order_relaxed); XCHECK_GT(ino, 1u); return InodeNumber{ino - 1}; } bool Overlay::tryIncOutstandingIORequests() { uint64_t currentOutstandingIO = outstandingIORequests_.load(std::memory_order_seq_cst); // Retry incrementing the IO count while we have not either successfully // updated outstandingIORequests_ or closed the overlay while (!(currentOutstandingIO & ioClosedMask)) { // If not closed, currentOutstandingIO now holds what // outstandingIORequests_ actually contained if (outstandingIORequests_.compare_exchange_weak( currentOutstandingIO, currentOutstandingIO + 1, std::memory_order_seq_cst)) { return true; } } // If we have broken out of the above loop, the overlay is closed and we // been unable to increment outstandingIORequests_. return false; } void Overlay::decOutstandingIORequests() { uint64_t outstanding = outstandingIORequests_.fetch_sub(1, std::memory_order_seq_cst); XCHECK_NE(0ull, outstanding) << "Decremented too far!"; // If the overlay is closed and we just finished our last IO request (meaning // the previous value of outstandingIORequests_ was 1), then wake the waiting // thread. if ((outstanding & ioClosedMask) && (outstanding & ioCountMask) == 1) { lastOutstandingRequestIsComplete_.post(); } } void Overlay::closeAndWaitForOutstandingIO() { uint64_t outstanding = outstandingIORequests_.fetch_or(ioClosedMask, std::memory_order_seq_cst); // If we have outstanding IO requests, wait for them. This should not block if // this baton has already been posted between the load in the fetch_or and // this if statement. if (outstanding & ioCountMask) { lastOutstandingRequestIsComplete_.wait(); } } void Overlay::gcThread() noexcept { for (;;) { std::vector requests; { auto lock = gcQueue_.lock(); while (lock->queue.empty()) { if (lock->stop) { return; } gcCondVar_.wait(lock.as_lock()); } requests = std::move(lock->queue); } for (auto& request : requests) { try { handleGCRequest(request); } catch (const std::exception& e) { XLOG(ERR) << "handleGCRequest should never throw, but it did: " << e.what(); } } } } void Overlay::handleGCRequest(GCRequest& request) { IORequest req{this}; if (std::holds_alternative( request.requestType)) { inodeCatalog_->maintenance(); return; } if (auto* flush = std::get_if(&request.requestType)) { flush->setValue(); return; } // Should only include inode numbers for trees. std::queue queue; // TODO: For better throughput on large tree collections, it might make // sense to split this into two threads: one for traversing the tree and // another that makes the actual unlink calls. auto safeRemoveOverlayFile = [&](InodeNumber inodeNumber) { try { removeOverlayFile(inodeNumber); } catch (const std::exception& e) { XLOG(ERR) << "Failed to remove overlay data for file inode " << inodeNumber << ": " << e.what(); } }; auto processDir = [&](const overlay::OverlayDir& dir) { for (const auto& entry : *dir.entries_ref()) { const auto& value = entry.second; if (!(*value.inodeNumber_ref())) { // Legacy-only. All new Overlay trees have inode numbers for all // children. continue; } auto ino = InodeNumber::fromThrift(*value.inodeNumber_ref()); if (S_ISDIR(*value.mode_ref())) { queue.push(ino); } else { // No need to recurse, but delete any file at this inode. Note that, // under normal operation, there should be nothing at this path // because files are only written into the overlay if they're // materialized. safeRemoveOverlayFile(ino); } } }; processDir(std::get(request.requestType)); while (!queue.empty()) { auto ino = queue.front(); queue.pop(); overlay::OverlayDir dir; try { freeInodeFromMetadataTable(ino); auto dirData = inodeCatalog_->loadAndRemoveOverlayDir(ino); if (!dirData.has_value()) { XLOG(DBG7) << "no dir data for inode " << ino; continue; } else { dir = std::move(*dirData); } } catch (const std::exception& e) { XLOG(ERR) << "While collecting, failed to load tree data for inode " << ino << ": " << e.what(); continue; } processDir(dir); } } void Overlay::addChild( InodeNumber parent, const std::pair& childEntry, const DirContents& content) { DurationScope statScope{stats_, &OverlayStats::addChild}; if (supportsSemanticOperations_) { inodeCatalog_->addChild( parent, childEntry.first, serializeOverlayEntry(childEntry.second)); } else { saveOverlayDir(parent, content); } } void Overlay::removeChild( InodeNumber parent, PathComponentPiece childName, const DirContents& content) { DurationScope statScope{stats_, &OverlayStats::removeChild}; if (supportsSemanticOperations_) { inodeCatalog_->removeChild(parent, childName); } else { saveOverlayDir(parent, content); } } void Overlay::removeChildren(InodeNumber parent, const DirContents& content) { DurationScope statScope{stats_, &OverlayStats::removeChildren}; saveOverlayDir(parent, content); } void Overlay::renameChild( InodeNumber src, InodeNumber dst, PathComponentPiece srcName, PathComponentPiece dstName, const DirContents& srcContent, const DirContents& dstContent) { DurationScope statScope{stats_, &OverlayStats::renameChild}; if (supportsSemanticOperations_) { inodeCatalog_->renameChild(src, dst, srcName, dstName); } else { saveOverlayDir(src, srcContent); if (dst.get() != src.get()) { saveOverlayDir(dst, dstContent); } } } void Overlay::maintenance() { gcQueue_.lock()->queue.emplace_back(Overlay::GCRequest::MaintenanceRequest{}); } } // namespace facebook::eden