mirror of
https://github.com/facebook/sapling.git
synced 2024-10-08 07:49:11 +03:00
4dc59b856b
Summary: This updates the `EdenServer` class so that the existing `getMount()` and `getMountPoints()` APIs only return mounts that have finished initializing. These APIs are primarily used by the thrift interfaces. In most cases the callers did not intend to operate on mounts that were still initializing, and doing so was unsafe. The code could potentially dereference a null pointer if it tried to access the mount's root inode before the root inode object had been created. New `getMountUnsafe()` and `getAllMountPoints()` APIs have been added for call sites that explicitly want to be able to access mounts that may still be initializing. Currently the `listMounts()` thrift API is the only location that needs this. Reviewed By: strager Differential Revision: D13981139 fbshipit-source-id: e6168d7a15694c79ca2bcc129dda46f82382e8e9
1451 lines
47 KiB
C++
1451 lines
47 KiB
C++
/*
|
|
* 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 "eden/fs/service/EdenServiceHandler.h"
|
|
|
|
#include <folly/Conv.h>
|
|
#include <folly/CppAttributes.h>
|
|
#include <folly/FileUtil.h>
|
|
#include <folly/String.h>
|
|
#include <folly/chrono/Conv.h>
|
|
#include <folly/container/Access.h>
|
|
#include <folly/futures/Future.h>
|
|
#include <folly/logging/Logger.h>
|
|
#include <folly/logging/LoggerDB.h>
|
|
#include <folly/logging/xlog.h>
|
|
#include <folly/stop_watch.h>
|
|
#include <folly/system/Shell.h>
|
|
#include <optional>
|
|
|
|
#ifdef EDEN_WIN
|
|
#include "eden/fs/service/gen-cpp2/eden_types.h"
|
|
#include "eden/win/fs/utils/stub.h" // @manual
|
|
#else
|
|
#include "eden/fs/fuse/FuseChannel.h"
|
|
#include "eden/fs/inodes/Differ.h"
|
|
#include "eden/fs/inodes/EdenDispatcher.h"
|
|
#include "eden/fs/inodes/EdenMount.h"
|
|
#include "eden/fs/inodes/FileInode.h"
|
|
#include "eden/fs/inodes/GlobNode.h"
|
|
#include "eden/fs/inodes/InodeError.h"
|
|
#include "eden/fs/inodes/InodeLoader.h"
|
|
#include "eden/fs/inodes/InodeMap.h"
|
|
#include "eden/fs/inodes/InodeTable.h"
|
|
#include "eden/fs/inodes/Overlay.h"
|
|
#include "eden/fs/inodes/TreeInode.h"
|
|
#include "eden/fs/utils/ProcessNameCache.h"
|
|
#endif // EDEN_WIN
|
|
|
|
#include "common/stats/ServiceData.h"
|
|
#include "eden/fs/config/ClientConfig.h"
|
|
#include "eden/fs/model/Blob.h"
|
|
#include "eden/fs/model/Hash.h"
|
|
#include "eden/fs/model/Tree.h"
|
|
#include "eden/fs/model/TreeEntry.h"
|
|
#include "eden/fs/service/EdenError.h"
|
|
#include "eden/fs/service/EdenServer.h"
|
|
#include "eden/fs/service/StreamingSubscriber.h"
|
|
#include "eden/fs/service/ThriftUtil.h"
|
|
#include "eden/fs/store/BlobMetadata.h"
|
|
#include "eden/fs/store/Diff.h"
|
|
#include "eden/fs/store/LocalStore.h"
|
|
#include "eden/fs/store/ObjectStore.h"
|
|
#include "eden/fs/tracing/Tracing.h"
|
|
#include "eden/fs/utils/Bug.h"
|
|
#include "eden/fs/utils/FaultInjector.h"
|
|
#include "eden/fs/utils/ProcUtil.h"
|
|
#include "eden/fs/utils/StatTimes.h"
|
|
|
|
using folly::Future;
|
|
using folly::makeFuture;
|
|
using folly::SemiFuture;
|
|
using folly::StringPiece;
|
|
using folly::Try;
|
|
using folly::Unit;
|
|
using std::make_unique;
|
|
using std::string;
|
|
using std::unique_ptr;
|
|
using std::vector;
|
|
|
|
namespace {
|
|
/*
|
|
* We need a version of folly::toDelim() that accepts zero, one, or many
|
|
* arguments so it can be used with __VA_ARGS__ in the INSTRUMENT_THRIFT_CALL()
|
|
* macro, so we create an overloaded method, toDelimWrapper(), to achieve that
|
|
* effect.
|
|
*/
|
|
constexpr StringPiece toDelimWrapper() {
|
|
return "";
|
|
}
|
|
|
|
std::string toDelimWrapper(StringPiece value) {
|
|
return value.str();
|
|
}
|
|
|
|
template <class... Args>
|
|
std::string toDelimWrapper(StringPiece arg1, const Args&... rest) {
|
|
std::string result;
|
|
folly::toAppendDelimFit(", ", arg1, rest..., &result);
|
|
return result;
|
|
}
|
|
|
|
using facebook::eden::Hash;
|
|
std::string logHash(StringPiece thriftArg) {
|
|
if (thriftArg.size() == Hash::RAW_SIZE) {
|
|
return Hash{folly::ByteRange{thriftArg}}.toString();
|
|
} else if (thriftArg.size() == Hash::RAW_SIZE * 2) {
|
|
return Hash{thriftArg}.toString();
|
|
} else {
|
|
return folly::hexlify(thriftArg);
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
#define TLOG(logger, level, file, line) \
|
|
FB_LOG_RAW(logger, level, file, line, "") \
|
|
<< "[" << folly::RequestContext::get() << "] "
|
|
|
|
namespace /* anonymous namespace for helper functions */ {
|
|
|
|
// Helper class to log where the request completes in Future
|
|
class ThriftLogHelper {
|
|
public:
|
|
ThriftLogHelper(ThriftLogHelper&&) = default;
|
|
ThriftLogHelper& operator=(ThriftLogHelper&&) = default;
|
|
|
|
template <typename... Args>
|
|
ThriftLogHelper(
|
|
const folly::Logger& logger,
|
|
folly::LogLevel level,
|
|
folly::StringPiece itcFunctionName,
|
|
folly::StringPiece itcFileName,
|
|
uint32_t itcLineNumber)
|
|
: itcFunctionName_(itcFunctionName),
|
|
itcFileName_(itcFileName),
|
|
itcLineNumber_(itcLineNumber),
|
|
level_(level),
|
|
itcLogger_(logger) {}
|
|
|
|
~ThriftLogHelper() {
|
|
if (wrapperExecuted_) {
|
|
// Logging of future creation at folly::LogLevel::DBG3.
|
|
TLOG(itcLogger_, folly::LogLevel::DBG3, itcFileName_, itcLineNumber_)
|
|
<< folly::format(
|
|
"{}() created future {:,}us",
|
|
itcFunctionName_,
|
|
itcTimer_.elapsed().count());
|
|
} else {
|
|
// If this object was not used for future creation
|
|
// log the elaped time here.
|
|
TLOG(itcLogger_, level_, itcFileName_, itcLineNumber_) << folly::format(
|
|
"{}() took {:,}us", itcFunctionName_, itcTimer_.elapsed().count());
|
|
}
|
|
}
|
|
|
|
template <typename ReturnType>
|
|
Future<ReturnType> wrapFuture(folly::Future<ReturnType>&& f) {
|
|
wrapperExecuted_ = true;
|
|
return std::move(f).thenValue(
|
|
[timer = itcTimer_,
|
|
logger = this->itcLogger_,
|
|
funcName = itcFunctionName_,
|
|
level = level_,
|
|
filename = itcFileName_,
|
|
linenumber = itcLineNumber_](ReturnType&& ret) {
|
|
// Logging completion time for the request
|
|
// The line number points to where the object was originally created
|
|
TLOG(logger, level, filename, linenumber) << folly::format(
|
|
"{}() took {:,}us", funcName, timer.elapsed().count());
|
|
return std::forward<ReturnType>(ret);
|
|
});
|
|
}
|
|
|
|
private:
|
|
folly::StringPiece itcFunctionName_;
|
|
folly::StringPiece itcFileName_;
|
|
uint32_t itcLineNumber_;
|
|
folly::LogLevel level_;
|
|
const folly::Logger& itcLogger_;
|
|
folly::stop_watch<std::chrono::microseconds> itcTimer_ = {};
|
|
bool wrapperExecuted_ = false;
|
|
};
|
|
|
|
#ifndef EDEN_WIN
|
|
facebook::eden::InodePtr inodeFromUserPath(
|
|
facebook::eden::EdenMount& mount,
|
|
StringPiece rootRelativePath) {
|
|
if (rootRelativePath.empty() || rootRelativePath == ".") {
|
|
return mount.getRootInode();
|
|
} else {
|
|
return mount.getInode(facebook::eden::RelativePathPiece{rootRelativePath})
|
|
.get();
|
|
}
|
|
}
|
|
#endif
|
|
} // namespace
|
|
|
|
// INSTRUMENT_THRIFT_CALL returns a unique pointer to
|
|
// ThriftLogHelper object. The returned pointer can be used to call wrapFuture()
|
|
// to attach a log message on the completion of the Future.
|
|
|
|
// When not attached to Future it will log the completion of the operation and
|
|
// time taken to complete it.
|
|
|
|
#define INSTRUMENT_THRIFT_CALL(level, ...) \
|
|
([&](folly::StringPiece functionName, \
|
|
folly::StringPiece fileName, \
|
|
uint32_t lineNumber) { \
|
|
static folly::Logger logger("eden.thrift." + functionName.str()); \
|
|
TLOG(logger, folly::LogLevel::level, fileName, lineNumber) \
|
|
<< functionName << "(" << toDelimWrapper(__VA_ARGS__) << ")"; \
|
|
return ThriftLogHelper( \
|
|
logger, folly::LogLevel::level, functionName, fileName, lineNumber); \
|
|
}(__func__, __FILE__, __LINE__))
|
|
|
|
namespace facebook {
|
|
namespace eden {
|
|
|
|
EdenServiceHandler::EdenServiceHandler(EdenServer* server)
|
|
: FacebookBase2("Eden"), server_(server) {
|
|
#ifndef EDEN_WIN
|
|
struct HistConfig {
|
|
int64_t bucketSize{250};
|
|
int64_t min{0};
|
|
int64_t max{25000};
|
|
};
|
|
auto methodConfigs = {
|
|
std::make_tuple("listMounts", HistConfig{20, 0, 1000}),
|
|
std::make_tuple("mount", HistConfig{}),
|
|
std::make_tuple("unmount", HistConfig{}),
|
|
std::make_tuple("checkOutRevision", HistConfig{}),
|
|
std::make_tuple("resetParentCommits", HistConfig{20, 0, 1000}),
|
|
std::make_tuple("getSHA1", HistConfig{}),
|
|
std::make_tuple("getBindMounts", HistConfig{20, 0, 1000}),
|
|
std::make_tuple("getCurrentJournalPosition", HistConfig{20, 0, 1000}),
|
|
std::make_tuple("getFilesChangedSince", HistConfig{}),
|
|
std::make_tuple("debugGetRawJournal", HistConfig{}),
|
|
std::make_tuple("getFileInformation", HistConfig{}),
|
|
std::make_tuple("glob", HistConfig{}),
|
|
std::make_tuple("globFiles", HistConfig{}),
|
|
std::make_tuple("getScmStatus", HistConfig{}),
|
|
std::make_tuple("getScmStatusBetweenRevisions", HistConfig{}),
|
|
std::make_tuple("getManifestEntry", HistConfig{}),
|
|
std::make_tuple("clearAndCompactLocalStore", HistConfig{}),
|
|
std::make_tuple("unloadInodeForPath", HistConfig{}),
|
|
std::make_tuple("flushStatsNow", HistConfig{20, 0, 1000}),
|
|
std::make_tuple("invalidateKernelInodeCache", HistConfig{}),
|
|
std::make_tuple("getStatInfo", HistConfig{}),
|
|
std::make_tuple("initiateShutdown", HistConfig{}),
|
|
};
|
|
for (const auto& methodConfig : methodConfigs) {
|
|
const auto& methodName = std::get<0>(methodConfig);
|
|
const auto& histConfig = std::get<1>(methodConfig);
|
|
exportThriftFuncHist(
|
|
std::string("EdenService.") + methodName,
|
|
facebook::fb303::PROCESS,
|
|
folly::small_vector<int>({50, 90, 99}), // percentiles to record
|
|
histConfig.bucketSize,
|
|
histConfig.min,
|
|
histConfig.max);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
facebook::fb303::cpp2::fb_status EdenServiceHandler::getStatus() {
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG4);
|
|
auto status = server_->getStatus();
|
|
switch (status) {
|
|
case EdenServer::RunState::STARTING:
|
|
return facebook::fb303::cpp2::fb_status::STARTING;
|
|
case EdenServer::RunState::RUNNING:
|
|
return facebook::fb303::cpp2::fb_status::ALIVE;
|
|
case EdenServer::RunState::SHUTTING_DOWN:
|
|
return facebook::fb303::cpp2::fb_status::STOPPING;
|
|
}
|
|
EDEN_BUG() << "unexpected EdenServer status " << static_cast<int>(status);
|
|
return facebook::fb303::cpp2::fb_status::WARNING;
|
|
}
|
|
|
|
void EdenServiceHandler::mount(std::unique_ptr<MountArgument> argument) {
|
|
auto helper = INSTRUMENT_THRIFT_CALL(INFO, argument->get_mountPoint());
|
|
try {
|
|
auto initialConfig = ClientConfig::loadFromClientDirectory(
|
|
AbsolutePathPiece{argument->mountPoint},
|
|
AbsolutePathPiece{argument->edenClientPath});
|
|
server_->mount(std::move(initialConfig)).get();
|
|
} catch (const EdenError& ex) {
|
|
XLOG(ERR) << "Error: " << ex.what();
|
|
throw;
|
|
} catch (const std::exception& ex) {
|
|
XLOG(ERR) << "Error: " << ex.what();
|
|
throw newEdenError(ex);
|
|
}
|
|
}
|
|
|
|
void EdenServiceHandler::unmount(std::unique_ptr<std::string> mountPoint) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(INFO, *mountPoint);
|
|
try {
|
|
server_->unmount(*mountPoint).get();
|
|
} catch (const EdenError& ex) {
|
|
throw;
|
|
} catch (const std::exception& ex) {
|
|
throw newEdenError(ex);
|
|
}
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::listMounts(std::vector<MountInfo>& results) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3);
|
|
for (const auto& edenMount : server_->getAllMountPoints()) {
|
|
MountInfo info;
|
|
info.mountPoint = edenMount->getPath().value();
|
|
info.edenClientPath = edenMount->getConfig()->getClientDirectory().value();
|
|
info.state = edenMount->getState();
|
|
results.push_back(info);
|
|
}
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::checkOutRevision(
|
|
std::vector<CheckoutConflict>& results,
|
|
std::unique_ptr<std::string> mountPoint,
|
|
std::unique_ptr<std::string> hash,
|
|
CheckoutMode checkoutMode) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(
|
|
DBG1,
|
|
*mountPoint,
|
|
logHash(*hash),
|
|
folly::get_default(
|
|
_CheckoutMode_VALUES_TO_NAMES, checkoutMode, "(unknown)"));
|
|
auto hashObj = hashFromThrift(*hash);
|
|
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
auto checkoutFuture = edenMount->checkout(hashObj, checkoutMode);
|
|
results = std::move(checkoutFuture).get();
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::resetParentCommits(
|
|
std::unique_ptr<std::string> mountPoint,
|
|
std::unique_ptr<WorkingDirectoryParents> parents) {
|
|
#ifndef EDEN_WIN
|
|
auto helper =
|
|
INSTRUMENT_THRIFT_CALL(DBG1, *mountPoint, logHash(parents->parent1));
|
|
ParentCommits edenParents;
|
|
edenParents.parent1() = hashFromThrift(parents->parent1);
|
|
if (parents->__isset.parent2) {
|
|
edenParents.parent2() =
|
|
hashFromThrift(parents->parent2_ref().value_unchecked());
|
|
}
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
edenMount->resetParents(edenParents);
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif
|
|
}
|
|
|
|
void EdenServiceHandler::getSHA1(
|
|
vector<SHA1Result>& out,
|
|
unique_ptr<string> mountPoint,
|
|
unique_ptr<vector<string>> paths) {
|
|
#ifndef EDEN_WIN
|
|
TraceBlock block("getSHA1");
|
|
auto helper = INSTRUMENT_THRIFT_CALL(
|
|
DBG3, *mountPoint, "[" + folly::join(", ", *paths.get()) + "]");
|
|
|
|
vector<Future<Hash>> futures;
|
|
for (const auto& path : *paths) {
|
|
futures.emplace_back(getSHA1ForPathDefensively(*mountPoint, path));
|
|
}
|
|
|
|
auto results = folly::collectAllSemiFuture(std::move(futures)).get();
|
|
for (auto& result : results) {
|
|
out.emplace_back();
|
|
SHA1Result& sha1Result = out.back();
|
|
if (result.hasValue()) {
|
|
sha1Result.set_sha1(thriftHash(result.value()));
|
|
} else {
|
|
sha1Result.set_error(newEdenError(result.exception()));
|
|
}
|
|
}
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
Future<Hash> EdenServiceHandler::getSHA1ForPathDefensively(
|
|
StringPiece mountPoint,
|
|
StringPiece path) noexcept {
|
|
#ifndef EDEN_WIN
|
|
return folly::makeFutureWith(
|
|
[&] { return getSHA1ForPath(mountPoint, path); });
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
Future<Hash> EdenServiceHandler::getSHA1ForPath(
|
|
StringPiece mountPoint,
|
|
StringPiece path) {
|
|
#ifndef EDEN_WIN
|
|
if (path.empty()) {
|
|
return makeFuture<Hash>(
|
|
newEdenError(EINVAL, "path cannot be the empty string"));
|
|
}
|
|
|
|
auto edenMount = server_->getMount(mountPoint);
|
|
auto relativePath = RelativePathPiece{path};
|
|
return edenMount->getInode(relativePath).thenValue([](const InodePtr& inode) {
|
|
auto fileInode = inode.asFilePtr();
|
|
if (!S_ISREG(fileInode->getMode())) {
|
|
// We intentionally want to refuse to compute the SHA1 of symlinks
|
|
return makeFuture<Hash>(
|
|
InodeError(EINVAL, fileInode, "file is a symlink"));
|
|
}
|
|
return fileInode->getSha1();
|
|
});
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::getBindMounts(
|
|
std::vector<string>& out,
|
|
std::unique_ptr<string> mountPointPtr) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3, *mountPointPtr);
|
|
auto mountPoint = *mountPointPtr.get();
|
|
auto mountPointPath = AbsolutePathPiece{mountPoint};
|
|
auto edenMount = server_->getMount(mountPoint);
|
|
|
|
for (auto& bindMount : edenMount->getBindMounts()) {
|
|
out.emplace_back(mountPointPath.relativize(bindMount.pathInMountDir)
|
|
.stringPiece()
|
|
.str());
|
|
}
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::getCurrentJournalPosition(
|
|
JournalPosition& out,
|
|
std::unique_ptr<std::string> mountPoint) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3, *mountPoint);
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
auto latest = edenMount->getJournal().getLatest();
|
|
|
|
out.mountGeneration = edenMount->getMountGeneration();
|
|
out.sequenceNumber = latest->toSequence;
|
|
out.snapshotHash = thriftHash(latest->toHash);
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::async_tm_subscribe(
|
|
std::unique_ptr<apache::thrift::StreamingHandlerCallback<
|
|
std::unique_ptr<JournalPosition>>> callback,
|
|
std::unique_ptr<std::string> mountPoint) {
|
|
#ifndef EDEN_WIN
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
|
|
// StreamingSubscriber manages the subscription lifetime and releases itself
|
|
// as appropriate.
|
|
StreamingSubscriber::subscribe(std::move(callback), std::move(edenMount));
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
#ifndef EDEN_WIN
|
|
apache::thrift::Stream<JournalPosition>
|
|
EdenServiceHandler::subscribeStreamTemporary(
|
|
std::unique_ptr<std::string> mountPoint) {
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
|
|
// We need a weak ref on the mount because the thrift stream plumbing
|
|
// may outlive the mount point
|
|
std::weak_ptr<EdenMount> weakMount(edenMount);
|
|
|
|
// We'll need to pass the subscriber id to both the disconnect
|
|
// and change callbacks. We can't know the id until after we've
|
|
// created them both, so we need to share an optional id between them.
|
|
auto handle = std::make_shared<std::optional<Journal::SubscriberId>>();
|
|
|
|
// This is called when the subscription channel is torn down
|
|
auto onDisconnect = [weakMount, handle] {
|
|
XLOG(ERR) << "streaming client disconnected";
|
|
auto mount = weakMount.lock();
|
|
if (mount) {
|
|
mount->getJournal().cancelSubscriber(handle->value());
|
|
}
|
|
};
|
|
|
|
// Set up the actual publishing instance
|
|
auto [reader, writer] =
|
|
createStreamPublisher<JournalPosition>(std::move(onDisconnect));
|
|
|
|
// A little wrapper around the StreamPublisher.
|
|
// This is needed because the destructor for StreamPublisherState
|
|
// triggers a FATAL if the stream has not been completed.
|
|
// We don't have an easy way to trigger this outside of just calling
|
|
// it in a destructor, so that's what we do here.
|
|
struct Publisher {
|
|
apache::thrift::StreamPublisher<JournalPosition> publisher;
|
|
|
|
explicit Publisher(
|
|
apache::thrift::StreamPublisher<JournalPosition> publisher)
|
|
: publisher(std::move(publisher)) {}
|
|
|
|
~Publisher() {
|
|
// We have to send an exception as part of the completion, otherwise
|
|
// thrift doesn't seem to notify the peer of the shutdown
|
|
std::move(publisher).complete(
|
|
folly::make_exception_wrapper<std::runtime_error>(
|
|
"subscriber terminated"));
|
|
}
|
|
};
|
|
|
|
auto stream = std::make_shared<Publisher>(std::move(writer));
|
|
|
|
// This is called each time the journal is updated
|
|
auto onJournalChange = [weakMount, stream]() mutable {
|
|
auto mount = weakMount.lock();
|
|
if (mount) {
|
|
auto& journal = mount->getJournal();
|
|
JournalPosition pos;
|
|
|
|
auto delta = journal.getLatest();
|
|
pos.sequenceNumber = delta->toSequence;
|
|
pos.snapshotHash = StringPiece(delta->toHash.getBytes()).str();
|
|
pos.mountGeneration = mount->getMountGeneration();
|
|
stream->publisher.next(pos);
|
|
}
|
|
};
|
|
|
|
// Register onJournalChange with the journal subsystem, and assign
|
|
// the subscriber id into the handle so that the callbacks can consume it.
|
|
handle->emplace(
|
|
edenMount->getJournal().registerSubscriber(std::move(onJournalChange)));
|
|
|
|
return std::move(reader);
|
|
}
|
|
#endif // !EDEN_WIN
|
|
|
|
void EdenServiceHandler::getFilesChangedSince(
|
|
FileDelta& out,
|
|
std::unique_ptr<std::string> mountPoint,
|
|
std::unique_ptr<JournalPosition> fromPosition) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3, *mountPoint);
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
auto delta = edenMount->getJournal().getLatest();
|
|
|
|
if (fromPosition->mountGeneration !=
|
|
static_cast<ssize_t>(edenMount->getMountGeneration())) {
|
|
throw newEdenError(
|
|
ERANGE,
|
|
"fromPosition.mountGeneration does not match the current "
|
|
"mountGeneration. "
|
|
"You need to compute a new basis for delta queries.");
|
|
}
|
|
|
|
out.toPosition.sequenceNumber = delta->toSequence;
|
|
out.toPosition.snapshotHash = thriftHash(delta->toHash);
|
|
out.toPosition.mountGeneration = edenMount->getMountGeneration();
|
|
|
|
out.fromPosition = out.toPosition;
|
|
|
|
// The +1 is because the core merge stops at the item prior to
|
|
// its limitSequence parameter and we want the changes *since*
|
|
// the provided sequence number.
|
|
auto merged = delta->merge(fromPosition->sequenceNumber + 1, true);
|
|
if (merged) {
|
|
out.fromPosition.sequenceNumber = merged->fromSequence;
|
|
out.fromPosition.snapshotHash = thriftHash(merged->fromHash);
|
|
out.fromPosition.mountGeneration = out.toPosition.mountGeneration;
|
|
|
|
for (const auto& entry : merged->changedFilesInOverlay) {
|
|
auto& path = entry.first;
|
|
auto& changeInfo = entry.second;
|
|
if (changeInfo.isNew()) {
|
|
out.createdPaths.emplace_back(path.stringPiece().str());
|
|
} else {
|
|
out.changedPaths.emplace_back(path.stringPiece().str());
|
|
}
|
|
}
|
|
|
|
for (auto& path : merged->uncleanPaths) {
|
|
out.uncleanPaths.emplace_back(path.stringPiece().str());
|
|
}
|
|
}
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif
|
|
}
|
|
|
|
namespace {
|
|
/**
|
|
* Starting from the provided delta, walks the chain backwards until it finds
|
|
* the delta whose [fromSequence, toSequence] range includes `target`.
|
|
*/
|
|
const JournalDelta* FOLLY_NULLABLE
|
|
findJournalDelta(const JournalDelta* delta, Journal::SequenceNumber target) {
|
|
#ifndef EDEN_WIN
|
|
// If the tip of the delta chain precedes the target, then do not bother to
|
|
// search.
|
|
if (delta == nullptr || delta->toSequence < target) {
|
|
return nullptr;
|
|
}
|
|
|
|
while (delta) {
|
|
if (delta->fromSequence <= target && target <= delta->toSequence) {
|
|
return delta;
|
|
}
|
|
delta = delta->previous.get();
|
|
}
|
|
return nullptr;
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
} // namespace
|
|
|
|
void EdenServiceHandler::debugGetRawJournal(
|
|
DebugGetRawJournalResponse& out,
|
|
std::unique_ptr<DebugGetRawJournalParams> params) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3, params->mountPoint);
|
|
auto edenMount = server_->getMount(params->mountPoint);
|
|
|
|
auto mountGeneration = params->fromPosition.mountGeneration;
|
|
if (mountGeneration !=
|
|
static_cast<ssize_t>(edenMount->getMountGeneration())) {
|
|
throw newEdenError(
|
|
ERANGE,
|
|
"fromPosition.mountGeneration does not match the current "
|
|
"mountGeneration. "
|
|
"You need to compute a new basis for delta queries.");
|
|
}
|
|
|
|
auto latest = edenMount->getJournal().getLatest();
|
|
|
|
// Walk the journal until we find toPosition.
|
|
auto toPos =
|
|
findJournalDelta(latest.get(), params->toPosition.sequenceNumber);
|
|
if (toPos == nullptr) {
|
|
throw newEdenError(
|
|
"no JournalDelta found for toPosition.sequenceNumber ",
|
|
params->toPosition.sequenceNumber);
|
|
}
|
|
|
|
// Walk the journal until we find a JournalDelta that preceeds fromPosition,
|
|
// or the beginning of the chain, whichever comes first.
|
|
auto current = toPos;
|
|
auto fromPos = params->fromPosition.sequenceNumber;
|
|
while (current) {
|
|
if (static_cast<ssize_t>(current->toSequence) < fromPos) {
|
|
break;
|
|
}
|
|
|
|
DebugJournalDelta delta;
|
|
JournalPosition fromPosition;
|
|
fromPosition.set_mountGeneration(mountGeneration);
|
|
fromPosition.set_sequenceNumber(current->fromSequence);
|
|
fromPosition.set_snapshotHash(thriftHash(current->fromHash));
|
|
delta.set_fromPosition(fromPosition);
|
|
|
|
JournalPosition toPosition;
|
|
toPosition.set_mountGeneration(mountGeneration);
|
|
toPosition.set_sequenceNumber(current->toSequence);
|
|
toPosition.set_snapshotHash(thriftHash(current->toHash));
|
|
delta.set_toPosition(toPosition);
|
|
|
|
for (const auto& entry : current->changedFilesInOverlay) {
|
|
auto& path = entry.first;
|
|
auto& changeInfo = entry.second;
|
|
|
|
DebugPathChangeInfo debugChangeInfo;
|
|
debugChangeInfo.existedBefore = changeInfo.existedBefore;
|
|
debugChangeInfo.existedAfter = changeInfo.existedAfter;
|
|
delta.changedPaths.emplace(path.stringPiece().str(), debugChangeInfo);
|
|
}
|
|
|
|
for (auto& path : current->uncleanPaths) {
|
|
delta.uncleanPaths.emplace(path.stringPiece().str());
|
|
}
|
|
|
|
out.allDeltas.push_back(delta);
|
|
current = current->previous.get();
|
|
}
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
folly::Future<std::unique_ptr<std::vector<FileInformationOrError>>>
|
|
EdenServiceHandler::future_getFileInformation(
|
|
std::unique_ptr<std::string> mountPoint,
|
|
std::unique_ptr<std::vector<std::string>> paths) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(
|
|
DBG3, *mountPoint, "[" + folly::join(", ", *paths) + "]");
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
auto rootInode = edenMount->getRootInode();
|
|
|
|
// Remember the current thrift worker thread so that we can
|
|
// perform the final result transformation in an appropriate thread.
|
|
auto threadMgr = getThreadManager();
|
|
|
|
return collectAllSemiFuture(
|
|
applyToInodes(
|
|
rootInode,
|
|
*paths,
|
|
[](InodePtr inode) {
|
|
return inode->getattr().thenValue([](Dispatcher::Attr attr) {
|
|
FileInformation info;
|
|
info.size = attr.st.st_size;
|
|
auto& ts = stMtime(attr.st);
|
|
info.mtime.seconds = ts.tv_sec;
|
|
info.mtime.nanoSeconds = ts.tv_nsec;
|
|
info.mode = attr.st.st_mode;
|
|
|
|
FileInformationOrError result;
|
|
result.set_info(info);
|
|
|
|
return result;
|
|
});
|
|
}))
|
|
.via(threadMgr)
|
|
.thenValue([](vector<Try<FileInformationOrError>>&& done) {
|
|
auto out = std::make_unique<vector<FileInformationOrError>>();
|
|
out->reserve(done.size());
|
|
for (auto& item : done) {
|
|
if (item.hasException()) {
|
|
FileInformationOrError result;
|
|
result.set_error(newEdenError(item.exception()));
|
|
out->emplace_back(std::move(result));
|
|
} else {
|
|
out->emplace_back(item.value());
|
|
}
|
|
}
|
|
return out;
|
|
});
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::glob(
|
|
vector<string>& out,
|
|
unique_ptr<string> mountPoint,
|
|
unique_ptr<vector<string>> globs) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(
|
|
DBG3, *mountPoint, "[" + folly::join(", ", *globs.get()) + "]");
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
auto rootInode = edenMount->getRootInode();
|
|
|
|
try {
|
|
// Compile the list of globs into a tree
|
|
GlobNode globRoot(/*includeDotfiles=*/true);
|
|
for (auto& globString : *globs) {
|
|
globRoot.parse(globString);
|
|
}
|
|
|
|
// and evaluate it against the root
|
|
auto matches = globRoot
|
|
.evaluate(
|
|
edenMount->getObjectStore(),
|
|
RelativePathPiece(),
|
|
rootInode,
|
|
/*fileBlobsToPrefetch=*/nullptr)
|
|
.get();
|
|
for (auto& fileName : matches) {
|
|
out.emplace_back(fileName.name.stringPiece().toString());
|
|
}
|
|
} catch (const std::system_error& exc) {
|
|
throw newEdenError(exc);
|
|
}
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
folly::Future<std::unique_ptr<Glob>> EdenServiceHandler::future_globFiles(
|
|
std::unique_ptr<GlobParams> params) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(
|
|
DBG3,
|
|
params->mountPoint,
|
|
"[" + folly::join(", ", params->globs) + "]",
|
|
params->includeDotfiles);
|
|
auto edenMount = server_->getMount(params->mountPoint);
|
|
auto rootInode = edenMount->getRootInode();
|
|
|
|
// Compile the list of globs into a tree
|
|
auto globRoot = std::make_shared<GlobNode>(params->includeDotfiles);
|
|
try {
|
|
for (auto& globString : params->globs) {
|
|
globRoot->parse(globString);
|
|
}
|
|
} catch (const std::system_error& exc) {
|
|
throw newEdenError(exc);
|
|
}
|
|
|
|
auto fileBlobsToPrefetch = params->prefetchFiles
|
|
? std::make_shared<folly::Synchronized<std::vector<Hash>>>()
|
|
: nullptr;
|
|
|
|
// and evaluate it against the root
|
|
return helper.wrapFuture(
|
|
globRoot
|
|
->evaluate(
|
|
edenMount->getObjectStore(),
|
|
RelativePathPiece(),
|
|
rootInode,
|
|
fileBlobsToPrefetch)
|
|
.thenValue([edenMount,
|
|
wantDtype = params->wantDtype,
|
|
fileBlobsToPrefetch,
|
|
suppressFileList = params->suppressFileList](
|
|
std::vector<GlobNode::GlobResult>&& results) {
|
|
auto out = std::make_unique<Glob>();
|
|
|
|
if (!suppressFileList) {
|
|
std::unordered_set<RelativePathPiece> seenPaths;
|
|
for (auto& entry : results) {
|
|
auto ret = seenPaths.insert(entry.name);
|
|
if (ret.second) {
|
|
out->matchingFiles.emplace_back(
|
|
entry.name.stringPiece().toString());
|
|
|
|
if (wantDtype) {
|
|
out->dtypes.emplace_back(static_cast<DType>(entry.dtype));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (fileBlobsToPrefetch) {
|
|
std::vector<folly::Future<folly::Unit>> futures;
|
|
|
|
auto store = edenMount->getObjectStore();
|
|
auto blobs = fileBlobsToPrefetch->rlock();
|
|
std::vector<Hash> batch;
|
|
|
|
for (auto& hash : *blobs) {
|
|
if (batch.size() >= 20480) {
|
|
futures.emplace_back(store->prefetchBlobs(batch));
|
|
batch.clear();
|
|
}
|
|
batch.emplace_back(hash);
|
|
}
|
|
if (!batch.empty()) {
|
|
futures.emplace_back(store->prefetchBlobs(batch));
|
|
}
|
|
|
|
return folly::collect(futures).thenValue(
|
|
[glob = std::move(out)](auto&&) mutable {
|
|
return makeFuture(std::move(glob));
|
|
});
|
|
}
|
|
return makeFuture(std::move(out));
|
|
})
|
|
.ensure([globRoot]() {
|
|
// keep globRoot alive until the end
|
|
}));
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
folly::Future<Unit> EdenServiceHandler::future_chown(
|
|
std::unique_ptr<std::string> mountPoint,
|
|
int32_t uid,
|
|
int32_t gid) {
|
|
#ifndef EDEN_WIN
|
|
return server_->getMount(*mountPoint)->chown(uid, gid);
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::getManifestEntry(
|
|
ManifestEntry& out,
|
|
std::unique_ptr<std::string> mountPoint,
|
|
std::unique_ptr<std::string> relativePath) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3, *mountPoint, *relativePath);
|
|
auto mount = server_->getMount(*mountPoint);
|
|
auto filename = RelativePathPiece{*relativePath};
|
|
auto mode = isInManifestAsFile(mount.get(), filename);
|
|
if (mode.has_value()) {
|
|
out.mode = mode.value();
|
|
} else {
|
|
NoValueForKeyError error;
|
|
error.set_key(*relativePath);
|
|
throw error;
|
|
}
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
// TODO(mbolin): Make this a method of ObjectStore and make it Future-based.
|
|
std::optional<mode_t> EdenServiceHandler::isInManifestAsFile(
|
|
const EdenMount* mount,
|
|
const RelativePathPiece filename) {
|
|
#ifndef EDEN_WIN
|
|
auto tree = mount->getRootTree();
|
|
auto parentDirectory = filename.dirname();
|
|
auto objectStore = mount->getObjectStore();
|
|
for (auto piece : parentDirectory.components()) {
|
|
auto entry = tree->getEntryPtr(piece);
|
|
if (entry != nullptr && entry->isTree()) {
|
|
tree = objectStore->getTree(entry->getHash()).get();
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
if (tree != nullptr) {
|
|
auto entry = tree->getEntryPtr(filename.basename());
|
|
if (entry != nullptr && !entry->isTree()) {
|
|
return modeFromTreeEntryType(entry->getType());
|
|
}
|
|
}
|
|
|
|
return std::nullopt;
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
folly::Future<std::unique_ptr<ScmStatus>>
|
|
EdenServiceHandler::future_getScmStatus(
|
|
std::unique_ptr<std::string> mountPoint,
|
|
bool listIgnored,
|
|
std::unique_ptr<std::string> commitHash) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(
|
|
DBG2,
|
|
*mountPoint,
|
|
folly::to<string>("listIgnored=", listIgnored ? "true" : "false"),
|
|
folly::to<string>("commitHash=", logHash(*commitHash)));
|
|
|
|
auto mount = server_->getMount(*mountPoint);
|
|
auto hash = hashFromThrift(*commitHash);
|
|
return helper.wrapFuture(diffMountForStatus(*mount, hash, listIgnored));
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
folly::Future<std::unique_ptr<ScmStatus>>
|
|
EdenServiceHandler::future_getScmStatusBetweenRevisions(
|
|
std::unique_ptr<std::string> mountPoint,
|
|
std::unique_ptr<std::string> oldHash,
|
|
std::unique_ptr<std::string> newHash) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(
|
|
DBG2,
|
|
*mountPoint,
|
|
folly::to<string>("oldHash=", logHash(*oldHash)),
|
|
folly::to<string>("newHash=", logHash(*newHash)));
|
|
auto id1 = hashFromThrift(*oldHash);
|
|
auto id2 = hashFromThrift(*newHash);
|
|
auto mount = server_->getMount(*mountPoint);
|
|
return helper.wrapFuture(diffCommits(mount->getObjectStore(), id1, id2)
|
|
.thenValue([](ScmStatus&& result) {
|
|
return make_unique<ScmStatus>(
|
|
std::move(result));
|
|
}));
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::debugGetScmTree(
|
|
vector<ScmTreeEntry>& entries,
|
|
unique_ptr<string> mountPoint,
|
|
unique_ptr<string> idStr,
|
|
bool localStoreOnly) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3, *mountPoint, logHash(*idStr));
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
auto id = hashFromThrift(*idStr);
|
|
|
|
std::shared_ptr<const Tree> tree;
|
|
auto store = edenMount->getObjectStore();
|
|
if (localStoreOnly) {
|
|
auto localStore = store->getLocalStore();
|
|
tree = localStore->getTree(id).get();
|
|
} else {
|
|
tree = store->getTree(id).get();
|
|
}
|
|
|
|
if (!tree) {
|
|
throw newEdenError("no tree found for id ", *idStr);
|
|
}
|
|
|
|
for (const auto& entry : tree->getTreeEntries()) {
|
|
entries.emplace_back();
|
|
auto& out = entries.back();
|
|
out.name = entry.getName().stringPiece().str();
|
|
out.mode = modeFromTreeEntryType(entry.getType());
|
|
out.id = thriftHash(entry.getHash());
|
|
}
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif
|
|
}
|
|
|
|
void EdenServiceHandler::debugGetScmBlob(
|
|
string& data,
|
|
unique_ptr<string> mountPoint,
|
|
unique_ptr<string> idStr,
|
|
bool localStoreOnly) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3, *mountPoint, logHash(*idStr));
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
auto id = hashFromThrift(*idStr);
|
|
|
|
std::shared_ptr<const Blob> blob;
|
|
auto store = edenMount->getObjectStore();
|
|
if (localStoreOnly) {
|
|
auto localStore = store->getLocalStore();
|
|
blob = localStore->getBlob(id).get();
|
|
} else {
|
|
blob = store->getBlob(id).get();
|
|
}
|
|
|
|
if (!blob) {
|
|
throw newEdenError("no blob found for id ", *idStr);
|
|
}
|
|
auto dataBuf = blob->getContents().cloneCoalescedAsValue();
|
|
data.assign(reinterpret_cast<const char*>(dataBuf.data()), dataBuf.length());
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif
|
|
}
|
|
|
|
void EdenServiceHandler::debugGetScmBlobMetadata(
|
|
ScmBlobMetadata& result,
|
|
unique_ptr<string> mountPoint,
|
|
unique_ptr<string> idStr,
|
|
bool localStoreOnly) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3, *mountPoint, logHash(*idStr));
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
auto id = hashFromThrift(*idStr);
|
|
|
|
std::optional<BlobMetadata> metadata;
|
|
auto store = edenMount->getObjectStore();
|
|
if (localStoreOnly) {
|
|
auto localStore = store->getLocalStore();
|
|
metadata = localStore->getBlobMetadata(id).get();
|
|
} else {
|
|
metadata = store->getBlobMetadata(id).get();
|
|
}
|
|
|
|
if (!metadata.has_value()) {
|
|
throw newEdenError("no blob metadata found for id ", *idStr);
|
|
}
|
|
result.size = metadata->size;
|
|
result.contentsSha1 = thriftHash(metadata->sha1);
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::debugInodeStatus(
|
|
vector<TreeInodeDebugInfo>& inodeInfo,
|
|
unique_ptr<string> mountPoint,
|
|
std::unique_ptr<std::string> path) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3);
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
|
|
auto inode = inodeFromUserPath(*edenMount, *path).asTreePtr();
|
|
inode->getDebugStatus(inodeInfo);
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::debugOutstandingFuseCalls(
|
|
std::vector<FuseCall>& outstandingCalls,
|
|
std::unique_ptr<std::string> mountPoint) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG2);
|
|
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
auto* fuseChannel = edenMount->getFuseChannel();
|
|
std::vector<fuse_in_header> fuseOutstandingCalls =
|
|
fuseChannel->getOutstandingRequests();
|
|
FuseCall fuseCall;
|
|
|
|
for (const auto& call : fuseOutstandingCalls) {
|
|
// Convert from fuse_in_header to fuseCall
|
|
// Conversion is done here to avoid building a dependency between
|
|
// FuseChannel and thrift
|
|
|
|
fuseCall.len = call.len;
|
|
fuseCall.opcode = call.opcode;
|
|
fuseCall.unique = call.unique;
|
|
fuseCall.nodeid = call.nodeid;
|
|
fuseCall.uid = call.uid;
|
|
fuseCall.gid = call.gid;
|
|
fuseCall.pid = call.pid;
|
|
|
|
outstandingCalls.push_back(fuseCall);
|
|
}
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::debugGetInodePath(
|
|
InodePathDebugInfo& info,
|
|
std::unique_ptr<std::string> mountPoint,
|
|
int64_t inodeNumber) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3);
|
|
auto inodeNum = static_cast<InodeNumber>(inodeNumber);
|
|
auto inodeMap = server_->getMount(*mountPoint)->getInodeMap();
|
|
|
|
auto relativePath = inodeMap->getPathForInode(inodeNum);
|
|
// Check if the inode is loaded
|
|
info.loaded = inodeMap->lookupLoadedInode(inodeNum) != nullptr;
|
|
// If getPathForInode returned none then the inode is unlinked
|
|
info.linked = relativePath != std::nullopt;
|
|
info.path = relativePath ? relativePath->stringPiece().str() : "";
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::debugSetLogLevel(
|
|
SetLogLevelResult& result,
|
|
std::unique_ptr<std::string> category,
|
|
std::unique_ptr<std::string> level) {
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG1);
|
|
// TODO: This is a temporary hack until Adam's upcoming log config parser
|
|
// is ready.
|
|
bool inherit = true;
|
|
if (level->length() && '!' == level->back()) {
|
|
*level = level->substr(0, level->length() - 1);
|
|
inherit = false;
|
|
}
|
|
|
|
auto& db = folly::LoggerDB::get();
|
|
result.categoryCreated = !db.getCategoryOrNull(*category);
|
|
folly::Logger(*category).getCategory()->setLevel(
|
|
folly::stringToLogLevel(*level), inherit);
|
|
}
|
|
|
|
void EdenServiceHandler::getAccessCounts(
|
|
GetAccessCountsResult& result,
|
|
int64_t duration) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3);
|
|
|
|
result.exeNamesByPid =
|
|
server_->getServerState()->getProcessNameCache()->getAllProcessNames();
|
|
|
|
auto mountList = server_->getMountPoints();
|
|
for (auto& mount : mountList) {
|
|
FuseMountAccesses& fma =
|
|
result.fuseAccessesByMount[mount->getPath().value()];
|
|
for (auto& [pid, count] :
|
|
mount->getFuseChannel()->getProcessAccessLog().getAllAccesses(
|
|
std::chrono::seconds{duration})) {
|
|
fma.fuseAccesses[pid].count = count;
|
|
}
|
|
}
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::clearAndCompactLocalStore() {
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG1);
|
|
server_->getLocalStore()->clearCachesAndCompactAll();
|
|
}
|
|
|
|
void EdenServiceHandler::debugClearLocalStoreCaches() {
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG1);
|
|
server_->getLocalStore()->clearCaches();
|
|
}
|
|
|
|
void EdenServiceHandler::debugCompactLocalStorage() {
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG1);
|
|
server_->getLocalStore()->compactStorage();
|
|
}
|
|
|
|
int64_t EdenServiceHandler::unloadInodeForPath(
|
|
unique_ptr<string> mountPoint,
|
|
std::unique_ptr<std::string> path,
|
|
std::unique_ptr<TimeSpec> age) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG1, *mountPoint, *path);
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
|
|
TreeInodePtr inode = inodeFromUserPath(*edenMount, *path).asTreePtr();
|
|
auto cutoff = std::chrono::system_clock::now() -
|
|
std::chrono::seconds(age->seconds) -
|
|
std::chrono::nanoseconds(age->nanoSeconds);
|
|
auto cutoff_ts = folly::to<timespec>(cutoff);
|
|
return inode->unloadChildrenLastAccessedBefore(cutoff_ts);
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif
|
|
}
|
|
|
|
void EdenServiceHandler::getStatInfo(InternalStats& result) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3);
|
|
auto mountList = server_->getMountPoints();
|
|
for (auto& mount : mountList) {
|
|
auto inodeMap = mount->getInodeMap();
|
|
// Set LoadedInde Count and unloaded Inode count for the mountPoint.
|
|
MountInodeInfo mountInodeInfo;
|
|
auto counts = inodeMap->getLoadedInodeCounts();
|
|
mountInodeInfo.loadedInodeCount = counts.fileCount + counts.treeCount;
|
|
mountInodeInfo.unloadedInodeCount = inodeMap->getUnloadedInodeCount();
|
|
mountInodeInfo.loadedFileCount = counts.fileCount;
|
|
mountInodeInfo.loadedTreeCount = counts.treeCount;
|
|
|
|
// TODO: Currently getting Materialization status of an inode using
|
|
// getDebugStatus which walks through entire Tree of inodes, in future we
|
|
// can add some mechanism to get materialized inode count without walking
|
|
// through the entire tree.
|
|
vector<TreeInodeDebugInfo> debugInfoStatus;
|
|
auto root = mount->getRootInode();
|
|
root->getDebugStatus(debugInfoStatus);
|
|
uint64_t materializedCount = 0;
|
|
for (auto& entry : debugInfoStatus) {
|
|
if (entry.materialized) {
|
|
materializedCount++;
|
|
}
|
|
}
|
|
mountInodeInfo.materializedInodeCount = materializedCount;
|
|
result.mountPointInfo[mount->getPath().stringPiece().str()] =
|
|
mountInodeInfo;
|
|
}
|
|
// Get the counters and set number of inodes unloaded by periodic unload job.
|
|
result.counters = stats::ServiceData::get()->getCounters();
|
|
result.periodicUnloadCount =
|
|
result.counters[kPeriodicUnloadCounterKey.toString()];
|
|
|
|
auto privateDirtyBytes = facebook::eden::proc_util::calculatePrivateBytes();
|
|
if (privateDirtyBytes) {
|
|
result.privateBytes = privateDirtyBytes.value();
|
|
}
|
|
|
|
auto vmRSSKBytes = facebook::eden::proc_util::getUnsignedLongLongValue(
|
|
proc_util::loadProcStatus(), kVmRSSKey.data(), kKBytes.data());
|
|
if (vmRSSKBytes) {
|
|
result.vmRSSBytes = vmRSSKBytes.value() * 1024;
|
|
}
|
|
|
|
// Note: this will be removed in a subsequent commit.
|
|
// We now report periodically via ServiceData
|
|
std::string smaps;
|
|
if (folly::readFile("/proc/self/smaps", smaps)) {
|
|
result.smaps = std::move(smaps);
|
|
}
|
|
|
|
const auto blobCacheStats = server_->getBlobCache()->getStats();
|
|
result.blobCacheStats.entryCount = blobCacheStats.blobCount;
|
|
result.blobCacheStats.totalSizeInBytes = blobCacheStats.totalSizeInBytes;
|
|
result.blobCacheStats.hitCount = blobCacheStats.hitCount;
|
|
result.blobCacheStats.missCount = blobCacheStats.missCount;
|
|
result.blobCacheStats.evictionCount = blobCacheStats.evictionCount;
|
|
result.blobCacheStats.dropCount = blobCacheStats.dropCount;
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::flushStatsNow() {
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG3);
|
|
server_->flushStatsNow();
|
|
}
|
|
|
|
Future<Unit> EdenServiceHandler::future_invalidateKernelInodeCache(
|
|
std::unique_ptr<std::string> mountPoint,
|
|
std::unique_ptr<std::string> path) {
|
|
#ifndef EDEN_WIN
|
|
auto helper = INSTRUMENT_THRIFT_CALL(DBG2, *mountPoint, *path);
|
|
auto edenMount = server_->getMount(*mountPoint);
|
|
InodePtr inode = inodeFromUserPath(*edenMount, *path);
|
|
auto* fuseChannel = edenMount->getFuseChannel();
|
|
|
|
// Invalidate cached pages and attributes
|
|
fuseChannel->invalidateInode(inode->getNodeId(), 0, 0);
|
|
|
|
const auto treePtr = inode.asTreePtrOrNull();
|
|
|
|
// Invalidate all parent/child relationships potentially cached.
|
|
if (treePtr != nullptr) {
|
|
const auto& dir = treePtr->getContents().rlock();
|
|
for (const auto& entry : dir->entries) {
|
|
fuseChannel->invalidateEntry(inode->getNodeId(), entry.first);
|
|
}
|
|
}
|
|
|
|
// Wait for all of the invalidations to complete
|
|
return fuseChannel->flushInvalidations();
|
|
#else
|
|
NOT_IMPLEMENTED();
|
|
#endif // !EDEN_WIN
|
|
}
|
|
|
|
void EdenServiceHandler::shutdown() {
|
|
auto helper = INSTRUMENT_THRIFT_CALL(INFO);
|
|
server_->stop();
|
|
}
|
|
|
|
void EdenServiceHandler::enableTracing() {
|
|
XLOG(INFO) << "Enabling tracing";
|
|
eden::enableTracing();
|
|
}
|
|
void EdenServiceHandler::disableTracing() {
|
|
XLOG(INFO) << "Disabling tracing";
|
|
eden::disableTracing();
|
|
}
|
|
|
|
void EdenServiceHandler::getTracePoints(std::vector<TracePoint>& result) {
|
|
auto compactTracePoints = getAllTracepoints();
|
|
for (auto& point : compactTracePoints) {
|
|
TracePoint tp;
|
|
tp.set_timestamp(point.timestamp.count());
|
|
tp.set_traceId(point.traceId);
|
|
tp.set_blockId(point.blockId);
|
|
tp.set_parentBlockId(point.parentBlockId);
|
|
if (point.name) {
|
|
tp.set_name(std::string(point.name));
|
|
}
|
|
if (point.start) {
|
|
tp.set_event(TracePointEvent::START);
|
|
} else if (point.stop) {
|
|
tp.set_event(TracePointEvent::STOP);
|
|
}
|
|
result.emplace_back(std::move(tp));
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
std::optional<folly::exception_wrapper> getFaultError(
|
|
apache::thrift::optional_field_ref<std::string&> errorType,
|
|
apache::thrift::optional_field_ref<std::string&> errorMessage) {
|
|
if (!errorType.has_value() && !errorMessage.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto createException =
|
|
[](StringPiece type, const std::string& msg) -> folly::exception_wrapper {
|
|
if (type == "runtime_error") {
|
|
return std::runtime_error(msg);
|
|
} else if (type.startsWith("errno:")) {
|
|
auto errnum = folly::to<int>(type.subpiece(6));
|
|
return std::system_error(errnum, std::generic_category(), msg);
|
|
}
|
|
// If we want to support other error types in the future they should
|
|
// be added here.
|
|
throw newEdenError("unknown error type {}", type);
|
|
};
|
|
|
|
return createException(
|
|
errorType.value_or("runtime_error"),
|
|
errorMessage.value_or("injected error"));
|
|
}
|
|
} // namespace
|
|
|
|
void EdenServiceHandler::injectFault(unique_ptr<FaultDefinition> fault) {
|
|
auto& injector = server_->getServerState()->getFaultInjector();
|
|
if (fault->block) {
|
|
injector.injectBlock(fault->keyClass, fault->keyValueRegex, fault->count);
|
|
return;
|
|
}
|
|
|
|
auto error = getFaultError(fault->errorType_ref(), fault->errorMessage_ref());
|
|
std::chrono::milliseconds delay(fault->delayMilliseconds);
|
|
if (error.has_value()) {
|
|
if (delay.count() > 0) {
|
|
injector.injectDelayedError(
|
|
fault->keyClass,
|
|
fault->keyValueRegex,
|
|
delay,
|
|
error.value(),
|
|
fault->count);
|
|
} else {
|
|
injector.injectError(
|
|
fault->keyClass, fault->keyValueRegex, error.value(), fault->count);
|
|
}
|
|
} else {
|
|
if (delay.count() > 0) {
|
|
injector.injectDelay(
|
|
fault->keyClass, fault->keyValueRegex, delay, fault->count);
|
|
} else {
|
|
injector.injectNoop(fault->keyClass, fault->keyValueRegex, fault->count);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EdenServiceHandler::removeFault(unique_ptr<RemoveFaultArg> fault) {
|
|
auto& injector = server_->getServerState()->getFaultInjector();
|
|
return injector.removeFault(fault->keyClass, fault->keyValueRegex);
|
|
}
|
|
|
|
int64_t EdenServiceHandler::unblockFault(unique_ptr<UnblockFaultArg> info) {
|
|
auto& injector = server_->getServerState()->getFaultInjector();
|
|
auto error = getFaultError(info->errorType_ref(), info->errorMessage_ref());
|
|
|
|
if (!info->keyClass_ref().has_value()) {
|
|
if (info->keyValueRegex_ref().has_value()) {
|
|
throw newEdenError(
|
|
"cannot specify a key value regex without a key class");
|
|
}
|
|
if (error.has_value()) {
|
|
return injector.unblockAllWithError(error.value());
|
|
} else {
|
|
return injector.unblockAll();
|
|
}
|
|
}
|
|
|
|
const auto& keyClass = info->keyClass_ref().value();
|
|
std::string keyValueRegex = info->keyValueRegex_ref().value_or(".*");
|
|
if (error.has_value()) {
|
|
return injector.unblockWithError(keyClass, keyValueRegex, error.value());
|
|
} else {
|
|
return injector.unblock(keyClass, keyValueRegex);
|
|
}
|
|
}
|
|
|
|
void EdenServiceHandler::initiateShutdown(std::unique_ptr<std::string> reason) {
|
|
auto helper = INSTRUMENT_THRIFT_CALL(INFO);
|
|
XLOG(INFO) << "initiateShutdown requested, reason: " << *reason;
|
|
server_->stop();
|
|
}
|
|
} // namespace eden
|
|
} // namespace facebook
|