inodes: prepopulate the InodeMap on mount

Summary:
On Windows, the working copy doesn't go away on unmount, instead placeholders
and full files[0] are still present on disk. For this reason, EdenFS needs to
either overly invalidate files and directories at update times, or need to
remember what is present on disk so the state can be recovered.

For now, this diff simply focus on reloading all the inodes that are present on
disk in the InodeMap.

[0]: https://docs.microsoft.com/en-us/windows/win32/projfs/cache-state

Reviewed By: chadaustin

Differential Revision: D28889082

fbshipit-source-id: 90170c1291da563bea455c8032dc8282a093c9b3
This commit is contained in:
Xavier Deguillard 2021-06-08 16:03:45 -07:00 committed by Facebook GitHub Bot
parent b0a08b6a4f
commit d1ad84db19
4 changed files with 159 additions and 27 deletions

View File

@ -264,6 +264,8 @@ FOLLY_NODISCARD folly::Future<folly::Unit> EdenMount::initialize(
.thenValue([this, takeover](TreeInodePtr initTreeNode) {
if (takeover) {
inodeMap_->initializeFromTakeover(std::move(initTreeNode), *takeover);
} else if (isWorkingCopyPersistent()) {
inodeMap_->initializeFromOverlay(std::move(initTreeNode), *overlay_);
} else {
inodeMap_->initialize(std::move(initTreeNode));
}

View File

@ -279,6 +279,17 @@ class EdenMount {
Nfsd3* FOLLY_NULLABLE getNfsdChannel() const;
#endif
/**
* Test if the working copy persist on disk after this mount will be
* destroyed.
*
* This is only true on Windows when using ProjectedFS as files are left
* around in the working copy after the mount is unmounted.
*/
constexpr bool isWorkingCopyPersistent() const {
return folly::kIsWindows;
}
ProcessAccessLog& getProcessAccessLog() const;
/**

View File

@ -122,20 +122,9 @@ inline void InodeMap::insertLoadedInode(
}
}
void InodeMap::initialize(TreeInodePtr root) {
auto data = data_.wlock();
XCHECK(!root_);
root_ = std::move(root);
insertLoadedInode(data, root_.get());
XDCHECK_EQ(1u, data->numTreeInodes_);
XDCHECK_EQ(0u, data->numFileInodes_);
}
void InodeMap::initializeFromTakeover(
FOLLY_MAYBE_UNUSED TreeInodePtr root,
FOLLY_MAYBE_UNUSED const SerializedInodeMap& takeover) {
auto data = data_.wlock();
void InodeMap::initializeRoot(
const folly::Synchronized<Members>::LockedPtr& data,
TreeInodePtr root) {
XCHECK_EQ(data->loadedInodes_.size(), 0ul)
<< "cannot load InodeMap data over a populated instance";
XCHECK_EQ(data->unloadedInodes_.size(), 0ul)
@ -146,6 +135,35 @@ void InodeMap::initializeFromTakeover(
insertLoadedInode(data, root_.get());
XDCHECK_EQ(1ul, data->numTreeInodes_);
XDCHECK_EQ(0ul, data->numFileInodes_);
}
void InodeMap::initialize(TreeInodePtr root) {
initializeRoot(data_.wlock(), std::move(root));
}
template <class... Args>
void InodeMap::initializeUnloadedInode(
const folly::Synchronized<Members>::LockedPtr& data,
InodeNumber parentIno,
InodeNumber ino,
Args&&... args) {
auto unloadedEntry = UnloadedInode(parentIno, std::forward<Args>(args)...);
auto result = data->unloadedInodes_.emplace(ino, std::move(unloadedEntry));
if (!result.second) {
auto message = fmt::format(
"failed to emplace inode number {}; is it already present in the InodeMap?",
ino);
XLOG(ERR) << message;
throw std::runtime_error(message);
}
}
void InodeMap::initializeFromTakeover(
TreeInodePtr root,
const SerializedInodeMap& takeover) {
auto data = data_.wlock();
initializeRoot(data, std::move(root));
for (const auto& entry : *takeover.unloadedInodes_ref()) {
if (*entry.numFsReferences_ref() < 0) {
auto message = folly::to<std::string>(
@ -156,8 +174,10 @@ void InodeMap::initializeFromTakeover(
throw std::runtime_error(message);
}
auto unloadedEntry = UnloadedInode(
initializeUnloadedInode(
data,
InodeNumber::fromThrift(*entry.parentInode_ref()),
InodeNumber::fromThrift(*entry.inodeNumber_ref()),
PathComponentPiece{*entry.name_ref()},
*entry.isUnlinked_ref(),
*entry.mode_ref(),
@ -165,18 +185,6 @@ void InodeMap::initializeFromTakeover(
? std::nullopt
: std::optional<Hash>{hashFromThrift(*entry.hash_ref())},
folly::to<uint32_t>(*entry.numFsReferences_ref()));
auto result = data->unloadedInodes_.emplace(
InodeNumber::fromThrift(*entry.inodeNumber_ref()),
std::move(unloadedEntry));
if (!result.second) {
auto message = folly::to<std::string>(
"failed to emplace inode number ",
*entry.inodeNumber_ref(),
"; is it already present in the InodeMap?");
XLOG(ERR) << message;
throw std::runtime_error(message);
}
}
XLOG(DBG2) << "InodeMap initialized mount " << mount_->getPath()
@ -184,6 +192,81 @@ void InodeMap::initializeFromTakeover(
<< " inodes registered";
}
namespace {
#ifdef _WIN32
/**
* Test if a file is present in the working copy.
*
* This needs to be called before the working copy is fully initialized to
* avoid the risk of recursively calling into EdenFS. Thus this will only true
* if the file is a placeholder/full file.
*
* See eden/fs/inodes/treeoverlay/TreeOverlayWindowsFsck.h for a description of
* placeholder/full file.
*/
bool isFileInWorkingCopy(AbsolutePathPiece path) {
PRJ_FILE_STATE state;
auto widePath = path.wide();
auto result = PrjGetOnDiskFileState(widePath.c_str(), &state);
return !FAILED(result);
}
#else
/**
* Test if a file is present in the backing overlay.
*
* On Linux and macOS, the working copy doesn't exist prior to mounting it, thus
* this always returns false.
*/
bool isFileInWorkingCopy(AbsolutePathPiece /*path*/) {
EDEN_BUG()
<< "Linux and macOS do not have a persistent working copy across mounts";
}
#endif
} // namespace
void InodeMap::initializeFromOverlay(TreeInodePtr root, Overlay& overlay) {
XCHECK(mount_->isWorkingCopyPersistent());
auto data = data_.wlock();
initializeRoot(data, std::move(root));
std::vector<std::tuple<AbsolutePath, InodeNumber>> pending;
pending.emplace_back(mount_->getPath(), root_->getNodeId());
while (!pending.empty()) {
auto [path, dirInode] = std::move(pending.back());
pending.pop_back();
auto dirEntries = overlay.loadOverlayDir(dirInode);
for (const auto& [name, dirent] : dirEntries) {
auto entryPath = path + name;
if (!isFileInWorkingCopy(entryPath)) {
continue;
}
auto ino = dirent.getInodeNumber();
if (dirent.isDirectory()) {
pending.emplace_back(std::move(entryPath), dirent.getInodeNumber());
}
initializeUnloadedInode(
data,
dirInode,
ino,
name,
false,
dirent.getInitialMode(),
dirent.getOptionalHash(),
1);
}
}
XLOG(DBG2) << "InodeMap initialized mount " << mount_->getPath()
<< " from overlay, " << data->unloadedInodes_.size()
<< " inodes registered";
}
ImmediateFuture<InodePtr> InodeMap::lookupInode(InodeNumber number) {
// Lock the data.
// We hold it while doing most of our work below, but explicitly unlock it

View File

@ -16,6 +16,7 @@
#include "eden/fs/inodes/InodeNumber.h"
#include "eden/fs/inodes/InodePtr.h"
#include "eden/fs/inodes/Overlay.h"
#include "eden/fs/model/Hash.h"
#include "eden/fs/takeover/gen-cpp2/takeover_types.h"
#include "eden/fs/utils/ImmediateFuture.h"
@ -134,6 +135,18 @@ class InodeMap {
TreeInodePtr root,
const SerializedInodeMap& takeover);
/**
* Initialize the InodeMap from the content of the overlay.
*
* This should be called on platforms where the working copy is persistent in
* between restarts so that the InodeMap is populated with all the inodes
* that are already present on disk. This needs to be called before the
* FsChannel is initialized for the mount.
*
* The only platform where this applies is Windows.
*/
void initializeFromOverlay(TreeInodePtr root, Overlay& overlay);
/**
* Get the root inode.
*/
@ -608,6 +621,29 @@ class InodeMap {
const folly::Synchronized<Members>::LockedPtr& data,
InodeBase* inode);
/**
* Verify the InodeMap precondition and initialize the root_ member.
*/
void initializeRoot(
const folly::Synchronized<Members>::LockedPtr& data,
TreeInodePtr root);
/**
* Construct an UnloadedInode and insert it onto the unloadedInodes_ map.
*
* Will throw a std::runtime_error if the passed in InodeNumber is already
* known by the the InodeMap.
*
* The argument list will be directly passed in to the UnloadedInode
* constructor.
*/
template <class... Args>
void initializeUnloadedInode(
const folly::Synchronized<Members>::LockedPtr& data,
InodeNumber parentIno,
InodeNumber ino,
Args&&... args);
/**
* The EdenMount that owns this InodeMap.
*/