sapling/eden/fs/inodes/EdenMount.cpp

310 lines
11 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include "EdenMount.h"
#include <folly/ExceptionWrapper.h>
#include <folly/futures/Future.h>
#include <glog/logging.h>
#include "eden/fs/config/ClientConfig.h"
#include "eden/fs/inodes/CheckoutContext.h"
#include "eden/fs/inodes/DiffContext.h"
#include "eden/fs/inodes/Dirstate.h"
#include "eden/fs/inodes/EdenDispatcher.h"
Flip Dirstate -> EdenMount dependency. Summary: Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes pointers to a `MountPoint` and an `ObjectStore` because it does not need the entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create the `Dirstate` itself, but that will be done in a follow-up commit. Fortunately, it was pretty easy to remove the references to `edenMount_` in `Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`. The one thing that I also decided to move was `getModifiedDirectoriesForMount()` because I already needed to create an `EdenMounts` file (admittedly not a great name) to collect some utility functions that use members of an `EdenMount` while not having access to the `EdenMount` itself. As part of this change, all of the code in `eden/fs/model/hg` has been moved to `eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change the `Dirstate` from an Hg-specific concept to a more general concept. `LocalDirstatePersistence` is no longer one of two implementations of `DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is just one concrete implementation called `DirstatePersistence` that takes its implementation from `LocalDirstatePersistence`. Because there is no longer a `FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that uses a `TemporaryFile`. Because `TestMount` now takes responsibility for creating the `Dirstate`, it must also give callers the ability to specify the user directives. To that end, `TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a `getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated as part of this revision, but `DirstateTest` needed quite a few updates (which were generally mechanical). Reviewed By: simpkins Differential Revision: D4230154 fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
#include "eden/fs/inodes/EdenMounts.h"
#include "eden/fs/inodes/FileInode.h"
#include "eden/fs/inodes/InodeError.h"
introduce a new InodeMap class Summary: This diff starts adding a new InodeMap class. This class will eventually consolidate the functionality of InodeNameMap plus the inode map stored in EdenDispatcher. This new class will bring several new benefits: - All inode mapping logic consolidated into a single class, with a single lock protecting the maps. - A well-defined model for loaded vs unloaded inodes. InodeMap explicitly tracks inodes that have InodeBase objects created for them vs inodes that have an inode number allocated (for FUSE) but do not have an InodeBase object in memory. This will make it possible to unload Inode objects on demand to reduce memory usage. - Tracking of pending loads, and de-duplicating load requests. This ensures that only one Inode object ever exists for a given inode number / path. If a second request to load an Inode arrives when a previous load request is still in progress, InodeMap deals with this situation properly. - Better support for using Inode objects without FUSE. With the old code, attempts to interact with Inode objects without going through the FUSE dispatch (say, when processing a thrift call) could result in inconsistent state. New inodes created would not be put into the EdenDispatcher map, which could result in problems. - More convenient child inode access from TreeInode. With this change, the TreeInode class can easily tell which of its children are loaded. This makes it easier to do tasks which only need to operate on existing loaded inode state (for instance, dirstate computation). - Support for saving and loading state, to implement graceful restart.. InodeMap provides a central place to write out inode state on shutdown and restoring it on startup. Saved inodes can easily be restored to an "unloaded" state on startup. This code is not implemented yet as part of this diff, but it should be straightforward to add in future diffs. Reviewed By: bolinfest Differential Revision: D4318060 fbshipit-source-id: d9b16430fc8367e3516e788d1e991e5163aa6997
2016-12-23 02:34:54 +03:00
#include "eden/fs/inodes/InodeMap.h"
Flip Dirstate -> EdenMount dependency. Summary: Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes pointers to a `MountPoint` and an `ObjectStore` because it does not need the entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create the `Dirstate` itself, but that will be done in a follow-up commit. Fortunately, it was pretty easy to remove the references to `edenMount_` in `Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`. The one thing that I also decided to move was `getModifiedDirectoriesForMount()` because I already needed to create an `EdenMounts` file (admittedly not a great name) to collect some utility functions that use members of an `EdenMount` while not having access to the `EdenMount` itself. As part of this change, all of the code in `eden/fs/model/hg` has been moved to `eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change the `Dirstate` from an Hg-specific concept to a more general concept. `LocalDirstatePersistence` is no longer one of two implementations of `DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is just one concrete implementation called `DirstatePersistence` that takes its implementation from `LocalDirstatePersistence`. Because there is no longer a `FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that uses a `TemporaryFile`. Because `TestMount` now takes responsibility for creating the `Dirstate`, it must also give callers the ability to specify the user directives. To that end, `TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a `getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated as part of this revision, but `DirstateTest` needed quite a few updates (which were generally mechanical). Reviewed By: simpkins Differential Revision: D4230154 fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
#include "eden/fs/inodes/Overlay.h"
#include "eden/fs/inodes/TreeInode.h"
#include "eden/fs/model/Hash.h"
#include "eden/fs/model/Tree.h"
#include "eden/fs/model/git/GitIgnoreStack.h"
#include "eden/fs/store/ObjectStore.h"
#include "eden/fuse/MountPoint.h"
using std::make_unique;
using std::unique_ptr;
using std::vector;
using folly::Future;
using folly::makeFuture;
using folly::StringPiece;
using folly::Unit;
namespace facebook {
namespace eden {
// We compute this when the process is initialized, but stash a copy
// in each EdenMount. We may in the future manage to propagate enough
// state across upgrades or restarts that we can preserve this, but
// as implemented today, a process restart will invalidate any cached
// mountGeneration that a client may be holding on to.
// We take the bottom 16-bits of the pid and 32-bits of the current
// time and shift them up, leaving 16 bits for a mount point generation
// number.
static const uint64_t globalProcessGeneration =
(uint64_t(getpid()) << 48) | (uint64_t(time(nullptr)) << 16);
// Each time we create an EdenMount we bump this up and OR it together
// with the globalProcessGeneration to come up with a generation number
// for a given mount instance.
static std::atomic<uint16_t> mountGeneration{0};
std::shared_ptr<EdenMount> EdenMount::makeShared(
std::unique_ptr<ClientConfig> config,
std::unique_ptr<ObjectStore> objectStore,
AbsolutePathPiece socketPath,
folly::ThreadLocal<fusell::EdenStats>* globalStats) {
return std::shared_ptr<EdenMount>{
new EdenMount{
std::move(config), std::move(objectStore), socketPath, globalStats},
EdenMountDeleter{}};
}
EdenMount::EdenMount(
std::unique_ptr<ClientConfig> config,
std::unique_ptr<ObjectStore> objectStore,
AbsolutePathPiece socketPath,
folly::ThreadLocal<fusell::EdenStats>* globalStats)
: globalEdenStats_(globalStats),
config_(std::move(config)),
inodeMap_{new InodeMap(this)},
dispatcher_{new EdenDispatcher(this)},
mountPoint_(
new fusell::MountPoint(config_->getMountPath(), dispatcher_.get())),
objectStore_(std::move(objectStore)),
overlay_(std::make_shared<Overlay>(config_->getOverlayPath())),
dirstate_(std::make_unique<Dirstate>(this)),
bindMounts_(config_->getBindMounts()),
mountGeneration_(globalProcessGeneration | ++mountGeneration),
socketPath_(socketPath) {
// Load the overlay, if present.
auto rootOverlayDir = overlay_->loadOverlayDir(FUSE_ROOT_ID);
// Load the current snapshot ID from the on-disk config
auto snapshotID = config_->getSnapshotID();
*currentSnapshot_.wlock() = snapshotID;
// Create the inode for the root of the tree using the hash contained
// within the snapshotPath file
TreeInodePtr rootInode;
if (rootOverlayDir) {
rootInode = TreeInodePtr::makeNew(this, std::move(rootOverlayDir.value()));
} else {
// Note: We immediately wait on the Future returned by
// getTreeForCommit().
//
// Loading the root tree may take a while. It may be better to refactor
// the code slightly so that this is done in a helper function, before the
// EdenMount constructor is called.
auto rootTree = objectStore_->getTreeForCommit(snapshotID).get();
rootInode = TreeInodePtr::makeNew(this, std::move(rootTree));
}
auto maxInodeNumber = overlay_->getMaxRecordedInode();
inodeMap_->initialize(std::move(rootInode), maxInodeNumber);
VLOG(2) << "Initializing eden mount " << getPath()
<< "; max existing inode number is " << maxInodeNumber;
// Record the transition from no snapshot to the current snapshot in
// the journal. This also sets things up so that we can carry the
// snapshot id forward through subsequent journal entries.
auto delta = std::make_unique<JournalDelta>();
delta->toHash = snapshotID;
journal_.wlock()->addDelta(std::move(delta));
// Set up the magic .eden dir
getRootInode()
->getOrLoadChildTree(PathComponentPiece{kDotEdenName})
.then([=](TreeInodePtr dotEdenInode) {
// We could perhaps do something here to ensure that it reflects the
// current state of the world, but for the moment we trust that it
// still reflects how things were when we set it up.
dotEdenInodeNumber_ = dotEdenInode->getNodeId();
})
.onError([=](const InodeError& err) {
auto dotEdenInode =
getRootInode()->mkdir(PathComponentPiece{kDotEdenName}, 0744);
dotEdenInodeNumber_ = dotEdenInode->getNodeId();
dotEdenInode->symlink(
PathComponentPiece{"root"}, config_->getMountPath().stringPiece());
dotEdenInode->symlink(
PathComponentPiece{"socket"}, getSocketPath().stringPiece());
})
.get();
}
EdenMount::~EdenMount() {}
void EdenMount::destroy() {
VLOG(1) << "beginning shutdown for EdenMount " << getPath();
inodeMap_->beginShutdown();
}
void EdenMount::shutdownComplete() {
VLOG(1) << "destruction complete for EdenMount " << getPath();
delete this;
}
fusell::Channel* EdenMount::getFuseChannel() const {
return mountPoint_->getChannel();
}
const AbsolutePath& EdenMount::getPath() const {
return mountPoint_->getPath();
}
const AbsolutePath& EdenMount::getSocketPath() const {
return socketPath_;
}
folly::ThreadLocal<fusell::EdenStats>* EdenMount::getStats() const {
return globalEdenStats_;
}
const vector<BindMount>& EdenMount::getBindMounts() const {
return bindMounts_;
}
TreeInodePtr EdenMount::getRootInode() const {
return inodeMap_->getRootInode();
}
folly::Future<std::unique_ptr<Tree>> EdenMount::getRootTreeFuture() const {
auto commitHash = Hash{*currentSnapshot_.rlock()};
return objectStore_->getTreeForCommit(commitHash);
}
fuse_ino_t EdenMount::getDotEdenInodeNumber() const {
return dotEdenInodeNumber_;
}
std::unique_ptr<Tree> EdenMount::getRootTree() const {
// TODO: We should convert callers of this API to use the Future-based
// version.
return getRootTreeFuture().get();
}
Future<InodePtr> EdenMount::getInode(RelativePathPiece path) const {
return inodeMap_->getRootInode()->getChildRecursive(path);
}
InodePtr EdenMount::getInodeBlocking(RelativePathPiece path) const {
return getInode(path).get();
}
TreeInodePtr EdenMount::getTreeInodeBlocking(RelativePathPiece path) const {
return getInodeBlocking(path).asTreePtr();
}
FileInodePtr EdenMount::getFileInodeBlocking(RelativePathPiece path) const {
return getInodeBlocking(path).asFilePtr();
}
folly::Future<std::vector<CheckoutConflict>> EdenMount::checkout(
Hash snapshotHash,
bool force) {
// Hold the snapshot lock for the duration of the entire checkout operation.
//
// This prevents multiple checkout operations from running in parallel.
auto snapshotLock = currentSnapshot_.wlock();
auto oldSnapshot = *snapshotLock;
auto ctx = std::make_shared<CheckoutContext>(std::move(snapshotLock), force);
VLOG(1) << "starting checkout for " << this->getPath() << ": " << oldSnapshot
<< " to " << snapshotHash;
auto fromTreeFuture = objectStore_->getTreeForCommit(oldSnapshot);
auto toTreeFuture = objectStore_->getTreeForCommit(snapshotHash);
return folly::collect(fromTreeFuture, toTreeFuture)
.then([this, ctx](
std::tuple<unique_ptr<Tree>, unique_ptr<Tree>> treeResults) {
auto& fromTree = std::get<0>(treeResults);
auto& toTree = std::get<1>(treeResults);
ctx->start(this->acquireRenameLock());
return this->getRootInode()->checkout(
ctx.get(), std::move(fromTree), std::move(toTree));
})
.then([this, ctx, oldSnapshot, snapshotHash]() {
// Save the new snapshot hash
VLOG(1) << "updating snapshot for " << this->getPath() << " from "
<< oldSnapshot << " to " << snapshotHash;
this->config_->setSnapshotID(snapshotHash);
auto conflicts = ctx->finish(snapshotHash);
// Write a journal entry
// TODO: We don't include any file changes for now. We'll need to
// figure out the desired data to pass to watchman. We intentionally
// don't want to give it the full list of files that logically
// changed--we intentionally don't process files that were changed but
// have never been accessed.
auto journalDelta = make_unique<JournalDelta>();
journalDelta->fromHash = oldSnapshot;
journalDelta->toHash = snapshotHash;
journal_.wlock()->addDelta(std::move(journalDelta));
return conflicts;
});
}
Future<Unit> EdenMount::diff(InodeDiffCallback* callback, bool listIgnored) {
// Create a DiffContext object for this diff operation.
auto context =
make_unique<DiffContext>(callback, listIgnored, getObjectStore());
const DiffContext* ctxPtr = context.get();
// TODO: Load the system-wide ignore settings and user-specific
// ignore settings.
auto ignore = make_unique<GitIgnoreStack>(nullptr);
auto* ignorePtr = ignore.get();
// stateHolder() exists to ensure that the DiffContext and GitIgnoreStack
// exists until the diff completes.
auto stateHolder =
[ ctx = std::move(context), ignore = std::move(ignore) ](){};
auto rootInode = getRootInode();
return getRootTreeFuture()
.then([ ctxPtr, ignorePtr, rootInode = std::move(rootInode) ](
std::unique_ptr<Tree> && rootTree) {
return rootInode->diff(
ctxPtr, RelativePathPiece{}, std::move(rootTree), ignorePtr, false);
})
.ensure(std::move(stateHolder));
}
void EdenMount::resetCommit(Hash snapshotHash) {
// We currently don't verify that snapshotHash refers to a valid commit
// in the ObjectStore. We could do that just for verification purposes.
auto snapshotLock = currentSnapshot_.wlock();
auto oldSnapshot = *snapshotLock;
VLOG(1) << "resetting snapshot for " << this->getPath() << " from "
<< oldSnapshot << " to " << snapshotHash;
*snapshotLock = snapshotHash;
this->config_->setSnapshotID(snapshotHash);
auto journalDelta = make_unique<JournalDelta>();
journalDelta->fromHash = oldSnapshot;
journalDelta->toHash = snapshotHash;
journal_.wlock()->addDelta(std::move(journalDelta));
}
RenameLock EdenMount::acquireRenameLock() {
return RenameLock{this};
}
SharedRenameLock EdenMount::acquireSharedRenameLock() {
return SharedRenameLock{this};
}
}
} // facebook::eden